1、Sa-Token 介绍
1.1 Sa-Token 开发文档:https://sa-token.cc
1.2 Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、微服务鉴权 等一系列权限相关问题。
1.3 功能结构图
2、 SpringBoot 3 集成 Sa-Token
2.1 Maven
<!-- Sa-Token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.38.0</version>
</dependency>
<!-- Sa-Token 集成 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>1.38.0</version>
</dependency>
<!-- Sa-Token 集成 redis, 并使用 jackson 序列化 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.38.0</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- fastjson2 处理 json 数据 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.48</version>
</dependency>
<!-- 使用 springdoc 生成 swagger 文档 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<!-- mybatis-plus-boot-starter 中 mybatis-spring 版本不够,排除之后引入新版本 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
2.2 yml 配置 sa-token
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/xxx
username: xxx
password: xxx
data:
redis:
database: 1
sa-token:
token-name: X-Token
# token有效期,单位s 默认30天, -1代表永不过期
timeout: -1
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: 3600
# jwt秘钥
jwt-secret-key: qazwsxedc
# 文件上传下载目录
files:
upload:
path: D:/files/
2.3 常量
package com.dragon.springboot3vue3.common;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "常量")
public class Constant {
// SaResult 默认设置了 200 为成功, 500 为 失败
/********** CODE & MSG **********/
public static final String TOKEN= "X-Token";
public static final String USER_PASSWORD= "123456";
public static final int TOKEN_INVALID_CODE = 20001; public static final String TOKEN_INVALID_MSG = "Token无效,请重新登录";
public static final int USERNAME_OCCUPIED_CODE = 20002; public static final String USERNAME_OCCUPIED_MSG = "用户名被占用,请重新输入";
public static final int USERNAME_OR_PASSWORD_ERROR_CODE = 20003; public static final String USERNAME_OR_PASSWORD_ERROR_MSG = "用户名或密码输入错误";
public static final int MISSING_NECESSARY_PARAMETERS_CODE = 20004; public static final String MISSING_NECESSARY_PARAMETERS_MSG = "缺少必要的参数";
public static final int ORIGINAL_PASSWORD_ERROR_CODE = 20005; public static final String ORIGINAL_PASSWORD_ERROR_MSG = "原密码输入错误";
public static final int PASSWORD_INCONSISTENCY_CODE = 20006; public static final String PASSWORD_INCONSISTENCY_MSG = "两次输入的新密码不一致";
// 请求白名单,请求会放行
public static final String[] WHITE_LIST = {
"/user/register",
"/user/login",
"/user/logout",
"/swagger-ui/**",
"/v3/**",
"/files/{fileName}",
};
}
2.4 config-配置类
2.4.1 跨域配置类
package com.dragon.springboot3vue3.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.List;
/**
* 跨域配置类
*/
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(List.of("http://localhost:3000", "http://127.0.0.1:5173","http://localhost:5173")); // 设置允许的来源
corsConfiguration.setAllowedMethods(List.of("*")); // 设置允许的方法
corsConfiguration.setAllowedHeaders(List.of("*")); // 设置允许的头部
corsConfiguration.setAllowCredentials(true); // 允许携带凭证
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
2.4.2 SaToken 配置类
package com.dragon.springboot3vue3.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import com.dragon.springboot3vue3.common.Constant;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// Sa-Token 整合 jwt (Simple 简单模式)
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForSimple();
}
// 注册 Sa-Token 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handle -> {
// 登录拦截,放行白名单
SaRouter.match("/**").notMatch(Constant.WHITE_LIST).check(r -> StpUtil.checkLogin());
}))
.addPathPatterns("/**");
}
}
2.4.3 Swagger 配置类
package com.dragon.springboot3vue3.config;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
OpenAPI openAPI = new OpenAPI();
openAPI.info(new Info().title("前后端分离管理系统")
.description("使用springboot3-vue3等技术")
.version("v1.0.0")
.license(new License().name("Apache 2.0").url("https://springdoc.org")));
openAPI.externalDocs(new ExternalDocumentation().description("项目API文档")
.url("/"));
return openAPI;
}
}
2.4.4 MybatisPlus 配置类
package com.dragon.springboot3vue3.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/**
* 添加 MybatisPlus 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
2.5 实现登录认证
2.5.1 SaResult - SaToken封装的结果集
2.5.2 StpUtil - SaToken的鉴权工具类(Sa-Token)
2.5.3 StringRedisTemplate - Redis工具类
2.5.4 BCrypt - 密码加密方式(Sa-Token)
2.5.5 @Tag、@Operation - Swagger(SpringDoc)注解
package com.dragon.springboot3vue3.controller;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dragon.springboot3vue3.common.Constant;
import com.dragon.springboot3vue3.controller.dto.entityDto.RegisterOrLoginDto;
import com.dragon.springboot3vue3.controller.dto.entityDto.UserDto;
import com.dragon.springboot3vue3.controller.dto.pageDto.UserPageDto;
import com.dragon.springboot3vue3.entity.User;
import com.dragon.springboot3vue3.service.IUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Tag(name = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Operation(summary = "注册")
@PostMapping("/register")
public SaResult register(@RequestBody @Validated RegisterOrLoginDto registerDto){
User user=userService.lambdaQuery().eq(User::getUsername,registerDto.getUsername()).one();
if(user!=null){
return SaResult.error(Constant.USERNAME_OCCUPIED_MSG).setCode(Constant.USERNAME_OCCUPIED_CODE);
}
user=new User();
BeanUtils.copyProperties(registerDto,user);
// BCrypt.hashpw() 密码加密
user.setPassword(BCrypt.hashpw(registerDto.getPassword(), BCrypt.gensalt()));
userService.save(user);
return SaResult.ok();
}
@Operation(summary = "登录")
@PostMapping("/login")
public SaResult login(@RequestBody @Validated RegisterOrLoginDto loginDto){
User user=userService.lambdaQuery().eq(User::getUsername,loginDto.getUsername()).one();
// BCrypt.checkpw(前端明文,后端密文)
if(user!=null && BCrypt.checkpw(loginDto.getPassword(),user.getPassword())){
// 登录认证
StpUtil.login(user.getId());
// 生成token,token 信息自动存入redis,在yml里配置 sa-token 相关信息
String token = StpUtil.getTokenValue();
// 将用户信息存入 redis
stringRedisTemplate.opsForValue().set(user.getId(), JSON.toJSONString(user),1, TimeUnit.DAYS);
return SaResult.ok().setData(token);
}
return SaResult.error(Constant.USERNAME_OR_PASSWORD_ERROR_MSG).setCode(Constant.USERNAME_OR_PASSWORD_ERROR_CODE);
}
@Operation(summary = "注销")
@PostMapping("/logout")
public SaResult logout(){
StpUtil.logout();
return SaResult.ok();
}
@Operation(summary = "获取登录用户信息")
@GetMapping("/userInfo")
public SaResult userInfo(){
// Redis 中获取登录的用户信息
String userInfo = stringRedisTemplate.opsForValue().get(StpUtil.getLoginIdAsString());
User user= JSON.parseObject(userInfo,User.class);
return SaResult.ok().setData(user);
}
}