1
1
.
.
1
1
1
1
J
J
W
W
T
T
(
(
J
J
S
S
O
O
N
N
W
W
e
e
b
b
T
T
o
o
k
k
e
e
n
n
)
)
I
I
n
n
f
f
o
o
[
[
G
G
]
]
[
[
R
R
]
]
This tutorial shows how to implement JWT for Spring Boot Application.
For simplicity reason Username and Role are returned as hard coded Claims.
You can also follow subsequent tutorials to get the same result through multiple steps (recommended for first timers).
JWT Authorities from DB tutorial shows how to use JWT in combination with Authorities from DB.
JWT chapter contains general information about JWT (for those who are not familiar with JWT).
Get JWT http://localhost:8080/GetJWT?enteredUsername=admin&enteredPassword=adminpassword [Results]
Send JWT (Filter gets Authorities from JWT & stores Authentication Object into Context)
Spring Boot Starters
GROUP
DEPENDENCY
DESCRIPTION
Web
Spring Web
Enables @Controller, @RequestMapping and Tomcat Server
Security
Spring Security
Enables Spring Security
O
O
v
v
e
e
r
r
v
v
i
i
e
e
w
w
JWTController contains single Endpoint /GetJWT that receives Username/Password and returns JWT
First it calls MyAuthenticationManager.authenticate() which
compares received Credentials with stored ones (hard coded in this example)
if they match returns Authentication Object with User's Role (hard coded in this example)
Then it calls JWTUtil.createJWT() to create JWT with given Username and Role
MyFilter intercepts HTTP Request and
extracts JWT from Authorization Header jwtUtil.extractJWTFromAuthorizationHeader()
extracts Username and Role from JWT jwtUtil.getClaims(jwt);
creates Authentication Object from Username and Role new UsernamePasswordAuthenticationToken()
stores Authentication Object into Context getContext().setAuthentication(authentication)
MyController
http://localhost:8080/ReadBoook
MyFilter
getHeader("Authorization")
getContext().setAuthentication()
JWT in Authorization Header
"username" : "admin",
"authorities" : "[book.read, book.delete]"
JWTController
MyAuthentication
Manager
JWTUtil
JWT
compares Credentials
returns Authentication Object
returns JWT with
Username/Role
"username" : "admin",
"authorities" : "[CRUD]"
authenticate()
createJWT()
/GetJWT
P
P
r
r
o
o
c
c
e
e
d
d
u
u
r
r
e
e
Edit File: pom.xml (add dependencies)
Create Package: config (inside Package security)
Create Class: JWTUtil.java (Create JWT, extract Claims)
Create Class: MyAuthenticationManager.java (Compare entered Credentials with stored ones)
Create Class: WebSecurityConfig.java (Add anonymous Endpoint "/Authenticate")
Create Class: MyFilter.java (Add anonymous Endpoint "/Authenticate")
Create Package: controllers (inside Package security)
Create Class: JWTController.java (/Authenticate returns JWT. Other Endpoints are for demonstration)
Create Class: MyController.java (Access to restricted /Hello)
pom.xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
JWTUtil.java
package com.ivoronline.springboot_security_jwt.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.HashMap;
import java.util.Map;
@Component
public class JWTUtil {
//USED TO CREATE & DECODE JWT
public final static String SECRET_KEY = "mysecretkey";
//========================================================================
// CREATE JWT
//========================================================================
public String createJWT(String username, String authorities) {
//HEADER (SPECIFY ALGORITHM)
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//PAYLOAD (SPECIFY CLAIMS)
Map<String, Object> customClaims = new HashMap<>();
customClaims.put("username" , username);
customClaims.put("authorities", authorities);
JwtBuilder builder = Jwts.builder()
.setClaims (customClaims) //Place them first not to override subsequent Claims
.setId ("1")
.setSubject("TestJWT")
.setIssuer ("ivoronline");
//SIGNATURE (SPECIFY SECRET KEY)
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//EXTRACT JWT
String jwt = builder.signWith(signatureAlgorithm, signingKey).compact();
//RETURN JWT
return jwt;
}
//========================================================================
// EXTRACT JWT FROM AUTHORIZATION HEADER
//========================================================================
public String extractJWTFromAuthorizationHeader(String authorization) {
//GET AUTHORIZATION HEADER
if (authorization == null || !authorization.startsWith("Bearer ")) {
System.out.println("Authorization Header not found");
return null;
}
//EXTRACT JWT
String jwt = authorization.substring(7);
//RETURN JWT
return jwt;
}
//========================================================================
// GET CLAIMS
//========================================================================
public Claims getClaims(String jwt) {
//GET CLAIMS
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter
.parseBase64Binary(SECRET_KEY))
.parseClaimsJws(jwt)
.getBody();
//RETURN CLAIMS
return claims;
}
//========================================================================
// GET USERNAME
//========================================================================
public String getUsername(String jwt) {
Claims claims = getClaims(jwt);
String username = (String) claims.get("username");
return username;
}
}
MyAuthenticationManager.java
package com.ivoronline.springboot_security_jwt.config;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class MyAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(Authentication enteredAuthentication) {
//GET ENTERED CREDENTIALS
String enteredUsername = (String) enteredAuthentication.getPrincipal(); //ENTERED USERNAME
String enteredPassword = (String) enteredAuthentication.getCredentials(); //ENTERED PASSWORD
//GET STORED CREDENTIALS
//Here they are hard coded (for simplicity reason)
//Call UserDetailsService(enteredUsername) to get UserDetails with Password & Authorities from DB
String storedUsername = "admin";
String storedPassword = "adminpassword";
String storedAuthorities = "book.read, book.delete";
//AUTHENTICATE USER (COMPARE ENTERED AND STORED CREDENTIALS)
if (!enteredUsername.equals(storedUsername)) { System.out.println("Username not found"); return null; }
if (!enteredPassword.equals(storedPassword)) { System.out.println("Incorrect Password"); return null; }
//CREATE AUTHORITIES
String[] authoritiesArray = storedAuthorities.split(", ");
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for(String authority : authoritiesArray) {
authorities.add(new SimpleGrantedAuthority(authority));
}
//CREATE VALIDATED AUTHENTICATION
Authentication validatedAuth = new UsernamePasswordAuthenticationToken(enteredUsername, null,
authorities);
//RETURN VALIDATES AUTHENTICATION
return validatedAuth;
}
}
WebSecurityConfig.java
package com.ivoronline.springboot_security_jwt.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private MyFilter myFilter;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//ANONYMOUS ACCESS
httpSecurity.authorizeRequests().antMatchers("/GetJWT" ).permitAll(); //To get JWT
httpSecurity.authorizeRequests().antMatchers("/CreateJWT" ).permitAll(); //To get JWT
httpSecurity.authorizeRequests().antMatchers("/GetClaims" ).permitAll(); //For Testing
httpSecurity.authorizeRequests().antMatchers("/GetUsername" ).permitAll(); //For Testing
//OTHER CONFIGURATION
httpSecurity.csrf().disable(); //Enables POST
httpSecurity.authorizeRequests().anyRequest().authenticated(); //Authenticated
httpSecurity.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class); //Add Filter
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); //No Session
}
}
MyFilter.java
package com.ivoronline.springboot_security_jwt.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Component
public class MyFilter implements Filter {
@Autowired JWTUtil jwtUtil;
//==================================================================================
// DO FILTER
//==================================================================================
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain)
throws IOException, ServletException {
//CAST TO GET ACCESS TO HEADERS
HttpServletRequest httpRequest = (HttpServletRequest) request;
//GET AUTHORIZATION HEADER
String authorizationHeader = httpRequest.getHeader("Authorization");
//IF AUTHORIZATION HEADER EXISTS USE JWT TO PUT AUTHENTICATION OBJECT INTO CONTEXT
if(authorizationHeader != null) { addAuthenticationObjectIntoContext(authorizationHeader); }
//FORWARD REQUEST
filterchain.doFilter(request, response);
}
//==================================================================================
// ADD AUTHENTICATION OBJECT INTO CONTEXT
//==================================================================================
private void addAuthenticationObjectIntoContext(String authorizationHeader) {
//EXTRACT JWT FROM AUTHORIZATION HEADER
String jwt = jwtUtil.extractJWTFromAuthorizationHeader(authorizationHeader);
//GET CLAIMS
Claims claims = jwtUtil.getClaims(jwt);
String username = (String) claims.get("username");
String authoritiesJWT = (String) claims.get("authorities"); //"[book.read, book.delete]"
//CREATE AUTHORITIES
String authoritiesString = authoritiesJWT.replace("[","").replace("]","").replace(" ","");
String[] authoritiesArray = authoritiesString.split(",");
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for(String authority : authoritiesArray) {
authorities.add(new SimpleGrantedAuthority(authority));
}
//AUTHENTICATE
Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);
//STORE AUTHENTICATION INTO CONTEXT (SESSION)
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
JWTController.java
package com.ivoronline.springboot_security_jwt.controllers;
import com.ivoronline.springboot_security_jwt.config.JWTUtil;
import com.ivoronline.springboot_security_jwt.config.MyAuthenticationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
public class JWTController {
@Autowired JWTUtil jwtUtil;
@Autowired MyAuthenticationManager myAuthenticationManager;
//==================================================================
// GET JWT
//==================================================================
@ResponseBody
@RequestMapping("/GetJWT")
public String getJWT(@RequestParam String enteredUsername, @RequestParam String enteredPassword) {
//AUTHENTICATE (COMPARE ENTERED AND STORED CREDENTIALS)
Authentication enteredAuth = new UsernamePasswordAuthenticationToken(enteredUsername, enteredPassword);
Authentication returnedAuth = myAuthenticationManager.authenticate(enteredAuth);
//CHECK RESULT OF AUTHENTICATION
if(returnedAuth == null) { return "User is NOT Authenticated"; }
//CREATE JWT
String username = (String) returnedAuth.getPrincipal();
String authorities = (String) returnedAuth.getAuthorities().toString(); //"[CREATE, READ]"
String jwt = jwtUtil.createJWT(username, authorities);
//RETURN JWT
return jwt;
}
}
MyController.java
package com.ivoronline.springboot_security_jwt.controllers;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;
@Controller
public class MyController {
@ResponseBody
@RequestMapping("/ReadBook")
@PreAuthorize("hasAuthority('book.read')")
public String hello() {
return "ADMIN can read books";
}
}
R
R
e
e
s
s
u
u
l
l
t
t
s
s
G
G
e
e
t
t
J
J
W
W
T
T
http://localhost:8080/GetJWT?enteredUsername=admin&enteredPassword=adminpassword
https://jwt.io/#debugger (Decoded JWT)
{
"username" : "admin"
"authorities" : "[book.read, book.delete]",
"iss" : "ivoronline",
"sub" : "TestJWT",
"jti" : "1"
}
S
S
e
e
n
n
d
d
J
J
W
W
T
T
(
(
W
W
i
i
t
t
h
h
P
P
o
o
s
s
t
t
m
m
a
a
n
n
i
i
n
n
A
A
u
u
t
t
h
h
e
e
n
n
t
t
i
i
c
c
a
a
t
t
i
i
o
o
n
n
H
H
e
e
a
a
d
d
e
e
r
r
)
)
GET or POST
http://localhost:8080/ReadBook
Headers (add Key-Value)
Authorization: Bearer
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJUZXN0SldUIiwicm9sZSI6IlJPTEVfVVNFUiIsImlzcyI6Iml2b3JvbmxpbmUiLCJqdGkiOiIxIiwi
dXNlcm5hbWUiOiJteXVzZXIifQ.g7CcreAHQvSR1JjQJqTBvT3eGcpz9kcuVOkgUFREiIc
Postman
Application Structure
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>