> 文章列表 > satoken+ gateway网关统一鉴权 初版

satoken+ gateway网关统一鉴权 初版

satoken+ gateway网关统一鉴权 初版

一:感谢大佬

本博客内容 参考了satoken官网实现,satoken官网地址:
https://sa-token.cc/doc.html#/micro/gateway-auth

二:项目层级介绍

  1. jinyi-gateway 网关服务
  2. jinyi-user-service 用户服务
    2.1 jinyi-user-api
    2.2 jinyi-user-client
    2.3 jinyi-user-provider
  3. jinyi-common 通用服务,定义了一些统一返回类,全局常量(R等)
    项目
    层级关系截图:
    satoken+ gateway网关统一鉴权 初版
    satoken+ gateway网关统一鉴权 初版

三:项目具体介绍

3.1jinyi-gateway 网关服务

3.1.1 pom.xml
标签里只是指明了springboot springcloud springcloud-alibaba等版本信息和通用的像lombok等通用的类。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"><parent><artifactId>jinyi-up-project</artifactId><groupId>com.jinyi.up</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>jinyi-gateway</artifactId><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version><maven.compiler.source>${java.version}</maven.compiler.source><maven.compiler.target>${java.version}</maven.compiler.target><alibaba.nacos.version>2.1.0</alibaba.nacos.version><jinyi-up.version>1.0.0</jinyi-up.version><sa-token.version>1.34.0</sa-token.version></properties><dependencies><!--Spring Cloud & Alibaba--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId><exclusions><!--Sa-Token 权限认证(Reactor响应式)--><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></exclusion></exclusions></dependency><!-- 注册中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!-- 配置中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--通用基础common--><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-common-base</artifactId><version>${jinyi-up.version}</version></dependency><!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:http://sa-token.dev33.cn/ --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-reactor-spring-boot-starter</artifactId><version>${sa-token.version}</version></dependency><!-- Sa-Token 整合 Redis (使用jackson序列化方式) --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-dao-redis-jackson</artifactId><version>${sa-token.version}</version></dependency><!-- 提供Redis连接池 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-user-client</artifactId><version>1.0.0</version></dependency><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-user-api</artifactId><version>1.0.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>com.spotify</groupId><artifactId>docker-maven-plugin</artifactId></plugin></plugins></build>
</project>

核心maven依赖

<!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:https://sa-token.cc -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-reactor-spring-boot-starter</artifactId><version>1.34.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-dao-redis-jackson</artifactId><version>1.34.0</version>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

