Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot Security Context return null when using a JWT token

I have created a REST API that require a authentication with JWT.

My implementation is very similar with the code found on https://auth0.com/blog/securing-spring-boot-with-jwts/

When I try to return the current user, I always receive a null return.

My code:

Websecurity:

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().authorizeRequests()
      // login
      .antMatchers(HttpMethod.POST, "/login")
      .permitAll()

      .anyRequest()
      .authenticated()
      .and()
      .addFilterBefore(new JWTLoginFilter(
        "/login", authenticationManager(), logService), UsernamePasswordAuthenticationFilter.class)
      .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}

JWTAuthenticationFilter:

public class JWTAuthenticationFilter extends GenericFilterBean {

  @Override
  public void doFilter(
      ServletRequest req,
      ServletResponse res,
      FilterChain filterChain) throws IOException, ServletException {

    Authentication authentication = TokenAuthenticationService.getAuthentication((HttpServletRequest)req);

    SecurityContextHolder.getContext().setAuthentication(authentication);
    filterChain.doFilter(req, res);
  }
}

I don't included all the code of JWT authentication, because JWT is working ok, user access too. I believe the problem is in the filter or some configuration.

Then, I made a facade to get the current user on a service or controller, with the following code (method 4 on http://www.baeldung.com/get-user-in-spring-security):

public Authentication getAuthentication() {
    return SecurityContextHolder.getContext().getAuthentication();
}

but this don't worked. - SecurityContextHolder.getContext() returned org.springframework.security.core.context.SecurityContextImpl@ffffffff: Null authentication. - SecurityContextHolder.getContext().getAuthentication() returned null object.

Update (and solution):

In my controller, if I use this code:

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();

I can get the current user, but, in my service, the exact same code don't work. But then, I remember that SecurityContext is "lost" on another thread (source: https://docs.spring.io/spring-security/site/docs/current/reference/html/concurrency.html), and my service is async

@Async
public CompletableFuture<Optional<ViewUserDto>> findByLogin(String login) throws InterruptedException {
...
}

So, using the code found here: https://stackoverflow.com/a/40347437/4794469, everything works correctly. I don't known if this can bring any side effects for my code yet (all unit tests worked)

like image 845
Roberto Correia Avatar asked Sep 04 '17 20:09

Roberto Correia


1 Answers

I have worked in an application that has a similar authorization flow as yours:

WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationProvider provider;

    @Autowired
    private TokenAuthenticationService tokenService;

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
        builder.authenticationProvider(provider);       
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().disable();         
        http.csrf().disable();


        http.authorizeRequests().antMatchers(HttpMethod.POST, "/v1/users", "/v1/oauth/token").permitAll()
            .anyRequest().authenticated()
            .and()          
            .addFilterBefore(new OAuthTokenFilter("/v1/oauth/token", authenticationManager(), tokenService), UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(new AuthorizationFilter(tokenService), UsernamePasswordAuthenticationFilter.class);            
    }   

}

AbstractAuthenticationProcessingFilter

public class OAuthTokenFilter extends AbstractAuthenticationProcessingFilter {

    private final ObjectMapper MAPPER = new ObjectMapper();

    private TokenAuthenticationService service;

    public OAuthTokenFilter(String url, AuthenticationManager manager, TokenAuthenticationService service) {
        super(new AntPathRequestMatcher(url));
        setAuthenticationManager(manager);
        this.service = service;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException, IOException, ServletException   {       
        Login login = MAPPER.readValue(request.getInputStream(), Login.class);              
        UsernamePasswordAuthenticationToken token = 
                new UsernamePasswordAuthenticationToken(login.getUsername(), login, Arrays.asList());

        return getAuthenticationManager().authenticate(token);
    }


    @Override
    protected void successfulAuthentication(
            HttpServletRequest request, 
            HttpServletResponse response, 
            FilterChain chain,
            Authentication authentication) throws IOException, ServletException {

        User credentials = (User)  authentication.getPrincipal();                   
        String token = service.jwt(credentials);                
        String json = MAPPER.writeValueAsString(new AuthorizationToken(token, "Bearer"));

        response.addHeader("Content-Type", "application/json");
        response.getWriter().write(json);
        response.flushBuffer();     
    }
}

GenericFilterBean

public class AuthorizationFilter extends GenericFilterBean {

    private TokenAuthenticationService service;

    public AuthorizationFilter(TokenAuthenticationService service) {
        this.service = service;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        Authentication authentication = service.getAuthentication((HttpServletRequest)request);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);
    }

}

TokenAuthenticationService

@Service
public class TokenAuthenticationService {

    public static final String JWT_SECRET_ENV = "JWT_SECRET";
    public static final String ISSUER = "my issuer";

    public static final String ROLE_CLAIM = "role";
    public static final String THIRDY_PARTY_ID_CLAIM = "thirdy_party_id";   
    public static final String TOKEN_PREFIX = "Bearer";
    public static final String HEADER = "Authorization";

    @Autowired
    private Environment environment;

    public Authentication getAuthentication(HttpServletRequest request) {
        String token  = request.getHeader(HEADER);      
        String secret = environment.getProperty(JWT_SECRET_ENV);

        if (token != null) {

            try {

                String bearer = token.replace(TOKEN_PREFIX, "").trim();

                Algorithm algorithm = Algorithm.HMAC256(secret);
                JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(ISSUER)
                    .build();

                DecodedJWT jwt = verifier.verify(bearer);

                User user = new User();
                user.setId(jwt.getSubject());
                user.setThirdPartyId(jwt.getClaim(THIRDY_PARTY_ID_CLAIM).asString());
                user.setRole(jwt.getClaim(ROLE_CLAIM).asString());

                List<GrantedAuthority> authorities =     AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());
                return new UsernamePasswordAuthenticationToken(user, null, authorities);

            } catch (Exception e){
                e.printStackTrace(System.out);
            }
        }

        return null;
    }

}

And then, the controller:

@RestController
public class UserController {

    @ResponseBody
    @GetMapping("/v1/users/{id}")   
    @PreAuthorize("hasAuthority('USER')")
    public User get(@PathVariable("id") String id, Authentication authentication) {     

        User user = (User) authentication.getPrincipal();
        return user;
    }
}
like image 104
Tom Melo Avatar answered Nov 15 '22 16:11

Tom Melo