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)
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With