config包中类:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;@Configuration
@EnableAsync
@RefreshScope
public class ExecutorConfig {@Value("${executor.corePoolSize:4}")private Integer corePoolSize;@Value("${executor.queueCapacity:1000}")private Integer queueCapacity;@Value("${executor.maxPoolSize:6}")private Integer maxPoolSize;@Value("${executor.keepAliveSeconds:30}")private Integer keepAliveSeconds;/* 线程池参数定义* @return*/@Beanpublic ThreadPoolTaskExecutor AsyncThreadPoolTaskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setThreadNamePrefix("async-task-executor-");executor.setCorePoolSize(corePoolSize);executor.setQueueCapacity(queueCapacity);executor.setMaxPoolSize(maxPoolSize);//线程大于coreSize,多余线程数超过30s则销毁executor.setKeepAliveSeconds(keepAliveSeconds);// 设置拒绝策略,调用当前线程执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}
}
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;import java.util.stream.Collectors;/* gateway是基于WebFlux的一个响应式组件,HttpMessageConverters不会像Spring Mvc一样自动注入,需要我们手动配置/
@Configuration
public class HttpMessageConvertersConfigure {@Bean@ConditionalOnMissingBeanpublic HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));}
}

其中最核心的SaTokenConfigures类

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 org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.server.ServerWebExchange;@Configuration
public class SaTokenConfigures {//需要放行的接口[白名单]private static final String[] PERMITURL = new String[]{"/user-api/jinyiUser/accountLogin"};// 注册 Sa-Token全局过滤器@Beanpublic SaReactorFilter getSaServletFilter() {return new SaReactorFilter()// 拦截地址.addInclude("/")// 开放地址【白名单】
//                .addExclude(PERMITURL)// 鉴权方法:每次访问进入.setAuth(obj -> {// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
//                  SaRouter.match("/", "/account-service/account/userLogin", r -> StpUtil.checkLogin());SaRouter.match("/").notMatch("/user-api/jinyiUser/accountLogin","//swagger-resources/", "//webjars/","//swagger-ui.html/","//doc.html/", "//error","//favicon.ico").check(r -> StpUtil.checkLogin());// 权限认证 -- 不同模块, 校验不同权限SaRouter.match("/jinyiUser/", r -> StpUtil.checkRole("admin"));
//                    SaRouter.match("/user-api/jinyiUser/", r -> StpUtil.checkRole("admin"));
//                    SaRouter.match("/jinyiUser/", r -> StpUtil.checkPermission("system"));
//                    SaRouter.match("/goods/", r -> StpUtil.checkPermission("goods"));
//                    SaRouter.match("/orders/", r -> StpUtil.checkPermission("orders"));// ...})// 异常处理方法:每次setAuth函数出现异常时进入.setError(e -> {// 设置错误返回格式为JSONServerWebExchange exchange = SaReactorSyncHolder.getContext();exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8");//登录或者权限相关的resp json 都是在下面的代码里封装返回的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();});}@Bean@Primarypublic SaTokenConfig getSaTokenConfigPrimary() {SaTokenConfig config = new SaTokenConfig();config.setTokenName("token");             // token名称 (同时也是cookie名称)config.setTimeout(30 * 24 * 60 * 60);       // token有效期,单位s 默认30天config.setActivityTimeout(-1);              // token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒config.setIsConcurrent(true);               // 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)config.setIsShare(true);                    // 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)config.setTokenStyle("uuid");               // token风格config.setIsLog(false);                     // 是否输出操作日志return config;}
}

exception包GlobalException类

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.jinyi.up.common.base.result.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@Slf4j
@RestControllerAdvice
public class GlobalException {@ExceptionHandlerpublic R handlerException(Exception e) {e.printStackTrace();// 不同异常返回不同状态码R r = null;if (e instanceof NotLoginException) {	// 如果是未登录异常NotLoginException ee = (NotLoginException) e;log.error("登录异常:{}",e.getMessage());R.failed(ee.getMessage());}else if(e instanceof NotRoleException) {		// 如果是角色异常NotRoleException ee = (NotRoleException) e;R.failed("无此角色:" + ee.getRole());}else if(e instanceof NotPermissionException) {	// 如果是权限异常NotPermissionException ee = (NotPermissionException) e;R.failed("无此权限:" + ee.getMessage());}
//        else if(e instanceof DisableLoginException) {	// 如果是被封禁异常
//            R.failed("账号被封禁:" + e.getDisableTime() + "秒后解封");
//        }else {	// 普通异常, 输出:500 + 异常信息R.failed();}// 返回给前端return r;}}

service包中

import cn.dev33.satoken.stp.StpInterface;
import com.google.common.collect.Lists;
import com.jinyi.up.common.base.result.R;
import com.jinyi.up.feignclient.UserProvideFeignClient;
import com.jinyi.up.user.vo.JinyiRoleVO;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;@Component
public class StpInterfaceImpl implements StpInterface {@Resourceprivate UserProvideFeignClient userFeignClient;@Resource@Qualifier(value = "AsyncThreadPoolTaskExecutor")ThreadPoolTaskExecutor executor;/* 返回指定账号id所拥有的权限码集合 @param loginId   账号id* @param loginType 账号类型* @return 该账号id具有的权限码集合*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 返回此 loginId 拥有的权限列表,没有设置权限表。此处为伪代码List<String> strs = new ArrayList<>();strs.add("system");return strs;}/* 返回指定账号id所拥有的角色标识集合 @param loginId   账号id* @param loginType 账号类型* @return 该账号id具有的角色标识集合*/@SneakyThrows@Overridepublic List<String> getRoleList(Object loginId, String loginType) {List<String> result = Lists.newArrayList();CompletableFuture<R> AsyncResp = CompletableFuture.supplyAsync(() -> {return userFeignClient.getUserRoleByUserId((Long) loginId);}, executor);R<List<JinyiRoleVO>> resp = AsyncResp.get();if (R.isSuccess(resp) && !CollectionUtils.isEmpty(resp.getData())) {result = resp.getData().stream().map(JinyiRoleVO::getCode).collect(Collectors.toList());}return result;}}

