Springcloud和shiro的⼀种权限实现⽅案
探讨下⼏种SpringCloud权限实现⽅案,以zuul和shiro的思路做了实现,简写了搭建过程.
github地址
⼀.⽬标
1.外部请求统⼀从⽹关zuul进⼊,并且服务内部互相调⽤接⼝要校验权限
2.cloud和shiro结合,达到单点登录,和集中⼀个服务完成权限管理,其他业务服务不需要关注权限如何实现
3.其他服务依然可以控制权限细粒度到接⼝,如在接⼝上使⽤@RequirePermisson等注解,⽅便开发
⼆.思路
SpirngCloud zuul⽹关有两个作⽤,⼀个是分配路由,⼀个是过滤。zuul的过滤器作⽤有限,只能简单的做⼀些某个url是否能够访问之类的,⽆法像shiro⼀样细粒度到某个⽤户是否有某种权限;
shiro单体应⽤⼤家都会做,那变成微服务后,难道每个服务都要写⼀套shiro框架?这显然也太⿇烦。这么⼏个思路:
1.在zuul服务⾥⽤shiro,做成动态url权限控制,就是把访问哪个url需要⽤什么权限,写⼊数据库,在过滤器读取与⽤户有的权限作对⽐;但是服务互相调⽤校验就⾏不通了,因为服务间调⽤不通过zuul
2.写⼀个服务专⽤于shiro认证和授权,包含⽤户、权限的curd,暴露出查询⼀个⽤户拥有什么权限的接⼝;在其他服务中,都写⼀个拿访问者token去授权服务拿此⽤户的权限,再跟请求的url对⽐;或者可以⾃定义注解⽤aop,注解标注的是访问此url需要什么权限,远程调⽤授权服务接⼝查询当前⽤户所有权限,与请求的url对⽐。
但是这个要⾃⼰实现。
3.第⼆种思路的简单版本。
server服务:专⽤于shiro认证和授权,包含⽤户、权限的curd,暴露出查询⼀个⽤户拥有什么权限的接⼝;
client项⽬:打成jar包供其他服务依赖,⽤shiro,client不同于server服务的是:
1.在realm中只有授权⽅法,没有认证⽅法,因为不需要client认证、需要server认证。将登陆地址配置为server服务的地址。这样未
登录的⽤户都会跳转到server服务登录,想办法保存下原路径,登录成功后再返回原服务.
springboot aop并且client的realm授权⽅法是调⽤server服务接⼝查询权限,再返回给client项⽬的安全管理器。同时做成session共享
其他业务服务:只需要依赖于client。
这种思路来⾃于《跟我学shiro》的多项⽬集中权限,其实想想这种思路是可以的,shiro本质也是靠进⾏权限校验,虽然相当于每个服务都开启了⼀套shiro,但也就是容器中多了⼀些shiro和实例,⽽且可以⽤shiro的各种功能,开发⽅便。可以完成我们的三个⽬标。
三.具体实现
因为shiro和cloud的细节太多,这⾥就不赘述,以下内容默认读者掌握shiro和springcloud基本组件。
我使⽤的是Finchley.BUILD-SNAPSHOT版本。
我们建两个服务Base和Notice,Base负责权限认证,⽤户访问Notice服务时先跳转到Base服务校验。
1.建⽴基本eureka,zuul,服务Base,服务Notice
将服务Base、Notice、zuul注册到eureka,能够通过zuul访问两个服务,不再赘述。
贴⼀下zuul的配置:
server:
port: 18900
spring:
application:
name: focus-zuul
datasource:
driverClassName: sql.jdbc.Driver
url: jdbc:mysql://localhost:3306/focus_cloud?useUnicode=true&characterEncoding=utf-8
username: root
password: pangtiemin
eureka:
instance:
prefer-ip-address: true
#metadata-map:
#zone: zone1 #此实例所处的zone
client:
#availability-zones: #可获得的region和其zone有哪些
#xian: zone1
#region: xian #此实例所处的 region
serviceUrl:
defaultZone: localhost:18800/eureka/
#zone1: localhost:18800/erueka
zuul:
#设置请求超时时间
connect-timeout-millis: 15000 #HTTP连接超时要⽐Hystrix的⼤
socket-timeout-millis: 60000 #socket超时
#SendErrorFilter:
# error:
# disable: true #禁⽤zuul默认的异常过滤器
errorControllerUrl: /error #⾃定义配置,异常处理接⼝
routes:
focus-base: # 通过服务名serviceId路由,不通过具体的url
path: /base/**
serviceId: focus-base
#默认敏感头是"Cookie", "Set-Cookie", "Authorization"这三项,取消这三项,向下游服务请求带上这些headers
#Access-Control-Allow-Origin,Access-Control-Allow-Methods 解决其他服务的js向zuul发起请求的跨域问题
sensitiveHeaders: Access-Control-Allow-Origin,Access-Control-Allow-Methods
focus-notice: # 通过服务名serviceId路由,不通过具体的url
path: /notice/**
serviceId: focus-notice
sensitiveHeaders: Access-Control-Allow-Origin,Access-Control-Allow-Methods
ribbon:
ReadTimeout: 120000
ConnectTimeout: 30000
2.在base服务搭建shiro
关于登陆、⽤户、⾓⾊、权限的接⼝都写在base服务中。要求能在base中单独认证成功。
使⽤redis做权限缓存,其他服务认证每次都访问base服务压⼒⽐较⼤,可以优先从缓存拿数据。如果你的shiro安全管理器和seesiondao同时实现了cache接⼝,直接将redis管理器注⼊安全管理器即可;或者可以⽤⽹上重写的redissessiondao,具体可看我这篇
⽤feign推荐的项⽬格式。maven⽗模块包含两个⼦模块。api模块写暴露出的接⼝,impl模块写具体的实现。
在base的接⼝中写⼀个通过⽤户id查询权限码的接⼝,⽤feign供其他服务调⽤。
@FeignClient(name = FocusMicroBaseConstants.SERVICE_APP_ID)
public interface BaseAuthorityRestService {
@GetMapping
public Message<List<BaseAuthorityVM>> getAuthorityByUser(String userId);
}
3.建⽴⼀个名为uaa项⽬
uaa项⽬依赖于base-api(要⽤base的接⼝)、open-feign(通过feign调⽤) 。此项⽬以后要打包,给notice之类的业务服务使⽤。
页⾯⾥的js css也要从zuul/服务名访问,否则会访问失败。
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会⾃动寻Web⼯程根⽬录下的"/login.jsp"页⾯
//访问的是后端url的地址,这⾥要写base 服务的公⽤登录接⼝。
shiroFilterFactoryBean.setLoginUrl("localhost:18900/base/loginpage");
// 登录成功后要跳转的链接;现在应该没⽤
//shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界⾯;可以写个公⽤的403页⾯
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/loginpage", "anon");
filterChainDefinitionMap.put("/swagger-ui.html#", "anon");
filterChainDefinitionMap.put("/base/test", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro⼯⼚类注⼊成功");
return shiroFilterFactoryBean;
}
uaa的realm不写doGetAuthenticationInfo认证逻辑,只写校验权限逻辑。并且校验逻辑获取此⽤户的权限码,是通过步骤2中base的接⼝获取的。
因为我的realm不是通过spring注⼊到 ShiroFilterFactoryBean 的,所以⽆法在realm中⽤@Autowired直接调⽤feign。但当spring启动成功后,注解FeignClient的base接⼝实例已经注⼊到了spring中,我这⾥从spring中⼿动获取BaseAuthorityRestService 实例再使⽤。
public class ShiroClientRealm extends AuthorizingRealm {
//这⾥没有直接注⼊实例,ShiroClientRealm被⽤在配置类中,直接注⼊报servercontext not set 的错。只能使⽤时从spring容器中拿
private BaseAuthorityRestService baseAuthorityRestService;
Boolean cachingEnabled=true;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
BaseUserVM user = (PrimaryPrincipal();
Superman()){//超管
simpleAuthorInfo.addStringPermission("administrator");
}else{
//这⾥没有直接注⼊实例,ShiroClientRealm被⽤在配置类中new出的,直接注⼊报servercontext not set 的错。只能使⽤时从spring容器中拿
baseAuthorityRestService = Bean(BaseAuthorityRestService.class);
Message<List<BaseAuthorityVM>> message = Id());
if(message!=null&&Data()!=null){
List<BaseAuthorityVM> authorityVMList = Data();
for (BaseAuthorityVM authority:authorityVMList) {
simpleAuthorInfo.AuthorityCode());
}
}
}
return simpleAuthorInfo;
}
/**
* 这个⽅法不会被调⽤,会到base服务校验是否登录
* @Author:Melo
* @Date: 2019/6/10 0010
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
return null;
}
}
4.改造uaa为springboot⾃动配置,notice依赖uaa
notice加uaa依赖,但这时启动notice服务,uaa中shiro并没有注⼊到spring中,很明显springboot只能扫描到启动类路径下的配置注解,jar包中的注解⽆法直接扫描到。这⾥我们通过加spring.factories的⽅式
⾃动配置具体可看我这篇
将notice依赖uaa、base-api。
@GetMapping("/notice1")
@RequiresPermissions("dd")
public String test1(){
return "noticetest";
}
6.zuul的cooike丢失问题
这时有权限的⽤户直接访问notice需要认证接⼝也是失败的,在base中debug发现cookie是空的,因为zuul把cookie过滤了。
需要设置zuul的敏感头。#默认敏感头是"Cookie", "Set-Cookie", "Authorization"这三项,取消这三项,向下游服务请求带上这些headers。sensitiveHeaders设置为空即可解决这个问题,我设置了其他值是⼀会要解决其他问题的。
zuul:
#设置请求超时时间
connect-timeout-millis: 15000 #HTTP连接超时要⽐Hystrix的⼤
socket-timeout-millis: 60000 #socket超时
#SendErrorFilter:
# error:
# disable: true #禁⽤zuul默认的异常过滤器
errorControllerUrl: /error #⾃定义配置,异常处理接⼝
routes:
focus-base: # 通过服务名serviceId路由,不通过具体的url
path: /base/**
serviceId: focus-base
#默认敏感头是"Cookie", "Set-Cookie", "Authorization"这三项,取消这三项,向下游服务请求带上这些headers
#Access-Control-Allow-Origin,Access-Control-Allow-Methods 解决其他服务的js向zuul发起请求的跨域问题
sensitiveHeaders: Access-Control-Allow-Origin,Access-Control-Allow-Methods
focus-notice: # 通过服务名serviceId路由,不通过具体的url
path: /notice/**
serviceId: focus-notice
sensitiveHeaders: Access-Control-Allow-Origin,Access-Control-Allow-Methods
7.zuul的跨域问题
我的js因为放在了base服务中,直接访问zuul接⼝,出现跨域问题:
多次请求的时候,会把这些header再带过来,然后请求zuul转发的接⼝⼜在写⼊⼀次,造成重复了,⽅案就是zuul转发的时候,过滤掉这些header,⽐如:
sensitiveHeaders: Access-Control-Allow-Origin,Access-Control-Allow-Methods
同时zuul添加配置
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论