If you’ve never heard of JWTs (JSON Web Tokens), well, you don’t work in tech, or you’ve purposely unplugged your computer from the Internet. JWTs are frequently used in OAuth2 as access and refresh tokens as well as a variety of other applications.

JWTs can be used wherever you need a stand-in to represent a “user” of some kind (in quotes, because the user could be another microservice). And, they’re used where you want to carry additional information beyond the value of the token itself and have that information cryptographically verifiable as security against corruption or tampering.

For more information on the background and structure of JWTs, here’s the IETF specification

The code that backs this post can be found on GitHub.

Spring Security & CSRF Protection

CSRF (Cross Site Request Forgery) is a technique in which an attacker attempts to trick you into performing an action using an existing session of a different website.

Spring Security when combined with Thymeleaf templates, automatically inserts a token into all web forms as a hidden field. This token must be present on form submission, or a Java Exception is thrown. This mitigates the risk of CSRF as an external site (an attacker) would not be able to reproduce this token.

For this sample project, the following dependencies are all that’s required to get Spring Boot, Spring Security, and Thymeleaf:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> 1 2 3 4 5 6 7 8 9 10 11 < dependencies > < dependency > < groupId > org . springframework . boot < / groupId > < artifactId > spring - boot - starter - thymeleaf < / artifactId > < / dependency > < dependency > < groupId > org . springframework . boot < / groupId > < artifactId > spring - boot - starter - security < / artifactId > < / dependency > < / dependencies >

Here’s a simple Thymeleaf form:

<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <body> <form method="post" th:action="@{/jwt-csrf-form}"> <input type="submit" class="btn btn-primary" value="Click Me!"/> </form> </body> </html> 1 2 3 4 5 6 7 8 9 < ! DOCTYPE html > < html lang = "en" xmlns : th = "http://www.thymeleaf.org" > < body > < form method = "post" th : action = "@{/jwt-csrf-form}" > < input type = "submit" class = "btn btn-primary" value = "Click Me!" / > < / form > < / body > < / html >

Notice the xmlns:th attribute in the html tag as well as th:action attribute of the form tag. It’s these attributes that triggers Spring Security to inject the CSRF protection token into the form. Here’s what that looks like:

<input type="hidden" name="_csrf" value="72501b07-8205-491d-ba95-ebab5cf450de" /> 1 2 < input type = "hidden" name = "_csrf" value = "72501b07-8205-491d-ba95-ebab5cf450de" / >

This is what’s called a “dumb” token. Spring Security keeps a record of this token, usually in the user’s session. When the form is submitted, it compares the value of the token to what Spring Security has on record. If the token is not present or is not the right value, an Exception is thrown.

We can improve on this using a JWT in the following ways:

Ensure that a given token can only be used once by using a nonce cache

Set a short expiration time for added security

Verify that the token hasn’t been tampered with using cryptographic signatures

Switching to JWTs for CSRF Protection

The JJWT (Java JWT) library is the premier open-source Java library for working with JSON Web Tokens. It’s clean design including a fluent interface has led to over 1,000 stars on Github.

We can easily add the JJWT library to our project by dropping in the following dependency:

<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jjwt.version}</version> </dependency> 1 2 3 4 5 6 < dependency > < groupId > io . jsonwebtoken < / groupId > < artifactId > jjwt < / artifactId > < version > $ { jjwt . version } < / version > < / dependency >

Spring Security makes it easy to override the default CSRF behavior. We add three components to make this happen:

CSRF Token Repository

CSRF Token Validator

Spring Security Configuration

CSRF Token Repository

Implementing the CsrfTokenRepository interface requires three methods: generateToken , saveToken , and loadToken .

Here’s our generateToken method:

