微服务项目->在线oj系统(Java-Spring)----6.0
Service@Autowired/*** 创建令牌*/// Jwt存储信息核心代码详解:首先我们通过UUID来获取一个唯一的userKey创建一个claim,存放着用户的userId以及刚刚创建的userKey;这个是调用方传入通过nacos得到的密钥,我们需要传给createToken我们通过claim,secret来生成一个token.然后我们创建一个变量key(其实就是一个常量+userK
创建token
在oj-common-security中引入依赖
创建jwt⼯具类:
package com.bite.common.security.utils;
import com.bite.common.core.comtains.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是创建所使用的算法
使用例子:
首先创建一个token,这里由于是实例,所以密钥随便即可
public static void main(String[] args) {
Map<String,Object> claim=new HashMap<>();
claim.put("userid",123456);
System.out.println(createToken(claim,"ddwdwdwdw5dfw5"));
}
获取到到token后通过JwtUtil的parseToken进行解码(需要token和secret)
public static void main(String[] args) {
String token="eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyaWQiOjEyMzQ1Nn0.F0jIBBpUgyhUPZSSrKVHOBBatVhFEIRSWh6LRjPXC5kdJ3TsFOzByhBryx_nV9nLlFpeE-4ZvOESQMWKn9nDjw";
Claims claims=parseToken("eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyaWQiOjEyMzQ1Nn0.F0jIBBpUgyhUPZSSrKVHOBBatVhFEIRSWh6LRjPXC5kdJ3TsFOzByhBryx_nV9nLlFpeE-4ZvOESQMWKn9nDjw","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;
}
}
核心代码详解:
首先我们通过UUID来获取一个唯一的userKey
创建一个claim,存放着用户的userId以及刚刚创建的userKey;
这个是调用方传入通过nacos得到的密钥,我们需要传给createToken
我们通过claim,secret来生成一个token.
然后我们创建一个变量key(其实就是一个常量+userKey);
然后将key存入redis中,value是用户身份
对login接口中Service部分进行修改
对oj-common-security项目进行修改
身份认证
引入依赖
填入白名单
创建一个白名单类,从nacos中去获取哪些是白名单中的url
@Configuration//配置类
@RefreshScope//实时刷新,使nacos上面的改变立刻生效
@ConfigurationProperties(prefix = "security.ignore")
//
public class IgnoreWhiteProperties {
/**
* 放⾏⽩名单配置,⽹关不校验此处的⽩名单
*/
private List<String> whites = new ArrayList<>();
public List<String> getWhites() {
//拿到nacos配置里面的白名单
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 {
//实现GlobalFilter相当于实现了一个自定义过滤器
//实现Order是为什么以后有多个过滤器的时候进行先后排序
// 排除过滤的 uri ⽩名单地址,在nacos⾃⾏添加
@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);
//如果是白名单里面的,则跳过身份认证
}
//从http请求头中获取token
String token = getToken(request);
//判断token是否为空,如果为空说明以前未登录或者登录超市;
if (StrUtil.isEmpty(token)) {
return unauthorizedResponse(exchange, "令牌不能为空");
}
Claims claims;
try {
claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析payload中信息
if (claims == null) {
return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
}
} catch (Exception e) {
return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
}
String userKey = JwtUtils.getUserKey(claims); //获取jwt中的key
boolean isLogin = redisService.hasKey(getTokenKey(userKey));//用得到的key去Redis中查找,如果找不到说明过期了
if (!isLogin) {
return unauthorizedResponse(exchange, "登录状态已过期");
}
String userid = JwtUtils.getUserId(claims); //判断jwt中的信息是否完整
if (StrUtil.isEmpty(userid)) {
return unauthorizedResponse(exchange, "令牌验证失败");
}
//c端用户只能请求C端功能,B端用户只能请求管理端功能
LoginUser user = redisService.getCacheObject(getTokenKey(userKey), LoginUser.class);//通过key去Redis里面查找value(存着用户是管理员还是用户)
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);
}
/**
* 查找指定url是否匹配指定匹配规则链表中的任意⼀个字符串
*
* @param url 指定url
* @param patternList 需要检查的匹配规则链表
* @return 是否匹配
*/
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;
}
/**
* 判断url是否与规则匹配
* 匹配规则中:
* ? 表⽰单个字符;
* * 表⽰⼀层路径内的任意字符串,不可跨层级;
* ** 表⽰任意层路径;
*
* @param pattern 匹配规则
* @param url 需要匹配的url
* @return 是否匹配
*/
private boolean isMatch(String pattern, String url) {
AntPathMatcher matcher = new AntPathMatcher();
//将pattern作为匹配规则
return matcher.match(pattern, url);
}
/**
* 获取缓存key
*/
private String getTokenKey(String token) {
return CacheConstants.LOGIN_TOKEN_KEY + token;
}
/**
* 从请求头中获取请求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());
}
//拼装webflux模型响应
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只对springweb起作用,而springcloud gataway属于webflux
让spring优先注册我们这个RedisTemplate
配置secret
配置redis
然后我们测试一下:
更多推荐
所有评论(0)