跳到主要内容微服务项目在线 OJ 系统基于 Java Spring 的 Token 与身份认证实现 | 极客日志Javajava
微服务项目在线 OJ 系统基于 Java Spring 的 Token 与身份认证实现
微服务在线 OJ 系统基于 Java Spring 实现 JWT Token 生成与身份认证。核心步骤包括构建 JwtUtils 工具类处理令牌加密解密,利用 Nacos 动态管理密钥避免硬编码,结合 Redis 存储用户登录状态及过期时间。通过自定义 Spring Cloud Gateway 全局过滤器 AuthFilter 实施鉴权,支持白名单配置与 URL 匹配规则。系统区分普通用户与管理员身份,校验请求路径权限,并在 WebFlux 环境下处理未授权响应,解决传统 Spring MVC 异常捕获不生效的问题,保障微服务接口安全。
颠三倒四4 浏览 创建 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 {
public static String createToken(Map<String, Object> claims, String secret) {
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
return token;
}
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;
}
引入依赖
<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();
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;
}
}
- 通过 UUID 获取唯一的 userKey。
- 创建 claim,存放用户的 userId 及 userKey。
- 调用方传入通过 Nacos 获取的密钥,传给
createToken。
- 生成 token。
- 创建缓存 key(常量 + userKey),将用户身份存入 Redis。
修改登录接口 Service 部分
修改 oj-common-security 项目
身份认证
引入依赖
填入白名单
@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;
}
}
自定义过滤器
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
测试验证
相关免费在线工具
- 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