Java项目:Java脚手架项目的登录认证服务(十三)

文章目录

  • 前言
  • 一、设计登录用户数据库表
  • 二、登录认证服务的实现
    • [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

登录认证服务完成

相关推荐
番茄去哪了1 小时前
苍穹外卖day05----店铺营业状态设置
java·数据库·ide·redis·git·maven·mybatis
QQ 31316378902 小时前
文华指标公式大全通道划线指标
java
前路不黑暗@2 小时前
Java项目:Java脚手架项目的 C 端用户服务(十五)
java·开发语言·spring boot·学习·spring cloud·maven·mybatis
暮色妖娆丶3 小时前
Spring 源码分析 事务管理的实现原理(下)
数据库·spring boot·spring
暮色妖娆丶3 小时前
Spring 源码分析 事务管理的实现原理(上)
数据库·spring boot·spring
好学且牛逼的马3 小时前
从“Oak”到“虚拟线程”:JDK 1.0到25演进全记录与核心知识点详解a
java·开发语言·python
追随者永远是胜利者3 小时前
(LeetCode-Hot100)62. 不同路径
java·算法·leetcode·职场和发展·go
好学且牛逼的马3 小时前
从“XML汪洋”到“智能原生”:Spring Framework 1.x 到 7.x 演进全记录与核心知识点详解(超详细版)
java
追随者永远是胜利者3 小时前
(LeetCode-Hot100)56. 合并区间
java·算法·leetcode·职场和发展·go