Shiro的鉴权方式
一、 怎么用
Shiro 支持三种方式的授权
- 编程式:通过写 if/else 授权代码块完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
- 注解式:通过在执行的 Java 方法上放置相应的注解完成:
@RequiresRoles("admin")
public void hello() {
//有权限
}
- JSP/GSP 标签:在 JSP/GSP 页面通过相应的标签完成
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
二、啥原理
三种方式背后的原理是一样的,由简单到复杂的顺序来看看。
1、标签式
shiro提供了RoleTag和PermissionTag的抽象类,还有两个简单的实现:
public class HasRoleTag extends RoleTag {
//TODO - complete JavaDoc
public HasRoleTag() {
}
protected boolean showTagBody(String roleName) {
return getSubject() != null && getSubject().hasRole(roleName);
}
}
public class HasPermissionTag extends PermissionTag {
//TODO - complete JavaDoc
public HasPermissionTag() {
}
protected boolean showTagBody(String p) {
return isPermitted(p);
}
}
分别调用的是DelegatingSubject的hasRole方法和isPermitted方法:
public boolean hasRole(String roleIdentifier) {
return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
}
public boolean isPermitted(String permission) {
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
然后又调用AuthorizingSecurityManager的相关方法,AuthorizingSecurityManager持有一个ModularRealmAuthorizer类型的Authorizer:
this.authorizer = new ModularRealmAuthorizer();
相关方法又转移至调用ModularRealmAuthorizer:
ModularRealmAuthorizer 进行多 Realm 匹配流程:
1、首先检查相应的 Realm 是否实现了实现了 Authorizer;
2、如果实现了 Authorizer,那么接着调用其相应的 isPermitted*/hasRole*接口进行匹配;
3、如果有一个 Realm 匹配那么将返回 true,否则返回 false。
2、注解式
@RequiresRoles("admin")
@RequiresPermissions("admin:role:view")
@RequestMapping(value = "/configIndex", method = { RequestMethod.GET })
public String index(Model model) {
return "rolemgr/roleConfig/configIndex";
}
技术细节
基于拦截器实现(AuthorizingAnnotationMethodInterceptor)
动态代理技术(CglibAopProxy)
spring InvocableHandlerMethod#invoke
CglibAopProxy.DynamicAdvisedInterceptor#intercept
AnnotationMethodInterceptor
AuthorizingAnnotationMethodInterceptor#assertAuthorized
RoleAnnotationHandler#assertAuthorized
委托调用
DelegatingSubject:
public void checkRole(String role) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkRole(getPrincipals(), role);
}
AuthorizingSecurityManager:
public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
this.authorizer.checkRole(principals, role);
}
ModularRealmAuthorizer:
public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
assertRealmsConfigured();
if (!hasRole(principals, role)) {
throw new UnauthorizedException("Subject does not have role [" + role + "]");
}
}
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).hasRole(principals, roleIdentifier)) {
return true;
}
}
return false;
}
AuthorizingRealm:
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
AuthorizationInfo info = getAuthorizationInfo(principal);
return hasRole(roleIdentifier, info);
}
protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) {
return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);
}
关于 getAuthorizationInfo 方法:
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;
}
AuthorizationInfo info = null;
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
}
if (info == null) {
// 如果cache中没有找到info则调用模板方法
info = doGetAuthorizationInfo(principals);
// info不为null并且cache已经创建了,则缓存info信息
if (info != null && cache != null) {
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
- 首先还是找可用的权限缓存:
private Cache<Object, AuthorizationInfo> getAvailableAuthorizationCache() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache == null && isAuthorizationCachingEnabled()) {
cache = getAuthorizationCacheLazy();
}
return cache;
}
回顾下安全管理器的结构,RealmSecurityManager继承了CachingSecurityManager,当CachingSecurityManager设置了cacheManager,会调用用子类的afterCacheManagerSet方法:
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
afterCacheManagerSet();
}
RealmSecurityManager将会为每个Realm设置cacheManager:
protected void afterCacheManagerSet() {
applyCacheManagerToRealms();
}
protected void applyCacheManagerToRealms() {
CacheManager cacheManager = getCacheManager();
Collection<Realm> realms = getRealms();
if (cacheManager != null && realms != null && !realms.isEmpty()) {
for (Realm realm : realms) {
if (realm instanceof CacheManagerAware) {
((CacheManagerAware) realm).setCacheManager(cacheManager);
}
}
}
}
看下AuthorizingRealm类关系图:
在不同层级的构造器中分别设置了是否启用权限缓存和身份验证缓存
this.authorizationCachingEnabled = true;
this.authenticationCachingEnabled = false;
再看CachingRealm,跟安全管理器的做法类似,并且为每个realm设置cacheManager的时候就已经触发了子类的相关操作:
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
afterCacheManagerSet();
}
AuthorizingRealm的实现就是通过cacheManager去获取权限相关的cache:
protected void afterCacheManagerSet() {
super.afterCacheManagerSet();
//trigger obtaining the authorization cache if possible
getAvailableAuthorizationCache();
}
如果我们没有明确配置cacheManager(作为securityManager的属性注入),那么此时是获取不到的,cache为null并且启用了权限缓存,现在就要临时构造一个:
cache = getAuthorizationCacheLazy();
- 获取AuthorizationInfo
以principals作为key取AuthorizationInfo:
info = cache.get(key);
if (info == null) {
info = doGetAuthorizationInfo(principals);
if (info != null && cache != null) {
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
这个时候就转到我们自己的实现了,我们自己去获取权限,然后返回一个AuthorizationInfo,就是权限相关的信息。
3、总结
编程式很暴力也很直接,直接操作subject的相关方法来鉴权,其他两种方式拐弯抹角地也是操作的subject,然后再委托给securityManager。
具体是AuthorizingSecurityManager层实现的,它是直接new了一个ModularRealmAuthorizer,相关操作又转交给它,它又梳理一下,交给我们实现的realm(父类AuthorizingRealm层实现)。
ModularRealmAuthorizer是怎么获取到realm的?
也是我们给securityManager配置的,类似上面cacheManager的set方法,安全管理器用了很多这样的方法,给它本身注入相关属性时,就把相关联的set了。
AuthorizingSecurityManager这样实现的:
protected void afterRealmsSet() {
super.afterRealmsSet();
if (this.authorizer instanceof ModularRealmAuthorizer) {
((ModularRealmAuthorizer) this.authorizer).setRealms(getRealms());
}
}
从类的调用关系来看:
DelegatingSubject -> AuthorizingSecurityManager -> ModularRealmAuthorizer -> Realm
- 注解调用栈
RequiresRoles :checkRole -> checkRole -> checkRole -> hasRole
RequiresPermissions:checkPermission -> checkPermission -> checkPermission -> isPermitted
三、学到什
- Shiro作为框架是如何承上启下的,上接servlet规范,中搭spring顺风车,下给开发者自己实现。
- 实现的技巧
- 开发中有效的配置,哪些是必要的,哪些是默认的。
- 如何提升开发效率
例如在自己的XxRealm中:
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (MySubjectUtils.isPatformAdmin()) {
simpleAuthorizationInfo.addStringPermission("*");
} else {
//查询关联的权限adminUser:create:01001001
}
}
或者在自己的权限标签中:
public class HasAnyPermissionTag extends PermissionTag {
@Override
protected boolean showTagBody(String permissions) {
boolean hasPermission = false;
Subject subject = getSubject();
if(MySubjectUtils.isPatformAdmin()){
return true;
}
//......
}
}
这样作为开发者对于角色权限的配置可以省掉了,将特定id或者name的开发人员设置为平台管理员,可以坐拥天下,在功能不断完善的情况下不需要再补充权限。
Shiro的鉴权方式的更多相关文章
- web开发常见的鉴权方式
结合网上找的资料整理了一下,以下是web开发中常见的鉴权方法: 预备:一些基本的知识 RBAC(Role-Based Access Control)基于角色的权限访问控制(参考下面①的连接) l ...
- apigw鉴权分析(1-3)百度 AI - 鉴权方式分析
http://ai.baidu.com/docs#/Begin/top 一.访问入口 二.鉴权方式分析 1.鉴权认证方式一 - access_token - 针对HTTP API调用者 2.鉴权认证方 ...
- apigw鉴权分析(1-1)阿里数加 - 鉴权方式分析
一.访问方式 1.访问阿里云首页 https://www.aliyun.com/?utm_medium=text&utm_source=bdbrand&utm_campaign=bdb ...
- 【JMeter连接SQLServer】採用window鉴权方式连接(原创)
大家都知道Jmeter能够连接各种数据库.这方面我也不多说了,假设你还不知道怎么连接的话.能够參看我看的另外一篇博文.这边有具体的介绍 http://blog.csdn.net/lzqinfen/ar ...
- 一口气说出 OAuth2.0 的四种鉴权方式,面试官会高看一眼
本文收录在个人博客:www.chengxy-nds.top,技术资源共享,一起进步 上周我的自研开源项目开始破土动工了,<开源项目迈出第一步,10 选 1?页面模板成了第一个绊脚石 > , ...
- springcloud zuul shiro网关鉴权并向服务传递用户信息
1.pom文件 <dependencies> <!--eureka客户端--> <dependency> <groupId>org.springfram ...
- shiro jwt 构建无状态分布式鉴权体系
一:JWT 1.令牌构造 JWT(json web token)是可在网络上传输的用于声明某种主张的令牌(token),以JSON 对象为载体的轻量级开放标准(RFC 7519). 一个JWT令牌的定 ...
- .NET Core中的鉴权授权正确方式(.NET5)
一.简介 前后端分离的站点一般都会用jwt或IdentityServer4之类的生成token的方式进行登录鉴权.这里要说的是小项目没有做前后端分离的时站点登录授权的正确方式. 一.传统的授权方式 这 ...
- 搭建一个分布式MongoDB鉴权集群
今天休假在家,测试并搭建了一个replica set shard MongoDB鉴权集群.replica set shard 鉴权集群中文资料比较少,本文是个人笔记,同时也希望对后来者有所帮助.本文仅 ...
随机推荐
- 2017北京国庆刷题Day1 morning
期望得分:100+100+100=300 实际得分:100+100+70=270 T1位运算1(bit) Time Limit:1000ms Memory Limit:128MB 题目描述 LYK ...
- C11线程管理:互斥锁
1.概述 锁类型 c11提供了跨平台的线程同步手段,用来保护多线程同时访问的共享数据. std::mutex,最基本的 Mutex 类,独占的互斥量,不能递归使用. std::time_mutex,带 ...
- linux的MySQL设为开机启动
linux开启启动的程序一般放在/etc/rc.d/init.d/里面,/etc/init.d/是其软连接 mysql设为linux服务cp /usr/local/mysql5/share/mysql ...
- CSS基础复习
重新撸一遍CSS的基础,因为以前面试的时候被问到,突然发现某些概念搞不清楚,瞬间懵逼了,其实我都知道的,就是因为不会炒概念,导致面试官觉得我很low,你特么连这个都不知道还敢来面试,回家种田去好嘛! ...
- iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址(2014年2月19日更新版)
//转载请注明出处--本文永久链接:http://www.cnblogs.com/ChenYilong/p/3496069.html 编号 iOS-Apple苹果官方文档翻译名称 博文链接地址 ...
- laravel 重定向路由带参数
转载: http://www.cnblogs.com/foreversun/p/5642176.html 举例: 路由: //任务列表页 $router->get('/taskDetail/{i ...
- python学习笔记(十一)之序列
之前学习的列表,元组,字符串都是序列类型,有很多共同特点: 通过索引得到每一个元素,索引从0开始 通过分片的方法得到一个范围的元素的集合 很多通用的操作符(重复操作符,拼接操作符,成员关系操作符) 序 ...
- mysql数据库 批量替换 某字段中的数据
当数据库出现这种情况: 表名:area id name 1 太仓 2 太仓市 3 太仓市 ... ... 我需要把 name字段中 的太仓市 的“市“去掉 可以使用: update `area` ...
- React Native DEMO for Android
Demo1: 主要知识:navigator,fecth 地址:https://github.com/hongguangKim/ReactNativeDEMO1 Demo2: 主要知识:navigato ...
- Feather包实现数据框快速读写,你值得拥有
什么是Feather? Feature是一种文件格式,支持R语言和Python的交互式存储,速度更快.目前支持R语言的data.frame和Python pandas 的DataFrame. Feat ...