双token机制
参考
https://zhuanlan.zhihu.com/p/699850219
1. 各文件作用
LoginSuccessHandler.java
- 功能:处理用户登录成功后的操作。
- 代码含义:
- 获取登录用户信息。
- 生成
accessToken
和refreshToken
。 - 将生成的token通过
ResponseResult.write
方法返回给前端。
RequestAuthenticationFilter.java
- 功能:过滤和验证每个请求的token。
- 代码含义:
- 从请求头或请求参数中获取
accessToken
。 - 如果
accessToken
存在且有效,解析token并设置用户的认证信息。 - 如果
accessToken
过期,尝试从请求头中获取refreshToken
,并使用refreshToken
生成新的accessToken
和refreshToken
,然后将新的token保存到Redis中。
- 从请求头或请求参数中获取
JwtUtils.java
- 功能:生成和解析JWT(JSON Web Token)。
- 代码含义:
generateToken
方法:使用私钥生成JWT。getUsernameFromToken
方法:使用公钥解析JWT并获取用户名。
RsaUtils.java
- 功能:处理RSA加密和解密。
- 代码含义:
- 生成公钥和私钥文件。
- 从文件中读取公钥和私钥。
ResponseResult.java
- 功能:统一返回结果的工具类。
- 代码含义:
- 提供了多种静态方法,用于生成不同类型的响应结果。
write
方法:将响应结果写入HttpServletResponse
。
SecurityConfig.java
- 功能:Spring Security的核心配置。
- 代码含义:
- 配置了自定义的登录成功处理器
LoginSuccessHandler
。 - 配置了自定义的请求过滤器
RequestAuthenticationFilter
。
- 配置了自定义的登录成功处理器
BUserController.java
- 功能:用户相关的前端控制器。
- 代码含义:
addUser
方法:处理用户注册请求,保存用户信息。
UserDetailServiceImpl.java
- 功能:从数据库查询登录用户信息及其权限。
- 代码含义:
loadUserByUsername
方法:根据用户名查询用户信息,并返回UserDetails
对象。
在 Spring Security 中,用户提交登录表单后,身份验证的过程是由 Spring Security 自动处理的。具体来说,Spring Security 会使用配置的 UserDetailsService
和 BCryptPasswordEncoder
来验证用户的用户名和密码。如果验证成功,Spring Security 会调用 LoginSuccessHandler
中的 onAuthenticationSuccess
方法。
以下是身份验证的关键步骤:
- 用户提交登录表单,表单数据会被发送到
SecurityConfig
中配置的loginProcessingUrl
。 - Spring Security 会使用配置的
UserDetailsService
加载用户信息。 - Spring Security 会使用配置的
BCryptPasswordEncoder
验证用户密码。 - 如果验证成功,Spring Security 会调用
LoginSuccessHandler
中的onAuthenticationSuccess
方法。
在这个配置中,/login
URL 会被 Spring Security 拦截并处理,UserDetailsServiceImpl
会加载用户信息,BCryptPasswordEncoder
会验证密码。如果验证成功,LoginSuccessHandler
会被调用。
AuthorityUtils.commaSeparatedStringToAuthorityList(auths)方法将权限字符串”echoes”转换为GrantedAuthority对象的列表,供Spring Security使用
RefreshTokenAspect.java
- 功能:AOP切面,处理token的刷新。
- 代码含义:
addToken
方法:在控制器方法执行前后,检查并刷新token。
RefreshTokenAspect.java
和 RequestAuthenticationFilter.java
的区别
RefreshTokenAspect.java
:- 作用:这是一个AOP切面,用于在控制器方法执行后,检查并添加新的访问令牌(access token)和刷新令牌(refresh token)。它会从Redis或请求头中读取token,并将其添加到响应结果中。
- 应用场景:当用户成功登录或刷新token时,系统会在响应中返回新的access token和refresh token。
- 请求例子:用户登录成功后,系统会返回一个包含新的access token和refresh token的响应。
RequestAuthenticationFilter.java
:- 作用:这是一个过滤器,用于在每个请求到达服务器时,验证请求中的access token。如果access token过期,则尝试使用refresh token生成新的access token和refresh token,并将其保存到Redis中。
- 应用场景:当用户发起需要身份验证的请求时,系统会检查请求中的access token,如果过期则使用refresh token重新生成token。
- 请求例子:用户访问受保护的资源时,系统会检查请求头中的access token,如果过期则使用refresh token重新生成并返回新的token。
通过这两个文件的配合,系统能够在每次请求时验证用户身份,并在必要时刷新token,确保用户的会话持续有效。
2. 详=各文件之间的关系
SecurityConfig
配置了LoginSuccessHandler
和RequestAuthenticationFilter
,确保登录成功后生成token,并在每个请求中验证token。LoginSuccessHandler
在用户登录成功后生成accessToken
和refreshToken
。RequestAuthenticationFilter
在每个请求中验证accessToken
,如果过期则使用refreshToken
生成新的token。JwtUtils
和RsaUtils
提供了生成和解析JWT的工具方法。ResponseResult
统一了返回结果的格式。BUserController
处理用户注册请求。UserDetailServiceImpl
从数据库查询用户信息。RefreshTokenAspect
在控制器方法执行前后,检查并刷新token。
3. 在登录、注册等请求发生时的工作流、
登录请求
- 用户提交登录请求。
SecurityConfig
配置的登录处理器拦截请求。- 用户认证成功后,
LoginSuccessHandler
生成accessToken
和refreshToken
,并返回给前端。 - 前端保存
accessToken
和refreshToken
。
注册请求
- 用户提交注册请求。
BUserController
的addUser
方法处理请求,保存用户信息到数据库。
其他请求
- 用户提交请求,携带
accessToken
。 RequestAuthenticationFilter
拦截请求,验证accessToken
。- 如果
accessToken
有效,解析token并设置用户的认证信息。 - 如果
accessToken
过期,使用refreshToken
生成新的accessToken
和refreshToken
,并保存到Redis中。 - 请求继续执行,返回结果。
4. 其他
Redis的使用
- Redis用于存储和管理
accessToken
和refreshToken
,确保在accessToken
过期后可以通过refreshToken
重新生成新的accessToken
。
AOP切面的使用
RefreshTokenAspect
通过AOP切面,在控制器方法执行前后,检查并刷新token,确保用户在使用过程中不会因为token过期而中断。
通过上述机制,该项目实现了双token机制,确保在accessToken
过期后可以通过refreshToken
重新生成新的accessToken
,从而提高了系统的安全性和用户体验。
@Column和@TableField
@Column用在jpa框架
@TableField用在MyBatis-Plus
@Slf4j
https://www.jb51.net/program/322645laz.htm
我一般使用的是
@SLf4j
作为日志输出。使用logback.xml,需要引入下面两个依赖。
如果你使用的是springboot,那么springboot的starter会自带这两个依赖,不用再重复导入。
IService
Service
中的方法通常是业务逻辑层的方法,用于处理具体的业务需求。以下是一些常见的 Service
方法:
保存数据:
boolean save(T entity);
批量保存数据:
boolean saveBatch(Collection<T> entityList);
保存或更新数据:
boolean saveOrUpdate(T entity);
批量保存或更新数据:
boolean saveOrUpdateBatch(Collection<T> entityList);
根据ID删除数据:
boolean removeById(Serializable id);
根据条件删除数据:
boolean remove(Wrapper<T> queryWrapper);
批量删除数据:
boolean removeByIds(Collection<? extends Serializable> idList);
根据ID更新数据:
boolean updateById(T entity);
根据条件更新数据:
boolean update(T entity, Wrapper<T> updateWrapper);
批量更新数据:
boolean updateBatchById(Collection<T> entityList);
根据ID查询数据:
T getById(Serializable id);
根据条件查询单条数据:
T getOne(Wrapper<T> queryWrapper);
查询所有数据:
List<T> list();
根据条件查询数据列表:
List<T> list(Wrapper<T> queryWrapper);
分页查询数据:
Page<T> page(Page<T> page);
// 假设要进行无条件的分页查询,每页显示10条记录,查询第1页
IPage<User> page = new Page<>(1, 10);
IPage<User> userPage = userService.page(page); // 调用 page 方法
List<User> userList = userPage.getRecords();
long total = userPage.getTotal();
System.out.println("Total users: " + total);
for (User user : userList) {
System.out.println("User: " + user);
}根据条件分页查询数据:
Page<T> page(Page<T> page, Wrapper<T> queryWrapper);
这些方法提供了对数据库进行增删改查的基本操作。
sql映射
在 MyBatis 中,可以通过在映射文件中定义 SQL 语句,并将其与 Java 接口方法关联起来。以下是一个简单的例子,展示了如何在 BUserMapper.xml
文件中定义 SQL 语句,并将其与 BUserMapper
接口中的方法进行映射。
首先,定义 BUserMapper
接口:
package com.example.mysecurity.dao; |
然后,在 BUserMapper.xml
文件中定义 SQL 语句:
|
在这个例子中:
BUserMapper
接口中定义了两个方法:findByUsername
和findAll
。findByUsername
方法使用了注解@Select
来直接在接口中定义 SQL 语句。findAll
方法在BUserMapper.xml
文件中定义了 SQL 语句,并通过id
属性与接口中的方法名进行关联。
通过这种方式,MyBatis 可以根据接口方法调用相应的 SQL 语句。
logaspect
package com.example.dorm.aspect; |