mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2024-12-22 20:54:26 +08:00
fix(JwtUtils): JWT载体有中文解析出来是问号乱码
Closes I3X8V6
This commit is contained in:
parent
2af3e2f493
commit
75ad8cffbd
Binary file not shown.
@ -11,26 +11,3 @@ spring:
|
|||||||
config:
|
config:
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||||
file-extension: yaml
|
file-extension: yaml
|
||||||
sentinel:
|
|
||||||
enabled: true
|
|
||||||
eager: true # 取消控制台懒加载,项目启动即连接Sentinel
|
|
||||||
transport:
|
|
||||||
client-ip: localhost
|
|
||||||
dashboard: localhost:8080
|
|
||||||
datasource:
|
|
||||||
# 限流规则,flow为key,随便定义
|
|
||||||
flow:
|
|
||||||
nacos:
|
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
|
||||||
dataId: ${spring.application.name}-flow-rules
|
|
||||||
groupId: SENTINEL_GROUP
|
|
||||||
data-type: json
|
|
||||||
rule-type: flow
|
|
||||||
# 降级规则
|
|
||||||
degrade:
|
|
||||||
nacos:
|
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
|
||||||
dataId: ${spring.application.name}-degrade-rules
|
|
||||||
groupId: SENTINEL_GROUP
|
|
||||||
data-type: json
|
|
||||||
rule-type: degrade
|
|
||||||
|
@ -61,7 +61,7 @@ public class OAuthController {
|
|||||||
* 方式一:client_id、client_secret放在请求路径中(注:当前版本已废弃)
|
* 方式一:client_id、client_secret放在请求路径中(注:当前版本已废弃)
|
||||||
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
|
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
|
||||||
*/
|
*/
|
||||||
String clientId = JwtUtils.getAuthClientId();
|
String clientId = JwtUtils.getOAuthClientId();
|
||||||
OAuthClientEnum client = OAuthClientEnum.getByClientId(clientId);
|
OAuthClientEnum client = OAuthClientEnum.getByClientId(clientId);
|
||||||
switch (client) {
|
switch (client) {
|
||||||
case TEST: // knife4j接口测试文档使用 client_id/client_secret : client/123456
|
case TEST: // knife4j接口测试文档使用 client_id/client_secret : client/123456
|
||||||
|
@ -124,11 +124,9 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码编码器
|
* 密码编码器
|
||||||
* <p>
|
*
|
||||||
* 委托方式,根据密码的前缀选择对应的encoder,例如:{bcypt}前缀->标识BCYPT算法加密;{noop}->标识不使用任何加密即明文的方式
|
* 委托方式,根据密码的前缀选择对应的encoder,例如:{bcypt}前缀->标识BCYPT算法加密;{noop}->标识不使用任何加密即明文的方式
|
||||||
* 密码判读 DaoAuthenticationProvider#additionalAuthenticationChecks
|
* 密码判读 DaoAuthenticationProvider#additionalAuthenticationChecks
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
@ -34,7 +34,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
String clientId = JwtUtils.getAuthClientId();
|
String clientId = JwtUtils.getOAuthClientId();
|
||||||
OAuthClientEnum client = OAuthClientEnum.getByClientId(clientId);
|
OAuthClientEnum client = OAuthClientEnum.getByClientId(clientId);
|
||||||
|
|
||||||
Result result;
|
Result result;
|
||||||
|
@ -9,38 +9,8 @@ spring:
|
|||||||
discovery:
|
discovery:
|
||||||
server-addr: http://localhost:8848
|
server-addr: http://localhost:8848
|
||||||
config:
|
config:
|
||||||
# docker启动nacos-server需要配置
|
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||||
file-extension: yaml
|
file-extension: yaml
|
||||||
group: DEFAULT_GROUP
|
group: DEFAULT_GROUP
|
||||||
sentinel:
|
|
||||||
enabled: true
|
|
||||||
eager: true # 取消控制台懒加载,项目启动即连接Sentinel
|
|
||||||
transport:
|
|
||||||
client-ip: localhost
|
|
||||||
dashboard: localhost:8080
|
|
||||||
datasource:
|
|
||||||
# 降级规则
|
|
||||||
degrade:
|
|
||||||
nacos:
|
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
|
||||||
dataId: ${spring.application.name}-degrade-rules
|
|
||||||
groupId: SENTINEL_GROUP
|
|
||||||
data-type: json
|
|
||||||
rule-type: degrade
|
|
||||||
|
|
||||||
# 开启feign对sentinel的支持
|
|
||||||
feign:
|
|
||||||
sentinel:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
# jwt 配置
|
|
||||||
jwt:
|
|
||||||
config:
|
|
||||||
enabled: true
|
|
||||||
key-location: jwt.jks
|
|
||||||
key-alias: jwt
|
|
||||||
key-pass: 123456
|
|
||||||
iss: youlai.tech
|
|
||||||
sub: all
|
|
||||||
access-exp-days: 30
|
|
@ -2,7 +2,6 @@ package com.youlai.common.constant;
|
|||||||
|
|
||||||
public interface AuthConstants {
|
public interface AuthConstants {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证请求头key
|
* 认证请求头key
|
||||||
*/
|
*/
|
||||||
|
@ -10,26 +10,40 @@ import org.apache.logging.log4j.util.Strings;
|
|||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
import sun.misc.BASE64Decoder;
|
import sun.misc.BASE64Decoder;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.net.URLDecoder;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT工具类
|
||||||
|
*
|
||||||
|
* @author xianrui
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class JwtUtils {
|
public class JwtUtils {
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
public static JSONObject getJwtPayload() {
|
public static JSONObject getJwtPayload() {
|
||||||
String jwtPayload = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(AuthConstants.JWT_PAYLOAD_KEY);
|
String payload = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(AuthConstants.JWT_PAYLOAD_KEY);
|
||||||
JSONObject jsonObject = JSONUtil.parseObj(jwtPayload);
|
JSONObject jsonObject = JSONUtil.parseObj(URLDecoder.decode(payload,"UTF-8"));
|
||||||
return jsonObject;
|
return jsonObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析JWT获取用户ID
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public static Long getUserId() {
|
public static Long getUserId() {
|
||||||
Long id = getJwtPayload().getLong(AuthConstants.USER_ID_KEY);
|
Long id = getJwtPayload().getLong(AuthConstants.USER_ID_KEY);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析JWT获取获取用户名
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public static String getUsername() {
|
public static String getUsername() {
|
||||||
String username = getJwtPayload().getStr(AuthConstants.USER_NAME_KEY);
|
String username = getJwtPayload().getStr(AuthConstants.USER_NAME_KEY);
|
||||||
return username;
|
return username;
|
||||||
@ -37,7 +51,7 @@ public class JwtUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取登录认证的客户端ID
|
* 获取登录认证的客户端ID
|
||||||
* <p>
|
*
|
||||||
* 兼容两种方式获取Oauth2客户端信息(client_id、client_secret)
|
* 兼容两种方式获取Oauth2客户端信息(client_id、client_secret)
|
||||||
* 方式一:client_id、client_secret放在请求路径中
|
* 方式一:client_id、client_secret放在请求路径中
|
||||||
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
|
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
|
||||||
@ -45,7 +59,7 @@ public class JwtUtils {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static String getAuthClientId() {
|
public static String getOAuthClientId() {
|
||||||
String clientId;
|
String clientId;
|
||||||
|
|
||||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||||
@ -66,15 +80,17 @@ public class JwtUtils {
|
|||||||
return clientId;
|
return clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT获取用户角色列表
|
||||||
|
*
|
||||||
|
* @return 角色列表
|
||||||
|
*/
|
||||||
public static List<String> getRoles() {
|
public static List<String> getRoles() {
|
||||||
|
List<String> roles = null;
|
||||||
JSONObject payload = getJwtPayload();
|
JSONObject payload = getJwtPayload();
|
||||||
if (payload != null && payload.size() > 0) {
|
if (payload != null && payload.containsKey(AuthConstants.JWT_AUTHORITIES_KEY)) {
|
||||||
List<String> list = payload.get(AuthConstants.JWT_AUTHORITIES_KEY, List.class);
|
roles = payload.get(AuthConstants.JWT_AUTHORITIES_KEY, List.class);
|
||||||
List<String> roles = list.stream().collect(Collectors.toList());
|
|
||||||
return roles;
|
|
||||||
}
|
}
|
||||||
return null;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.youlai.gateway.component;
|
package com.youlai.gateway.component;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import com.youlai.common.constant.GlobalConstants;
|
import com.youlai.common.constant.GlobalConstants;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.connection.Message;
|
import org.springframework.data.redis.connection.Message;
|
||||||
@ -11,13 +10,13 @@ import java.nio.charset.StandardCharsets;
|
|||||||
public class RedisChannelListener implements MessageListener {
|
public class RedisChannelListener implements MessageListener {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AdminRoleLocalCache adminRoleLocalCache;
|
private UrlPermRolesLocalCache urlPermRolesLocalCache;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(Message message, byte[] bytes) {
|
public void onMessage(Message message, byte[] bytes) {
|
||||||
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
|
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
|
||||||
String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
|
String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
|
||||||
adminRoleLocalCache.remove(GlobalConstants.URL_PERM_ROLES_KEY);
|
urlPermRolesLocalCache.remove(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,7 +16,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class AdminRoleLocalCache<T> {
|
public class UrlPermRolesLocalCache<T> {
|
||||||
private Cache<String,T> localCache = null;
|
private Cache<String,T> localCache = null;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
@ -3,13 +3,11 @@ package com.youlai.gateway.security;
|
|||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.json.JSON;
|
|
||||||
import cn.hutool.json.JSONObject;
|
|
||||||
import cn.hutool.json.JSONUtil;
|
|
||||||
import com.youlai.common.constant.AuthConstants;
|
import com.youlai.common.constant.AuthConstants;
|
||||||
import com.youlai.common.constant.GlobalConstants;
|
import com.youlai.common.constant.GlobalConstants;
|
||||||
import com.youlai.gateway.component.AdminRoleLocalCache;
|
import com.youlai.gateway.component.UrlPermRolesLocalCache;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
@ -25,27 +23,29 @@ import org.springframework.util.AntPathMatcher;
|
|||||||
import org.springframework.util.PathMatcher;
|
import org.springframework.util.PathMatcher;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网关自定义鉴权管理器
|
* 网关自定义鉴权管理器
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
|
||||||
* @date 2020-05-01
|
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@AllArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ResourceServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {
|
public class ResourceServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {
|
||||||
|
|
||||||
private RedisTemplate redisTemplate;
|
private final RedisTemplate redisTemplate;
|
||||||
private AdminRoleLocalCache adminRoleLocalCache;
|
|
||||||
|
// 本地缓存
|
||||||
|
private final UrlPermRolesLocalCache urlPermRolesLocalCache;
|
||||||
|
|
||||||
// 是否演示环境
|
// 是否演示环境
|
||||||
@Value("${demo}")
|
@Value("${local-cache.enabled}")
|
||||||
private Boolean isDemoEnv;
|
private Boolean localCacheEnabled;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
|
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
|
||||||
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
|
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
|
||||||
@ -53,66 +53,68 @@ public class ResourceServerManager implements ReactiveAuthorizationManager<Autho
|
|||||||
if (request.getMethod() == HttpMethod.OPTIONS) {
|
if (request.getMethod() == HttpMethod.OPTIONS) {
|
||||||
return Mono.just(new AuthorizationDecision(true));
|
return Mono.just(new AuthorizationDecision(true));
|
||||||
}
|
}
|
||||||
PathMatcher pathMatcher = new AntPathMatcher(); // 【声明定义】Ant路径匹配模式,“请求路径”和缓存中权限规则的“URL权限标识”匹配
|
PathMatcher pathMatcher = new AntPathMatcher(); // Ant匹配器
|
||||||
|
String method = request.getMethodValue();
|
||||||
String path = request.getURI().getPath();
|
String path = request.getURI().getPath();
|
||||||
|
String restfulPath = method + ":" + path; // Restful接口权限设计 @link https://www.cnblogs.com/haoxianrui/p/14961707.html
|
||||||
|
|
||||||
|
|
||||||
|
// 移动端请求需认证但无需鉴权判断
|
||||||
String token = request.getHeaders().getFirst(AuthConstants.AUTHORIZATION_KEY);
|
String token = request.getHeaders().getFirst(AuthConstants.AUTHORIZATION_KEY);
|
||||||
|
|
||||||
// 移动端请求无需鉴权,只需认证(即JWT的验签和是否过期判断)
|
|
||||||
if (pathMatcher.match(GlobalConstants.APP_API_PATTERN, path)) {
|
if (pathMatcher.match(GlobalConstants.APP_API_PATTERN, path)) {
|
||||||
// 如果token以"bearer "为前缀,到这一步说明是经过NimbusReactiveJwtDecoder#decode和JwtTimestampValidator#validate等解析和验证通过的,即已认证
|
// 如果token以"bearer "为前缀,到这里说明JWT有效即已认证
|
||||||
if (StrUtil.isNotBlank(token) && token.startsWith(AuthConstants.AUTHORIZATION_PREFIX)) {
|
if (StrUtil.isNotBlank(token)
|
||||||
|
&& token.startsWith(AuthConstants.AUTHORIZATION_PREFIX)) {
|
||||||
return Mono.just(new AuthorizationDecision(true));
|
return Mono.just(new AuthorizationDecision(true));
|
||||||
} else {
|
} else {
|
||||||
return Mono.just(new AuthorizationDecision(false));
|
return Mono.just(new AuthorizationDecision(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restful接口权限设计 @link https://www.cnblogs.com/haoxianrui/p/14396990.html
|
// 缓存取 URL权限-角色集合 规则数据
|
||||||
String restfulPath = request.getMethodValue() + ":" + path;
|
// urlPermRolesRules = [{'key':'GET:/api/v1/users/*','value':['ADMIN','TEST']},...]
|
||||||
log.info("请求方法:RESTFul请求路径:{}", restfulPath);
|
Map<String, Object> urlPermRolesRules;
|
||||||
Map<String, Object> permRolesRules = (Map<String, Object>) adminRoleLocalCache.getCache(GlobalConstants.URL_PERM_ROLES_KEY);
|
if (localCacheEnabled) {
|
||||||
if (isDemoEnv){
|
urlPermRolesRules = (Map<String, Object>) urlPermRolesLocalCache.getCache(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||||
// 缓存取【URL权限标识->角色集合】权限规则
|
if (null == urlPermRolesRules) {
|
||||||
if(null==permRolesRules){
|
urlPermRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||||
permRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
urlPermRolesLocalCache.setLocalCache(GlobalConstants.URL_PERM_ROLES_KEY, urlPermRolesRules);
|
||||||
adminRoleLocalCache.setLocalCache(GlobalConstants.URL_PERM_ROLES_KEY,permRolesRules);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
urlPermRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据 “请求路径” 和 权限规则中的“URL权限标识”进行Ant匹配,得出拥有权限的角色集合
|
|
||||||
Set<String> hasPermissionRoles = CollectionUtil.newHashSet(); // 【声明定义】有权限的角色集合
|
|
||||||
boolean needToCheck = false; // 【声明定义】是否需要被拦截检查的请求,如果缓存中权限规则中没有任何URL权限标识和此次请求的URL匹配,默认不需要被鉴权
|
|
||||||
|
|
||||||
for (Map.Entry<String, Object> permRoles : permRolesRules.entrySet()) {
|
// 根据请求路径判断有访问权限的角色列表
|
||||||
String perm = permRoles.getKey(); // 缓存权限规则的键:URL权限标识
|
List<String> authorizedRoles = new ArrayList<>(); // 拥有访问权限的角色
|
||||||
|
boolean requireCheck = false; // 是否需要鉴权,默认“没有设置权限规则”不用鉴权
|
||||||
|
|
||||||
|
for (Map.Entry<String, Object> permRoles : urlPermRolesRules.entrySet()) {
|
||||||
|
String perm = permRoles.getKey();
|
||||||
if (pathMatcher.match(perm, restfulPath)) {
|
if (pathMatcher.match(perm, restfulPath)) {
|
||||||
List<String> roles = Convert.toList(String.class, permRoles.getValue()); // 缓存权限规则的值:有请求路径访问权限的角色集合
|
List<String> roles = Convert.toList(String.class, permRoles.getValue());
|
||||||
hasPermissionRoles.addAll(Convert.toList(String.class, roles));
|
authorizedRoles.addAll(Convert.toList(String.class, roles));
|
||||||
if (needToCheck == false) {
|
if (requireCheck == false) {
|
||||||
needToCheck = true;
|
requireCheck = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("拥有接口访问权限的角色:{}", hasPermissionRoles.toString());
|
if (requireCheck == false) {
|
||||||
// 没有设置权限规则放行;注:如果默认想拦截所有的请求请移除needToCheck变量逻辑即可,根据需求定制
|
|
||||||
if (needToCheck == false) {
|
|
||||||
return Mono.just(new AuthorizationDecision(true));
|
return Mono.just(new AuthorizationDecision(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断用户JWT中携带的角色是否有能通过权限拦截的角色
|
// 判断JWT中携带的用户角色是否有权限访问
|
||||||
Mono<AuthorizationDecision> authorizationDecisionMono = mono
|
Mono<AuthorizationDecision> authorizationDecisionMono = mono
|
||||||
.filter(Authentication::isAuthenticated)
|
.filter(Authentication::isAuthenticated)
|
||||||
.flatMapIterable(Authentication::getAuthorities)
|
.flatMapIterable(Authentication::getAuthorities)
|
||||||
.map(GrantedAuthority::getAuthority)
|
.map(GrantedAuthority::getAuthority)
|
||||||
.any(authority -> {
|
.any(authority -> {
|
||||||
log.info("用户权限 : {}", authority); // ROLE_ROOT
|
String roleCode = authority.substring(AuthConstants.AUTHORITY_PREFIX.length()); // 用户的角色
|
||||||
String role = authority.substring(AuthConstants.AUTHORITY_PREFIX.length()); // 角色编码:ROOT
|
if (GlobalConstants.ROOT_ROLE_CODE.equals(roleCode)) {
|
||||||
if (GlobalConstants.ROOT_ROLE_CODE.equals(role)) { // 如果是超级管理员则放行
|
return true; // 如果是超级管理员则放行
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
boolean hasPermission = CollectionUtil.isNotEmpty(hasPermissionRoles) && hasPermissionRoles.contains(role); // 用户角色中只要有一个满足则通过权限校验
|
boolean hasAuthorized = CollectionUtil.isNotEmpty(authorizedRoles) && authorizedRoles.contains(roleCode);
|
||||||
return hasPermission;
|
return hasAuthorized;
|
||||||
})
|
})
|
||||||
.map(AuthorizationDecision::new)
|
.map(AuthorizationDecision::new)
|
||||||
.defaultIfEmpty(new AuthorizationDecision(false));
|
.defaultIfEmpty(new AuthorizationDecision(false));
|
||||||
|
@ -7,10 +7,10 @@ import com.nimbusds.jose.JWSObject;
|
|||||||
import com.youlai.common.constant.AuthConstants;
|
import com.youlai.common.constant.AuthConstants;
|
||||||
import com.youlai.common.result.ResultCode;
|
import com.youlai.common.result.ResultCode;
|
||||||
import com.youlai.gateway.util.ResponseUtils;
|
import com.youlai.gateway.util.ResponseUtils;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.logging.log4j.util.Strings;
|
import org.apache.logging.log4j.util.Strings;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
@ -23,22 +23,23 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安全拦截全局过滤器
|
* 安全拦截全局过滤器
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
|
||||||
* @date 2020-06-12
|
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
@Autowired
|
private final RedisTemplate redisTemplate;
|
||||||
private RedisTemplate redisTemplate;
|
|
||||||
|
|
||||||
// 是否演示环境
|
|
||||||
@Value("${demo}")
|
@Value("${spring.profiles.active}")
|
||||||
private Boolean isDemoEnv;
|
private String env;
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Override
|
@Override
|
||||||
@ -47,8 +48,8 @@ public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
|||||||
ServerHttpRequest request = exchange.getRequest();
|
ServerHttpRequest request = exchange.getRequest();
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
|
|
||||||
// 演示环境禁止删除和修改
|
// 线上演示环境禁止修改和删除
|
||||||
if (isDemoEnv
|
if (env.equals("prod")
|
||||||
&& (HttpMethod.DELETE.toString().equals(request.getMethodValue()) // 删除方法
|
&& (HttpMethod.DELETE.toString().equals(request.getMethodValue()) // 删除方法
|
||||||
|| HttpMethod.PUT.toString().equals(request.getMethodValue())) // 修改方法
|
|| HttpMethod.PUT.toString().equals(request.getMethodValue())) // 修改方法
|
||||||
) {
|
) {
|
||||||
@ -74,7 +75,7 @@ public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
|||||||
|
|
||||||
// 存在token且不是黑名单,request写入JWT的载体信息
|
// 存在token且不是黑名单,request写入JWT的载体信息
|
||||||
request = exchange.getRequest().mutate()
|
request = exchange.getRequest().mutate()
|
||||||
.header(AuthConstants.JWT_PAYLOAD_KEY, payload)
|
.header(AuthConstants.JWT_PAYLOAD_KEY, URLEncoder.encode(payload,"UTF-8"))
|
||||||
.build();
|
.build();
|
||||||
exchange = exchange.mutate().request(request).build();
|
exchange = exchange.mutate().request(request).build();
|
||||||
return chain.filter(exchange);
|
return chain.filter(exchange);
|
||||||
|
@ -11,7 +11,7 @@ import springfox.documentation.swagger.web.*;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author haoxr
|
* @author xianrui
|
||||||
* @Date 2021-02-25 16:34
|
* @Date 2021-02-25 16:34
|
||||||
* @Version 1.0.0
|
* @Version 1.0.0
|
||||||
* @ https://gitee.com/xiaoym/swagger-bootstrap-ui-demo/blob/master/knife4j-spring-cloud-gateway/service-doc/src/main/java/com/xiaominfo/swagger/service/doc/handler/SwaggerHandler.java
|
* @ https://gitee.com/xiaoym/swagger-bootstrap-ui-demo/blob/master/knife4j-spring-cloud-gateway/service-doc/src/main/java/com/xiaominfo/swagger/service/doc/handler/SwaggerHandler.java
|
||||||
|
@ -8,9 +8,9 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @auth haoxr
|
* @link https://gitee.com/xiaoym/swagger-bootstrap-ui-demo/blob/master/knife4j-spring-cloud-gateway/service-doc/src/main/java/com/xiaominfo/swagger/service/doc/config/SwaggerHeaderFilter.java
|
||||||
|
* @auth xianrui
|
||||||
* @date 2021-02-25 16:29
|
* @date 2021-02-25 16:29
|
||||||
* @link https://gitee.com/xiaoym/swagger-bootstrap-ui-demo/blob/master/knife4j-spring-cloud-gateway/service-doc/src/main/java/com/xiaominfo/swagger/service/doc/config/SwaggerHeaderFilter.java
|
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
|
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
|
||||||
|
@ -13,29 +13,7 @@ spring:
|
|||||||
config:
|
config:
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||||
file-extension: yaml
|
file-extension: yaml
|
||||||
sentinel:
|
|
||||||
enabled: false # 网关流控开关
|
|
||||||
eager: true # 取消控制台懒加载,项目启动即连接Sentinel
|
|
||||||
transport:
|
|
||||||
client-ip: localhost
|
|
||||||
dashboard: localhost:8080
|
|
||||||
datasource:
|
|
||||||
# 网关限流规则,gw-flow为key,随便定义
|
|
||||||
gw-flow:
|
|
||||||
nacos:
|
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
|
||||||
dataId: ${spring.application.name}-gw-flow-rules
|
|
||||||
groupId: SENTINEL_GROUP
|
|
||||||
data-type: json
|
|
||||||
rule-type: gw-flow
|
|
||||||
# 网关API自定义分组
|
|
||||||
gw-api-group:
|
|
||||||
nacos:
|
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
|
||||||
dataId: ${spring.application.name}-gw-api-group-rules
|
|
||||||
groupId: SENTINEL_GROUP
|
|
||||||
data-type: json
|
|
||||||
rule-type: gw-api-group
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,24 +15,3 @@ spring:
|
|||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||||
file-extension: yaml
|
file-extension: yaml
|
||||||
namespace: prod_namespace_id
|
namespace: prod_namespace_id
|
||||||
sentinel:
|
|
||||||
eager: true
|
|
||||||
transport:
|
|
||||||
dashboard: e.youlai.tech:8858
|
|
||||||
datasource:
|
|
||||||
# 网关限流
|
|
||||||
gw-flow:
|
|
||||||
nacos:
|
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
|
||||||
dataId: ${spring.application.name}-gw-flow-rules
|
|
||||||
groupId: SENTINEL_GROUP
|
|
||||||
data-type: json
|
|
||||||
rule-type: gw-flow
|
|
||||||
# 网关API自定义分组
|
|
||||||
gw-api-group:
|
|
||||||
nacos:
|
|
||||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
|
||||||
dataId: ${spring.application.name}-gw-api-group-rules
|
|
||||||
groupId: SENTINEL_GROUP
|
|
||||||
data-type: json
|
|
||||||
rule-type: gw-api-group
|
|
||||||
|
Loading…
Reference in New Issue
Block a user