spring security整合JWT鉴权

关于JWT是什么就不赘述了,可以看看阮一峰大佬的文章:https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

定义JWT Body

首先需要定义一个Bean用来存放JWT Body,字段可以根据具体业务决定。这一步当然可以跳过,你可以用Map传值,只不过可维护性会差点

1
2
3
4
5
6
7
8
@Data
public class JwtBody {

private String id;
private String user;
private String role;

}

编写JWT工具类

首先添加Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class JwtUtil {
//传入JwtBody和密钥,生成Token返回,有效期两个小时
@SuppressWarnings("unchecked")
public static String generate(JwtBody claims, String secret) {
final SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
return Jwts.builder()
.setExpiration(Date.from(ZonedDateTime.now().plusHours(2).toInstant()))
.addClaims(BeanMap.create(claims))
.signWith(secretKey).compact();
}
//传入Token和密钥,验证Token并解析JwtBody返回
public static JwtBody parse(String token, String secret) {
final SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
final JwtBody claims = new JwtBody();
final Claims body = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody();
BeanMap.create(claims).putAll(body);
return claims;
}

}

两个方法,一个编码,一个解码

编写Login Controller

编写一个登录API接口,用来生成Token返回给客户端

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

private final AuthService authService;

@PostMapping("/login")
public UserLoginResponse login(@RequestBody UserLoginRequest userLoginRequest) {
String token = authService.login(userLoginRequest.getUsername(), userLoginRequest.getPassword());
return UserLoginResponse.builder().token(token).build();
}

}

登录方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String login(String username, String password) {
User user = userService.getByUsername(username);
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("Password error");
}

JwtBody claims = new JwtBody() {
{
setId(user.getId().toString());
setUser(user.getUsername());
setRole(user.getRole().name());
}
};
return JwtUtil.generate(claims, config.getJwtSecret())
}

编写JWT Filter

需要编写一个过滤器,添加到Spring Security过滤器链,用来验证Token授权

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
@Component
public class JwtFilter extends OncePerRequestFilter {

private final static String TOKEN_PREFIX = "Bearer";
private AuthenticationManager authenticationManager;
//从Header中提取Token封装成JwtAuthentication传入AuthenticationManager进行验证,然后将验证之后的JwtAuthentication设置到Spring Security上下文
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null && header.startsWith(TOKEN_PREFIX)) { //只有在Header中包含Token的时候才会进行授权
final String token = header.replaceAll("^" + TOKEN_PREFIX, "").trim();
Authentication auth = new JwtAuthentication(token);
try {
Authentication authResult = authenticationManager.authenticate(auth);
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (JwtException e) {
logger.warn("Authentication failed: " + e.getLocalizedMessage());
}
}
filterChain.doFilter(request, response);
}

@Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

}

JwtAuthentication实现

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
37
38
39
40
41
42
43
44
45
46
47
48
49
public class JwtAuthentication implements Authentication {

@Getter
private final String token;
@Getter
@Setter
private User user;
private Boolean isAuthenticated = false;

public JwtAuthentication(String token) {
this.token = token;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}

@Override
public Object getCredentials() {
return null;
}

@Override
public Object getDetails() {
return user;
}

@Override
public Object getPrincipal() {
return user.getUsername();
}

@Override
public boolean isAuthenticated() {
return isAuthenticated;
}

@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
this.isAuthenticated = isAuthenticated;
}

@Override
public String getName() {
return getPrincipal().toString();
}

}

编写JwtAuthenticationProvider

这个类用来实现具体的Token验证操作

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
@Component
@RequiredArgsConstructor
public class JwtAuthenticationProvider implements AuthenticationProvider {

private final Config config;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
JwtAuthentication jwtAuthentication = (JwtAuthentication) authentication;
JwtBody claims;
try {
claims = JwtUtil.parse(jwtAuthentication.getToken(), config.getJwtSecret()); //解析Token失败,直接抛出异常,让Filter捕获
} catch (SignatureException e) {
throw new AccessDeniedException(e.getLocalizedMessage());
}
final User user = new User();
user.setId(Long.valueOf(claims.getId().toString()));
user.setUsername(claims.getUser());
user.setRole(Role.valueOf(claims.getRole()));
jwtAuthentication.setUser(user);
jwtAuthentication.setAuthenticated(true); //认证完毕,将认证状态设为已认证
return jwtAuthentication;
}

@Override
public boolean supports(Class<?> authentication) { //此处表示只认证JwtAuthentication及其子类
return JwtAuthentication.class.isAssignableFrom(authentication);
}

}

编写Spring Security配置类

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
37
38
39
40
41
42
43
44
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

private JwtFilter jwtFilter;
private JwtAuthenticationProvider jwtAuthenticationProvider;

@Bean
@Override //将AuthenticationManager作为Bean添加到Spring容器
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(jwtAuthenticationProvider); //添加JwtAuthenticationProvider
}

@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().logout().disable().formLogin().disable() //启用CORS,关闭CSRF防御,关闭登出,关闭表单登录
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //使用无状态会话,即不使用cookie
.authorizeRequests(request -> {
request.antMatchers("/api/users/*").hasAuthority(Role.ADMIN.name());
request.antMatchers("/login", "/register").permitAll(); //放行登陆、注册接口
})
.addFilterAt(jwtFilter, UsernamePasswordAuthenticationFilter.class); //将JwtFilter添加到过滤器链
}
//为了防止循环依赖注入,使用setter注入依赖,而不是构造器
@Autowired
public void setJwtFilter(JwtFilter jwtFilter) {
this.jwtFilter = jwtFilter;
}

@Autowired
public void setJwtAuthenticationProvider(JwtAuthenticationProvider jwtAuthenticationProvider) {
this.jwtAuthenticationProvider = jwtAuthenticationProvider;
}

}
作者

udp_bbr

发布于

2021-10-31

更新于

2022-09-08

许可协议