Boot

Spring Boot Security and JWT Hello World Example

Hi Java Code Geeks. Come follow me as we create a Spring Boot Security and JWT (JSON Web Token) Hello World application. This article is best understood by following it from top to bottom. Concepts and definitions will be explained along the way. This article is not for the absolute beginner. It is assumed that you know your way around Eclipse. You are familiar with Maven and Spring. Basically, this article assumes that you have done some coding. So let’s get to it.

You can also check this tutorial in the following video:

Spring Boot Security JWT Video

1. Tools

  1. Apache Maven
  2. Oxygen Eclipse
  3. Spring Boot
  4. Spring Boot Security
  5. Spring Security Reference
  6. JWT
  7. Postman

2. Spring Initializr

Let’s bootstrap our application. Head over to Spring Initializr. Select the following options:

  • Project: Maven Project
  • Language: Java
  • Spring Boot: 2.1.9 as of this writing
  • Group: com.javacodegeeks.example
  • Artifact: spring-boot-security-jwt
  • Options > Package Name: com.javacodegeeks.example
  • Options > Packaging: Project: Jar
  • Options > Java: Project: 8
  • Dependencies: Project: Spring Web, Spring Security

The Spring Initializr web page looks something like below.

spring boot security and jwt - spring initializer
Spring Initializer

Click Generate and we should get a spring-boot-security-jwt.zip. Let’s import the generated Maven project in Eclipse. We now have a barebones Spring Boot app. Check that your pom.xml has the following dependencies, spring-boot-starter-web and spring-boot-starter-security. We should also have a SpringBootSecurityJwtApplication.java source file.

3. Without Spring Security

First off, comment out the spring-boot-starter-security dependency and add a /hello REST endpoint. Your POM should look like the one below (some parts are snipped):

pom.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
     
    ...
    <dependencies>
                ...
        <!--
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        -->
                ...
    </dependencies>
    ...
</project>

Your java code should look like the one below (some parts are snipped):

SpringBootSecurityJwtApplication.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
...
@SpringBootApplication
@RestController
public class SpringBootSecurityJwtApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(SpringBootSecurityJwtApplication.class, args);
    }
 
    @RequestMapping("/hello")
    public String hello() {
        return "Hello world";
    }
}

The @SpringBootApplication annotation is a convenient alternative to @Configuration, @EnableAutoConfiguration, and @ComponentScan. This annotation tells Spring Boot to scan for other components, add beans based on the classpath, and tags the class as a source of bean definitions. The @RestController annotation is convenient alternative to @Controller and @ResponseBody. Types that carry this annotation are treated as controllers where @RequestMapping methods assume @ResponseBody semantics by default. @RequestMapping is an annotation for mapping web requests onto methods in request-handling classes with flexible method signatures.

Run the application and access http://localhost:8080/hello using Postman. Postman will return “Hello world.” as shown below.

spring boot security and jwt - no spring security yet
No Spring Security Yet

4. With Spring Security

Now that we are serving “Hello world.” through our REST endpoint, let’s add security to it. Stop the application and uncomment the spring-boot-starter-security dependency. Run the application again access “/hello” using a browser or Postman. What did the browser return? What did Postman return? On the browser, you were prompted to login. On postman, you got a 401 unauthorized error. Spring security automatically secured our endpoints.

spring boot security and jwt - postman 401 unauthorized
Postman 401 Unauthorized

Let’s configure spring security. Create SignInDto.java and WebSecurityConfiguration.java.

SignInDto.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.javacodegeeks.example;
 
import javax.validation.constraints.NotNull;
 
public class SignInDto {
 
    @NotNull
    private String username;
 
    @NotNull
    private String password;
 
    protected SignInDto() {}
 
    public SignInDto(String username, String password) {
        this.username = username;
        this.password = password;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
}

This class is fairly self-explanatory. It’s a Data Transfer Object with getters and setters.

WebSecurityConfiguration.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.javacodegeeks.example;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
 
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private UserDetailsService userDetailsService;
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .csrf().disable()
        .authorizeRequests()
            .antMatchers("/signin").permitAll()
            .anyRequest().authenticated()
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
 
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
 
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();
 
        return new InMemoryUserDetailsManager(user);
    }
}

The @Configuration indicates that this class declares one or more @Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime. Having a field @Autowired means that it will be automatically wired by Spring’s dependency injection facilities. It’s the same as @Inject in JavaEE. The authenticationManagerBean() and userDetailsService() methods produces a bean to be managed by the Spring container. We extend the WebSecurityConfigurerAdapter for creating a WebSecurityConfigurer instance which allows customization to the WebSecurity. We override the configure method in order to change the default behavior of the HTTP security object. We will tell Spring security how to handle different APIs. We permit all access to “/signin” but any other request must be authenticated. We disabled cross-site request forgery detection because our restful API is stateless and that no sessions should be created for it. So session creation policy is stateless.

