Skip to content

Commit 2e8e667

Browse files
author
Denis Kokorin
committed
Use Spring credentials expiration check instead of JWT's
1 parent 35783a1 commit 2e8e667

File tree

6 files changed

+74
-10
lines changed

6 files changed

+74
-10
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Spring JWT integration done right
2+
3+
On github you can find many examples how to integrate JWT with Spring.
4+
5+
Many of them use OncePerRequestFilter with custom logic to authenticate user
6+
which put Authentication into SecurityContextHolder.
7+
8+
Such solutions IMHO are not beautiful and are error-prone.
9+
10+
This repository shows how to do the same but using standard Spring Security filter:
11+
UsernamePasswordAuthenticationFilter and AbstractPreAuthenticatedProcessingFilter
12+
13+
# How to use
14+
15+
## Login (get JWT)
16+
17+
```
18+
curl -X POST http://localhost:8080/login -v -d username=user -d password=user
19+
```
20+
21+
Check AUTHORIZATION header
22+
23+
## Invoke (use JWT)
24+
25+
```
26+
curl http://localhost:8080/greet -H "Authorization: Bearer JWT_YOU_RECEIVED_ON_LOGIN" -v
27+
```
28+
29+
## Check JWT expiration
30+
31+
Use the following JWT: `eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNTQ5NjM1NjMyLCJleHAiOjE1NDk2MzU2NDIsImF1dGhvcml0aWVzIjoiUk9MRV9VU0VSIn0.aVJVydkEAO-sJ29_7SveFTDipl2yEcmAIZt-DyB6UV0`:
32+
33+
```
34+
curl http://localhost:8080/greet -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNTQ5NjM1NjMyLCJleHAiOjE1NDk2MzU2NDIsImF1dGhvcml0aWVzIjoiUk9MRV9VU0VSIn0.aVJVydkEAO-sJ29_7SveFTDipl2yEcmAIZt-DyB6UV0" -v
35+
```

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
<version>${jjwt.version}</version>
4040
<scope>runtime</scope>
4141
</dependency>
42+
<dependency>
43+
<groupId>io.jsonwebtoken</groupId>
44+
<artifactId>jjwt-jackson</artifactId>
45+
<version>${jjwt.version}</version>
46+
<scope>runtime</scope>
47+
</dependency>
4248

4349
<dependency>
4450
<groupId>org.springframework.boot</groupId>

src/main/java/com/github/kokorin/springjwtdoneright/GreetController.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
package com.github.kokorin.springjwtdoneright;
22

3+
import org.springframework.http.MediaType;
34
import org.springframework.security.core.GrantedAuthority;
45
import org.springframework.security.core.annotation.AuthenticationPrincipal;
56
import org.springframework.web.bind.annotation.RequestMapping;
67
import org.springframework.web.bind.annotation.RequestMethod;
78
import org.springframework.web.bind.annotation.RestController;
89

9-
import java.security.Principal;
1010
import java.sql.Date;
1111
import java.util.stream.Collectors;
1212

13-
@RestController("/greet")
13+
@RestController
1414
public class GreetController {
15-
@RequestMapping(method = RequestMethod.GET, path = "/")
16-
public UserDetailsDto greet(@AuthenticationPrincipal Principal principal) {
17-
SimpleUserDetails userDetails = (SimpleUserDetails) principal;
18-
15+
@RequestMapping(method = RequestMethod.GET,path = "/greet", produces = MediaType.APPLICATION_JSON_VALUE)
16+
public UserDetailsDto greet(@AuthenticationPrincipal SimpleUserDetails userDetails) {
1917
return new UserDetailsDto(
2018
userDetails.getUsername(),
2119
userDetails.getAuthorities().stream()

src/main/java/com/github/kokorin/springjwtdoneright/SimpleAuthenticationUserDetailsService.java renamed to src/main/java/com/github/kokorin/springjwtdoneright/JwtAuthenticationUserDetailsService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import org.springframework.security.core.userdetails.UsernameNotFoundException;
77
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
88

9-
public class SimpleAuthenticationUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
9+
public class JwtAuthenticationUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
1010
@Autowired
1111
private JwtService jwtService;
1212

src/main/java/com/github/kokorin/springjwtdoneright/JwtService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public String toJwt(SimpleUserDetails userDetails) {
4141
public SimpleUserDetails fromJwt(String jwt) {
4242
Claims claims = (Claims) Jwts.parser()
4343
.setSigningKey(key)
44+
// Disable expiration check in JWT, never throw ExpiredJwtException
45+
// Expiration is controlled by Spring through UserDetails.isCredentialsNonExpired
46+
// SimpleUserDetails knows expiration time.
47+
// It's checked in org.springframework.security.core.userdetails.UserDetailsChecker.check()
48+
// at org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider.authenticate()
49+
.setAllowedClockSkewSeconds(Integer.MAX_VALUE)
4450
.parse(jwt)
4551
.getBody();
4652

src/main/java/com/github/kokorin/springjwtdoneright/WebSecurityConfig.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
import org.springframework.context.annotation.Bean;
44
import org.springframework.context.annotation.Configuration;
55
import org.springframework.security.authentication.AuthenticationProvider;
6+
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
7+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
68
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
79
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
810
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
11+
import org.springframework.security.config.http.SessionCreationPolicy;
912
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
1013
import org.springframework.security.core.userdetails.UserDetailsService;
1114
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -18,17 +21,25 @@
1821
@Configuration
1922
@EnableWebSecurity
2023
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
24+
@Override
25+
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
26+
auth
27+
.authenticationProvider(daoAuthenticationProvider())
28+
.authenticationProvider(preAuthenticationProvider());
29+
}
2130

2231
@Override
2332
protected void configure(HttpSecurity http) throws Exception {
2433
http
2534
.csrf().disable()
2635

36+
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
37+
38+
.and()
39+
2740
.addFilter(usernamePasswordFilter())
2841
.addFilter(authenticationFilter())
2942

30-
.authenticationProvider(preAuthenticationProvider())
31-
3243
.authorizeRequests()
3344
.antMatchers("/login").permitAll()
3445
.antMatchers("/**").authenticated();
@@ -80,6 +91,14 @@ public JwtService jwtService() {
8091
return new JwtService();
8192
}
8293

94+
@Bean
95+
public AuthenticationProvider daoAuthenticationProvider() {
96+
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
97+
provider.setUserDetailsService(simpleUserDetailsService());
98+
provider.setPasswordEncoder(plaintextPasswordEncoder());
99+
return provider;
100+
}
101+
83102
@Bean
84103
public AuthenticationProvider preAuthenticationProvider() {
85104
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
@@ -92,7 +111,7 @@ public AuthenticationProvider preAuthenticationProvider() {
92111

93112
@Bean
94113
public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> preAuthenticatedUserDetailsService() {
95-
return new SimpleAuthenticationUserDetailsService();
114+
return new JwtAuthenticationUserDetailsService();
96115
}
97116

98117
@Bean

0 commit comments

Comments
 (0)