前面的文章中介绍了网关集成Spring Security实现网关层面的统一的认证鉴权 。
- 鉴权放在各个微服务中如何做?
- feign的调用如何做到的鉴权?
前面的几篇文章陈某都是将鉴权和认证统一的放在了网关层面,架构如下:
文章插图
微服务中的鉴权还有另外一种思路:将鉴权交给下游的各个微服务,网关层面只做路由转发 。
这种思路其实实现起来也是很简单,下面针对网关层面鉴权的代码改造一下即可完成:实战干货!Spring Cloud Gateway 整合 OAuth2.0 实现分布式统一认证授权!
1. 干掉鉴权管理器在网关统一鉴权实际是依赖的鉴权管理器ReactiveAuthorizationManager,所有的请求都需要经过鉴权管理器的去对登录用户的权限进行鉴权 。
这个鉴权管理器在网关鉴权的文章中也有介绍,在陈某的《Spring Cloud Alibaba 实战》中配置拦截也很简单,如下:
【只要三个注解,优雅的实现微服务鉴权!】
文章插图
除了配置的白名单,其他的请求一律都要被网关的鉴权管理器拦截鉴权,只有鉴权通过才能放行路由转发给下游服务 。
看到这里思路是不是很清楚了,想要将鉴权交给下游服务,只需要在网关层面直接放行,不走鉴权管理器,代码如下:
http .... //白名单直接放行.pathMatchers(ArrayUtil.toArray(whiteUrls.getUrls(), String.class)).permitAll() //其他的任何请求直接放行.anyExchange().permitAll().....
2. 定义三个注解经过第①步,鉴权已经下放给下游服务了,那么下游服务如何进行拦截鉴权呢?其实Spring Security 提供了3个注解用于控制权限,如下:
- @Secured
- @PreAuthorize
- @PostAuthorize
陈某这里并不打算使用的内置的三个注解实现,而是自定义了三个注解,如下:
1).@RequiresLogin见名知意,只有用户登录才能放行,代码如下:
/** * @author 公众号:码猿技术专栏 * @url: www.JAVA-family.cn * @description 登录认证的注解,标注在controller方法上,一定要是登录才能的访问的接口 */@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface RequiresLogin {}
2).@RequiresPermissions见名知意,只有拥有指定权限才能放行,代码如下:/** * @author 公众号:码猿技术专栏 * @url: www.java-family.cn * @description 标注在controller方法上,确保拥有指定权限才能访问该接口 */@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface RequiresPermissions {/*** 需要校验的权限码*/String[] value() default {};/*** 验证模式:AND | OR,默认AND*/Logical logical() default Logical.AND;}
3).@RequiresRoles见名知意,只有拥有指定角色才能放行,代码如下:/** * @author 公众号:码猿技术专栏 * @url: www.java-family.cn * @description 标注在controller方法上,确保拥有指定的角色才能访问该接口 */@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface RequiresRoles {/*** 需要校验的角色标识,默认超管和管理员*/String[] value() default {OAuthConstant.ROLE_ROOT_CODE,OAuthConstant.ROLE_ADMIN_CODE};/*** 验证逻辑:AND | OR,默认AND*/Logical logical() default Logical.AND;}
以上三个注解的含义想必都很好理解,这里就不再解释了....3. 注解切面定义注解有了,那么如何去拦截呢?这里陈某定义了一个切面进行拦截,关键代码如下:
/** * @author 公众号:码猿技术专栏 * @url: www.java-family.cn * @description @RequiresLogin,@RequiresPermissions,@RequiresRoles 注解的切面 */@Aspect@Componentpublic class PreAuthorizeAspect {/*** 构建*/public PreAuthorizeAspect() {}/*** 定义AOP签名 (切入所有使用鉴权注解的方法)*/public static final String POINTCUT_SIGN = " @annotation(com.mugu.blog.common.annotation.RequiresLogin) || "+ "@annotation(com.mugu.blog.common.annotation.RequiresPermissions) || "+ "@annotation(com.mugu.blog.common.annotation.RequiresRoles)";/*** 声明AOP签名*/@Pointcut(POINTCUT_SIGN)public void pointcut() {}/*** 环绕切入** @param joinPoint 切面对象* @return 底层方法执行后的返回值* @throws Throwable 底层方法抛出的异常*/@Around("pointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 注解鉴权MethodSignature signature = (MethodSignature) joinPoint.getSignature();checkMethodAnnotation(signature.getMethod());try {// 执行原有逻辑Object obj = joinPoint.proceed();return obj;} catch (Throwable e) {throw e;}}/*** 对一个Method对象进行注解检查*/public void checkMethodAnnotation(Method method) {// 校验 @RequiresLogin 注解RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);if (requiresLogin != null) {doCheckLogin();}// 校验 @RequiresRoles 注解RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);if (requiresRoles != null) {doCheckRole(requiresRoles);}// 校验 @RequiresPermissions 注解RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);if (requiresPermissions != null) {doCheckPermissions(requiresPermissions);}}/*** 校验有无登录*/private void doCheckLogin() {LoginVal loginVal = SecurityContextHolder.get();if (Objects.isNull(loginVal))throw new ServiceException(ResultCode.INVALID_TOKEN.getCode(), ResultCode.INVALID_TOKEN.getMsg());}/*** 校验有无对应的角色*/private void doCheckRole(RequiresRoles requiresRoles){String[] roles = requiresRoles.value();LoginVal loginVal = OauthUtils.getCurrentUser();//该登录用户对应的角色String[] authorities = loginVal.getAuthorities();boolean match=false;//and 逻辑if (requiresRoles.logical()==Logical.AND){match = Arrays.stream(authorities).filter(StrUtil::isNotBlank).allMatch(item -> CollectionUtil.contains(Arrays.asList(roles), item));}else{//OR 逻辑match = Arrays.stream(authorities).filter(StrUtil::isNotBlank).anyMatch(item -> CollectionUtil.contains(Arrays.asList(roles), item));}if (!match)throw new ServiceException(ResultCode.NO_PERMISSION.getCode(), ResultCode.NO_PERMISSION.getMsg());}/*** TODO 自己实现,由于并未集成前端的菜单权限,根据业务需求自己实现*/private void doCheckPermissions(RequiresPermissions requiresPermissions){}}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 中年人再就业,这三个职业“没面子”,却“有钱途”。
- 饵料|爆护的钓友普遍有五大特征,满足其中三个以上,你也可以
- 张凯|女演员高空坠落死亡后续!曝家中有三个未成年儿女,经纪公司被扒
- |塔罗测:未来三个月能找到心仪工作嘛?
- 成龙|康天庥澄清退出剧组的传言,他直言只要收到通知都会继续拍下去
- 主持人|看67岁白发奶奶才明白:只要会打扮,满头白发又怎样?照样有魅力
- 张碧晨|精明的张碧晨:不要名分只要孩子,看似一无所有,但已经赢了一切
- 求职|面试官贬低求职者,却通知他被录用?深刻的三个原因
- APT 如何运用JAVA注解处理器
- 高校|春招即将截止,银行还有最后一波招聘,满足三个条件都能去