gateway 项目启动类

import com.jinyi.up.feignclient.UserProvideFeignClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
//@FeignClinet 和 @EnableFeignClients 不是同一个包。指定basePackages包扫描路径,指定basePackageClasses扫描类
@EnableFeignClients(basePackageClasses = {UserProvideFeignClient.class})
public class JinyiGateWayApplication {public static void main(String[] args) {SpringApplication.run(JinyiGateWayApplication.class,args);}
}

yml配置文件bootstrap.yml 和 application-dev.yml
bootstrap.yml 如下

server:port: 8070spring:application:name: jinyi-gatewaymain:allow-bean-definition-overriding: trueprofiles:active: devcloud:nacos:# 注册中心discovery:server-addr: 127.0.0.1:8848# 配置中心config:server-addr: 127.0.0.1:8848file-extension: yamlgroup: GATEWAY_GROUP   # 配置中心  配置中心是空的
#        shared-configs[0]:
#          data-id: jinyi-common.yaml
#          group: COMMON_GROUP
#          refresh: true#gateway的配置gateway:
#      discovery:
#        locator:
#          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
#          lower-case-service-id: true#路由规则routes:- id: jinyi-user-provider # 路由的id,没有规定规则但要求唯一,建议配合服务名uri: lb://jinyi-user-provider #uri: lb://路由项目的spring.application.name#断言 用于路由规则的匹配 路径相匹配的进行路由predicates:- Path=/user-api/  #用户路由filters:- StripPrefix=1  #去掉路由url中的前缀 /jinyi-user# 关键在下面一句,值为true则开启认证,false则不开启# 这种配置方式和spring cloud gateway内置的GatewayFilterFactory一致#  - Authorize=false #自定义的过滤工厂
logging:level:spring.: DEBUG# Sa-Token配置
sa-token:# token名称 (同时也是cookie名称)token-name: token# token有效期,单位秒,默认30天, -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中读取tokenis-read-cookie: false# 是否从head中读取tokenis-read-header: true# token前缀
#  token-prefix: Bearer

application-dev.yml如下

spring:redis:# Redis数据库索引(默认为0)database: 1# Redis服务器地址host: 127.0.0.1# Redis服务器连接端口port: 6379# Redis服务器连接密码(默认为空)password: # 连接超时时间timeout: 10slettuce:pool:# 连接池最大连接数max-active: 200# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: 3000ms# 连接池中的最大空闲连接max-idle: 10# 连接池中的最小空闲连接min-idle: 0

3.2jinyi-user-service服务
3.2.1 jinyi-user-api项目
pom.xml文件

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><artifactId>jinyi-user-service</artifactId><groupId>com.jinyi.up</groupId><version>1.0.0</version></parent><groupId>com.jinyi.up</groupId><artifactId>jinyi-user-api</artifactId><version>1.0.0</version><packaging>jar</packaging><name>jinyi-user-api</name><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties>
</project>

dto 和 vo目录下的类分别是

import io.swagger.annotations.ApiModel;
import lombok.Data;@Data
@ApiModel(value = "用户账号登录")
public class UserAccountLoginDTO {private String account;private String password;
}
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;@Data
@ApiModel("用户角色VO")
public class JinyiRoleVO {@ApiModelProperty("主键id")private Long id;@ApiModelProperty("角色名称")private String name;@ApiModelProperty("角色编码")private String code;@ApiModelProperty("角色状态:1-正常;0-停用")private Boolean status;
}

jinyi-user-client项目:
pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>jinyi-user-service</artifactId><groupId>com.jinyi.up</groupId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>jinyi-user-client</artifactId><name>jinyi-user-client</name><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version><maven.compiler.source>${java.version}</maven.compiler.source><maven.compiler.target>${java.version}</maven.compiler.target><jinyi-up.version>1.0.0</jinyi-up.version></properties><dependencies><!--引入base common--><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-common-base</artifactId><version>${jinyi-up.version}</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><!--            <optional>true</optional>--></dependency><!-- openfeign依赖 1. http客户端选择okhttp 2. loadbalancer替换ribbon --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId></dependency><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-user-api</artifactId></dependency><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-user-api</artifactId><version>1.0.0</version><scope>compile</scope></dependency></dependencies></project>

