Securing Spring Boot Microservices Using JWT Token

unsplash-logoJR Korpa
In Last few articles, We Discuss Different aspects of Microservices. In this post we will add Security to our rest services using Json Web Token And Spring Security
There are multiple options available for adding Authentication and Authorization, Today we will be focusing on JSON Web Tokens, or JWT in short.

What Is JSON Web Tokens (JWT)?

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. JWT Token has three Parts Header, Payload & Signature

Header of the JWT contains information about how the JWT signature should be computed. Header contains information about type and hashing algorithm used.

Header
1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

Payload contains the data also referred as claims. The Internet drafts define the certain standard fields (“claims”) that can be used inside a JWT claim set. Such as Issuer, Subject, Issued at, JWT ID etc You can Find full list Here. We Can also pass other needed information using private claims.
Payload
1
2
3
4
5
6
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"Roles":"Admin"
}

Signature Make sure that the token is not changed on the way. Signature is created by taking the header and payload together and passing it through the specified algorithm along with a known secret.

For More Details about JWT Please check jwt.io

The code for this post is available for download here.

Sample Application Using JWT And Spring Security

An overview of the security mechanism that we will be using in our sample application.

Client will call Authenticate Endpoint by providing valid Username and Password to get The Token
Clients will send this JWT token in the Authorization header for all the requests to access any protected resources. For Valid Tokens, Access will be granted to resources.
In Case of missing Token or invalid token, Response code 401 unauthorized will be return.

We will also Add Role Base Authentication. Student Endpoint Will be accessible to Users having ADMIN or USER roles. And Subject Endpoint will be accessible to Users having ADMIN roles.

This Will be long post, So I divided it into multiple Steps. Github repo has Branch corresponding to each Step

Step 1: Create Spring Boot Rest Endpoints

Create Two Simple Rest endpoints For Our Student and Subject Domain objects. User Login Details are saved in User table using User Entity.

Setting Up Things For playing with jWT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

//Student Resource
@RestController
@RequestMapping("/Student")
public class StudentResource {
private final StudentRepository studentRepository;
public StudentResource(StudentRepository studentRepository) {
this.studentRepository=studentRepository;
}
@GetMapping
public List<Student> getAllStudents(){
return studentRepository.findAll();
}
}
//Subject Resource
@RestController
@RequestMapping("/Subject")
public class SubjectResource {
private final SubjectRepository subjectRepository;

public SubjectResource(SubjectRepository subjectRepository) {
this.subjectRepository = subjectRepository;
}
@GetMapping
public List<Subject> getAllSubjects(){
return subjectRepository.findAll();
}
}

Add User Entity to Store userId & password in in-memory database. Password are stored in BCrypt encrypted Format

Setting Up Things For playing with jWT
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
@Id
private String email;
private String password;
private String role;
}
}

Populate Database during application Startup using ApplicationRunner

Setting Up Things For playing with jWT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@SpringBootApplication
public class GwtTokenApplication {

public static void main(String[] args) {
SpringApplication.run(GwtTokenApplication.class, args);
}
@Bean
ApplicationRunner init(UserRepository userRepository, StudentRepository studentRepository, SubjectRepository subjectRepository) {
return (ApplicationArguments args) -> dataSetup(userRepository,studentRepository,subjectRepository);
}

private void dataSetup(UserRepository userRepository, StudentRepository studentRepository, SubjectRepository subjectRepository) {
User niraj = new User("niraj.sonawane@gmail.com", "$2a$10$yRxRYK/s8vZCp.bgmZsD/uXmHjekuPU/duM0iPZw04ddt1ID9H7kK", "Admin");
User test = new User("test@gmail.com", "$2a$10$YWDqYU0XJwwBogVycbfPFOnzU7vsG/XvAyQlrN34G/oA1SbhRW.W.", "User");
userRepository.save(niraj);
userRepository.save(test);

Student student1 = new Student(1L,"Ram");
Student student2 = new Student(2L,"Sham");
studentRepository.save(student1);
studentRepository.save(student2);

Subject math = new Subject(1l,"Math");
Subject science = new Subject(2l,"Science");
subjectRepository.save(math);
subjectRepository.save(science);
}
}

At This point, We have Setup Two Reset endpoints and User Table to Store UserID and Password.

Step 2: Add Authentication Endpoint To Return JWT Token and Secure All Other Endpoint

Student and Subject endpoints should be accessible only if Valid token is provided. Authenticate endpoint Should be accessible to everyone.

SecurityConfig
1
2
3
4
5
6
7
8
9
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
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserAuthDetailsService userAuthDetailsService;
@Autowired
private InvalidLoginAttemptHandler invalidLoginAttemptHandler;

@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userAuthDetailsService)
.passwordEncoder(passwordEncoder());

}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {

http
.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(invalidLoginAttemptHandler)
.and()
.authorizeRequests()
.antMatchers("/authenticate/**")
.permitAll()
.anyRequest().authenticated();
}
}

lets discuss, What Configurations we have done here.

@EnableWebSecurity This is main Spring annotation that is used to enable web security in a project.

