跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

微服务项目在线 OJ 系统基于 Java Spring 的 Token 与身份认证实现

微服务在线 OJ 系统基于 Java Spring 实现 JWT Token 生成与身份认证。核心步骤包括构建 JwtUtils 工具类处理令牌加密解密,利用 Nacos 动态管理密钥避免硬编码,结合 Redis 存储用户登录状态及过期时间。通过自定义 Spring Cloud Gateway 全局过滤器 AuthFilter 实施鉴权,支持白名单配置与 URL 匹配规则。系统区分普通用户与管理员身份,校验请求路径权限,并在 WebFlux 环境下处理未授权响应,解决传统 Spring MVC 异常捕获不生效的问题,保障微服务接口安全。

颠三倒四发布于 2026/3/16更新于 2026/6/1417 浏览
微服务项目在线 OJ 系统基于 Java Spring 的 Token 与身份认证实现

创建 Token

在 oj-common-security 中引入依赖

文章配图

创建 JWT 工具类

package com.bite.common.security.utils;

import com.bite.common.core.contains.JwtContains;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.HashMap;
import java.util.Map;

public class JwtUtils {
    /**
     * 生成令牌
     *
     * @param claims 数据
     * @param secret 密钥
     * @return 令牌
     */
    public static String createToken(Map<String, Object> claims, String secret) {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
        return token;
    }

    /**
     * 从令牌中获取数据
     *
     * @param token 令牌
     * @param secret 密钥
     * @return 数据
     */
    public static Claims parseToken(String token, String secret) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }
}

createToken: 根据传入的 claims 和 secret 创建一个 token。

注意:这里的 secret 需要保密随机,不能硬编码,建议定期更换。可采用 Nacos 配置管理 secret。

硬编码(Hard Coding)是指在程序代码中直接嵌入固定数值、字符串或逻辑,而非通过变量、配置文件或外部输入来动态获取的编程方式。这种做法会导致代码灵活性降低,维护成本增加。

SignatureAlgorithm.HS512 是创建时使用的算法。

使用示例:

public static void main(String[] args) {
    Map<String,Object> claim=new HashMap<>();
    claim.put("userid",123456);
    System.out.println(createToken(claim,"ddwdwdwdw5dfw5"));
}

获取到 token 后通过 JwtUtils.parseToken 进行解码(需要 token 和 secret)。

public static void main(String[] args) {
    String token="eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyaWQiOjEyMzQ1Nn0.F0jIBBpUgyhUPZSSrKVHOBBatVhFEIRSWh6LRjPXC5kdJ3TsFOzByhBryx_nV9nLlFpeE-4ZvOESQMWKn9nDjw";
    Claims claims=parseToken(token,"ddwdwdwdw5dfw5");
    System.out.println(claims);
}

在 OJ-modules 中引入 oj-common-security

文章配图

定义常量类

public class CacheConstants {
    public final static String LOGIN_TOKEN_KEY = "login_tokens:";
    public final static long EXPIRATION = 720;
}

public class JwtContains {
    public static final String LOGIN_USER_ID="userId";
    public static final String LOGIN_USER_KEY="userKey";
}

定义登录用户

@Data
public class LoginUser {
    private Integer identity; //普通用户 1 管理员 2
}

引入依赖

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>${hutool-all.version}</version>
</dependency>
<dependency>
    <groupId>com.bite</groupId>
    <artifactId>oj-common-redis</artifactId>
    <version>${oj-common-redis.version}</version>
    <scope>compile</scope>
</dependency>

定义 TokenService 类

@Service
public class TokenService {
    @Autowired
    private RedisService redisService;

    /**
     * 创建令牌
     */
    public String createToken(Long userId,String secret,Integer identity) {
        String userKey = UUID.fastUUID().toString();
        // Jwt 存储信息
        Map<String, Object> claim = new HashMap<String, Object>();
        claim.put(JwtContains.LOGIN_USER_ID,userId);
        claim.put(JwtContains.LOGIN_USER_KEY,userKey);
        String token=JwtUtils.createToken(claim,secret);
        String key=CacheConstants.LOGIN_TOKEN_KEY+userKey;
        LoginUser loginUser=new LoginUser();
        loginUser.setIdentity(UserIdentity.ADMIN.getCode());
        redisService.setCacheObject(key, loginUser, CacheConstants.EXPIRATION, TimeUnit.MINUTES);
        return token;
    }
}

核心逻辑:

  1. 通过 UUID 获取唯一的 userKey。
  2. 创建 claim,存放用户的 userId 及 userKey。
  3. 调用方传入通过 Nacos 获取的密钥,传给 createToken。
  4. 生成 token。
  5. 创建缓存 key(常量 + userKey),将用户身份存入 Redis。

修改登录接口 Service 部分

文章配图

修改 oj-common-security 项目

