文章目录
- 前言
- 一、设计登录用户数据库表
- 二、登录认证服务的实现
-
- [2.1 在 common-security 下创建 domain、service、utils包](#2.1 在 common-security 下创建 domain、service、utils包)
- [2.2 添加 maven 依赖](#2.2 添加 maven 依赖)
- [2.3 在 domain 包下创建dto、vo](#2.3 在 domain 包下创建dto、vo)
- [2.4 在 utils 包下创建工具类](#2.4 在 utils 包下创建工具类)
- [2.5 在 service 包下创建 TokenService 服务](#2.5 在 service 包下创建 TokenService 服务)
- [2.6 在 resources 下创建自动配置](#2.6 在 resources 下创建自动配置)
- 三、网关引入鉴权服务
-
- [3.1 在网关服务下创建 config、filter 包](#3.1 在网关服务下创建 config、filter 包)
- [3.2 加入 maven 依赖](#3.2 加入 maven 依赖)
- [3.3 在 config 包下创建配置类](#3.3 在 config 包下创建配置类)
- [3.4 在 filter 添加过滤器](#3.4 在 filter 添加过滤器)
- END
鸡汤:
● 你并不孤单。此刻,有人正为你默默加油(包括我呀)。今天,记得对自己说:"辛苦了,你已经做得很好。"
● 生活偶尔会下雨,但你心里有光。试着捕捉今天的小确幸:窗台新芽的绿意,陌生人善意的微笑,完成一件小事的满足。这些点滴温暖,都是生活悄悄塞给你的糖。
前言
前面将配置服务实现完成,今天我们来实现一下登录认证服务。
一、设计登录用户数据库表
首先我们要确定我们的项目要提供登录的用户群体,一个是 B 端用户,一个是 C 端用户,B 端为管理端,而 C 端为用户端。
● B 端用户表结构
sql
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '⾃增主键',
`nick_name` varchar(64) NOT NULL COMMENT '昵称',
`phone_number` varchar(64) NOT NULL COMMENT '电话',
`password` varchar(255) NOT NULL COMMENT '密码',
`identity` varchar(16) NOT NULL COMMENT '⾝份',
`remark` varchar(50) NULL DEFAULT NULL COMMENT '备注',
`status` varchar(10) NOT NULL COMMENT '状态',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_phone`(`phone_number`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10000001 CHARACTER SET = utf8mb4 COMMENT = '管理端⼈员表';
INSERT INTO sys_user
(nick_name,phone_number,password,`identity`,remark,status)
VALUES('admin','e64c5f44dc95e4ca77d99136ea2c88c6','15e2b0d3c33891ebb0f1ef609ec419420
c20e320ce94c65fbc8c3312448eb225','super_admin',NULL,'enable');
● C 端用户表结构
sql
DROP TABLE IF EXISTS `app_user`;
CREATE TABLE `app_user` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '⾃增主键',
`nick_name` varchar(64) NULL DEFAULT NULL COMMENT '昵称',
`phone_number` varchar(64) NULL DEFAULT NULL COMMENT '电话',
`open_id` varchar(64) NULL DEFAULT NULL COMMENT '微信openId',
`avatar` varchar(255) NULL DEFAULT NULL COMMENT '头像',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_phone`(`phone_number`) USING BTREE,
UNIQUE INDEX `uk_open_id`(`open_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10000001 CHARACTER SET = utf8mb4 COMMENT = '应⽤端⼈员表';
二、登录认证服务的实现
因为登录认证服务是通用的所以我们将他放在 common-security 模块里。
2.1 在 common-security 下创建 domain、service、utils包
handler是之前写的全局异常处理,就不多说了

2.2 添加 maven 依赖
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>
<groupId>com.my</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.my</groupId>
<artifactId>common-security</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringCloud Loadbalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<scope>runtime</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.apache.commons</groupId>-->
<!-- <artifactId>commons-lang3</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>com.my</groupId>
<artifactId>common-domain</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-redis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.3 在 domain 包下创建dto、vo

dto:
LoginUserDTO:
java
package com.my.commonsecurity.domain.dto;
import lombok.Getter;
import lombok.Setter;
/**
* 用户信息上下文
*/
@Getter
@Setter
public class LoginUserDTO {
/**
* 用户标识
*/
private String token;
/**
* 用户Id
*/
private Long userId;
/**
* 用户来源
*/
private String userFrom;
/**
* 用户名
*/
private String userName;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
}
TokenDTO:
java
package com.my.commonsecurity.domain.dto;
import com.my.commonsecurity.domain.vo.TokenVO;
import lombok.Getter;
import lombok.Setter;
/**
* token信息
*/
@Getter
@Setter
public class TokenDTO {
/**
* 访问令牌
*/
private String accessToken;
/**
* 过期时间
*/
private Long expires;
public TokenVO convertTokenVO() {
TokenVO tokenVO = new TokenVO();
tokenVO.setAccessToken(accessToken);
tokenVO.setExpires(expires);
return tokenVO;
}
}
vo:
LoginUserVO:
java
package com.my.commonsecurity.domain.vo;
import lombok.Data;
@Data
public class LoginUserVO {
/**
* 用户标识
*/
private String token;
/**
* 用户Id
*/
private Long userId;
/**
* 用户名
*/
private String userName;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
}
TokenVO:
java
package com.my.commonsecurity.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 返回前端token信息
*/
@Data
public class TokenVO implements Serializable {
/**
* 访问令牌
*/
private String accessToken;
/**
* 过期时间
*/
private Long expires;
}
2.4 在 utils 包下创建工具类

我们使用 JWT 来做登录认证服务的,所以需要 JWTUtil 工具类
JWTUtil:
java
package com.my.commonsecurity.utils;
import com.my.commondomain.constants.SecurityConstants;
import com.my.commondomain.constants.TokenConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Map;
/**
* JWT工具类
*/
public class JWTUtil {
/**
* 令牌密钥
*/
private static final String SECRET = TokenConstants.TOKEN_SECRET;
/**
* 生成安全密钥:将一个Base64编码的密钥解码并创建一个HMAC SHA密钥。
*/
private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET));
/**
* 从原始数据声明生成令牌
* @param claims 数据声明
* @return 令牌
*/
public static String createToken(Map<String,Object> claims){
String token = Jwts.builder().setClaims(claims).signWith(SECRET_KEY).compact();
return token;
}
/**
* 根据令牌获取数据声明
* @param token 令牌
* @return 数据声明
*/
public static Claims parseToken(String token){
Claims body = Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token).getBody();
return body;
}
/**
* 根据令牌获取用户ID
* @param token 令牌
* @return 用户ID
*/
public static String getUserId(String token){
Claims claims = parseToken(token);
return getValue(claims,SecurityConstants.USER_ID);
}
/**
* 根据数据声明获取用户ID
* @param claims 数据声明
* @return 用户ID
*/
public static String getUserId(Claims claims){
return getValue(claims,SecurityConstants.USER_ID);
}
/**
* 根据令牌获取用户标识
* @param token 令牌
* @return 用户标识
*/
public static String getUserKey(String token) {
Claims claims = parseToken(token);
return getValue(claims,SecurityConstants.USER_KEY);
}
/**
* 根据数据声明获取用户标识
* @param claims 数据声明
* @return 用户标识
*/
public static String getUserKey(Claims claims) {
return getValue(claims,SecurityConstants.USER_KEY);
}
/**
* 根据令牌获取用户来源
* @param token 令牌
* @return 用户来源
*/
public static String getUserFrom(String token) {
Claims claims = parseToken(token);
return getValue(claims,SecurityConstants.USER_FROM);
}
/**
* 根据数据声明获取用户来源
* @param claims 数据声明
* @return 用户来源
*/
public static String getUserFrom(Claims claims) {
return getValue(claims,SecurityConstants.USER_FROM);
}
/**
* 根据令牌获取用户名称
* @param token 令牌
* @return 用户名称
*/
public static String getUserName(String token) {
Claims claims = parseToken(token);
return getValue(claims,SecurityConstants.USERNAME);
}
/**
* 根据数据声明获取用户名称
* @param claims 数据声明
* @return 用户名称
*/
public static String getUserName(Claims claims) {
return getValue(claims,SecurityConstants.USERNAME);
}
private static String getValue(Claims body,String key){
Object result = body.get(key);
if(result == null){
return "";
}
if(result instanceof String){
return (String)result;
}
return result.toString();
}
}
安全工具类
SecurityUtil:
java
package com.my.commonsecurity.utils;
import com.my.commoncore.utils.ServletUtil;
import com.my.commondomain.constants.SecurityConstants;
import com.my.commondomain.constants.TokenConstants;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
/**
* 安全工具类
*/
public class SecurityUtil {
/**
* 获取请求token
* @return token信息
*/
public static String getToken() {
return getToken(ServletUtil.getRequest());
}
/**
* 根据request获取请求token
* @param request 请求
* @return token信息
*/
public static String getToken(HttpServletRequest request) {
String token = request.getHeader(SecurityConstants.AUTHENTICATION);
return replaceTokenPrefix(token);
}
/**
* 裁剪token前缀
* @param token 前端可能设置了令牌的前缀
* @return token信息
*/
private static String replaceTokenPrefix(String token) {
if(StringUtils.isNotBlank(token) && token.startsWith(TokenConstants.TOKEN_PREFIX)){
token.replaceFirst(TokenConstants.TOKEN_PREFIX,"");
}
return token;
}
}
2.5 在 service 包下创建 TokenService 服务

具体的服务类
TokenService:
java
package com.my.commonsecurity.service;
import com.my.commoncore.utils.ServletUtil;
import com.my.commondomain.constants.CacheConstants;
import com.my.commondomain.constants.SecurityConstants;
import com.my.commondomain.constants.TokenConstants;
import com.my.commonredis.service.RedisService;
import com.my.commonsecurity.domain.dto.LoginUserDTO;
import com.my.commonsecurity.domain.dto.TokenDTO;
import com.my.commonsecurity.utils.JWTUtil;
import com.my.commonsecurity.utils.SecurityUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* token服务类
*/
@Component
public class TokenService {
/**
* 毫秒
*/
private static long MILLIS_SECOND = 1000;
/**
* 分钟
*/
private static long MILLIS_MINUTE = 60 * MILLIS_SECOND;
/**
* 过期时间 720
*/
private static Long EXPIRE_TIME = CacheConstants.EXPIRATION;
/**
* 存redis 的key
*/
private static String ACCESS_TOKEN_KEY = TokenConstants.REDIS_LOGIN_TOKEN_KEY;
/**
* 120分钟
*/
private final static Long TOKEN_REFRESH_THRESHOLD = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;
@Autowired
private RedisService redisService;
/**
* 创建token
* @param loginUserDTO 登录信息
* @return token信息
*/
public TokenDTO createToken(LoginUserDTO loginUserDTO) {
// 1 随机产生用户标识
String token = UUID.randomUUID().toString();
loginUserDTO.setToken(token);
refreshToken(loginUserDTO);
// 2 生成原始数据声明
Map<String,Object> map = new HashMap<>();
map.put(SecurityConstants.USER_KEY,token);
map.put(SecurityConstants.USER_ID,loginUserDTO.getUserId());
map.put(SecurityConstants.USERNAME,loginUserDTO.getUserName());
map.put(SecurityConstants.USER_FROM,loginUserDTO.getUserFrom());
// 3 生成TokenDTO
TokenDTO tokenDTO = new TokenDTO();
tokenDTO.setAccessToken(JWTUtil.createToken(map));
tokenDTO.setExpires(loginUserDTO.getExpireTime());
return tokenDTO;
}
/**
* 根据令牌获取用户信息
* @param token 令牌
* @return 用户信息
*/
public LoginUserDTO getLoginUser(String token) {
// 1 初始化用户信息
LoginUserDTO user = null;
// 2 解析令牌获取用户信息
try {
if(StringUtils.isNotBlank(token)){
String userKey = JWTUtil.getUserKey(token);
user = redisService.getCacheObject(getTokenKey(userKey),LoginUserDTO.class);
}
}catch (Exception e){
}
// 3 返回user
return user;
}
/**
* 根据请求来获取用户信息
* @param request 请求
* @return 用户信息
*/
public LoginUserDTO getLoginUser(HttpServletRequest request) {
String token = SecurityUtil.getToken(request);
return getLoginUser(token);
}
/**
* 不传参数获取用户信息
* @return 用户信息
*/
public LoginUserDTO getLoginUser() {
return getLoginUser(ServletUtil.getRequest());
}
/**
* 根据令牌删除用户登录态
* @param token 令牌
*/
public void delLoginUser(String token) {
if(StringUtils.isNotBlank(token)){
String userKey = JWTUtil.getUserKey(token);
redisService.deleteObject(userKey);
}
}
/**
* 允许超管删除别人的登录状态
* @param userId 用户ID
* @param userFrom 用户来源
*/
public void delLoginUser(Long userId,String userFrom) {
if(userId == null) {
return;
}
Collection<String> keys = redisService.keys(ACCESS_TOKEN_KEY + "*");
for(String key : keys){
LoginUserDTO user = redisService.getCacheObject(key,LoginUserDTO.class);
if(user != null && user.getUserId().equals(userId) && user.getUserFrom().equals(userFrom)){
redisService.deleteObject(key);
return;
}
}
}
/**
* 验证令牌有效期 不到120分钟 自动刷新下
* @param loginUserDTO 用户信息
*/
public void verifyToken(LoginUserDTO loginUserDTO) {
Long expireTime = loginUserDTO.getExpireTime();
Long currentTime = System.currentTimeMillis();
if(currentTime - expireTime <= TOKEN_REFRESH_THRESHOLD){
refreshToken(loginUserDTO);
}
}
/**
* 设置用户身份信息,允许登录
* @param loginUserDTO 用户信息
*/
public void updateLoginUser(LoginUserDTO loginUserDTO) {
if (loginUserDTO != null && StringUtils.isNotBlank(loginUserDTO.getToken())) {
refreshToken(loginUserDTO);
}
}
/**
* 缓存用户信息设置令牌有效期
* @param loginUserDTO 用户信息
*/
private void refreshToken(LoginUserDTO loginUserDTO) {
loginUserDTO.setLoginTime(System.currentTimeMillis());
loginUserDTO.setExpireTime(loginUserDTO.getLoginTime() + EXPIRE_TIME * MILLIS_MINUTE);
// 根据随机产生用户标识生成key
String key = getTokenKey(loginUserDTO.getToken());
// 生成loginUserDTO缓存
redisService.setCacheObject(key,loginUserDTO,EXPIRE_TIME, TimeUnit.MINUTES);
}
/**
* 获取token key的信息
* @param token token
* @return tokenKey
*/
private String getTokenKey(String token) {
return ACCESS_TOKEN_KEY + token;
}
}
2.6 在 resources 下创建自动配置
自动配置目录 : META.spring.org.springframework.boot.autoconfigure.AutoConfiguration.imports

三、网关引入鉴权服务
3.1 在网关服务下创建 config、filter 包
handler 在前面统一异常处理时讲过了,就不多说了

3.2 加入 maven 依赖
pom.xml:
java
<?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>
<groupId>com.my</groupId>
<artifactId>frameworkjava</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.my</groupId>
<artifactId>gateway</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-domain</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-redis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.my</groupId>
<artifactId>common-security</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.45.0</version>
<configuration>
<!--指定远程服务器的Docker服务访问地址-->
<dockerHost>${dockerhost.addr}</dockerHost>
<certPath>${project.basedir}/../deploy/test/app/config/cert</certPath>
<!--指定私有仓库的访问路径-->
<pushRegistry>${dockerregistry.url}</pushRegistry>
<!--指定库的用户名与密码-->
<authConfig>
<username>${dockerregistry.username}</username>
<password>${dockerregistry.password}</password>
</authConfig>
<containerNamePattern>${dockerregistry.namespace}-%n-%i</containerNamePattern>
<images>
<image>
<!--指定私有仓库访问地址/镜像名称-->
<name>${dockerregistry.namespace}/${project.build.finalName}:${project.version}</name>
<build>
<!--指定Dockerfile的路径-->
<dockerFileDir>${project.basedir}</dockerFileDir>
</build>
<run>
<ports>
<port>18080:18080</port>
</ports>
<env>
<NACOS_ADDR>${nacos.addr}</NACOS_ADDR>
<RUN_ENV>${runenv}</RUN_ENV>
<JAVA_OPTS>${java.opts.default}</JAVA_OPTS>
</env>
</run>
</image>
</images>
</configuration>
<executions>
<execution>
<id>build</id>
<phase>deploy</phase>
<goals>
<goal>stop</goal>
<goal>build</goal>
<goal>start</goal>
<!--推送到远端仓库 -->
<goal>push</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3.3 在 config 包下创建配置类

在 nacos 读取白名单配置
IgnoreWhiteProperties:
java
package com.my.gateway.config;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* 白名单配置
*/
@Data
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties {
/**
* 放行白名单
*/
private List<String> whites;
}
3.4 在 filter 添加过滤器

AuthFilter:
java
package com.my.gateway.filter;
import com.my.commoncore.utils.ServletUtil;
import com.my.commoncore.utils.StringUtil;
import com.my.commondomain.constants.SecurityConstants;
import com.my.commondomain.constants.TokenConstants;
import com.my.commondomain.domain.ResultCode;
import com.my.commonredis.service.RedisService;
import com.my.commonsecurity.utils.JWTUtil;
import com.my.gateway.config.IgnoreWhiteProperties;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired
private IgnoreWhiteProperties ignoreWhiteProperties;
@Autowired
private RedisService redisService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取请求
ServerHttpRequest request = exchange.getRequest();
// 2. 从请求中获取 url、 path
String path = request.getURI().getPath();
// 3. 匹配白名单 如果成功直接放行
if(StringUtil.matches(path,ignoreWhiteProperties.getWhites())) {
return chain.filter(exchange);
}
// 4. 从请求中获取token
String token = getToken(request);
if(!StringUtils.isNotBlank(token)) {
return unauthorizedResponse(exchange, ResultCode.TOKEN_EMPTY);
}
// 5. 根据令牌获取有效信息
Claims claims = JWTUtil.parseToken(token);
if(claims == null) {
return unauthorizedResponse(exchange, ResultCode.TOKEN_INVALID);
}
// 6. 根据 userKey 在 redis中查找 , 如果没有说明没有登录
String userKey = JWTUtil.getUserKey(token);
Boolean isLogin = redisService.hasKey(getTokenKey(userKey));
if(!isLogin) {
return unauthorizedResponse(exchange, ResultCode.LOGIN_STATUS_OVERTIME);
}
// 7. 获取用户数据信息
String userId = JWTUtil.getUserId(token);
String userName = JWTUtil.getUserName(token);
String userFrom = JWTUtil.getUserFrom(token);
if(!StringUtils.isNotBlank(userId) || !StringUtils.isNotBlank(userName)) {
return unauthorizedResponse(exchange, ResultCode.TOKEN_CHECK_FAILED);
}
// 8. 设置用户信息到请求
ServerHttpRequest.Builder mutate = request.mutate();
addHeader(mutate,SecurityConstants.USER_KEY,userId);
addHeader(mutate,SecurityConstants.USER_ID,userId);
addHeader(mutate,SecurityConstants.USERNAME,userId);
addHeader(mutate,SecurityConstants.USER_FROM,userId);
// 9. 加上header信息之后继续执行
return chain.filter(exchange.mutate().request(mutate.build()).build());
}
/**
* 获取缓存key
* @param userKey token信息
* @return tokenKey
*/
private String getTokenKey(String userKey) {
return TokenConstants.REDIS_LOGIN_TOKEN_KEY + userKey;
}
/**
* 添加header
* @param mutate 请求
* @param key key
* @param value 值
*/
private void addHeader(ServerHttpRequest.Builder mutate, String key, Object value) {
if(value == null) {
return;
}
String newValue = value.toString();
if(newValue == null) {
return;
}
mutate.header(key,newValue);
}
/**
* 未授权返回
* @param exchange ServerWebExchange
* @param resultCode 结果码
* @return void
*/
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, ResultCode resultCode) {
log.error("[鉴权处理异常]请求路径:{}", exchange.getRequest().getPath());
ServerHttpResponse response = exchange.getResponse();
return ServletUtil.webFluxResponseWriter(
response,
HttpStatus.valueOf(Integer.parseInt(String.valueOf(resultCode.getCode()).substring(0,3))),
resultCode.getMsg(),
resultCode.getCode()
);
}
/**
* 根据http请求获取token
* @param request 请求
* @return token令牌
*/
private String getToken(ServerHttpRequest request) {
String token = request.getHeaders().getFirst(SecurityConstants.AUTHENTICATION);
if(StringUtils.isNotBlank(token) && token.startsWith(TokenConstants.TOKEN_PREFIX)) {
token.replaceFirst(TokenConstants.TOKEN_PREFIX, "");
}
return token;
}
@Override
public int getOrder() {
return -200;
}
}
END
登录认证服务完成