Spring Security JWT

本文将介绍 Spring Security 配合 JWT 的使用。

一、JWT

具体请看:

前后端分离 JSON Web Token

二、核心要点及实现

1. 关闭 Session

(1) 说明

信息存储在用户处,服务端无需依靠 Session存储用户信息。这里的关闭 Session 是关闭 Spring Security 对 Session 的使用,而非关闭 Session。

(2) 实现

修改 Spring Security 配置类如下:

1
2
3
4
5
6
7
8
9
10
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭Session
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

2. 登录功能

(1) 说明

在这里,登录接口需要完成对登录请求的校验,生成 JWT 并返回。

设置登录状态(在安全上下文中放置 Authentication 对象)是没有必要的

此前之所以这样做,是为了登录状态保持。而这里我们已经禁用了 Session,不再需要登录状态保持,每一个请求都是独立的

(2) 实现

登录功能有两种实现方式:

  • 自定义登录接口需要完成以下工作:
    • 验证登录请求
    • 若登录成功,
      • 需要生成 Authentication 对象,放置到 Security 上下文中
      • 需要生成 JWT,在请求响应中返回
  • 修改 .formLogin().successHandler() ,在其中生成 JWT 并返回

自定义登录接口更加自由,并且可以减少诸如 “设置登录状态” 这样的多余操作,因此这里以自定义登录接口为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class AccountController {

@PostMapping("/login")
public String login(@RequestBody LoginParam loginParam) {

// ···登录参数校验···

// ···生成JWT···

return JWT;
}

}

3. 登录状态保持

(1) 说明

在基于 Session 的身份认证方式中,登录状态保持分为以下两个部分:

  • 登录状态保存
  • 登录状态读取

在基于 JWT 的身份认证方式中,JWT 自身已经保存了用户信息,不再耗费服务器资源做登录状态保存。因此,在基于 Session 的身份认证方式中,只需要完成登录状态读取即可。

登录状态读取需要完成以下工作:

  • 从请求中读取 JWT
  • 校验 JWT
  • 根据 JWT 创建 UsernamePasswordAuthenticationToken,设置登录状态(在安全上下文中放置 Authentication 对象)

(2) 实现

过滤器编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class JWTVerifyFilter extends GenericFilterBean {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String jwtToken = req.getHeader("authorization");
if (!"/login".equals(req.getServletPath()) && jwtToken != null) {

// ···校验JWT···

// ···根据JWT生成Authentication对象···

// ···设置登录状态···
SecurityContextHolder.getContext().setAuthentication(···);

}
filterChain.doFilter(request, response);
}
}

过滤器配置

1
2
3
4
5
6
7
8
9
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(···, UsernamePasswordAuthenticationFilter.class)
}
}

参考