目录
- 前言
- 一、创建工程项目🎍
- 1.1 创建后端工程
- 1.2 创建前端工程
- 二、业务代码🎊
- 后端代码
- 前端代码
- 三、测试
- 参考资料
前言
Sa-Token 是一款 Java 语言的权限认证框架,提供了灵活、高效、易用的权限认证和会话管理功能。它是 SpringBoot、Spring MVC、Servlet 等 Java 技术体系下的轻量级权限认证组件,可以帮助开发者快速实现用户认证、授权和会话管理等功能。
Sa-Token官方文档
功能结构图
认证流程图
框架特性
一、创建工程项目🎍
1.1 创建后端工程
引入依赖
org.springframework.boot spring-boot-starter-parent 2.6.13 org.springframework.boot spring-boot-starter-web cn.dev33 sa-token-spring-boot-starter 1.38.0 cn.dev33 sa-token-redis-fastjson2 1.37.0 org.springframework.boot spring-boot-starter-data-redis org.projectlombok lombok true 注:如果你使用的是 SpringBoot 3.x,只需要将 sa-token-spring-boot-starter 修改为 sa-token-spring-boot3-starter 即可。
基础配置
server: port: 8081 spring: redis: database: 1 host: 127.0.0.1 port: 6379 password: timeout: 5000 jedis: pool: max-active: 8 max-wait: -1 max-idle: 8 min-idle: 0 #Sa-token相关配置(与官网一致) sa-token: # token 名称(同时也是 cookie 名称) token-name: satoken # token 有效期(单位:秒) 默认30天,-1 代表永久有效 timeout: 2592000 # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 active-timeout: -1 # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) is-concurrent: true # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token) is-share: true # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik) token-style: uuid # 是否输出操作日志 is-log: true
1.2 创建前端工程
本项目使用的vue3,创建一个vue项目,引入下方依赖即可
"dependencies": { "axios": "^1.6.8", "element-plus": "^2.7.3", "pinia": "^2.1.7", "qs": "^6.12.1", "vue": "^3.2.37", "vue-router": "^4.2.5" },
二、业务代码🎊
后端代码
User.java
@Data public class User{ //id @TableId(type = IdType.ASSIGN_ID) private Long id; //用户名 private String username; //密码 private String password; //账户是否锁住(1被锁0未被锁) private Integer isLocked; //账户是否被删除(1删除0未被删除) @TableLogic private Integer isDelete; //创建时间 @TableField(fill = FieldFill.INSERT) private Date createTime; //更新时间 @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; }
UserDTO.java
@EqualsAndHashCode(callSuper = true) @Data public class UserDTO extends User { //记住我 private boolean rememberMe; //角色列表 private List
roleList; //登录的设备 private String device; //图形验证码 private String code; //图形验证码的key private String codeKey; } UserController.java
注意:代码中定义了一些常量,可以自行替代。
@RestController @RequestMapping("/user") @CrossOrigin public class UserController { @Autowired UserService userService; @Autowired StringRedisTemplate stringRedisTemplate; @PostMapping("/doLogin") public SaResult doLogin(@RequestBody UserDto userDto) { //从redis中取生成的验证码 String validateCode = stringRedisTemplate.opsForValue().get("code:validate:" + userDto.getCodeKey()); if (userDto.getCode().equals(validateCode)) { //验证码正确才进行用户验证,先验证了用户存不存在,再验证了密码是否正确 if (userService.login(userDto.getUsername(), userDto.getPassword())) { StpUtil.login(userDto.getUsername(), new SaLoginModel() //实现‘记住我’功能 .setIsLastingCookie(userDto.isRememberMe()) //设置登录设备,主要用于实现同端互斥登录,此处没有实现该功能,可以不用管 .setDevice("PC")); //验证成功就以json的形式返回token HashMap
resultMap = new HashMap<>(); resultMap.put("token", StpUtil.getTokenValue()); return SaResult .ok(ResponseCode.LOGIN_SUCCESS.getMessage()) .setCode(ResponseCode.LOGIN_SUCCESS.getCode()) .setData(resultMap); } else { return SaResult .error(ResponseCode.USERNAME_PASSWORD_ERROR.getMessage()) .setCode(ResponseCode.USERNAME_PASSWORD_ERROR.getCode()); } } else { return SaResult .error(ResponseCode.VALIDATE_CODE_ERROR.getMessage()) .setCode(ResponseCode.VALIDATE_CODE_ERROR.getCode()); } } } StpUtils.login:
- 检查此账号是否之前已有登录;
- 为账号生成 Token 凭证与 Session 会话;
- 记录 Token 活跃时间;
- 通知全局侦听器,xx 账号登录成功;
- 将 Token 注入到请求上下文;
- 等等其它工作……
SaResult:这个也是一个由Satoken封装的结果响应类,还是挺好用的。
UserService.java
public interface UserService { boolean login(String username, String password); }
UserServiceImpl.java
@Service public class UserServiceImpl implements UserService{ @Autowired UserDao userDao; @Override public boolean login(String username, String password) { User user = userDao.selectOne(new QueryWrapper
().eq("username", username)); //数据库中的密码进行了加密BCrypt也是Satoken提供的一个工具类 return user != null && BCrypt.checkpw(password, user.getPassword()); } } UserDao.java
/** * (User)表数据库访问层 * * @author yzk * @since 2024-05-15 16:44:29 */ public interface UserDao extends BaseMapper
{ } SaAuthenticationConfigure.java
@Configuration public class SaAuthenticationConfigure implements WebMvcConfigurer { // 注册 Sa-Token 拦截器,打开注解式鉴权功能 @Override public void addInterceptors(InterceptorRegistry registry) { // 注册 Sa-Token 拦截器,打开注解式鉴权功能 registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin())) //所有接口都会检查是否登录了 .addPathPatterns("/**") //以下接口不检查,直接放行 .excludePathPatterns("/user/doLogin") } }
SaTokenFilter.java
@Configuration public class SaTokenFilter implements WebMvcConfigurer { /** * 注册 [Sa-Token 全局过滤器] */ @Bean public SaServletFilter getSaServletFilter() { return new SaServletFilter() // 指定 [拦截路由] 与 [放行路由] .addInclude("/**").addExclude("/favicon.ico") // 认证函数: 每次请求执行 .setAuth(obj -> { SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue()); // ... }) // 异常处理函数:每次认证函数发生异常时执行此函数 .setError(e -> { return SaResult.error(e.getMessage()); }) // 前置函数:在每次认证函数之前执行 .setBeforeAuth(obj -> { SaHolder.getResponse() // ---------- 设置跨域响应头 ---------- // 允许指定域访问跨域资源 .setHeader("Access-Control-Allow-Origin", "*") // 允许所有请求方式 .setHeader("Access-Control-Allow-Methods", "*") // 允许的header参数 .setHeader("Access-Control-Allow-Headers", "*") // 有效时间 .setHeader("Access-Control-Max-Age", "3600") ; // 如果是预检请求,则立即返回到前端 SaRouter.match(SaHttpMethod.OPTIONS) .free(r -> System.out.println("--------OPTIONS预检请求,不做处理")) .back(); }) ; } }
注意这个过滤器!!!!!!!!!!务必配置这个过滤器,用SaToken时,后端使用@CrossOrigin进行了跨域的配置,但是!前端发起请求还是会报跨域问题,而且后端会报'未读取到有效token',是因为前端发起请求时,先发起了预检请求,SaToken拦截了预检请求,预检请求的headers中没有token,所以一直报错。
前端代码
此处删减了部分代码,只保留了登录登录相关的代码
login.vue
登录
前端点击登录按钮时,发后端接口发起请求,请求成功后,取出响应结果中的token存入localStorage。
requests.js
import axios from 'axios'; import {router} from "@/router"; axios.defaults.crossDomain = true export const requests = axios.create({ baseURL: 'http://localhost:8081', timeout: 10000, // 请求超时时间 headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } }); // 请求拦截器 requests.interceptors.request.use( (config) => { // 在发送请求之前做些什么 const token = localStorage.getItem('token'); // 假设token存储在localStorage中 if (token) { config.headers.Token = `Bearer ${token}`; // 添加token到请求头 } return config; }, (error) => { // 对请求错误做些什么 return Promise.reject(error); } ); // 响应拦截器 requests.interceptors.response.use( (response) => { console.log(response.data.code) const code=response.data.code if (code === 5006 ||code===5007|| code===5008) { //返回登录界面 router.push('/').then(r =>{ location.reload() } ) // router.go(0) //删除当前localStorage中的token localStorage.removeItem("token") } return response.data; }, (error) => { // 对响应错误做点什么 return Promise.reject(error); } );
注意请求拦截器,每次发起请求时,都会从localStorage中取出token,并将其放入headers中
三、测试
集成了redis后,记得先开启redis服务,在登录时,框架会自动保存数据
-
开启前后端服务
本文的代码是从完整项目中抽出的,上述代码只有登录功能
输入用户名和密码后,进入主界面
此时,登录的信息已经自动的被框架自动保存进redis
IDEA控制台打印出相应的信息
参考资料
Sa-Token
使用 Sa-Token 的全局过滤器解决跨域问题(三种方式全版) - 掘金 (juejin.cn)
Sa-Token功能结构图
还没有评论,来说两句吧...