前言JAVA下常用的安全框架主要有Spring Security和shiro , 都可提供非常强大的功能 , 但学习成本较高 。在微服务下鉴权多多少少都会对服务有一定的入侵性 。为了降低依赖 , 减少入侵 , 让鉴权功能相对应用服务透明 , 我们采用网关拦截资源请求的方式进行鉴权 。
一、整体架构
文章插图
整体结构
用户鉴权模块位于API GateWay服务中 , 所有的API资源请求都需要从此通过 。
- 做身份认证 , 通过则缓存用户权限数据 , 不通过返回401
- 做用户鉴权 , 比对当前访问资源(URI和Method)是否在已缓存的用户权限数据中 , 在则转发请求给对应应用服务 , 不在则返回403
文章插图
登陆鉴权流程
1. 用户登陆
public LoginUser login(String userName, String password){// 检查密码User user = userService.checkUser(userName, password);LoginUser loginUser = LoginUser.builder().userName(userName).realName(user.getRealName()).userToken(UUID.randomUUID().toString()).loginTime(new Date()).build();// 保存sessionsession.saveSession(loginUser);// 查询权限List<Permission> permissions = permissionRepository.findByUserName(userName);// 保存用户权限到缓存中session.saveUserPermissions(userName, permissions);return loginUser;}// ...// 缓存用户权限到redispublic void saveUserPermissions(String userName, List<Permission> permissions) {String key = String.format("login:permission:%s", userName);HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();hashOperations.putAll(key, permissions.stream().collect(Collectors.toMap(p -> p.getMethod().concat(":").concat(p.getUri()),Permission::getName, (k1, k2) -> k2)));if (expireTime != null) {redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);}}
- 用户验证通过后 , 下发userToken , 保存当前登陆信息 , 缓存用户授权列表
- 缓存授权列表时 , 为了方便读取使用hash方式保存为list , 切勿直接将数组对象保存为一个object
@Slf4j@Componentpublic class AuthorizationFilter extends AbstractGatewayFilterFactory {@Autowiredprivate Session session;@Overridepublic GatewayFilter Apply(Object config) {return (exchange, chain) -> {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();String uri = request.getURI().getPath();String method = request.getMethodValue();// 1.从AuthenticationFilter中获取userNameString key = "X-User-Name";if (!request.getHeaders().containsKey(key)) {response.setStatusCode(HttpStatus.FORBIDDEN);return response.setComplete();}String userName = Objects.requireNonNull(request.getHeaders().get(key)).get(0);// 2.验证权限if (!session.checkPermissions(userName, uri, method)) {log.info("用户:{}, 没有权限", userName);response.setStatusCode(HttpStatus.FORBIDDEN);return response.setComplete();}return chain.filter(exchange);};}}
- 第一步从取出身份认证模块传递的X-User-Name
- 第二步去缓存中检查是否有相应的权限
public boolean checkPermissions(String userName, String uri, String method) {String key = String.format("login:permission:%s", userName);String hashKey = String.format("%s:%s", method, uri);if (redisTemplate.opsForHash().hasKey(key, hashKey)){returntrue;}String allKey = "login:permission:all";// 权限列表中没有则通过return !redisTemplate.opsForHash().hasKey(allKey, hashKey);}
- 权限列表中没有则通过 主要是放过一些没有必要配置的公共资源 , 默认都可以访问的资源
- login:permission:all 所有配置过的权限列表需要在程序启动时放入缓存 , 并需要保持数据的更新
spring:cloud:gateway:routes:- id: cloud-useruri: lb://cloud-user# 后端服务名predicates:- Path=/user/**# 路由地址filters:- name: AuthenticationFilter# 身份认证- name: AuthorizationFilter# 用户鉴权- StripPrefix=1 # 去掉前缀
- 特别注意filter的顺序 , 必须先做身份认证后再进行鉴权
- 如果有较多的路由都需要配置 , 可使用default-filters默认Filter配置
nested exception is java.lang.NoClassDefFoundError: javax/validation/ValidationException
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 穿衣搭配|尖头细跟短靴,搭配紧身裤,一头紫色短发,精致干练的穿搭
- Nginx配置跳转的常用方式
- NodeJS常用 API 整理
- 貂蝉的五个男人 貂蝉跟了多少男人
- 雍正与隆科多关系 隆科多跟雍正太后有什么关系
- 跟随林黛玉,我们见到了贾府的哪些人物 黛玉带到贾府的小丫头是
- 生存法则|职场生存法则:跟同事走得太远,是一个人幸运的开始!
- 唐玄宗李隆基跟太平公主是什么关系 太平公主为什么会死在侄儿李隆基手中
- 美团 iOS 端开源框架 Graver 在动态化上的探索与实践
- ROS 的常用命令行工具