Spring Security
一、简介
Spring Security是Spring家族中的一个安全管理框架,一般Web应用都需要 认证 和 授权
认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户
授权:经过认证后判断当前用户是否有权限进行某个操作
二、快速入门
2.1 开发步骤
1、导入坐标
Spring Security 启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2、随便编写一个接口
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/login")
public String login() {
return "Spring Security";
}
}
3、启动
![在这里插入图片描述](https://img-blog.csdnimg.cn/956548061ebf44b8ab0dd56b06277922.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAcmFiYml0X3psaQ==,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
可以看到,Spring Security已经起作用了,默认的用户名是:user,而密码则在控制台上
当我们输入用户名密码后,就能访问到我们的接口数据了~
三、认证
3.1 登录校验流程
在一般的开发场景中,登录校验的流程大致如下:
1. 前端携带一个 用户名和密码 访问登录接口;
2. 后端通过数据库校验用户名和密码;
3. 如果用户名密码正确,则使用 User的部门信息(用户名、用户ID)生成一个JWT;
4. 将JWT响应给前端;
5. 前端登录后,每次发送请求都会携带Token;
6. 后端获取请求头中的Token进行解析,获取UserID;
7. 根据UserID获取用户相关信息,如果有权限则允许访问资源;
8. 访问到目标资源,响应给前端。
3.2 Spring Security完整流程
Spring Security的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器
1、UsernamePasswordAuthenticationFilter,负责处理登录页面填写了用户名密码后的登录请求。
2、ExceptionTranslationFilter:处理过滤器链中抛出的任何 AccessDeniedException
3、FilterSecurityInterceptor:负责权限校验的过滤器
![在这里插入图片描述](https://img-blog.csdnimg.cn/7dccae5d3f1c477ba3ba18f8658b3596.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAcmFiYml0X3psaQ==,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
3.3 解决问题
一、思路
登录:
1. 自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成JWT
把用户信息存入Redis
2. 自定义UserDetailService
在这个实现列中去查询数据库
校验:
1. 定义JWT认证过滤器
获取Token
解析Token获取其中的UserID
从Redis中获取用户信息
存入SecurityContextHolder
二、实现方式
-
导入坐标
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
</dependencies>
-
统一响应体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Boolean success;
private String errorMsg;
private Object data;
private Long total;
public static Result ok(){
return new Result(true, null, null, null);
}
public static Result ok(Object data){
return new Result(true, null, data, null);
}
public static Result ok(List<?> data, Long total){
return new Result(true, null, data, total);
}
public static Result fail(String errorMsg){
return new Result(false, errorMsg, null, null);
}
}
-
编写实体类
@Data
public class User implements Serializable {
private Long id;
private LocalDateTime birthday;
private String gender;
private String username;
private String password;
private String telephone;
private String station;
private String remark;
}
-
编写 UserMapper.java
接口
@Mapper
@Repository
public interface UserMapper {
List<User> findAll();
}
-
编写 UserService.java
接口
public interface UserService {
List<User> findAll();
}
-
编写 UserServiceImpl.java
实现类
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.findAll();
}
}
-
编写 UserMapper.xml
映射配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rabbit.springbootsecurity.mapper.UserMapper">
<select id="findAll" resultType="user">
select * from t_user
</select>
</mapper>
-
编写 application.yaml
配置文件
server:
port: 8083
spring:
datasource:
url: jdbc:mysql://localhost:3306/health?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.rabbit.springbootsecurity.pojo
-
编写 UserController.java
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/login")
public Result login() {
return Result.ok(userService.findAll());
}
}
测试结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/874114629f01428a93711a6ea9e28b21.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAcmFiYml0X3psaQ==,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
三、存在问题
以上的入门案例存在一定问题:
我们每次访问接口都需要使用到 SpringSecurity 为我们提供的默认 用户名(user)和密码(控制台有),这显然不是我们想要的,所以我们需要创建一个类,实现 UserDeatailsService接口,重写其中的方法 loadUserByUsername() ,表示用户名密码从数据库中查询。
问题解决:
-
编写一个类,UserDetailsServiceImpl.java
,实现 UserDetailsService
接口,重写里边的方法,表示我们需要从数据库中获取用户信息
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.findUserByUsername(username);
if (Objects.isNull(user)) {
throw new RuntimeException("用户名或密码错误");
}
return new LoginUser(user);
}
}
-
可以看到,实现的类中重写的方法返回值是 UserDetails
,所以我们需要编写一个类,实现 UserDetails
接口并重写里边的方法
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
注意:
如果要测试,若数据库中的密码是明文存储,需要在数据库的密码列的值前边加上 `{noop}`,表示是明文存储
四、密码加密解密
实际项目中,我们不会把密码明文的存储到数据库中;
我们一般会使用Spring Security为我们提供的 BCryptPasswordEncoder;
我们只需要把 BCryptPasswordEncoder对象注入到Spring容器中,Spring Security就会使用该PasswordEncoder来进行密码校验,所以我们可以定义一个Spring Security配置类,该配置类需要继承 WebSecurityConfigurerAdapter。
使用步骤:
-
编写一个配置类,继承 WebSecurityConfigurerAdapter
类
该类就是一个配置类,该配置类提供一个方法,需要返回一个 BCryptPasswordEncoder 对象
-
编写一个返回 BCryptPasswordEncoder Bean对象的方法
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
测试加密方法:
@Test
void testBCryptPasswordEncoder() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode_pwd_1 = passwordEncoder.encode("rabbit");
String encode_pwd_2 = passwordEncoder.encode("rabbit");
log.info("encode_pwd_1:{}", encode_pwd_1);
log.info("encode_pwd_2:{}", encode_pwd_2);
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/61e05b3c002d45808d09611945274730.png#pic_center)
测试校验方法:
@Test
void testBCryptPasswordEncoder() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
boolean flag_true = passwordEncoder.
matches("rabbit", "$2a$10$MBcThIW7Tqm9liaBAXooPepAeovbD8XbM1tV3xvHOA6FHaI6vD4hO");
boolean flag_false = passwordEncoder.
matches("root", "$2a$10$MBcThIW7Tqm9liaBAXooPepAeovbD8XbM1tV3xvHOA6FHaI6vD4hO");
log.info("flag_true:{}", flag_true);
log.info("flag_false:{}", flag_false);
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/d45b3b8d7a32474d909ba426155d08d0.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAcmFiYml0X3psaQ==,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
五、Jwt的使用和使用Jwt进行登录
5.1 什么是Jwt
Jwt 是 JSON WEB TOKEN 英文的缩写,它是一个开放标准,它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全传输信息。该信息可以被验证和信任,因为它是数字签名用的。
使用jwt最多的场景就是登陆,一旦用户登陆,那么后续的每个请求都应该包含jwt。
5.2 Jwt 组成
Jwt由三部分组成,每一部分之间用符号"."进行分割,整体可以看做是一个长字符串。
一个经典的jwt的样子:
xxx.xxx.xxx
Jwt的三部分是:
-
Header 头部:
-
头部由两部分组成,第一部分是声明类型,在 Jwt 中声明类型就 Jwt,第二部门就是声明加密算法,一般是话是采用 Hash256
-
一个经典的头部:
{
'typ': 'JWT',
'alg': 'HS256'
}
-
Payload 载荷、载体
-
这一部分是 Jwt 的主体部分,这一部分也是 Json 对象,可以包含需要传递的数据,其中 Jw t指定了七个默认的字段选择,这七个字段是推荐但是不强制使用的:
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于识别该JWt
-
除了上述的七个默认字段之外,还可以自定义字段,通常我们说 Jwt 用于用户登陆,就可以在这个地方放置 userId、username
-
下面这个Json对象是一个 Jwt 的 Payload 部分:
{
"sub": "1234567890",
"nickname": "rabbit",
"id": "9527"
}
注意:这里不能存放敏感信息,否则可能会被截取
-
signature 签证:
-
这部分是 对前两部分进行 base64 编码 再进行加密,这个加密的方式使用的是 Jwt的头部声明中的加密方式,在加上一个加密盐(secret)组成的,secret通常是一个随机的字符串,这个secret是服务器特有的,不能够让其他人知道。这部分的组成公式是:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
5.3 为什么选择 Jwt
-
session 的缺点:
首先在我的认知里 Jwt 用处最多的就是作为用户登陆的凭证,以往这个凭证是使用session和cookie进行存储的,session技术的存储在服务器端的一种技术,构造一个类似于哈希表存储用户id和用户的一些信息,将这个用户id放在cookie里返回给用户,用户每次登陆的时候带上这个cookie,在哈希表中如果可以查到信息,那么说明用户登陆并且得到对应用户的信息;
但是session存放在服务器端,当用户量很大时,占用了服务器过多的宝贵的内存资源。同时因为如果有多台服务器,那么当用户登陆时访问了服务器A,那么就只有服务器A上会存储这个用户的信息,当用户访问其他页面时,也许请求会发给服务器B,这时服务器B中是没有用户的信息的,会判定用户处于非登录的状态。也就是说session无法很好的在微服务的架构之中使用;
因为session是和cookie结合使用的,如果cookie被截获,那么就会存在安全危机。
-
Jwt 的优点:
Json形式,而Json非常通用性可以让它在很多地方使用;
Jwt所占字节很小,便于传输信息;
需要服务器保存信息,易于扩展;
5.4 一个 Jwt 的工具类
package com.rabbit.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
public class JwtUtil {
public static final Long JWT_TTL = 60 * 60 *1000L;
public static final String JWT_KEY = "rabbit";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("sg")
.setIssuedAt(now)
.signWith(signatureAlgorithm, secretKey)
.setExpiration(expDate);
}
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);
return builder.compact();
}
public static void main(String[] args) throws Exception {
Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyOTY2ZGE3NGYyZGM0ZDAxOGU1OWYwNjBkYmZkMjZhMSIsInN1YiI6IjIiLCJpc3MiOiJzZyIsImlhdCI6MTYzOTk2MjU1MCwiZXhwIjoxNjM5OTY2MTUwfQ.NluqZnyJ0gHz-2wBIari2r3XpPp06UMn4JS2sWHILs0");
String subject = claims.getSubject();
System.out.println(subject);
}
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
六、登录接口
在实际开发中,我们通常有的接口,不需要登录也能访问,比如:登录页面、注册、忘记密码等,所以我们需要在 Spring Security配置一些信息以及编写一些放行的方法:
在接口中我们通过 AuthenticationManager 的 authenticate() 方法来进行用户认证,所以需要在 SecurityConfig 中配置把 AuthenticationManager 注入Spring IoC容器
认证成功的话,需要生成一个 JWT,放入响应中返回。并且为了让用户下回请求时能通过 JWT 识别处具体是哪个用户,我们需要把用户信息存入Redis
6.1 准备工作
-
创建一个用于存放 Redis 的 Key 名称的类
public class RedisConstants {
public static final String LOGIN_USER_KEY = "jwt:login:token:";
public static final Long LOGIN_USER_TTL = 36000L;
}
-
创建一个用于存放登录信息的实体类 LoginDto.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginDto implements Serializable {
private String username;
private String password;
private String telephone;
}
-
创建一个实体类,用于保存部分信息至Redis的 UserDto.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDto implements Serializable {
private Long id;
private String username;
}
6.2 开发步骤
-
在开发中,我们不可能对任何请求都进行拦截,肯定是部分请求不要拦截,所以我们需要在 SecurityConfig.java
中重写 configure(http)
方法,配置对哪些请求不拦截
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/loginByPassword").anonymous()
.antMatchers("/user/loginByPhone").anonymous()
.anyRequest().authenticated();
}
-
使用 AuthenticationManager
进行用户认证,将用户登录的 username、password 封装成 Authentication
对象,随后使用 authenticationManager
帮助我们完成认证操作,而 authentcationManager
最终调用 authentcate()
方法完成认证
-
在 SecurityConfig.java
中,重写方法 authenticationManagerBean()
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
-
在 UserServiceImpl.java
中实现登录接口
@Override
public Result loginByPassword(LoginDto loginDto) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (ObjectUtil.isEmpty(authenticate)) {
log.info("message:{}",ResultMessage.LOGIN_FAIL);
throw new RuntimeException("用户名或密码错误!");
}
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String user_id = loginUser.getUser().getId().toString();
User user = loginUser.getUser();
String jwtToken = JwtUtils.getJwtToken(user_id);
UserDto userDto_redis = BeanUtil.copyProperties(user, UserDto.class);
Map<String, Object> map = BeanUtil.beanToMap(userDto_redis, new HashMap<>(),
CopyOptions.create().setIgnoreNullValue(true)
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
String login_key = RedisConstants.LOGIN_USER_KEY + user_id;
stringRedisTemplate.opsForHash().putAll(login_key, map);
stringRedisTemplate.expire(login_key, RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
Map<String, String > hashMap = new HashMap<>();
hashMap.put("authorization", jwtToken);
return Result.ok(hashMap);
}
6.3 总结
1. 自定义登录接口,让 Spring Security 对这个接口放行,即让用户不需要登录也能进行访问,对应的也就是在 SecurityConfig.java 中通过继承 WebSecurityConfigurerAdapter 类重写 configure(http) 方法然后进行一些必要的配置;
2. 通过在 SecurityConfig.java 中重写 authenticationManagerBean() 方法,暴露 authenticationManagerBean,随后在 UserServiceImpl中国注入该Bean,表示我们可以通过 authenticate()方法进行用户认证;
3. 认证成功的话就可以生成一个Jwt,放入响应中返回,并且为了让用户下回请求时能通过Jwt识别出具体是哪个用户,我们需要把用户信息存储到Redis中
七、校验Token
7.1 简介
前面我们已经生成了一个Token,所以接下来我们需要进行一些必要的Jwt解析认证,流程如下:
1. 定义一个Token解析过滤器,我们使用Spring为我们提供的过滤器接口,原生的Filter过滤器接口在不同的Tomcat版本中存在一点问题;
2. 获取Token;
3. 解析Token获取其中的UserId;
4. 根据UserID获取Redis中的用户信息;
5. 讲用户信息存入SecurityContextHolder,我们前面了解到,Spring Security其实就是一整个过滤器链,所以我们需要把用户认证信息存储到其中一个过滤器,然后后面的过滤器会去该过滤器中查找有无用户信息,有的话才会放行
7.2 编写 Token 解析过滤器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
filterChain.doFilter(request, response);
return;
}
String userId = null;
try {
Claims claims = JwtUtil.parseJWT(token);
userId = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Token异常!");
}
String redis_key = RedisConstants.LOGIN_USER_KEY + userId;
Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(redis_key);
if (map.isEmpty()) {
throw new RuntimeException("Token异常!");
}
UserDto userDto = BeanUtil.fillBeanWithMap(map, new UserDto(), false);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDto,null,null);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
7.3 配置过滤器链
简介:
前面我们已经编写好了 Spring Security 的Token解析过滤器,但我们还需去配置过滤器,并且我们把过滤器配置好后还需指定其在Spring容器中的顺序,所以需将我们前面写好的解析Token的过滤器配置到用户认证授权的过滤器UsernamePasswordAuthenticationFilter前面。
在 SecurityConfig.java 中的 configure() 方法中配置 http.addFilterBefore()
步骤:
-
在 SecurityConfig.java
中注入自定义的解析Token过滤器
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
-
把该过滤器放到用户认证拦截器之前
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/loginByPassword").anonymous()
.antMatchers("/user/loginByPhone").anonymous()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
八、退出登录
流程分析
我们只需要从 SecurityContextHolder 中获取用户ID,然后根据用户ID删除Redis中的内容即可
代码实现
@Override
public Result logout() {
UsernamePasswordAuthenticationToken authenticationToken
= (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDto userDto = (UserDto) authenticationToken.getPrincipal();
Long userId = userDto.getId();
stringRedisTemplate.delete(RedisConstants.LOGIN_USER_KEY + userId);
return Result.ok("退出成功~");
}
九、总结
Authentication在我看来就是一个载体,在未得到认证之前它用来携带登录的关键参数,比如用户名和密码、验证码;在认证成功后它携带用户的信息和角色集。
交给AuthenticationManager认证。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)