Reputation: 1971
I'm working on an implementation of JWT with Spring boot based on some open source projects and some of the documentations found on the net.
What's working? I'm able to generate my tokens and in the first hit when I try to call some secured methods I'm revoked which is good.
What's the problem? Once I generate my token, seems like I'm able to call my secured methods without the need to add my Authorization header.
I was debugging my code and found out that I set the authentication in the SecurityContextHolder but I don't empty this variable once the request is finished. In every implementation found no one does this, so my question is do I have to do this to get my code work as supposed, retrieving secured paths just when there's an authorization header with a valid token?
My code:
WebSecurityConfig class:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserSecurityService userSecurityService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/public").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated();
// And filter other requests to check the presence of JWT in header
http.addFilterBefore(jwtAuthenticationFilterBean(),
UsernamePasswordAuthenticationFilter.class);
// Disable page caching
http.headers().cacheControl();
}
@Bean
public JWTAuthenticationFilter jwtAuthenticationFilterBean() {
return new JWTAuthenticationFilter();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userSecurityService);
}
}
JWTAuthenticationFiler class
public class JWTAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Value("${jwt.token.header}")
private String tokenHeader;
@Autowired
TokenAuthenticationService tokenAuthenticationService;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader(tokenHeader);
String username = tokenAuthenticationService.getUsernameFromToken(token);
if(username != null && SecurityContextHolder.getContext().getAuthentication() != null){
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (tokenAuthenticationService.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
TokenAuthenticationService class
@Service
public class TokenAuthenticationService implements Serializable {
static final String CLAIM_KEY_USERNAME = "sub";
static final String CLAIM_KEY_AUDIENCE = "audience";
static final String CLAIM_KEY_CREATED = "created";
static final String CLAIM_KEY_EXPIRED = "exp";
static final long EXPIRATIONTIME = 864_000_000; // 10 days
static final String SECRET = "ThisIsASecret";
static final String TOKEN_PREFIX = "Bearer";
static final String HEADER_STRING = "Authorization";
@Value("${jwt.token.expiration}")
private Long expiration;
@Value("${jwt.token.secret}")
private String secret;
public String generateToken(UserDetails user) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, user.getUsername());
final Date createdDate = new Date();
claims.put(CLAIM_KEY_CREATED, createdDate);
final Date expirationDate = new Date(createdDate.getTime() + expiration * 1000);
return Jwts.builder()
.setClaims(claims)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
String username;
try{
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
}catch (Exception e){
username = null;
}
return username;
}
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
claims = null;
}
return claims;
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
final Date created = getCreatedDateFromToken(token);
if(userDetails.getUsername().equals(username) && !isTokenExpired(token)){
return true;
}
return false;
}
private boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
private Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
}catch (Exception e){
expiration = null;
}
return expiration;
}
private Date getCreatedDateFromToken(String token) {
Date createdDate;
try{
final Claims claims = getClaimsFromToken(token);
createdDate = new Date((Long) claims.get(CLAIM_KEY_CREATED));
}catch (Exception e){
createdDate = null;
}
return createdDate;
}
}
and this my controller test class
@RestController
public class TestController {
@GetMapping("/public")
public String testPublic(){
return "Welcom to the public place";
}
@GetMapping("/private")
@PreAuthorize("hasRole('USER')")
public String testPrivate(){
return "Welcome to the private place";
}
@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public String testAdmin(){
return "Welcome to the admin place";
}
}
Thank you
Upvotes: 1
Views: 2187
Reputation: 1971
So the answer to my question needs two modifications:
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null)
Upvotes: 1