上一篇说了认证,通过令牌可以知道当前用户是谁,并把令牌信息从网关到微服务,以及微服务与微服务之间传递用户上下文的信息,这一篇来聊一下授权。
一、最简单的情况ACL权限控制
用户有哪些权限直接在scope里写着,只要在程序里判断一下要访问某个方法,是否有访问权限就可以了这种适用于权限简单的场景。
使用 @PreAuthorize("") 注解标记在Controller方法,可以控制,哪些请求有权限访问该服务。注意要想是该注解生效需要在启动类上加上注解: @EnableGlobalMethodSecurity(prePostEnabled = true) 使其生效。
value值有两种表达式:
1,@PreAuthorize("#oauth2.hasScope('fly')") 表示,scope包含“fly”的令牌,才可以访问该服务。这个针对客户端应用的,不能具体到某一个人。
2,@PreAuthorize("hasRole('ROLE_USER')") 具体到每一个人,这个人包含哪些角色,就能访问,这个角色在认证服务器的 UserDetailsService 类的 loadUserByUsername 方法里获取。
实验@PreAuthorize("#oauth2.hasScope('fly')") :
用orderService 客户端通过网关 ,获取token
这个token的scope只包含 read,write ,但是创建订单服务需要token的scope包含fly才能访问,用这个token通过网关访问创建订单服务:
将创建订单服务的权限控制表达式换成 @PreAuthorize("#oauth2.hasScope('write')") 就可以正常访问。
实验@PreAuthorize("#hasRole('ROLE_USER')") :
通过网关获取令牌
创建订单服务,有ROLE_USER角色,才能访问
认证服务器的UserDetailsService ,写死的权限,只有ROLE_ADMIN
通过网关拿token 创建订单
权限控制注解换为 @PreAuthorize("hasRole('ROLE_ADMIN')") ,则可正常调用创建订单服务。
使用@PreAuthorize 注解处理简单角色很方便,但是没办法处理复杂的场景。如果权限总是变化的,这个就不适合了,因为权限控制是硬编码在Controller了,每次修改权限信息,还得重启服务。
二、在网关上做复杂的权限控制
假设已经有了一个权限系统,怎么跟网关接起来?
1,在 GatewaySecurityConfig 配置类上,需要做如下的修改
a) 指定权限访问规则
在 GatewaySecurityConfig.configure(HttpSecurity http) 方法里,配置 http.access("#permissionService.hasPermission(request,authorization)") ,指定权限访问规则,permissionService需要自己实现,返回布尔值,true-能访问;false-无权限,传进去2个参数,参数1-当前请求 ,参数2-当前用户。
b) 新建 PermissionService 接口以及实现类 ,实现自己的权限控制逻辑。
c) 新建表达式处理 GatewayWebSecurityExpressionHandler
只指定这个权限访问规则http.access("#permissionService.hasPermission(request,authorization)")是没用的,因为Spring不认识,所以还得新建一个类:GatewayWebSecurityExpressionHandler (表达式处理器),用来往权限表达式处理器里设置变量,变量名就是 permissionService,变量值是
自定义的权限处理类 PermissionService
d)指定表达式处理器
GatewaySecurityConfig.configure(ResourceServerSecurityConfigurer resources)里 指定 resources.expressionHandler(gatewayWebSecurityExpressionHandler)表达式处理器
通过网关申请令牌,通过网关访问创建订单服务,有一半的可能性访问失败,一半可能性成功。
到目前为止的项目框架结构是这样的:
应该是有一个权限服务的,权限服务也是一个微服务,认证服务+权限服务=安全中心。
目前的权限都在网关控制的,这样可能存在越权:
比如你有权限访问订单服务,没权限访问库存服务,但是订单服务又调用了库存服务,这样就越权了。
怎么解决这个越权呢:
a) 可以在每个微服务上,都调用redis检查权限,做法和上边演示的在网关上的做法是一样的,但是不建议这么做,所有微服务都依赖redis是大量的耦合。
b) 95%的细粒度的权限都在网关上做,微服务之期间互相调用的权限,只做一个粗粒度的黑白名单的控制,比如你一个结算服务,有一个白名单,只允许订单服务来调用,其他人调不了。(后面介绍sentinel怎么方便的进行黑白名单的控制)。微服务之间的调用不用像网关那么做细粒度的控制,(这个服务你能不能调,那个服务你能不能调),但是要有一个黑白名单的控制就可以了,控制住那些微服务之间能调用就可以了。
代码 :https://github.com/lhy1234/springcloud-security/tree/chapt-6-3-permission 如果帮到了你给个小星星吧