SpringSecurity
代码传送门
链接:https://pan.baidu.com/s/19ii5ffaweX0p52ptRGTD-w
提取码:m6j7
第一章 简介
SpringSecurity https://spring.io/projects/spring-security#overviewopen in new window
1、概念
Spring家族当中,一个安全管理框架。
Shiro也是一个安全框架,提供了很多安全功能。Shiro比较老,旧的项目当中,可能还在使用。上手还挺简单。
在新项目当中,一线互联网大型项目,都是使用SpringSecurity 。
2、认证 鉴权
一般的web项目当中,总会有登陆和鉴权的需求。但是大家一定要区分开。
- 认证:验证当前访问的用户是不是本系统中的用户。确定是哪一个具体的用户。
- 鉴权:经过认证,判断当前登陆用户有没有权限来执行某个操作。
所以说,安全框架SpringSecurity 当中,必定会有认证和鉴权的两大核心功能。
第二章 入门
1、准备web项目
(1)创建springboot web项目
快速构建
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<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>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
创建controller
@RestController
public class TestController {
@GetMapping("test")
public String test() {
return "123Test";
}
}
启动 测试 访问 :http://localhost:8099/test
2、引入SpringSecurity
(1)引入依赖
<!-- 引入security起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
(2)测试
访问 : http://localhost:8099/test
Security 自带的登陆页面
可以输入自带默认用户名 user 和 密码(控制台)
Using generated security password: b6092291-ce28-4c5c-afc9-cb2e8c5fde06
就能访问到数据了
(3)自带退出
http://localhost:8099/logout
第三章 认证
1、web登陆流程
缺点可以改进:
- 现在使用的是security自带的登陆页面,比较丑。 想换成自己项目的,优化的登录页。
- 用户使用的是security给的用户名和密码。 想真实地去数据库里,获取真实的用户名和密码。
- security自带的cookie\session模式。 想自己生成jwt,无状态登陆。
- 前端页面怎么携带jwt。 想请求头里带上。
- 鉴权操作完全没有。 想鉴权做完善。
总而言之,自己的一些特定需求,都没有实现。
2、看源码
springsecurity 就是通过一些过滤器、拦截器,实现登陆鉴权的流程的。
(1)springsecurity 登陆流程
springsecurity就是一个过滤器链,内置了关于springsecurity的过滤器。
- UsernamePasswordAuthenticationFilter:处理我们登陆页面输入的用户名和密码是否正确的过滤器。
- ExceptionTranslationFilter:处理前面的几个过滤器中,有了问题,抛出错误,不让用户登录。
- FilterSecurityInterceptor:经行一个权限校验的拦截器。
我们可以找到当前boot项目中的,所有有关security的过滤器链。
3、自定义登录
(1)思路
登陆: 1自定义登录接口
调用prodivermanager auth方法
登陆成功生成jwt
存入redis
2自定义userdetailsmanager实现类
从数据库中获取系统用户
访问资源:自定义认证过滤器
获取token
从token中获取userid
从redis中通过userid获取用户信息
存SecurityContextHolder
(2) JWT简介
a.概念
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。无状态。
好处:不需要服务器端 存session。
特点:可以看到,但是不能篡改,因为第三部分用了秘钥。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。 abcd.abcd.abcd
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{"typ":"JWT","alg":"HS256"}
在头部指明了签名算法是HS256算法。 我们进行BASE64编码[https://base64.us/
载荷(playload)
载荷就是存放有效信息的地方。
定义一个payload:
{"phone":"1234567890","login_user_key":"1"}
签证(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header
payload
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
hs256("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Iml0bGlscyIsImFkbWluIjp0cnVlLCJhZ2UiOjE4fQ==",secret)
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=.JTdCJTIyc3ViJTIyJTNBJTIyMTIzNDU2Nzg5MCUyMiUyQyUyMm5hbWUlMjIlM0ElMjJqYWNrJTIyJTJDJTIyYWRtaW4lMjIlM0F0cnVlJTdE.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
b. JWT签发与验证token
JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
官方文档:
https://github.com/jwtk/jjwtopen in new window
c. 创建token
public String generateToken(String phone) {
Calendar instance = Calendar.getInstance();
// 设置过期时间
instance.add(Calendar.SECOND, 1000);
return Jwts.builder()
.setSubject(phone)//主题
.setIssuedAt(new Date(System.currentTimeMillis()))//签发日期
.setExpiration(instance.getTime())// 设置过期时间
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
d.解析token
我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。
public LoginUser getLoginUser(HttpServletRequest request) {
String token = request.getHeader(header);
if(Objects.isNull(token) || ObjectUtils.isEmpty(token)) {
return null;
}
Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
// 解析对应的权限以及用户信息
String username = claims.get("sub").toString();
LoginUser user = redisCache.getCacheObject(CacheConstants.USER_INFO_KEY + username);
System.out.println(user);
return user;
}
JWT工具类代码
@Component
@Slf4j
public class JwtUtilService {
/**
* 注入header值
*/
@Value("${token.header}")
private String header;
/**
* 注入secret密钥
*/
@Value("${token.secret}")
private String SECRET;
public String generateToken(String phone) {
Calendar instance = Calendar.getInstance();
// 设置过期时间
instance.add(Calendar.SECOND, 1000);
return Jwts.builder()
.setSubject(phone)//主题
.setIssuedAt(new Date(System.currentTimeMillis()))//签发日期
.setExpiration(instance.getTime())// 设置过期时间
.signWith(SignatureAlgorithm.HS256, SECRET)
.compact();
}
public String getLoginUser(HttpServletRequest request) {
String token = request.getHeader(header);
if(Objects.isNull(token) || ObjectUtils.isEmpty(token)) {
return null;
}
Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
// 解析对应的权限以及用户信息
String username = claims.get("sub").toString();
return username;
}
/**
* 检查token是否过期
*
* @param token token
* @return boolean
*/
public boolean isExpiration(String token) {
Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
return claims.getExpiration().before(new Date());
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
log.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
log.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
log.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
log.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
log.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
利用Spring注入机制
server:
port: 8099
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: Yx7GcP3UY194v8U.fLyhBiFZFxKOagQZt1baEhKlTfMW
# 令牌有效期(默认30分钟)
expireTime: 30
LoginUser类
@Data
public class LoginUser {
//账号
private String userName;
//密码
private String password;
//验证码
private Integer code;
}
注意:设置签名key必须和生成时一致。
(3) 准备新项目
①添加依赖
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!--JAXB API是java EE 的API,因此在java SE 9.0 中不再包含这个 Jar 包。-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
② 添加Redis相关配置
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey) {
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
@Configuration
public class RedisConfig {
/**
* RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的一串很长的值
* 缺点:可读性查、浪费存储空间
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
// 1.创建 redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 2.设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 3.设置序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// key 和 hashkey 采用 String 序列化
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setKeySerializer(RedisSerializer.string());
// value 和 hashvalue 采用 json 序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
return redisTemplate;
};
}
③ 响应类
package com.qf.common;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* @auther: sin
* @Date: 2023/6/23 - 06 - 23 - 14:13
* @Description: com.qf.common.pojo
* @version: 1.0
*/
@Data
// 设置链式数据
@ToString
@Accessors(chain = true)
public class ResponseResult<T> {
/**
* 状态码
*/
private int code;
/**
* 返回信息
*/
private String message;
/**
* 返回数据
*/
private T data;
/**
* 自定义返回成功数据
* @param data
* @return
* @param <T>
*/
public static <T> ResponseResult success(T data) {
return new ResponseResult().setCode(200).setMessage("操作成功").setData(data);
}
/**
* 自定义返回失败数据
* @param data
* @return
* @param <T>
*/
public static <T> ResponseResult fail(String message, T data) {
return new ResponseResult().setCode(400).setMessage(message).setData(data);
}
}
**重要!**实现真实从数据库获取系统用户信息
a. 数据库校验用户
创建UserDetailsService实现类,重写其中的方法。用户名从数据库中查询用户信息。
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名去数据库查询用户信息
SysUser user = new SysUser();
//如果查询不到数据就通过抛出异常来给出提示
if(Objects.isNull(user)){
throw new RuntimeException("用户名错误");
}
//TODO 根据用户查询权限信息 添加到LoginUser中
//封装成UserDetails对象返回
return new MyUserDetails(user);
}
}
b.密码加密存储
1 实际项目中我们不会把密码明文存储在数据库中
2 默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder。
我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。
我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。
我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。
SecurityConfig配置
由于spring boot3.0废弃了extends WebSecurityConfigurerAdapter的方式,所以这里采用添加@Bean新方式
@Configuration
public class SecurityConfig{
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
密码加密:
1 12345 md5 --> asdfasdfasdfasdfa 默认密码12345
2 12345 md5(12345|itlils)---->uiowertupouert 加盐
3 BCryptPasswordEncoder 自动加盐
过时问题:
首先,过时也能用,如果看着不爽,可以使用如下方法。
以前我们自定义类继承自 WebSecurityConfigurerAdapter 来配置我们的 Spring Security,我们主要是配置两个东西:
- configure(HttpSecurity)
- configure(WebSecurity)
前者主要是配置 Spring Security 中的过滤器链,后者则主要是配置一些路径放行规则。
现在在 WebSecurityConfigurerAdapter 的注释中,人家已经把意思说的很明白了:
- 以后如果想要配置过滤器链,可以通过自定义 SecurityFilterChainBean来实现。
- 以后如果想要配置 WebSecurity,可以通过 WebSecurityCustomizerBean来实现。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
/**
* 认证失败处理类
*/
@Resource
private AuthenticationEntryPointImpl unauthorizedHandler;
@Resource
private AccessDeniedHandlerImpl accessDeniedHandler;
/**
* 登出处理
*/
@Resource
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
*
*/
@Resource
private AuthenticationConfiguration authenticationConfiguration;
/**
* token拦截器
*/
@Resource
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
/**
* 强散列哈希加密实现
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
// 认证失败处理类
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.requestMatchers("/login").anonymous()
// 无论是否登录都可以访问
.requestMatchers("/captchaImage").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.requestMatchers().authenticated();
//把token校验过滤器添加到过滤器链中
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加Logout filter
http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 允许跨域
http.cors();
return http.build();
}
@Bean
public AuthenticationManager authenticationManager() throws Exception{
AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
return authenticationManager;
}
}
c.自定义登陆接口
分析需求:
1 自定义一个controller登陆接口
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@RequestMapping("/login")
public ResponseResult Login(@RequestBody LoginUser user, HttpServletResponse response) {
return loginService.login(response, user.getUserName(), user.getPassword());
}
}
2 放行自定义登陆接口
// 对于登录接口 允许匿名访问
.requestMatchers("/login").anonymous()
3使用ProviderManager auth方法进行验证
4自己生成jwt给前端
5系统用户相关所有信息放入redis
d.认证过滤器
1.获取token
2.解析token
3.获取userid
4.封装Authentication
5.存入SecurityContextHolder
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
AuthenticationManager authenticationManager;
@Autowired
JwtUtilService jwtUtil;
@Autowired
RedisCache redisCache;
@Override
public ResponseResult login(HttpServletResponse response, String username, String password) {
// 1登录前置校验
// PreCheck.loginPreCheck(username, password);
// 2验证码校验
// PreCheck.validateCaptcha(code, uuid, redisCache);
// 3使用ProviderManager auth方法进行验证
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
// 校验失败了
if (Objects.isNull(authenticate)) {
return new ResponseResult<>().setCode(HttpStatus.ERROR).setMessage("用户名或密码错误!");
}
MyUserDetails userDetails = (MyUserDetails) (authenticate.getPrincipal());
// 4自己生成jwt token给前端
String token = jwtUtil.generateToken(username);
// 5系统用户token放入redis
//redisCache.setCacheObject(CacheConstants.LOGIN_TOKEN_KEY + username, token, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 6系统用户信息放入redis
//redisCache.setCacheObject(CacheConstants.USER_INFO_KEY + username, userDetails, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
System.out.println(userDetails);
//System.out.println(loginUser.getUser());
// 根据用户id获取所有的角色信息
// List<SysUserRole> roleList = sysUserRoleService.RoleIdList(loginUser.getUser().getUserId());
Map<String, Object> map = new HashMap();
map.put("token", token);
map.put("userInfo", userDetails.getUser());
return new ResponseResult<>().setCode(HttpStatus.SUCCESS).setMessage(Constants.SUCCESS).setData(map);
}
}
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
RedisCache redisCache;
@Autowired
JwtUtilService jwtUtilService;
//
//@Autowired
//SysUserService sysUserService;
//@Autowired
//UserDetailsServiceImpl userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//1解析token
MyUserDetails userDetails = jwtUtilService.getLoginUser(request);
if(!Objects.isNull(userDetails)) {
//2封装Authentication
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(userDetails, null, null);
System.out.println(usernamePasswordAuthenticationToken);
//5存入SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
//放行,让后面的过滤器执行
filterChain.doFilter(request, response);
}
}
测试