核心feign类

import com.jinyi.up.common.base.result.R;
import com.jinyi.up.feignclient.fallback.UserProvideFeignFallback;
import com.jinyi.up.user.dto.UserAccountLoginDTO;
import com.jinyi.up.user.vo.JinyiRoleVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;import java.util.List;/* @FeignClient注解 value 指定的要代理的服务的spring:application:name配置信息* path 值的被代理的服务的统一路径*/
@FeignClient(value = "jinyi-user-provider", path = "/jinyiUser",fallback = UserProvideFeignFallback.class) //实现UserProvideFeign服务降级
//        fallbackFactory = UserProvideFeignFallbackFactory.class)
public interface UserProvideFeignClient {@PostMapping("/accountLogin")R phoneLogin(@RequestBody UserAccountLoginDTO userPhoneLoginDTO);@GetMapping("/getUserRoleByUserId/{userId}")R<List<JinyiRoleVO>> getUserRoleByUserId(@PathVariable("userId") Long userId);@PostMapping("/userLogout")R userLogout();
}

jinyi-user-provider项目
pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><artifactId>jinyi-user-service</artifactId><groupId>com.jinyi.up</groupId><version>1.0.0</version></parent><groupId>com.jinyi.up</groupId><artifactId>jinyi-user-provider</artifactId><version>1.0.0</version><name>jinyi-user-provider</name><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version><maven.compiler.source>${java.version}</maven.compiler.source><maven.compiler.target>${java.version}</maven.compiler.target><jinyi-up.version>1.0.0</jinyi-up.version><sa-token.version>1.34.0</sa-token.version></properties><dependencies><!-- 配置读取 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId></dependency><!-- Spring Cloud & Alibaba --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><!-- 注册中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!-- 配置中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--common基础依赖--><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-common-redis</artifactId><version>${jinyi-up.version}</version></dependency><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-common-mybatis-plus</artifactId><version>${jinyi-up.version}</version></dependency><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-common-web</artifactId><version>${jinyi-up.version}</version></dependency><!-- sa-token --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>${sa-token.version}</version></dependency><!-- Sa-Token 整合 Redis (使用jackson序列化方式) --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-dao-redis-jackson</artifactId><version>${sa-token.version}</version></dependency><!-- 提供Redis连接池 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-user-api</artifactId></dependency><dependency><groupId>com.jinyi.up</groupId><artifactId>jinyi-user-api</artifactId><version>1.0.0</version><scope>compile</scope></dependency></dependencies><build><pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --><plugins><!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle --><plugin><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version></plugin><!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --><plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.8.0</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.1</version></plugin><plugin><artifactId>maven-jar-plugin</artifactId><version>3.0.2</version></plugin><plugin><artifactId>maven-install-plugin</artifactId><version>2.5.2</version></plugin><plugin><artifactId>maven-deploy-plugin</artifactId><version>2.8.2</version></plugin><!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle --><plugin><artifactId>maven-site-plugin</artifactId><version>3.7.1</version></plugin></plugins></pluginManagement></build>
</project>

启动类