文章配图

身份认证

引入依赖

文章配图

填入白名单

创建白名单类,从 Nacos 获取放行 URL。

@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties {
    private List<String> whites = new ArrayList<>();

    public List<String> getWhites() {
        return whites;
    }

    public void setWhites(List<String> whites) {
        this.whites = whites;
    }
}

修改 Nacos 配置:

文章配图

自定义过滤器

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.bite.common.core.comtains.CacheConstants;
import com.bite.common.core.comtains.HttpConstants;
import com.bite.common.core.domain.R;
import com.bite.common.core.enums.ResultCode;
import com.bite.common.core.enums.UserIdentity;
import com.bite.common.redis.service.RedisService;
import com.bite.common.security.model.LoginUser;
import com.bite.common.security.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;

@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String url = request.getURI().getPath();

        if (matches(url, ignoreWhite.getWhites())) {
            return chain.filter(exchange);
        }

        String token = getToken(request);
        if (StrUtil.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空");
        }

        Claims claims;
        try {
            claims = JwtUtils.parseToken(token, secret);
            if (claims == null) {
                return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
            }
        } catch (Exception e) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
        }

        String userKey = JwtUtils.getUserKey(claims);
        boolean isLogin = redisService.hasKey(getTokenKey(userKey));
        if (!isLogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }

        String userid = JwtUtils.getUserId(claims);
        if (StrUtil.isEmpty(userid)) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        LoginUser user = redisService.getCacheObject(getTokenKey(userKey), LoginUser.class);
        if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) && !UserIdentity.ADMIN.getCode().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        if (url.contains(HttpConstants.FRIEND_URL_PREFIX) && !UserIdentity.ORDINARY.getCode().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        return chain.filter(exchange);
    }

    private boolean matches(String url, List<String> patternList) {
        if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) {
            return false;
        }
        for (String pattern : patternList) {
            if (isMatch(pattern, url)) {
                return true;
            }
        }
        return false;
    }

    private boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }

    private String getTokenKey(String token) {
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }

    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return token;
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理] 请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg, ResultCode.FAILED_UNAUTHORIZED.getCode());
    }

    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg, int code) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        R<?> result = R.fail(code, msg);
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return -200;
    }
}

异常处理说明

此处未抛出异常而是直接返回响应,是因为 @RestControllerAdvice 仅对 Spring Web 有效,而 Spring Cloud Gateway 属于 WebFlux 环境。

文章配图

文章配图

RedisConfig 配置

RedisConfig 修改需增加 @AutoConfigureBefore(RedisAutoConfiguration.class) 注解,否则报错。目的是让 Spring 优先注册自定义的 RedisTemplate。

文章配图

配置 Secret

文章配图

配置 Redis

文章配图

测试验证

文章配图

文章配图

文章配图

目录

  1. 创建 Token
  2. 在 oj-common-security 中引入依赖
  3. 创建 JWT 工具类
  4. 在 OJ-modules 中引入 oj-common-security
  5. 定义常量类
  6. 定义登录用户
  7. 引入依赖
  8. 定义 TokenService 类
  9. 修改登录接口 Service 部分
  10. 修改 oj-common-security 项目
  11. 身份认证
  12. 引入依赖
  13. 填入白名单
  14. 自定义过滤器
  15. 异常处理说明
  16. RedisConfig 配置
  17. 配置 Secret
  18. 配置 Redis
  19. 测试验证
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 本地部署 DeepSeek-R1 模型:电脑与手机方案及硬件需求详解
  • 2026 年 3 月全球 AI 前沿动态与技术突破
  • 主流大模型架构全景:GPT、LLaMA、DeepSeek 与 Qwen 深度对比
  • whisperX 入门指南:从安装到实现语音识别功能
  • 互联网大厂 Android 面试题大全与核心知识点解析
  • OpenClaw 对接飞书:配置多个机器人实现群聊
  • 自学 Python 使用 PyCharm 是否友好及学习建议
  • 知网AIGC检测原理解析与针对性降疑策略
  • 文心一言开源版部署与多维测评实践
  • CTF 竞赛 Web 题目基本解题流程
  • 使用 C++ 实现 2048 小游戏
  • 文心一言大模型使用指南:指令编写与进阶应用
  • 国内股票分析 AI 开源项目精选:GitHub 热门榜单
  • 滑动窗口算法:找到字符串中所有字母异位词
  • Neo4j 图数据库从搭建到实战应用详解
  • 字节扣子搭建大模型擂台:匿名 PK 与用户评价机制
  • Web 开发中五种常用加密算法实战及原理详解
  • Windows 下 MATLAB 与 C/C++ 混合编程:DLL 生成与调用
  • Node.js 安装与环境配置实战指南
  • 2025 年 6 月 GESP 真题及题解 (C++ 七级): 选择题和判断题

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online