跟常用框架SpringSecurity比 微服务到底胜在哪?

前言JAVA下常用的安全框架主要有Spring Security和shiro , 都可提供非常强大的功能 , 但学习成本较高 。在微服务下鉴权多多少少都会对服务有一定的入侵性 。为了降低依赖 , 减少入侵 , 让鉴权功能相对应用服务透明 , 我们采用网关拦截资源请求的方式进行鉴权 。
一、整体架构

跟常用框架SpringSecurity比 微服务到底胜在哪?

文章插图
 
整体结构
用户鉴权模块位于API GateWay服务中 , 所有的API资源请求都需要从此通过 。
  1. 做身份认证 , 通过则缓存用户权限数据 , 不通过返回401
  2. 做用户鉴权 , 比对当前访问资源(URI和Method)是否在已缓存的用户权限数据中 , 在则转发请求给对应应用服务 , 不在则返回403
二、实现步骤
跟常用框架SpringSecurity比 微服务到底胜在哪?

文章插图
 
登陆鉴权流程
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
2. 拦截请求@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
  • 第二步去缓存中检查是否有相应的权限
【跟常用框架SpringSecurity比 微服务到底胜在哪?】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 所有配置过的权限列表需要在程序启动时放入缓存 , 并需要保持数据的更新
3. 鉴权Filter配置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


推荐阅读