Shiro
1.背景介绍
Shiro是什么?
Apache Shiro 是一个开源安全框架,提供身份验证、授权、密码和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。
与它类似的框架还有springSecurity
Shiro的产生背景:Shiro的前身是JSecurity。2004年,Les Hazlewood和Jeremy Haile创办了Jsecurity。当时他们找不到适用于应用程序级别的合适Java安全框架,同时又对JAAS非常失望。2010年9月22日,Shrio成为Apache软件基金会的*项目(TLP)。
Java认证和授权服务(Java Authentication and Authorization Service,简称JAAS)是一个Java以用户为中心的安全框架,作为Java以代码为中心的安全的补充。自Java运行环境(JRE) 1.4起,JAAS就被集成到JRE,而之前是作为一个扩展库由Sun公司提供的。
2.知识剖析
Shiro的几个术语
Subject
SecurityManager
Relam
Role&Permission
常见过滤器
Subject
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager
安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Relam
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
Role&Permission
角色与权限:一个角色可以对应多个权限。Shiro验证有两种方式。
常见过滤器
anon 、authc、logout、roles
anon:
这个过滤器允许进入一个路径,不需要有任何安全的检测。
常用于排除策略,比如你定义了一个url,访问这个url需要一定的权限,但是这个url的子级是运行任何的访问的。
举个例子,如果你有一个用户专区的url站点,你要求每个访问这个专区url的用户必须是验证过的,
[urls]
/user/** = authc
但是你想让/user/signup/**对任何人开放,你想要排除这个路径但是它是上面路径的子集,
这时,AnonymousFilter过滤器(anon)就排上用场了,
[urls]
/user/signup/** = anon
/user/** = authc
因为url的匹配遵循第一匹配胜出原则,所以过滤器anon 会匹配/user/signup/**而不会执行后面的匹配原则(/user/**)。
authc :
要求请求的用户必须是authenticated 这个请求才能继续,否则跳转到你设置的loginUrl来强制用户登录。
logout:
简单的过滤器,在收到请求后,将立即注销当前执行的主体,然后将其重定向到已配置的redirectUrl。
perms:
如果当前用户具有映射值指定的权限,则允许访问;如果用户没有指定的所有权限,则拒绝访问。
3.常见问题
①如何使用Shiro?
②权限注解不生效?
4.解决方案
① 代码
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
铁蛋 = 654321, 游击队员
二蛋 = 123456,初级赛车手
三蛋 = 123456,中级赛车手
没蛋 = 123456,高级赛车手
# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
游击队员 = 枪:突突突,手榴弹
初级赛车手 = 法拉利跑车
中级赛车手 = 法拉利跑车:开
高级赛车手 = 法拉利跑车:开:时速200
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("拿到了正确的值! [" + value + "]");
}
// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("铁蛋", "654321");
token.setRememberMe(false);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("未知用户: " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("用户名: " + token.getPrincipal() + " 的密码错误");
} catch (LockedAccountException lae) {
log.info("用户名: " + token.getPrincipal() + " 对应的账户被锁, " +
"请联系管理员解锁.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
if (currentUser.hasRole("游击队员")) {
log.info("光荣的游击队战士");
} else {
log.info("该用户不具有次角色");
}
//test a role:
if (currentUser.hasRole("admin")) {
log.info("管理员大人好");
} else {
log.info("该用户不具有此角色");
}
//test a typed permission (not instance-level)
if (currentUser.isPermitted("枪:突突突")) {
log.info(" 子弹已上膛,请瞄准你的敌人.");
} else {
log.info("Sorry, 非战斗人员不能用枪.");
}
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("手榴弹")) {
log.info(" 扔过去,给他个惊喜!");
} else {
log.info("非战斗人员不要玩手榴弹!");
}
/* 赛车手 */
//test a typed permission (not instance-level)
if (currentUser.isPermitted("法拉利跑车:开:时速200")) {
log.info(" 冲冲冲.");
} else {
log.info("非高级专业人士请慢点开车.");
}
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("手榴弹")) {
log.info(" 扔过去,给他个惊喜!");
} else {
log.info("非战斗人员不要玩手榴弹!");
}
//all done - log out!
currentUser.logout();
System.exit(0);
}
}
② 配置开启注解
5.编码实战
6.扩展思考
Remembered vs. Authenticated
正如上面的例子展示的,shiro除了支持一般的登录过程,还支持"remember me"的概念。shiro对两者做出了精确的区分:
-
Remembered: 一个remembered 的用户是一个非匿名的,有一个已知的身份( 比如:subject.getPrincipals() 是非空的). 但是这个身份是来自于上次回话的验证(authentication). 在 subject.isRemembered() 返回 true 是,这个subject被认为是remembered.
-
Authenticated: 一个Authenticated的用户是指一个用户在当前回话中成功的验证,(比如: login方法顺利执行没有抛出一个异常)。如果subject.isAuthenticated() 返回 true,那个这个用户被认为是authenticated。
Remembered 和 authenticated 两者状态是互斥的,一方的true往往意味着另一方的false。
为什么不同?
单词authentication有一个关于"校验、证明"的强烈内涵,就是说关于用户已经证明他是他自己,有一个明确的担保。
当用户仅因为之前和应用交互过而获得remembered的状态时, 他是他自己的证据已经不存在了:remembered的身份
只是告诉系统这个用户可能是谁,但是事实上,你没有百分百绝对的担保能确定这个用户就是我们期望的那个用户(没准是伪造的)。
而一旦这个用户校验成功,他将不再被系统任务是remembered因为他的身份已经在当前回话中成功验证过了。
所以尽管应用中的很多部分基于remembered的信息,还是在执行这个用户的一些特定业务逻辑,比如给这个用户展示定制化的视图(比如淘宝的定制化首页), 但是在你成功的通过authentication验证你的合法身份之前,绝不会执行高度敏感信息的操作。
比如,检查一个用户是否可以访问财务信息大多依赖于 isAuthenticated(),而不是 isRemembered(),来保证这个用户是我们期望的那个。
举个例子:
比如你在上亚马逊,用成功的登录了进去然后把一些书放到了你的购物车,但是你此时需要离开去开个会,但是忘了log out
等到会开完了你要回家了于是离开了办公室
第二天,你回来了继续工作,突然想起你还没完成昨天的购买,于是你又到了亚马逊.com,这次,亚马逊"记着"你是谁,首页上有
你的名字作为问候,并且仍然给你推荐个性化的书(猜你喜欢),对于此时的亚马逊,此时的 subject.isRemembered() 会返回true.
但是,如果你尝试进入你的账户系统更新你的信用卡信息来买书时会发生什么呢? 当亚马逊"记着你"时 (isRemembered() == true) ,
它并不能保证你确实是你 (比如也可能是你的同事在用你的电脑), 所以当你要执行更新银行卡信息这些敏感操作时,亚马逊会强制你去登录,来证明你的身份。等你登录成功后,你的信息才被证实,此时对于亚马逊来说, isAuthenticated()会返回true。
7.参考文献
http://shiro.apache.org/
8.更多讨论
1.Shiro的整个架构是怎么样的?
2.Shiro的rememberMe适用场景?
在数据访问要求不严格的场景,比如微博、论坛之类的,用户不用等了即可访问。
3. 什么是RABC?
基于角色的权限访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注。在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。在一个组织中,角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据需要而从某角色中回收。角色与角色的关系可以建立起来以囊括更广泛的客观情况。
感谢观看,如有出错,恳请指正