Then we add a few lines in SpringBootSecurityJwtApplication.java like so.

SpringBootSecurityJwtApplication.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.javacodegeeks.example;
 
import javax.validation.Valid;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@SpringBootApplication
@RestController
public class SpringBootSecurityJwtApplication {
 
    @Autowired
    private AuthenticationManager authenticationManager;
 
    public static void main(String[] args) {
        SpringApplication.run(SpringBootSecurityJwtApplication.class, args);
    }
 
    @RequestMapping("/hello")
    public String hello() {
        return "Hello world";
    }
 
    @PostMapping("/signin")
    public Authentication signIn(@RequestBody @Valid SignInDto signInDto) {
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(signInDto.getUsername(), signInDto.getPassword()));
    }
}

For mapping HTTP POST requests onto specific handler methods, we use @PostMapping. Which means the signIn method will handle HTTP POST requests to the “/signin” endpoint. We are applying automatic validation to the argument with @Valid. The @RequestBody annotation indicates that the method parameter should be bound to the body of the web request. The JSON marshalling is automatically done by Spring. Let’s try what we have so far. Using Postman, send an HTTP POST to “/signin”. Make sure the Content-Type is application/json and the username and password is in the request body in JSON format. Try it with a wrong password or username and you’ll get a 403 forbidden access denied. Your response should look like the one below.

spring boot security and jwt - with spring security
With Spring Security

Merely adding the authentication manger to the application context and implementing user details service was all we need for this step. But we still can’t access “/hello”. Accessing it gives us a 403 forbidden access denied. This is where Spring Boot Security and JWT comes in.

5. Spring Boot Security and JWT

In a nutshell, here’s what we need to do to have Spring Boot Security and JWT working together.

  • Add the jjwt dependency. This library handles the JSON Web Token stuff.
  • Create new classes: JwtTokenProvider (creating the token), JwtTokenFilter (web filter that handles JWT validation).
  • Edit WebSecurityConfiguration, application.properties, and “/signin” so that it takes JWT into account.

Adding the jjwt dependency should be straightforward. We are adding a third party library because Spring does support JWT out of the box. Your POM should look like the one below (some parts are snipped):

pom.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
     
    ...
    <dependencies>
                ...
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
                ...
    </dependencies>
    ...
</project>

Next is the JwtTokenProvider class.

JwtTokenProvider.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.javacodegeeks.example;
 
import java.util.Base64;
import java.util.Date;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
 
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
 
@Component
public class JwtProvider {
 
    private String secretKey;
    private long validityInMilliseconds;
 
    @Autowired
    public JwtProvider(@Value("${security.jwt.token.secret-key}") String secretKey,
                       @Value("${security.jwt.token.expiration}") long milliseconds) {
 
        this.secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
        this.validityInMilliseconds = milliseconds;
    }
 
    public String createToken(String username) {
        //Add the username to the payload
        Claims claims = Jwts.claims().setSubject(username);
 
        //Build the Token
        Date now = new Date();
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + validityInMilliseconds))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }
}

The @Component tells Spring that this class is considered as a candidate for auto-detection when using annotation-based configuration and classpath scanning. The @Value tells Spring to get the value from the properties file using the specified key. The createToken method creates the JWT token using the jjwt library. The username is the subject a.k.a. the payload. It is signed using the secret key from the properties file and the validity of the token is also specified in the properties file. The resulting token will have the format of Header.Payload.Signature. The header contains the type (JSON Web Token) and hashing algorithm (HMAC SHA256). The payload a.k.a. claims contains the subject (sub) of the token, expiration date in numeric date value (exp), time the JWT was issued (iat), unique JWT identifier (jti), and application specific key value pairing separeted by a colon. The signature is the hash value of the header and payload using the secret key embedded in the application.

Here we have the application.properties. Expiration is in milliseconds, so the below configuration is 20 minutes.

application.properties

1
2
security.jwt.token.secret-key=jwt-token-secret-key-for-encryption
security.jwt.token.expiration=1200000

Next is the JwtTokenFilter class.

JwtTokenFilter.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.javacodegeeks.example;
 
import java.io.IOException;
import java.util.Base64;
import java.util.Date;
import java.util.Optional;
import java.util.function.Function;
 
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
 
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.GenericFilterBean;
 
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
 
public class JwtTokenFilter extends GenericFilterBean {
    private String secret;
 
    private static final String BEARER = "Bearer";
 
    private UserDetailsService userDetailsService;
 
