I am new to JWT. There isn't much information available in the web, since I came here as a last resort. I already developed a spring boot application using spring security using spring session. Now instead of spring session we are moving to JWT. I found few links and now I can able to authenticate a user and generate token. Now the difficult part is, I want to create a filter which will be authenticate every request to the server,
The JSON Web Token (JWT) Authentication filter checks if the incoming request has a valid JSON Web Token (JWT). It checks the validity of the JWT by verifying the JWT signature, audiences and issuer based on the HTTP filter configuration.
JWT is a particularly useful technology for API authentication and server-to-server authorization.
Like local storage, session storage is accessible by any javascript code running on the same domain that the web application is hosted. So the only thing that changes, is that when a user closes their browser, the JWT will disappear and the user will have to login again in their next visit to your web application.
Here is a filter that can do what you need :
public class JWTFilter extends GenericFilterBean { private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class); private final TokenProvider tokenProvider; public JWTFilter(TokenProvider tokenProvider) { this.tokenProvider = tokenProvider; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String jwt = this.resolveToken(httpServletRequest); if (StringUtils.hasText(jwt)) { if (this.tokenProvider.validateToken(jwt)) { Authentication authentication = this.tokenProvider.getAuthentication(jwt); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(servletRequest, servletResponse); this.resetAuthenticationAfterRequest(); } catch (ExpiredJwtException eje) { LOGGER.info("Security exception for user {} - {}", eje.getClaims().getSubject(), eje.getMessage()); ((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED); LOGGER.debug("Exception " + eje.getMessage(), eje); } } private void resetAuthenticationAfterRequest() { SecurityContextHolder.getContext().setAuthentication(null); } private String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader(SecurityConfiguration.AUTHORIZATION_HEADER); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { String jwt = bearerToken.substring(7, bearerToken.length()); return jwt; } return null; } }
And the inclusion of the filter in the filter chain :
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { public final static String AUTHORIZATION_HEADER = "Authorization"; @Autowired private TokenProvider tokenProvider; @Autowired private AuthenticationProvider authenticationProvider; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(this.authenticationProvider); } @Override protected void configure(HttpSecurity http) throws Exception { JWTFilter customFilter = new JWTFilter(this.tokenProvider); http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); // @formatter:off http.authorizeRequests().antMatchers("/css/**").permitAll() .antMatchers("/images/**").permitAll() .antMatchers("/js/**").permitAll() .antMatchers("/authenticate").permitAll() .anyRequest().fullyAuthenticated() .and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll() .and().logout().permitAll(); // @formatter:on http.csrf().disable(); } }
The TokenProvider class :
public class TokenProvider { private static final Logger LOGGER = LoggerFactory.getLogger(TokenProvider.class); private static final String AUTHORITIES_KEY = "auth"; @Value("${spring.security.authentication.jwt.validity}") private long tokenValidityInMilliSeconds; @Value("${spring.security.authentication.jwt.secret}") private String secretKey; public String createToken(Authentication authentication) { String authorities = authentication.getAuthorities().stream().map(authority -> authority.getAuthority()).collect(Collectors.joining(",")); ZonedDateTime now = ZonedDateTime.now(); ZonedDateTime expirationDateTime = now.plus(this.tokenValidityInMilliSeconds, ChronoUnit.MILLIS); Date issueDate = Date.from(now.toInstant()); Date expirationDate = Date.from(expirationDateTime.toInstant()); return Jwts.builder().setSubject(authentication.getName()).claim(AUTHORITIES_KEY, authorities) .signWith(SignatureAlgorithm.HS512, this.secretKey).setIssuedAt(issueDate).setExpiration(expirationDate).compact(); } public Authentication getAuthentication(String token) { Claims claims = Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody(); Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(AUTHORITIES_KEY).toString().split(",")).stream() .map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList()); User principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, "", authorities); } public boolean validateToken(String authToken) { try { Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(authToken); return true; } catch (SignatureException e) { LOGGER.info("Invalid JWT signature: " + e.getMessage()); LOGGER.debug("Exception " + e.getMessage(), e); return false; } } }
Now to answer your questions :
/login
URI (/authenticate
in my code)I will focus in the general tips on JWT, without regarding code implemementation (see other answers)
How will the filter validate the token? (Just validating the signature is enough?)
RFC7519 specifies how to validate a JWT (see 7.2. Validating a JWT), basically a syntactic validation and signature verification.
If JWT is being used in an authentication flow, we can look at the validation proposed by OpenID connect specification 3.1.3.4 ID Token Validation. Summarizing:
iss
contains the issuer identifier (and aud
contains client_id
if using oauth)
current time between iat
and exp
Validate the signature of the token using the secret key
sub
identifies a valid user
If someone else stolen the token and make rest call, how will I verify that.
Possesion of a JWT is the proof of authentication. An attacker who stoles a token can impersonate the user. So keep tokens secure
Encrypt communication channel using TLS
Use a secure storage for your tokens. If using a web front-end consider to add extra security measures to protect localStorage/cookies against XSS or CSRF attacks
set short expiration time on authentication tokens and require credentials if token is expired
How will I by-pass the login request in the filter? Since it doesn't have authorization header.
The login form does not require a JWT token because you are going to validate the user credential. Keep the form out of the scope of the filter. Issue the JWT after successful authentication and apply the authentication filter to the rest of services
Then the filter should intercept all requests except the login form, and check:
if user authenticated? If not throw 401-Unauthorized
if user authorized to requested resource? If not throw 403-Forbidden
Access allowed. Put user data in the context of request( e.g. using a ThreadLocal)
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