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:
1. Tools
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.
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 | < project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > ... < 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.
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.
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.
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 | < project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > ... < 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.
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.
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.
You can download the source code of this example here: Spring Boot Security and JWT Hello World Example