    public JwtTokenFilter(UserDetailsService userDetailsService, String secret) {
        this.userDetailsService = userDetailsService;
        this.secret = Base64.getEncoder().encodeToString(secret.getBytes());
    }
 
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
            throws IOException, ServletException {
 
        String headerValue = ((HttpServletRequest)req).getHeader("Authorization");
        getBearerToken(headerValue).ifPresent(token-> {
            String username = getClaimFromToken(token, Claims::getSubject);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
 
            if (username.equals(userDetails.getUsername()) && !isJwtExpired(token)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(
                        new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest)req));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        });
 
        filterChain.doFilter(req, res);
    }
 
    private Optional getBearerToken(String headerVal) {
        if (headerVal != null && headerVal.startsWith(BEARER)) {
            return Optional.of(headerVal.replace(BEARER, "").trim());
        }
        return Optional.empty();
    }
 
    private  T getClaimFromToken(String token, Function<claims, t=""> claimsResolver) {
        final Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        return claimsResolver.apply(claims);
    }
 
    private Boolean isJwtExpired(String token) {
        Date expirationDate = getClaimFromToken(token, Claims::getExpiration);
        return expirationDate.before(new Date());
    }
}
 

This is the same as a web filter in JavaEE. This web filter checks if the token is expired, isJwtExpired. It parses the payload, getClaimsFromToken. It strips the bearer string, which is the value of the authorization header. If the token passes all the checks, the doFilter method will configure Spring security to manually set the authentication in the context. We specify that the current user is authenticated making it pass successfully.

Next is to add some lines in WebSecurityConfiguration class.

WebSecurityConfiguration.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.javacodegeeks.example;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
 
    @Value("${security.jwt.token.secret-key}")
    private String secret;
 
    @Autowired
    private UserDetailsService userDetailsService;
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .csrf().disable()
        .authorizeRequests()
            .antMatchers("/signin").permitAll()
            .anyRequest().authenticated()
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
 
        http.addFilterBefore(new JwtTokenFilter(userDetailsService, secret), UsernamePasswordAuthenticationFilter.class);
    }
 
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
 
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();
 
        return new InMemoryUserDetailsManager(user);
    }
}

Did you notice what lines were added? Of course you have! It is highlighted. We have injected the secret key and added our JWT token filter.

Lastly, we changed the “/signin” method handler in the SpringBootSecurityJwtApplication class.

SpringBootSecurityJwtApplication.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.javacodegeeks.example;
 
import javax.validation.Valid;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@SpringBootApplication
@RestController
public class SpringBootSecurityJwtApplication {
 
    @Autowired
    private AuthenticationManager authenticationManager;
 
    @Autowired
    private JwtProvider jwtProvider;
 
    public static void main(String[] args) {
        SpringApplication.run(SpringBootSecurityJwtApplication.class, args);
    }
 
    @RequestMapping("/hello")
    public String hello() {
        return "Hello world";
    }
 
//  @PostMapping("/signin")
//  public Authentication signIn(@RequestBody @Valid SignInDto signInDto) {
//      return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(signInDto.getUsername(), signInDto.getPassword()));
//  }
 
    @PostMapping("/signin")
    public String signIn(@RequestBody @Valid SignInDto signInDto) {
        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(signInDto.getUsername(), signInDto.getPassword()));
            return jwtProvider.createToken(signInDto.getUsername());
        } catch (AuthenticationException e){
            System.out.println("Log in failed for user, " + signInDto.getUsername());
        }
 
        return "";
    }
 
}

What have we added? We have injected the JwtProvider and our sign in handler returns a token if the username and password are correct. Let’s see it in action. Run the application and send a POST to “/signin” with the correct credentials and it should return a JSON Web Token similar to the image below.

spring boot security and jwt token response
JWT Token Response

Copy the token and create a GET request to “/hello”. The header should have a key value paring of Authorization and Bearer plus the token. Take note that there is a space between the “Bearer” and the token. The endpoint should now return “Hello world” like the image below.

spring boot security and jwt header authorization bearer
JWT Header Authorization Bearer

6. Spring Boot Security and JWT Summary

And there you have it. The Spring Boot Security and JWT Hello World Example is finished. In summary, when the sign in authentication passes we create a JSON Web token and return it to the caller. The caller then places the JWT in the header with an authorization key in its subsequent GET requests. A web filter checks the validity of the token. If valid, the web filter let it pass through the filter chain and returns “Hello world”. Hope you had fun following this example.

7. Download the Source Code

This is an example about Spring Boot Security and JWT.

Download
You can download the source code of this example here: Spring Boot Security and JWT Hello World Example

Joel Patrick Llosa

I graduated from Silliman University in Dumaguete City with a degree in Bachelor of Science in Business Computer Application. I have contributed to many Java related projects at Neural Technologies Ltd., University of Southampton (iSolutions), Predictive Technologies, LLC., Confluence Service, North Concepts, Inc., NEC Telecom Software Philippines, Inc., and NEC Technologies Philippines, Inc. You can also find me in Upwork freelancing as a Java Developer.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button