@Override public CsrfToken generateToken(HttpServletRequest request) { String id = UUID.randomUUID().toString().replace("-", ""); Date now = new Date(); Date exp = new Date(now.getTime() + (1000*30)); // 30 seconds String token = Jwts.builder() .setId(id) .setIssuedAt(now) .setNotBefore(now) .setExpiration(exp) .signWith(SignatureAlgorithm.HS256, secret) .compact(); return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @ Override public CsrfToken generateToken ( HttpServletRequest request ) { String id = UUID . randomUUID ( ) . toString ( ) . replace ( "-" , "" ) ; Date now = new Date ( ) ; Date exp = new Date ( now . getTime ( ) + ( 1000 * 30 ) ) ; // 30 seconds String token = Jwts . builder ( ) . setId ( id ) . setIssuedAt ( now ) . setNotBefore ( now ) . setExpiration ( exp ) . signWith ( SignatureAlgorithm . HS256 , secret ) . compact ( ) ; return new DefaultCsrfToken ( "X-CSRF-TOKEN" , "_csrf" , token ) ; }

Here we see the JJWT fluent interface in action. We chain all the claims settings together and call the compact terminator method to give us the final JWT string. Most importantly, this JWT will expire after 30 seconds.

The saveToken and loadToken methods do just what they say. In this example, they are saving the token to and loading the token from the user’s session.

CSRF Token Validator

Spring Security will already do the “dumb” part of the CSRF check and verify that the string it has stored matches the string that’s passed in exactly. In addition, we want to leverage the the information encoded in the JWT. This is implemented as a filter.

Here’s the core of the JwtCsrfValidatorFilter :

// CsrfFilter already made sure the token matched. Here, we'll make sure it's not expired try { Jwts.parser() .setSigningKeyResolver(secretService.getSigningKeyResolver()) .parseClaimsJws(token.getToken()); } catch (JwtException e) { // most likely an ExpiredJwtException, but this will handle any request.setAttribute("exception", e); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); RequestDispatcher dispatcher = request.getRequestDispatcher("expired-jwt"); dispatcher.forward(request, response); } 1 2 3 4 5 6 7 8 9 10 11 12 13 // CsrfFilter already made sure the token matched. Here, we'll make sure it's not expired try { Jwts . parser ( ) . setSigningKeyResolver ( secretService . getSigningKeyResolver ( ) ) . parseClaimsJws ( token . getToken ( ) ) ; } catch ( JwtException e ) { // most likely an ExpiredJwtException, but this will handle any request . setAttribute ( "exception" , e ) ; response . setStatus ( HttpServletResponse . SC_BAD_REQUEST ) ; RequestDispatcher dispatcher = request . getRequestDispatcher ( "expired-jwt" ) ; dispatcher . forward ( request , response ) ; }

If the JWT is parseable, processing will continue. As you can see in the catch block, if parsing the JWT fails for any reason, we forward the request to an error page.

Spring Security Configuration

The Spring Security configuration ties it all together by registering our CSRF Token Repository with Spring Security. Here’s the configure method:

protected void configure(HttpSecurity http) throws Exception { http .addFilterAfter(new JwtCsrfValidatorFilter(), CsrfFilter.class) .csrf() .csrfTokenRepository(jwtCsrfTokenRepository) .ignoringAntMatchers(ignoreCsrfAntMatchers) .and() .authorizeRequests() .antMatchers("/**") .permitAll(); } 1 2 3 4 5 6 7 8 9 10 11 12 protected void configure ( HttpSecurity http ) throws Exception { http . addFilterAfter ( new JwtCsrfValidatorFilter ( ) , CsrfFilter . class ) . csrf ( ) . csrfTokenRepository ( jwtCsrfTokenRepository ) . ignoringAntMatchers ( ignoreCsrfAntMatchers ) . and ( ) . authorizeRequests ( ) . antMatchers ( "/**" ) . permitAll ( ) ; }

Line 3 adds our validator filter after the default Spring Security CSRF Filter.

Line 6 tells Spring Security to use our JWT CSRF Token Repository instead of the default one.

JWT CSRF Protection in Action

To run the sample app, clone the GitHub repo and execute:

cd JavaRoadStorm2016/roadstorm-jwt-csrf-tutorial mvn clean install mvn spring-boot:run 1 2 3 4 cd JavaRoadStorm2016 / roadstorm - jwt - csrf - tutorial mvn clean install mvn spring - boot : run

In your browser, you can go to: http://localhost:8080 . You will see a humble button:

If you view source, you can see how things are setup:

NOTE: When you view source, it will invalidate the token in the page since a new one is fetched to show source. Just refresh the original page.

If you wait for more than 30 seconds and click the button, you will see an error:

Next Up: Securing Microservices with JWTs

In this post, we’ve seen the benefit of using JWTs for CSRF protection with Spring Security.

JWTs are very powerful for general token use due to their inherent smarts – the encoded claims within them as well as their ability to be cryptographically verified.

In the next post, we’ll dive a little deeper in an example for establishing trust and communicating between microservices. Have questions or feedback? Leave a comment or hit me up on Twitter @afitnerd!