Sa-Token介绍:Sa-Token 开发公司是一个轻量级 Java 开发公司权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、开发公司微服务网关鉴权 开发公司等一系列权限相关问题
开发公司本文章框架使用:
SpringCloudAlibaba、SpringBoot2.1.13、sa-token1.30.0、redis
服务架构
开始
一、开发公司首先配置服务
1、pom.xml
- <!-- Sa-Token 权限认证(Reactor开发公司响应式集成) -->
- <dependency>
- <groupId>cn.dev33</groupId>
- <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
- <version>1.30.0</version>
- </dependency>
- <!-- Sa-Token 整合 Redis (使用jackson开发公司序列化方式) -->
- <dependency>
- <groupId>cn.dev33</groupId>
- <artifactId>sa-token-dao-redis-jackson</artifactId>
- <version>1.30.0</version>
- </dependency>
- <!--GateWay 网关-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-gateway</artifactId>
- </dependency>
2、bootstrap.yml引入sa-token配置
-
- # Sa-Token配置
- sa-token:
- # token名称 (同时也是cookie名称)
- token-name: satoken
- # token有效期,单位秒,-1开发公司代表永不过期
- timeout: 2592000
- # token开发公司临时有效期 (开发公司指定时间内无操作就视为token过期),单位秒
- activity-timeout: -1
- # 开发公司是否允许同一账号并发登录 (为false开发公司时新登录挤掉旧登录)
- is-concurrent: true
- # 开发公司在多人登录同一账号时,是否共用一个token (为false时每次登录新建一个token)
- is-share: false
- # token风格
- token-style: uuid
- # 是否输出操作日志
- is-log: false
- # 是否从cookie中读取token
- is-read-cookie: false
- # 是否从head中读取token
- is-read-head: true
3、新建类SaTokenConfigure,实现网关拦截
- package com.frontop.meta.config;
-
- import cn.dev33.satoken.config.SaTokenConfig;
- import cn.dev33.satoken.context.SaHolder;
- import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
- import cn.dev33.satoken.reactor.filter.SaReactorFilter;
- import cn.dev33.satoken.router.SaHttpMethod;
- import cn.dev33.satoken.router.SaRouter;
- import cn.dev33.satoken.stp.StpUtil;
- import cn.dev33.satoken.util.SaResult;
- import com.frontop.meta.util.ResultJsonUtil;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.Primary;
- import org.springframework.web.server.ServerWebExchange;
-
- /**
- * @author YangBoss
- * @title: SaTokenConfigure
- * @projectName meta
- * @description: TODO
- * @date 2022/8/18 10:12
- */
- @Configuration
- public class SaTokenConfigure {
- // 注册 Sa-Token全局过滤器
- @Bean
- public SaReactorFilter getSaReactorFilter() {
- return new SaReactorFilter()
- // 拦截地址
- .addInclude("/**")
- // 开放地址
- .addExclude("/favicon.ico")
- // 鉴权方法:每次访问进入
- .setAuth(obj -> {
- // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
- SaRouter.match("/**", "/meta-auth/phoneLogin", r -> StpUtil.checkLogin());
- // 角色认证 -- 拦截以 admin 开头的路由,必须具备 admin 角色或者 super-admin 角色才可以通过认证
- SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin"));
- // 权限认证 -- 不同模块, 校验不同权限
- SaRouter.match("/meta-system/**", r -> StpUtil.checkPermission("system-no"));
- SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
- SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
- SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
- })
- // 异常处理方法:每次setAuth函数出现异常时进入
- .setError(e -> {
- // 设置错误返回格式为JSON
- ServerWebExchange exchange = SaReactorSyncHolder.getContext();
- exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8");
- // return new ResultJsonUtil().fail(e.getMessage());
- return SaResult.error(e.getMessage());
- })
- .setBeforeAuth(obj -> {
- // ---------- 设置跨域响应头 ----------
- SaHolder.getResponse()
- // 允许指定域访问跨域资源
- .setHeader("Access-Control-Allow-Origin", "*")
- // 允许所有请求方式
- .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
- // 有效时间
- .setHeader("Access-Control-Max-Age", "3600")
- // 允许的header参数
- .setHeader("Access-Control-Allow-Headers", "*");
-
- // 如果是预检请求,则立即返回到前端
- SaRouter.match(SaHttpMethod.OPTIONS)
- .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
- .back();
- });
- }
-
-
- }
4、新建全局异常处理类GlobalException
- package com.frontop.meta.config;
-
- import cn.dev33.satoken.exception.DisableLoginException;
- import cn.dev33.satoken.exception.NotLoginException;
- import cn.dev33.satoken.exception.NotPermissionException;
- import cn.dev33.satoken.exception.NotRoleException;
- import com.frontop.meta.constant.ResponseCodeConstant;
- import com.frontop.meta.constant.ResponseMessageConstant;
- import com.frontop.meta.util.ResultJsonUtil;
- import org.springframework.web.bind.annotation.ExceptionHandler;
- import org.springframework.web.bind.annotation.ResponseBody;
-
- /**
- * @author YangBoss
- * @title: GlobalException
- * @projectName meta
- * @description: 拦截全局异常类
- * @date 2022/8/19 15:39
- */
- public class GlobalException {
- // 全局异常拦截(拦截项目中的所有异常)
- @ResponseBody
- @ExceptionHandler
- public ResultJsonUtil<Object> handlerException(Exception e) {
-
- // 打印堆栈,以供调试
- // System.out.println("全局异常---------------");
- e.printStackTrace();
-
- // 不同异常返回不同状态码
- ResultJsonUtil<Object> re = null;
- if (e instanceof NotLoginException) { // 如果是未登录异常
- NotLoginException ee = (NotLoginException) e;
- re = new ResultJsonUtil().customized(
- ResponseCodeConstant.OAUTH_TOKEN_FAILURE,
- ResponseMessageConstant.OAUTH_TOKEN_MISSING,
- null
- );
- }
- else if(e instanceof NotRoleException) { // 如果是角色异常
- NotRoleException ee = (NotRoleException) e;
- re = new ResultJsonUtil().customized(
- ResponseCodeConstant.OAUTH_TOKEN_DENIED,
- "无此角色:" + ee.getRole(),
- null
- );
- }
- else if(e instanceof NotPermissionException) { // 如果是权限异常
- NotPermissionException ee = (NotPermissionException) e;
- re = new ResultJsonUtil().customized(
- ResponseCodeConstant.OAUTH_TOKEN_DENIED,
- "无此角色:" + ee.getCode(),
- null
- );
- }
- else if(e instanceof DisableLoginException) { // 如果是被封禁异常
- DisableLoginException ee = (DisableLoginException) e;
- re = new ResultJsonUtil().customized(
- ResponseCodeConstant.USER_LOCK,
- "账号被封禁:" + ee.getDisableTime() + "秒后解封",
- null
- );
- }
- else { // 普通异常, 输出:500 + 异常信息
- re = new ResultJsonUtil().fail(e.getMessage());
- }
-
- // 返回给前端
- return re;
- }
- }
5、新建类StpInterfaceImpl,实现获取当前账号权限角色集合
- package com.frontop.meta.config;
-
- import cn.dev33.satoken.stp.StpInterface;
- import org.springframework.stereotype.Component;
-
- import java.util.ArrayList;
- import java.util.List;
-
- /**
- * @author YangBoss
- * @title: StpInterfaceImpl
- * @projectName meta
- * @description: TODO
- * @date 2022/8/18 10:26
- */
- @Component
- public class StpInterfaceImpl implements StpInterface {
- /**
- *当前全部是模拟数据,真实情况使用根据loginId动态查询对应角色和权限
- */
- @Override
- public List<String> getPermissionList(Object loginId, String loginType) {
- // 返回此 loginId 拥有的权限列表
- List<String> strs = new ArrayList<>();
- strs.add("system");
- return strs;
- }
-
- @Override
- public List<String> getRoleList(Object loginId, String loginType) {
- // 返回此 loginId 拥有的角色列表
- List<String> strs = new ArrayList<>();
- strs.add("admin");
- return strs;
- }
-
- }
二、配置授权服务
1、pom.xml
- <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
- <dependency>
- <groupId>cn.dev33</groupId>
- <artifactId>sa-token-spring-boot-starter</artifactId>
- <version>1.30.0</version>
- </dependency>
-
-
- <!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
- <dependency>
- <groupId>cn.dev33</groupId>
- <artifactId>sa-token-dao-redis-jackson</artifactId>
- <version>1.30.0</version>
- </dependency>
2、bootstarp.yml引入sa-token配置
-
- # Sa-Token配置
- sa-token:
- # token名称 (同时也是cookie名称)
- token-name: satoken
- # token有效期,单位秒,-1代表永不过期
- timeout: 2592000
- # token临时有效期 (指定时间内无操作就视为token过期),单位秒
- activity-timeout: -1
- # 是否允许同一账号并发登录 (为false时新登录挤掉旧登录)
- is-concurrent: true
- # 在多人登录同一账号时,是否共用一个token (为false时每次登录新建一个token)
- is-share: false
- # token风格
- token-style: uuid
- # 是否输出操作日志
- is-log: false
- # 是否从cookie中读取token
- is-read-cookie: false
- # 是否从head中读取token
- is-read-head: true
3、编写一个简单的登录
- package com.frontop.meta.controller;
-
- import cn.dev33.satoken.stp.StpUtil;
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.frontop.meta.util.ResultJsonUtil;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- /**
- * @author YangBoss
- * @title: UserLoginController
- * @projectName meta
- * @description: TODO
- * @date 2022/8/19 14:44
- */
- @RestController
- @Api(tags = "用户授权登录")
- public class UserLoginController {
-
- @ApiOperation(value = "手机+密码登录")
- @PostMapping("/phoneLogin")
- public ResultJsonUtil<Object> getAwardCount(String phone,String password) {
-
- if(phone.equals("18874288923") && password.equals("123")){
- StpUtil.login(1001,"PC");
- return new ResultJsonUtil().success(StpUtil.getTokenInfo());
- }
- return new ResultJsonUtil().fail("手机号或密码错误");
- }
-
-
- }
三、测试效果
1、启动网关服务和授权服务后调用登录接口
redis中
到这里简单的登录就完成了
2、在system业务服务中简单配置一个测试接口
system业务服务中也需要引入sa-token,bootsrap.yml配置都是一样的
- <!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:http://sa-token.dev33.cn/ -->
- <dependency>
- <groupId>cn.dev33</groupId>
- <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
- <version>1.30.0</version>
- </dependency>
- <!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
- <dependency>
- <groupId>cn.dev33</groupId>
- <artifactId>sa-token-dao-redis-jackson</artifactId>
- <version>1.30.0</version>
- </dependency>
3、不携带token值访问接口
4、携带token访问
这里报无权限的原因就是网关实现了拦截,在上面配置中网关配置了meta-system路由的权限必须使用system-no
而我们在添加权限集合时候没有该权限所以被拦截
角色拦截配置也是类似
四、使用注解拦截
1、如果想使用注解拦截,只能写在业务服务的接口层
2、首先要在业务服务中开启注解拦截配置
- package com.frontop.meta;
-
- import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
- /**
- * @author YangBoss
- * @title: SaTokenConfigure
- * @projectName meta
- * @description: TODO
- * @date 2022/9/7 10:53
- */
- @Configuration
- public class SaTokenConfigure implements WebMvcConfigurer {
- // 注册Sa-Token的注解拦截器,打开注解式鉴权功能
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关)
- registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
- }
- }
3、也需要配置获取角色和权限的集合类
- package com.frontop.meta;
-
- import cn.dev33.satoken.stp.StpInterface;
- import org.springframework.stereotype.Component;
-
- import java.util.ArrayList;
- import java.util.List;
-
- /**
- * @author YangBoss
- * @title: StpInterfaceImpl
- * @projectName meta
- * @description: TODO
- * @date 2022/8/18 10:26
- */
- @Component
- public class StpInterfaceImpl implements StpInterface {
-
- @Override
- public List<String> getPermissionList(Object loginId, String loginType) {
- // 返回此 loginId 拥有的权限列表
- List<String> strs = new ArrayList<>();
- strs.add("system-no");
- return strs;
- }
-
- @Override
- public List<String> getRoleList(Object loginId, String loginType) {
- // 返回此 loginId 拥有的角色列表
- List<String> strs = new ArrayList<>();
- strs.add("admin");
- return strs;
- }
-
- }
4、接口配置注解拦截
- @RestController
- @RequestMapping("/test")
- @Api(tags = "测试")
- public class TestContorller {
-
- @ApiOperation(value = "请求汇总",consumes = "application/json;charset=UTF-8")
- @RequestMapping(value = "/apiGather", method = RequestMethod.POST)
- @SaCheckRole("super-admin2")//必须拥有该角色可访问
- @SaCheckPermission("system-no")//必须拥有该权限可访问
- public ResultJsonUtil<Object> apiGather(){
- return new ResultJsonUtil().success("111");
- }
-
- }
5、测试效果