mirror of
https://gitee.com/dromara/RuoYi-Cloud-Plus.git
synced 2026-04-24 11:28:39 +08:00
!15 合并 新功能/satoken 分支
This commit is contained in:
@@ -1,117 +1,34 @@
|
||||
package com.ruoyi.gateway.filter;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.ruoyi.common.core.constant.CacheConstants;
|
||||
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import com.ruoyi.common.core.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.constant.SecurityConstants;
|
||||
import com.ruoyi.common.core.constant.TokenConstants;
|
||||
import com.ruoyi.common.core.utils.JwtUtils;
|
||||
import com.ruoyi.common.core.utils.ServletUtils;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||
import com.ruoyi.gateway.config.properties.IgnoreWhiteProperties;
|
||||
import com.ruoyi.gateway.utils.WebFluxUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 网关鉴权
|
||||
*
|
||||
* @author ruoyi
|
||||
* [Sa-Token 权限认证] 拦截器
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Component
|
||||
public class AuthFilter implements GlobalFilter, Ordered {
|
||||
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
|
||||
@Configuration
|
||||
public class AuthFilter {
|
||||
|
||||
// 排除过滤的 uri 地址,nacos自行添加
|
||||
@Autowired
|
||||
private IgnoreWhiteProperties ignoreWhite;
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpRequest.Builder mutate = request.mutate();
|
||||
|
||||
String url = request.getURI().getPath();
|
||||
// 跳过不需要验证的路径
|
||||
if (StringUtils.matches(url, ignoreWhite.getWhites())) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
String token = getToken(request);
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
return unauthorizedResponse(exchange, "令牌不能为空");
|
||||
}
|
||||
JSONObject claims = JwtUtils.parseToken(token);
|
||||
if (claims == null) {
|
||||
return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
|
||||
}
|
||||
String userkey = JwtUtils.getUserKey(claims);
|
||||
boolean islogin = RedisUtils.hasKey(getTokenKey(userkey));
|
||||
if (!islogin) {
|
||||
return unauthorizedResponse(exchange, "登录状态已过期");
|
||||
}
|
||||
String userid = JwtUtils.getUserId(claims);
|
||||
String username = JwtUtils.getUserName(claims);
|
||||
if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) {
|
||||
return unauthorizedResponse(exchange, "令牌验证失败");
|
||||
}
|
||||
|
||||
// 设置用户信息到请求
|
||||
addHeader(mutate, SecurityConstants.USER_KEY, userkey);
|
||||
addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
|
||||
addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
|
||||
// 内部请求来源参数清除
|
||||
removeHeader(mutate, SecurityConstants.FROM_SOURCE);
|
||||
return chain.filter(exchange.mutate().request(mutate.build()).build());
|
||||
// 注册 Sa-Token全局过滤器
|
||||
@Bean
|
||||
public SaReactorFilter getSaReactorFilter(IgnoreWhiteProperties ignoreWhite) {
|
||||
return new SaReactorFilter()
|
||||
// 拦截地址
|
||||
.addInclude("/**")
|
||||
// 开放地址
|
||||
.setExcludeList(ignoreWhite.getWhites())
|
||||
.addExclude("/favicon.ico")
|
||||
// 鉴权方法:每次访问进入
|
||||
.setAuth(obj -> {
|
||||
// 登录校验 -- 拦截所有路由
|
||||
SaRouter.match("/**", r -> StpUtil.checkLogin());
|
||||
}).setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
|
||||
}
|
||||
|
||||
private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
String valueStr = value.toString();
|
||||
String valueEncode = ServletUtils.urlEncode(valueStr);
|
||||
mutate.header(name, valueEncode);
|
||||
}
|
||||
|
||||
private void removeHeader(ServerHttpRequest.Builder mutate, String name) {
|
||||
mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
|
||||
}
|
||||
|
||||
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
|
||||
log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
|
||||
return WebFluxUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存key
|
||||
*/
|
||||
private String getTokenKey(String token) {
|
||||
return CacheConstants.LOGIN_TOKEN_KEY + token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求token
|
||||
*/
|
||||
private String getToken(ServerHttpRequest request) {
|
||||
String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
|
||||
// 如果前端设置了令牌前缀,则裁剪掉前缀
|
||||
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
|
||||
token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.ruoyi.gateway.filter;
|
||||
|
||||
import cn.dev33.satoken.id.SaIdUtil;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 转发认证过滤器(内部服务外网隔离)
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Component
|
||||
public class ForwardAuthFilter implements GlobalFilter {
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest newRequest = exchange
|
||||
.getRequest()
|
||||
.mutate()
|
||||
// 为请求追加 Id-Token 参数
|
||||
.header(SaIdUtil.ID_TOKEN, SaIdUtil.getToken())
|
||||
.build();
|
||||
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
|
||||
return chain.filter(newExchange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
package com.ruoyi.gateway.service.impl;
|
||||
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.ruoyi.common.core.constant.Constants;
|
||||
import com.ruoyi.common.core.exception.CaptchaException;
|
||||
import com.ruoyi.common.core.utils.SpringUtils;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import com.ruoyi.common.core.utils.reflect.ReflectUtils;
|
||||
import com.ruoyi.common.core.web.domain.AjaxResult;
|
||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||
import com.ruoyi.gateway.config.properties.CaptchaProperties;
|
||||
import com.ruoyi.gateway.enums.CaptchaType;
|
||||
import com.ruoyi.gateway.service.ValidateCodeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 验证码实现处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
public class ValidateCodeServiceImpl implements ValidateCodeService {
|
||||
@Autowired
|
||||
private CaptchaProperties captchaProperties;
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult createCapcha() throws IOException, CaptchaException {
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
boolean captchaOnOff = captchaProperties.getEnabled();
|
||||
ajax.put("captchaOnOff", captchaOnOff);
|
||||
if (!captchaOnOff) {
|
||||
return ajax;
|
||||
}
|
||||
|
||||
// 保存验证码信息
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
||||
// 生成验证码
|
||||
CaptchaType captchaType = captchaProperties.getType();
|
||||
boolean isMath = CaptchaType.MATH == captchaType;
|
||||
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
|
||||
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
|
||||
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
|
||||
captcha.setGenerator(codeGenerator);
|
||||
captcha.createCode();
|
||||
String code = isMath ? getCodeResult(captcha.getCode()) : captcha.getCode();
|
||||
RedisUtils.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
|
||||
ajax.put("uuid", uuid);
|
||||
ajax.put("img", captcha.getImageBase64());
|
||||
return ajax;
|
||||
}
|
||||
|
||||
private String getCodeResult(String capStr) {
|
||||
int numberLength = captchaProperties.getNumberLength();
|
||||
int a = Convert.toInt(StringUtils.substring(capStr, 0, numberLength).trim());
|
||||
char operator = capStr.charAt(numberLength);
|
||||
int b = Convert.toInt(StringUtils.substring(capStr, numberLength + 1, numberLength + 1 + numberLength).trim());
|
||||
switch (operator) {
|
||||
case '*':
|
||||
return Convert.toStr(a * b);
|
||||
case '+':
|
||||
return Convert.toStr(a + b);
|
||||
case '-':
|
||||
return Convert.toStr(a - b);
|
||||
default:
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*/
|
||||
@Override
|
||||
public void checkCapcha(String code, String uuid) throws CaptchaException {
|
||||
if (StringUtils.isEmpty(code)) {
|
||||
throw new CaptchaException("验证码不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(uuid)) {
|
||||
throw new CaptchaException("验证码已失效");
|
||||
}
|
||||
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
||||
String captcha = RedisUtils.getCacheObject(verifyKey);
|
||||
RedisUtils.deleteObject(verifyKey);
|
||||
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
throw new CaptchaException("验证码错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.ruoyi.gateway.service.impl;
|
||||
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.ruoyi.common.core.constant.Constants;
|
||||
import com.ruoyi.common.core.exception.CaptchaException;
|
||||
import com.ruoyi.common.core.utils.SpringUtils;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import com.ruoyi.common.core.utils.reflect.ReflectUtils;
|
||||
import com.ruoyi.common.core.web.domain.AjaxResult;
|
||||
import com.ruoyi.common.redis.utils.RedisUtils;
|
||||
import com.ruoyi.gateway.config.properties.CaptchaProperties;
|
||||
import com.ruoyi.gateway.enums.CaptchaType;
|
||||
import com.ruoyi.gateway.service.ValidateCodeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 验证码实现处理
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
public class ValidateCodeServiceImpl implements ValidateCodeService {
|
||||
@Autowired
|
||||
private CaptchaProperties captchaProperties;
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
@Override
|
||||
public AjaxResult createCapcha() throws IOException, CaptchaException {
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
boolean captchaOnOff = captchaProperties.getEnabled();
|
||||
ajax.put("captchaOnOff", captchaOnOff);
|
||||
if (!captchaOnOff) {
|
||||
return ajax;
|
||||
}
|
||||
|
||||
// 保存验证码信息
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
||||
// 生成验证码
|
||||
CaptchaType captchaType = captchaProperties.getType();
|
||||
boolean isMath = CaptchaType.MATH == captchaType;
|
||||
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
|
||||
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
|
||||
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
|
||||
captcha.setGenerator(codeGenerator);
|
||||
captcha.createCode();
|
||||
String code = isMath ? getCodeResult(captcha.getCode()) : captcha.getCode();
|
||||
RedisUtils.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
|
||||
ajax.put("uuid", uuid);
|
||||
ajax.put("img", captcha.getImageBase64());
|
||||
return ajax;
|
||||
}
|
||||
|
||||
private String getCodeResult(String capStr) {
|
||||
int numberLength = captchaProperties.getNumberLength();
|
||||
int a = Convert.toInt(StringUtils.substring(capStr, 0, numberLength).trim());
|
||||
char operator = capStr.charAt(numberLength);
|
||||
int b = Convert.toInt(StringUtils.substring(capStr, numberLength + 1, numberLength + 1 + numberLength).trim());
|
||||
switch (operator) {
|
||||
case '*':
|
||||
return Convert.toStr(a * b);
|
||||
case '+':
|
||||
return Convert.toStr(a + b);
|
||||
case '-':
|
||||
return Convert.toStr(a - b);
|
||||
default:
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*/
|
||||
@Override
|
||||
public void checkCapcha(String code, String uuid) throws CaptchaException {
|
||||
if (StringUtils.isEmpty(code)) {
|
||||
throw new CaptchaException("验证码不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(uuid)) {
|
||||
throw new CaptchaException("验证码已失效");
|
||||
}
|
||||
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
||||
String captcha = RedisUtils.getCacheObject(verifyKey);
|
||||
RedisUtils.deleteObject(verifyKey);
|
||||
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
throw new CaptchaException("验证码错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user