WebSecurityConfigurerAdapter This class provides default security configurations and allows other classes to extend it and customize the security configurations by overriding its methods.

UserAuthDetailsService - This class is needed by Spring security to get the user details from Database, LDAP or other other user store.UserAuthDetailsService Service loads the user details from our User Table.

AuthenticationManager This class uses UserAuthDetailsService to do user Authentication.

InvalidLoginAttemptHandler This class is responsible for taking action if Authentication fails.

.antMatchers("/authenticate/**").permitAll().anyRequest().authenticated() This Configuration allows any user to access our authenticate endpoint and all other Request need to be authenticated.

Authenticate User and Create JWT Token

AuthResource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/authenticate")
@Slf4j
public class AuthResource {

@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JWTTokenProvider jwtTokenProvider;
@PostMapping
public ResponseEntity authenticateUser(@RequestBody AuthenticateRequest authenticateRequest) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticateRequest.getUserName(), authenticateRequest.getPassword()));
String token =jwtTokenProvider.generateToken((UserPrincipal)authentication.getPrincipal());
log.info("Token Created {}",token);
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
}
}

JWTTokenProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class JWTTokenProvider {

@Value("${jwt.secret}")
private String jwtSecret;

@Value("${jwt.expirationInMs}")
private int jwtExpirationInMs;

public String generateToken(UserPrincipal userPrincipal){
List<String> roles = userPrincipal
.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
return Jwts.builder().setIssuer("Demo App")
.setIssuedAt(new Date())
.setExpiration(new Date(new Date().getTime() + jwtExpirationInMs))
.claim("Roles",roles)
.signWith(SignatureAlgorithm.HS512,jwtSecret).compact();
}
}

After successful authentication of User we create JWT Token using jsonwebtoken library. jsonwebtoken provides fluent api to create JWT Token.
We are Adding Roles in Claim. We Can Use these role for role based authorization.
In Case authentication fails, InvalidLoginAttemptHandler Will be called which we have configured in exceptionHandling section of our SecurityConfig.

Now If we Call authenticate endpoints with Valid userid and password, JWT Token will send back in Response.

curl -X POST http://localhost:8080/authenticate -H "Content-Type:application/json" -d "{\"userName\":\"niraj.sonawane@gmail.com\",\"password\":\"test\"}"

Step 3: Add AuthenticationFilter To Get JWT token from the request and Validate It

After receiving jwt token, Clients Need to pass this token in Authorization header to access the protected resource, in our case student or subject resource.
Sample curl for same

curl
1
2
curl -X GET http://localhost:8080/Subject -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJEZW1vIEFwcCIsInN1YiI6InRlc3RAZ21haWwuY29tIiwiaWF0IjoxNTYzMTAwODk2LCJleHAiOjE1NjMxNTA4OTYsIlJvbGVzIjpbIlJPTEVfVVNFUiJdfQ.XSUpdBhRkXL0b1U5gD0y-siCrSMMzQaJupV4bJOTnAA7txYmDNTZ8O18ueCG72K7XdwueLZGXWX5C2NtCWghaA"
}

JwtAuthenticationFilter Filter will be called OncePerRequest and will validate the provided token. After Successful Validation of Token We need to pass on UsernamePasswordAuthenticationToken to filter chain using SecurityContextHolder

JwtAuthenticationFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JWTTokenProvider tokenProvider;
@Autowired
private UserAuthDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.info("Validating Token!!!!!");
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
logger.info("Token is Valid ");
String userNameFromToken = tokenProvider.getUserNameFromToken(jwt);
UserPrincipal userDetails = userDetailsService.loadUserByUsername(userNameFromToken);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}

Token Can be validated like this
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(jwt)

At This point, We are able to Create JWT Token for valid Users. And able to provide access to protected resources based on token.

lets go one step further and use roles to grant access at granular level.

Step 4: Role Based Access

lets Say we want Student Endpoint to be accessible to Users having ADMIN or USER roles. And Subject Endpoint to be accessible to Users having ADMIN role.
In order to use method level security, we need to enable this in the security configuration using @EnableGlobalMethodSecurity.

Configuration
1
2
3
4
5
6
7
8
@Configuration
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

}

The annotations @PreAuthorize and @PostAuthorize support Spring Expression Language (SpEL) and provide expression-based access control.
Note: The Roles In Database need to saved with Prefix as ROLE_

StudentResource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/Student")
public class StudentResource {

private final StudentRepository studentRepository;

public StudentResource(StudentRepository studentRepository) {
this.studentRepository=studentRepository;
}

@GetMapping
@PreAuthorize("hasRole('ADMIN') OR hasRole('USER')")
public List<Student> getAllStudents(){
return studentRepository.findAll();
}
}

SubjectResource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping("/Subject")
public class SubjectResource {

private final SubjectRepository subjectRepository;
public SubjectResource(SubjectRepository subjectRepository) {
this.subjectRepository = subjectRepository;
}
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public List<Subject> getAllSubjects(){
return subjectRepository.findAll();
}
}

The code for this post is available for download here.

Share Comments