5

I'm trying to get spring security with JWT to work with an app. I've read many tutorials and examples but nothing really fits my use case. We do not authorize via username/password, we use twilio to authenticate a mobile number, I then want to create a simple JWT token given a mobile number as the subject. I've been able to do that

here is a simple endpoint that exists in /api/v1/jwt

@GetMapping("/jwt")
    fun jwt(@RequestParam(value = "number", required = true) number: String): String? {

        val jwtToken = Jwts.builder().setSubject(number).claim("roles", "user").setIssuedAt(Date()).signWith(SignatureAlgorithm.HS256, Base64.getEncoder().encodeToString("secret".toByteArray())).compact()

        return jwtToken

    }

which returns a valid JWT token.

My security config isn't working anymore though, now all endpoints seem protected,

@Configuration
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    @Bean
    override fun authenticationManagerBean(): AuthenticationManager {
        return super.authenticationManagerBean()
    }

    override fun configure(web: WebSecurity) {
        web.ignoring().antMatchers("/v2/api-docs",
                "/configuration/ui",
                "/swagger-resources/**",
                "/configuration/security",
                "/swagger-ui.html",
                "/webjars/**");
    }

    override fun configure(http: HttpSecurity) {

        http.csrf()
                .disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/api/v1/auth/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(JwtFilter(), UsernamePasswordAuthenticationFilter::class.java)
    }
}

JWT Filter


    @Throws(IOException::class, ServletException::class)
    override fun doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) {
        val request = req as HttpServletRequest
        val response = res as HttpServletResponse
        val authHeader = request.getHeader("authorization")
        if ("OPTIONS" == request.method) {
            response.status = HttpServletResponse.SC_OK
            chain.doFilter(req, res)
        } else {
            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                throw ServletException("Missing or invalid Authorization header")
            }
            val token = authHeader.substring(7)
            try {
                val claims = Jwts.parser().setSigningKey(Base64.getEncoder().encodeToString("secret".toByteArray())).parseClaimsJws(token).body
                request.setAttribute("claims", claims)
            } catch (e: SignatureException) {
                throw ServletException("Invalid token")
            }
            chain.doFilter(req, res)
        }
    }
}

It seems the filter gets hit any time regardless of the permitall above it. Shouldn't the filter get ignored on any api/v1/auth/ path? I think im missing something.

Second question is there a way to apply this filter without having to addbefore or after and not extend https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html

edit: antPathRequestMatcher isn't firing for configure, but I even added the path to websecurity configure, i get this logging

2019-12-30 14:44:44.792 DEBUG 81181 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/auth/request?number=5555555 has an empty filter list```
Brian
  • 4,328
  • 13
  • 58
  • 103
  • Your path is incorrect if it does not get permitted. Enable logging for Spring Security, it shows exactly what paths are matched when requesting that endpoint. For your second question no it is not possible, there are different alternatives, but you have to plug them into security chain the same way. – Vaelyr Dec 30 '19 at 18:45
  • It looks like its not trying to look at any matchers from httpSecurity only webSecurity. I must be building it wrong?? – Brian Dec 30 '19 at 19:28
  • this is crazy im getting these logs ```2019-12-30 14:44:44.792 DEBUG 81181 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/api/v1/auth/request'; against '/api/v1/auth/request' 2019-12-30 14:44:44.792 DEBUG 81181 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : /api/v1/auth/request?number=5555555555 has an empty filter list``` – Brian Dec 30 '19 at 22:45
  • Your rule says to permit everything that comes after "/api/v1/auth/**", and any other request requires authentication, as you said your JWT endpoint is "/api/v1/jwt", which means your JWT endpoint requires auth too. "/api/v1/auth/request" is not protected, or anything that comes after "auth/**". – Vaelyr Jan 01 '20 at 08:55
  • the controller is mapped as ``` @RestController @RequestMapping("/api/v1/auth") class AuthController : MainController() {}``` so the full path is api/v1/auth/jwt so it should not hit the filter – Brian Jan 02 '20 at 22:21

3 Answers3

4

In your configuration you have:

override fun configure(http: HttpSecurity) {
  http.csrf()
  .disable()
  .sessionManagement()
  .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  .and()
  .authorizeRequests()
  .antMatchers("/api/v1/auth/**").permitAll()
  .anyRequest().authenticated()
  .and()
  .addFilterBefore(JwtFilter(), UsernamePasswordAuthenticationFilter::class.java)
}

I see the following:

.antMatchers("/api/v1/auth/**").permitAll()
.anyRequest().authenticated()

So spring security is checking authentication for all the requests. In my spring configuration I usually do:

.antMatchers("/swagger-ui.html","/webjars/**","/swagger-resources/**", "/v2/**","/csrf")
.permitAll()
.antMatchers("/**")
.authenticated()

So try to change your configuration by setting:

override fun configure(http: HttpSecurity) {
  http.csrf()
  .disable()
  .sessionManagement()
  .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  .and()
  .authorizeRequests()
  .antMatchers("/api/v1/auth/**").permitAll()
  .antMatchers("/**").authenticated()
  .and()
  .addFilterBefore(JwtFilter(), UsernamePasswordAuthenticationFilter::class.java)
}

Regarding to your second question:

Second question is there a way to apply this filter without having to addbefore or after and not extend https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html

In this case honestly I'd move on OAuth/OpenID by using spring security and spring security oauth sprign security jwt.

In this case I'd create a ResourceServer configuration class and an AuthorizationServer configuration class (or the same class can be used as AuthorizationServer and ResourceServer); a good example is here https://www.baeldung.com/spring-security-oauth-jwt

I hope it's useful

Angelo

Angelo Immediata
  • 6,635
  • 4
  • 33
  • 65
4

You may use configure(web: WebSecurity), which will bypass the spring security filters and endpoints can be accessed publicly.

override fun configure(web: WebSecurity) {
        web.ignoring().antMatchers("/api/v1/auth/**",
                "/v2/api-docs",
                "/configuration/ui",
                "/swagger-resources/**",
                "/configuration/security",
                "/swagger-ui.html",
                "/webjars/**");
    }

You might use configure(http: HttpSecurity) for session management and role-based authentication. You may see HttpSecurity vs WebSecurity.

For custom filters which have @Component(or any flavor of @Bean), WebSecurityConfigurerAdapter will tell Spring Security to ignore any filters added through it. The filter was then still being invoked because the @Component (or any flavor of @Bean) annotation told Spring to add the filter (again) outside of the security chain. So while the filter was being ignored in the security chain, it was not being ignored by the other (non-security?) chain. (See Here)

Romil Patel
  • 12,879
  • 7
  • 47
  • 76
0

Working example

@EnableWebSecurity
class SecurityConfig() : WebSecurityConfigurerAdapter() {

    @Autowired
    lateinit var tokenService: TokenService

    override fun configure(web: WebSecurity) {
        web.ignoring().antMatchers("/v2/api-docs",
                "/configuration/ui",
                "/swagger-resources/**",
                "/configuration/security",
                "/swagger-ui.html",
                "/webjars/**","/api/v1/auth/**");
    }

    override fun configure(http: HttpSecurity) {
        http.cors().and().csrf()
                .disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilterBefore(JwtFilter(tokenService), UsernamePasswordAuthenticationFilter::class.java)


    }
}
Brian
  • 4,328
  • 13
  • 58
  • 103