mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2024-12-23 05:00:25 +08:00
Merge remote-tracking branch 'origin/develop' into feature/I3YZQP
# Conflicts: # youlai-gateway/src/main/java/com/youlai/gateway/security/ResourceServerManager.java # youlai-gateway/src/main/java/com/youlai/gateway/security/SecurityGlobalFilter.java
This commit is contained in:
commit
770def4ce3
@ -157,10 +157,14 @@ youlai-mall
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| ![image-20210622000304570](https://gitee.com/haoxr/image/raw/master/image-20210622000304570.png) | ![image-20210622000046029](https://gitee.com/haoxr/image/raw/master/image-20210622000046029.png) |
|
||||
|
||||
## 项目文档
|
||||
|
||||
[项目文档地址](https://www.cnblogs.com/haoxianrui/)
|
||||
|
||||
## Stargazers over time
|
||||
## Star趋势
|
||||
- Github
|
||||
[![Github](https://starchart.cc/hxrui/youlai-mall.svg)](https://starchart.cc/hxrui/youlai-mall)
|
||||
- Gitee
|
||||
[![Gitee](https://whnb.wang/stars/youlaitech/youlai-mall)](https://whnb.wang/stars/youlaitech/youlai-mall)
|
||||
|
||||
## contributors
|
||||
@ -168,7 +172,7 @@ youlai-mall
|
||||
|
||||
|
||||
## 联系信息
|
||||
因为微信交流群超过200人了,只能通过邀请进入群聊,添加开发人员(PS:日常上班时间建议选择没有头像的)后由其拉进群,相互交流学习,备注“**有来**”即可。
|
||||
因为微信交流群满200人只能通过邀请进入,如果想进入交流群学习可添加以下开发人员,备注“**有来**“由其拉进群。
|
||||
|
||||
| ![](https://gitee.com/haoxr/image/raw/master/default/113__6c5ed5b1b73ea9cd4cf32848ed350c07_b9b214638a2a406e52dbf51e9bf9a2ef.png) | ![](https://gitee.com/haoxr/image/raw/master/hxr.jpg) | ![](https://gitee.com/haoxr/image/raw/master/huawei.jpg) | ![](https://gitee.com/haoxr/image/raw/master/default/1625149769(1).png) |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
|
Binary file not shown.
14
pom.xml
14
pom.xml
@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.0</version>
|
||||
<version>2.5.2</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@ -247,4 +247,16 @@
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<configuration>
|
||||
<aggregate>true</aggregate>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.youlai.admin.service.impl.MinioService;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@ -90,9 +90,11 @@ public class RoleController {
|
||||
@PostMapping
|
||||
public Result add(@RequestBody SysRole role) {
|
||||
int count = iSysRoleService.count(new LambdaQueryWrapper<SysRole>()
|
||||
.eq(SysRole::getCode, role.getCode())
|
||||
.eq(SysRole::getCode, role.getCode() )
|
||||
.or()
|
||||
.eq(SysRole::getName,role.getName())
|
||||
);
|
||||
Assert.isTrue(count == 0, "角色编码已存在");
|
||||
Assert.isTrue(count == 0, "角色名称或角色编码重复,请检查!");
|
||||
boolean result = iSysRoleService.save(role);
|
||||
return Result.judge(result);
|
||||
}
|
||||
@ -108,9 +110,11 @@ public class RoleController {
|
||||
@RequestBody SysRole role) {
|
||||
int count = iSysRoleService.count(new LambdaQueryWrapper<SysRole>()
|
||||
.eq(SysRole::getCode, role.getCode())
|
||||
.or()
|
||||
.eq(SysRole::getName,role.getName())
|
||||
.ne(SysRole::getId, id)
|
||||
);
|
||||
Assert.isTrue(count == 0, "角色编码已存在");
|
||||
Assert.isTrue(count == 0, "角色名称或角色编码重复,请检查!");
|
||||
boolean result = iSysRoleService.updateById(role);
|
||||
if (result) {
|
||||
iSysPermissionService.refreshPermRolesRules();
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import com.youlai.admin.pojo.vo.RouteVO;
|
||||
import com.youlai.admin.service.ISysMenuService;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import com.youlai.admin.service.ITokenService;
|
||||
import com.youlai.common.result.Result;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.admin.controller.v1;
|
||||
package com.youlai.admin.controller;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
@ -106,7 +106,7 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
|
||||
.forEach(menu -> {
|
||||
RouteVO routeVO = new RouteVO();
|
||||
|
||||
routeVO.setName(menu.getRouteName()); // 根据name路由跳转 this.$router.push({path:xxx})
|
||||
routeVO.setName(menu.getId() + ""); // 根据name路由跳转 this.$router.push({path:xxx})
|
||||
routeVO.setPath(menu.getRoutePath()); // 根据path路由跳转 this.$router.push({name:xxx})
|
||||
routeVO.setRedirect(menu.getRedirect());
|
||||
routeVO.setComponent(menu.getComponent());
|
||||
|
@ -11,26 +11,3 @@ spring:
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
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放在请求路径中(注:当前版本已废弃)
|
||||
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
|
||||
*/
|
||||
String clientId = JwtUtils.getAuthClientId();
|
||||
String clientId = JwtUtils.getOAuthClientId();
|
||||
OAuthClientEnum client = OAuthClientEnum.getByClientId(clientId);
|
||||
switch (client) {
|
||||
case TEST: // knife4j接口测试文档使用 client_id/client_secret : client/123456
|
||||
|
@ -124,11 +124,9 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
|
||||
|
||||
/**
|
||||
* 密码编码器
|
||||
* <p>
|
||||
*
|
||||
* 委托方式,根据密码的前缀选择对应的encoder,例如:{bcypt}前缀->标识BCYPT算法加密;{noop}->标识不使用任何加密即明文的方式
|
||||
* 密码判读 DaoAuthenticationProvider#additionalAuthenticationChecks
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
|
@ -34,7 +34,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
String clientId = JwtUtils.getAuthClientId();
|
||||
String clientId = JwtUtils.getOAuthClientId();
|
||||
OAuthClientEnum client = OAuthClientEnum.getByClientId(clientId);
|
||||
|
||||
Result result;
|
||||
|
@ -9,38 +9,8 @@ spring:
|
||||
discovery:
|
||||
server-addr: http://localhost:8848
|
||||
config:
|
||||
# docker启动nacos-server需要配置
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
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 {
|
||||
|
||||
|
||||
/**
|
||||
* 认证请求头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.ServletRequestAttributes;
|
||||
import sun.misc.BASE64Decoder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
*
|
||||
* @author xianrui
|
||||
*/
|
||||
@Slf4j
|
||||
public class JwtUtils {
|
||||
|
||||
@SneakyThrows
|
||||
public static JSONObject getJwtPayload() {
|
||||
String jwtPayload = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(AuthConstants.JWT_PAYLOAD_KEY);
|
||||
JSONObject jsonObject = JSONUtil.parseObj(jwtPayload);
|
||||
String payload = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(AuthConstants.JWT_PAYLOAD_KEY);
|
||||
JSONObject jsonObject = JSONUtil.parseObj(URLDecoder.decode(payload,"UTF-8"));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析JWT获取用户ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Long getUserId() {
|
||||
Long id = getJwtPayload().getLong(AuthConstants.USER_ID_KEY);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析JWT获取获取用户名
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getUsername() {
|
||||
String username = getJwtPayload().getStr(AuthConstants.USER_NAME_KEY);
|
||||
return username;
|
||||
@ -37,7 +51,7 @@ public class JwtUtils {
|
||||
|
||||
/**
|
||||
* 获取登录认证的客户端ID
|
||||
* <p>
|
||||
*
|
||||
* 兼容两种方式获取Oauth2客户端信息(client_id、client_secret)
|
||||
* 方式一:client_id、client_secret放在请求路径中
|
||||
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
|
||||
@ -45,7 +59,7 @@ public class JwtUtils {
|
||||
* @return
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String getAuthClientId() {
|
||||
public static String getOAuthClientId() {
|
||||
String clientId;
|
||||
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
@ -66,15 +80,17 @@ public class JwtUtils {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT获取用户角色列表
|
||||
*
|
||||
* @return 角色列表
|
||||
*/
|
||||
public static List<String> getRoles() {
|
||||
List<String> roles = null;
|
||||
JSONObject payload = getJwtPayload();
|
||||
if (payload != null && payload.size() > 0) {
|
||||
List<String> list = payload.get(AuthConstants.JWT_AUTHORITIES_KEY, List.class);
|
||||
List<String> roles = list.stream().collect(Collectors.toList());
|
||||
if (payload != null && payload.containsKey(AuthConstants.JWT_AUTHORITIES_KEY)) {
|
||||
roles = payload.get(AuthConstants.JWT_AUTHORITIES_KEY, List.class);
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.youlai.gateway.component;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.connection.Message;
|
||||
@ -11,13 +10,13 @@ import java.nio.charset.StandardCharsets;
|
||||
public class RedisChannelListener implements MessageListener {
|
||||
|
||||
@Autowired
|
||||
private AdminRoleLocalCache adminRoleLocalCache;
|
||||
private UrlPermRolesLocalCache urlPermRolesLocalCache;
|
||||
|
||||
@Override
|
||||
public void onMessage(Message message, byte[] bytes) {
|
||||
String msg = new String(message.getBody(), 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 javax.annotation.PostConstruct;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@ -17,7 +16,7 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AdminRoleLocalCache<T> {
|
||||
public class UrlPermRolesLocalCache<T> {
|
||||
private Cache<String,T> localCache = null;
|
||||
|
||||
@PostConstruct
|
@ -5,7 +5,7 @@ import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.common.constant.AuthConstants;
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import com.youlai.gateway.component.AdminRoleLocalCache;
|
||||
import com.youlai.gateway.component.UrlPermRolesLocalCache;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -22,15 +22,14 @@ import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 网关自定义鉴权管理器
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2020-05-01
|
||||
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ -38,11 +37,13 @@ import java.util.Set;
|
||||
public class ResourceServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
private final AdminRoleLocalCache adminRoleLocalCache;
|
||||
|
||||
// 是否演示环境
|
||||
@Value("${demo}")
|
||||
private Boolean isDemoEnv;
|
||||
// 本地缓存
|
||||
private final UrlPermRolesLocalCache urlPermRolesLocalCache;
|
||||
|
||||
// 是否开启本地缓存
|
||||
@Value("${local-cache.enabled}")
|
||||
private Boolean localCacheEnabled;
|
||||
|
||||
@Override
|
||||
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
|
||||
@ -51,69 +52,68 @@ public class ResourceServerManager implements ReactiveAuthorizationManager<Autho
|
||||
if (request.getMethod() == HttpMethod.OPTIONS) {
|
||||
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 restfulPath = method + ":" + path; // Restful接口权限设计 @link https://www.cnblogs.com/haoxianrui/p/14961707.html
|
||||
|
||||
|
||||
// 移动端请求需认证但无需鉴权判断
|
||||
String token = request.getHeaders().getFirst(AuthConstants.AUTHORIZATION_KEY);
|
||||
|
||||
// 移动端请求无需鉴权,只需认证(即JWT的验签和是否过期判断)
|
||||
if (pathMatcher.match(GlobalConstants.APP_API_PATTERN, path)) {
|
||||
// 如果token以"bearer "为前缀,到这一步说明是经过NimbusReactiveJwtDecoder#decode和JwtTimestampValidator#validate等解析和验证通过的,即已认证
|
||||
if (StrUtil.isNotBlank(token) && token.startsWith(AuthConstants.AUTHORIZATION_PREFIX)) {
|
||||
// 如果token以"bearer "为前缀,到这里说明JWT有效即已认证
|
||||
if (StrUtil.isNotBlank(token)
|
||||
&& token.startsWith(AuthConstants.AUTHORIZATION_PREFIX)) {
|
||||
return Mono.just(new AuthorizationDecision(true));
|
||||
} else {
|
||||
return Mono.just(new AuthorizationDecision(false));
|
||||
}
|
||||
}
|
||||
|
||||
// Restful接口权限设计 @link https://www.cnblogs.com/haoxianrui/p/14396990.html
|
||||
String restfulPath = request.getMethodValue() + ":" + path;
|
||||
log.info("请求方法:RESTFul请求路径:{}", restfulPath);
|
||||
Map<String, Object> permRolesRules = (Map<String, Object>) adminRoleLocalCache.getCache(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
if (isDemoEnv) {
|
||||
// 缓存取【URL权限标识->角色集合】权限规则
|
||||
if (null == permRolesRules) {
|
||||
permRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
adminRoleLocalCache.setLocalCache(GlobalConstants.URL_PERM_ROLES_KEY, permRolesRules);
|
||||
// 缓存取 URL权限-角色集合 规则数据
|
||||
// urlPermRolesRules = [{'key':'GET:/api/v1/users/*','value':['ADMIN','TEST']},...]
|
||||
Map<String, Object> urlPermRolesRules;
|
||||
if (localCacheEnabled) {
|
||||
urlPermRolesRules = (Map<String, Object>) urlPermRolesLocalCache.getCache(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
if (null == urlPermRolesRules) {
|
||||
urlPermRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
urlPermRolesLocalCache.setLocalCache(GlobalConstants.URL_PERM_ROLES_KEY, urlPermRolesRules);
|
||||
}
|
||||
} else {
|
||||
urlPermRolesRules = redisTemplate.opsForHash().entries(GlobalConstants.URL_PERM_ROLES_KEY);
|
||||
}
|
||||
|
||||
// 根据 “请求路径” 和 权限规则中的“URL权限标识”进行Ant匹配,得出拥有权限的角色集合
|
||||
Set<String> hasPermissionRoles = CollectionUtil.newHashSet(); // 【声明定义】有权限的角色集合
|
||||
boolean needToCheck = false; // 【声明定义】是否需要被拦截检查的请求,如果缓存中权限规则中没有任何URL权限标识和此次请求的URL匹配,默认不需要被鉴权
|
||||
|
||||
if (CollectionUtil.isNotEmpty(permRolesRules)) {
|
||||
for (Map.Entry<String, Object> ruleEntry : permRolesRules.entrySet()) {
|
||||
String perm = ruleEntry.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)) {
|
||||
List<String> roles = Convert.toList(String.class, ruleEntry.getValue()); // 缓存权限规则的值:有请求路径访问权限的角色集合
|
||||
hasPermissionRoles.addAll(Convert.toList(String.class, roles));
|
||||
if (needToCheck == false) {
|
||||
needToCheck = true;
|
||||
List<String> roles = Convert.toList(String.class, permRoles.getValue());
|
||||
authorizedRoles.addAll(Convert.toList(String.class, roles));
|
||||
if (requireCheck == false) {
|
||||
requireCheck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("拥有接口访问权限的角色:{}", hasPermissionRoles.toString());
|
||||
// 没有设置权限规则放行;注:如果默认想拦截所有的请求请移除needToCheck变量逻辑即可,根据需求定制
|
||||
if (needToCheck == false) {
|
||||
if (requireCheck == false) {
|
||||
return Mono.just(new AuthorizationDecision(true));
|
||||
}
|
||||
|
||||
// 判断用户JWT中携带的角色是否有能通过权限拦截的角色
|
||||
// 判断JWT中携带的用户角色是否有权限访问
|
||||
Mono<AuthorizationDecision> authorizationDecisionMono = mono
|
||||
.filter(Authentication::isAuthenticated)
|
||||
.flatMapIterable(Authentication::getAuthorities)
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.any(authority -> {
|
||||
log.info("用户权限 : {}", authority); // ROLE_ROOT
|
||||
String role = authority.substring(AuthConstants.AUTHORITY_PREFIX.length()); // 角色编码:ROOT
|
||||
if (GlobalConstants.ROOT_ROLE_CODE.equals(role)) { // 如果是超级管理员则放行
|
||||
return true;
|
||||
String roleCode = authority.substring(AuthConstants.AUTHORITY_PREFIX.length()); // 用户的角色
|
||||
if (GlobalConstants.ROOT_ROLE_CODE.equals(roleCode)) {
|
||||
return true; // 如果是超级管理员则放行
|
||||
}
|
||||
boolean hasPermission = CollectionUtil.isNotEmpty(hasPermissionRoles) && hasPermissionRoles.contains(role); // 用户角色中只要有一个满足则通过权限校验
|
||||
return hasPermission;
|
||||
boolean hasAuthorized = CollectionUtil.isNotEmpty(authorizedRoles) && authorizedRoles.contains(roleCode);
|
||||
return hasAuthorized;
|
||||
})
|
||||
.map(AuthorizationDecision::new)
|
||||
.defaultIfEmpty(new AuthorizationDecision(false));
|
||||
|
@ -11,7 +11,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
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;
|
||||
@ -24,22 +23,23 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
|
||||
/**
|
||||
* 安全拦截全局过滤器
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2020-06-12
|
||||
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
// 是否演示环境
|
||||
@Value("${demo}")
|
||||
private Boolean isDemoEnv;
|
||||
|
||||
@Value("${spring.profiles.active}")
|
||||
private String env;
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
@ -48,8 +48,8 @@ public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
// 演示环境禁止删除和修改
|
||||
if (isDemoEnv
|
||||
// 线上演示环境禁止修改和删除
|
||||
if (env.equals("prod")
|
||||
&& (HttpMethod.DELETE.toString().equals(request.getMethodValue()) // 删除方法
|
||||
|| HttpMethod.PUT.toString().equals(request.getMethodValue())) // 修改方法
|
||||
) {
|
||||
@ -75,7 +75,7 @@ public class SecurityGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
// 存在token且不是黑名单,request写入JWT的载体信息
|
||||
request = exchange.getRequest().mutate()
|
||||
.header(AuthConstants.JWT_PAYLOAD_KEY, payload)
|
||||
.header(AuthConstants.JWT_PAYLOAD_KEY, URLEncoder.encode(payload,"UTF-8"))
|
||||
.build();
|
||||
exchange = exchange.mutate().request(request).build();
|
||||
return chain.filter(exchange);
|
||||
|
@ -11,7 +11,7 @@ import springfox.documentation.swagger.web.*;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @Author haoxr
|
||||
* @author xianrui
|
||||
* @Date 2021-02-25 16:34
|
||||
* @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
|
||||
|
@ -8,9 +8,9 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @auth haoxr
|
||||
* @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
|
||||
* @auth xianrui
|
||||
* @date 2021-02-25 16:29
|
||||
*/
|
||||
@Component
|
||||
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
|
||||
|
@ -13,29 +13,7 @@ spring:
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
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}
|
||||
file-extension: yaml
|
||||
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