import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;import java.net.InetAddress;
import java.net.UnknownHostException;@Slf4j
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan(value = "com.jinyi.up.user.mapper")
public class JinyiUserProviderApplication {public static void main(String[] args) throws UnknownHostException {ConfigurableApplicationContext applicationContext = SpringApplication.run(JinyiUserProviderApplication.class, args);Environment env = applicationContext.getEnvironment();String ip = InetAddress.getLocalHost().getHostAddress();String port = env.getProperty("server.port");
//        String activePrn ofile = SpringContext.getActiveProfile();log.info("\\n----------------------------------------------------------\\n\\t" +"Application is running! Access URLs:\\n\\t" +"Doc: \\t\\thttp://" + ip + ":" + port + "/doc.html\\n\\t" +"Swagger: \\thttp://" + ip + ":" + port + "/swagger-ui/\\n\\t" +"----------------------------------------------------------");}
}

yml配置文件bootstrap.yml 和 application-dev.yml
bootstrap.yml 如下

server:port: 8060spring:mvc:pathmatch:matching-strategy: ant_path_matcherapplication:name: jinyi-user-providermain:allow-bean-definition-overriding: trueprofiles:active: devcloud:nacos:# 注册中心discovery:server-addr: 1127.0.0.1:8848# 配置中心config:server-addr: 127.0.0.1:8848file-extension: yaml  group: USER_GROUP #        shared-configs[0]:
#          data-id: jinyi-common.yaml
#          group: COMMON_GROUP
#          refresh: truelogging:level:spring.: DEBUG# Sa-Token配置
sa-token:# token名称 (同时也是cookie名称)token-name: token# token有效期,单位秒,默认30天, -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中读取tokenis-read-cookie: false# 是否从head中读取tokenis-read-header: true# token前缀
#  token-prefix: Bearermybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
spring:datasource:url: "jdbc:mysql://127.0.0.1:3306/jinyi_user_db?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true"username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
#    driver-class-name: com.mysql.jdbc.Driverhikari:minimum-idle: 16maximum-pool-size: 40connection-timeout: 3000idle-timeout: 50000validation-timeout: 60000max-lifetime: 86370redis:# Redis数据库索引(默认为0)database: 1# Redis服务器地址host: 127.0.0.1# Redis服务器连接端口port: 6379# Redis服务器连接密码(默认为空)password: # 连接超时时间timeout: 10slettuce:pool:# 连接池最大连接数max-active: 200# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: 3000ms# 连接池中的最大空闲连接max-idle: 10# 连接池中的最小空闲连接min-idle: 0

核心登录方法:

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import com.jinyi.up.common.base.result.R;
import com.jinyi.up.user.dto.UserAccountLoginDTO;
import com.jinyi.up.user.entity.JinyiRole;
import com.jinyi.up.user.entity.JinyiUser;
import com.jinyi.up.user.entity.JinyiUserRole;
import com.jinyi.up.user.service.IJinyiRoleService;
import com.jinyi.up.user.service.IJinyiUserRoleService;
import com.jinyi.up.user.service.IJinyiUserService;
import com.jinyi.up.user.vo.JinyiRoleVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;@Api(tags = "用户相关接口")
@RestController
@RequestMapping("/jinyiUser")
public class JinyiUserController {@Resource //用户服务private IJinyiUserService userService;@Resource //用户角色关联表服务private IJinyiUserRoleService userRoleService;@Resource //用户角色服务private IJinyiRoleService roleService;@ApiOperation(value = "用户账号登录", httpMethod = "POST")@PostMapping("/accountLogin")public R accountLogin(@RequestBody UserAccountLoginDTO userPhoneLoginDTO) {String account = userPhoneLoginDTO.getAccount();String password = userPhoneLoginDTO.getPassword();JinyiUser jinyiUser = userService.accountLogin(account, password);if (null != jinyiUser) {//密码校验通过后,直接调用登录StpUtil.login(jinyiUser.getId(), "PC");//登录成功后,返回相关的token信息return R.ok(StpUtil.getTokenInfo());}return R.failed("账号密码错误");}@ApiOperation(value = "查询用户对应的角色", httpMethod = "GET")@GetMapping("/getUserRoleByUserId/{userId}")public R<List<JinyiRoleVO>> getUserRoleByUserId(@PathVariable("userId") Long userId) {List<JinyiUserRole> userRoles = userRoleService.getByUserId(userId);if (CollectionUtils.isEmpty(userRoles)) {return R.ok();}Set<Integer> roleIds = userRoles.stream().map(JinyiUserRole::getRoleId).collect(Collectors.toSet());List<JinyiRole> roles = roleService.getByRoleIds(roleIds);if (CollectionUtils.isEmpty(roles)) {return R.ok();}List<JinyiRoleVO> jinyiRoleVOS = BeanUtil.copyToList(roles, JinyiRoleVO.class);return R.ok(jinyiRoleVOS);}@ApiOperation(value = "退出登录", httpMethod = "POST")@PostMapping("/userLogout")public R userLogout() {// 当前会话注销登录StpUtil.logout();return R.ok();}
}

最后测试

1.通过网关转发的登录
http://localhost:8070/user-api/jinyiUser/accountLogin
satoken+ gateway网关统一鉴权 初版
2.网关转发的其他需要登录带token的访问
satoken+ gateway网关统一鉴权 初版
satoken+ gateway网关统一鉴权 初版

土特产网