有偿问答
面经分享
技术探讨
资料领取
登录
极简入门,Shiro的认证与授权流程解析
社长
1年前
⋅ 460 阅读
#### 小Hub领读: 接下来的几天,我们开讲Shiro,从入门到分析、集成、单点登录整合等几篇。今天我们先来认识一下Shiro吧~ --------- 其实Shiro框架并不难,我梳理了一下,你只需要学会以下内容基本就足够了: * 登陆、授权流程 * shiro过滤器链 * 整合Springboot、redis做共享会话 * 结合xxl-sso实现单点登录 接下来我会分为几篇文章分别去介绍,这篇我们先来了解一下shiro的一些基础知识,以及登录授权逻辑。 ### Shiro简介 在Web系统中我们经常要涉及到权限问题,例如不同角色的人登录系统,他操作的功能、按钮、菜单是各不相同的,这就是所谓的权限。 而构建一个互联网应用,权限校验管理是很重要的安全措施,这其中主要包含: * 用户认证 - 用户身份识别,即登录 * 用户授权 - 访问控制 * 密码加密 - 加密敏感数据防止被偷窥 * 会话管理 - 与用户相关的时间敏感的状态信息 Shiro对以上功能都进行了很好的支持,它可以非常容易的开发出足够好的应用。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。而且Shiro的API也是非常简单。 官方源码:[https://github.com/apache/shiro](https://github.com/apache/shiro) ### 整体结构与重要组件 ![图片](//image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/20200505/ec6badf94ec34b14bd260d2d6626cc34.png) 从上图可以看出,Security Manager是Shiro的核心管理器,认证授权会话缓存等都是在其内部完成,然后会委托给具体的组件来处理,比如认证过程委托给Authenticator,授权委托给Authorizer组件。所以,整理还是比较清晰,源代码也容易追踪。 我们来具体聊聊所有的组件: **Subject:** 主体,可以看到主体可以是任何可以与应用交互的“用户”; **SecurityManager:** Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;负责所有Subject、且负责进行认证和授权、及会话、缓存的管理。 * Authenticator:认证器,判断用户是否正常登陆 * Authorizer:授权器,判断用户是否有权限操作资源 **Realm:** 可以有1个或多个Realm,主要提供认证和授权的数据; **Session:** Shiro提供一个权限的企业级Session解决方案,session的生命周期都在SessionManager中进行管理。 **SessionManager:** shiro的会话管理器; **SessionDAO:** 用于会话的CRUD,比如存储到ehcache或者redis中的会话增删改查; **CacheManager:** 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能 **Cryptography:** 密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。 ### 官方简单示例 官网例子:[http://shiro.apache.org/tutorial.html](http://shiro.apache.org/tutorial.html) 刚入门Shiro的同学,真的需要去看看这个官方例子,你可以更加深入了解Shiro的权限校验流程。我还是贴一下代码吧,一些同学比较懒: * shiro.ini ``` # ----------------------------------------------------------------------------- # Users and their (optional) assigned roles # username = password, role1, role2, ..., roleN # ----------------------------------------------------------------------------- [users] root = secret, admin guest = guest, guest presidentskroob = 12345, president darkhelmet = ludicrousspeed, darklord, schwartz lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # roleName = perm1, perm2, ..., permN # ----------------------------------------------------------------------------- [roles] admin = * schwartz = lightsaber:* goodguy = winnebago:drive:eagle5 ``` 上面代码中,`root = secret, admin`表示,用户名root,密码secret,角色是admin;`schwartz = lightsaber:*`表示角色schwartz拥有权限lightsaber:*。你其实可以把这个文件看成一个Realm,其实就是shiro默认的IniRealm。 * 测试类Tutorial ``` public class Tutorial { private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class); public static void main(String[] args) { log.info("My First Apache Shiro Application"); Factory
factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // get the currently executing user: 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("Retrieved the correct value! [" + value + "]"); } // let's login the current user so we can check against roles and permissions: if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); try { currentUser.login(token); } catch (UnknownAccountException uae) { log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } // ... 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("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); } //test a typed permission (not instance-level) if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } //a (very powerful) Instance Level permission: if (currentUser.isPermitted("winnebago:drive:eagle5")) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } //all done - log out! currentUser.logout(); System.exit(0); } } ``` 从上面的实例中,我们可以总结一下常用的API: #### 常用API ``` #获取当前用户 Subject currentUser = SecurityUtils.getSubject(); #判断用户已经认证 currentUser.isAuthenticated() #用户登录凭证 UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); #记住我 token.setRememberMe(true); #登陆校验 currentUser.login(token); #判断是否有角色权限 currentUser.hasRole("schwartz") #判断是否有资源操作权限 currentUser.isPermitted("lightsaber:wield") #登出 currentUser.logout(); ``` 其实稍微梳理一下,可以发现上面代码主要有两个步骤: * 认证: ``` UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); currentUser.login(token); currentUser.logout(); ``` * 判断权限 ``` currentUser.hasRole("schwartz") currentUser.isPermitted("winnebago:drive:eagle5") ``` 接下来,我们去探讨一下shiro的认证与授权流程,并从源码层去解析一下shiro各个组件之间的关系。 ### 认证流程 ![图片](//image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/20200505/5c8538540a534bdabef4871cf2e69dbe.png) 上面图片中,根据序号,其实我们大概能猜出里shiro的认证流程: 1. Subject进行login操作,参数是封装了用户信息的token 2. Security Manager进行登录操作 3. Security Manager委托给Authenticator进行认证逻辑处理 4. 调用AuthenticationStrategy进行多Realm身份验证 5. 调用对应Realm进行登录校验,认证成功则返回用户属性,失败则抛出对应异常 我们从login方法开始debug一下流程,用简要方式追踪shiro源码的认证逻辑: ``` currentUser.login(token); | Subject subject = this.securityManager.login(this, token); | AuthenticationInfo info = this.authenticate(token); | this.authenticator.authenticate(token); | AuthenticationInfo info = this.doAuthenticate(token); | Collection
realms = this.getRealms(); doSingleRealmAuthentication(realm, token); | AuthenticationInfo info = realm.getAuthenticationInfo(token); | AuthenticationInfo info = realm.doGetAuthenticationInfo(token); ``` ok,一条线下来,从login到委托给authenticator,再最后调用realm的doGetAuthenticationInfo方法。 所以,从源码上来看,如果要实现shiro的认证逻辑,至少要准备一个Realm组件、和初始化securityManager组件。 #### 常见异常 * DisabledAccountException(禁用的帐号) * LockedAccountException(锁定的帐号) * UnknownAccountException(错误的帐号) * ExcessiveAttemptsException(登录失败次数过多) * IncorrectCredentialsException (错误的凭证) * ExpiredCredentialsException(过期的凭证) ### 授权流程 ![图片](//image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/20200505/e40433714ea0464aa14946ad5171b05c.png) 从上图中,我们可以知道授权流程如下: * 调用Subject.isPermitted*/hasRole*接口 * 委托给SecurityManager * 而SecurityManager接着会委托给Authorizer * Authorizer会判断Realm的角色/权限是否和传入的匹配 * 匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败 追踪一下源码如下: ``` currentUser.hasRole("schwartz") | this.securityManager.hasRole(this.getPrincipals(), roleIdentifier) | this.authorizer.hasRole(principals, roleIdentifier) | AuthorizationInfo info = this.getAuthorizationInfo(principal); return info.getRoles().contains(roleIdentifier) | info = this.doGetAuthorizationInfo(principals);(realm) ``` 所以shiro判断用户是否有权限首先会从realm中获取用户所拥有的权限角色信息,然后再匹配当前的角色或权限是否包含,从而判定用户是否有权限! 说到权限,很多人自然会想起权限系统,涉及到几个关键对象: * 主体(Subject) * 资源(Resource) * 权限(Permission) * 角色(Role) 通过这几个要素,可以设计出比较合理的权限系统。 #### Shiro常见3种授权判断方式: * 编码实现 ``` Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“admin”)) { //有权限 } else { //无权限 } ``` * 注解实现 ``` @RequiresRoles("admin") public void hello() { //有权限 } ``` * JSP Taglig实现,freemarker等类似 ```
``` jsp页面引入shiro标签 ``` <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%> ``` ### 在线会话管理 #### 获取当前会话总人数 ``` @Autowired private SessionDAO sessionDAO; //获取会话数量 int size = sessionDAO.getActiveSessions().size() ``` #### 强制下线 ``` //强制退出 Session session = sessionDAO.readSession(subject.getSession().getId()); sessionDAO.delete(session); // logout,可作为强制退出 subject.logout(); Assert.isTrue(!subject.isAuthenticated()); ``` ### 结束语 ok,感觉是高度极简的一篇文章,主要把重要的组件和登录、授权几个流程搞清楚之后,其实shiro基本已经学会了,后面我们再学一下shiro的几个主要内置过滤器怎么使用,如何集成SpringBoot,基本就差不多了。 --------- 推荐阅读: [分享一套SpringBoot开发博客系统源码,以及完整开发文档!速度保存!](https://mp.weixin.qq.com/s/jGEkHTf2X8l-wUenc-PpEw) [Github上最值得学习的100个Java开源项目,涵盖各种技术栈!](https://mp.weixin.qq.com/s/jGEkHTf2X8l-wUenc-PpEw) [2020年最新的常问企业面试题大全以及答案](https://mp.weixin.qq.com/s/jGEkHTf2X8l-wUenc-PpEw)
阅读全部
全部评论:
0
条
我有话说:
@
发送
-- 目录 --
关注官方公众号:
Java问答社
接收最新有赏问答推送!
最新发布
1.
SpringBoot 接口数据加解密技巧,so easy!
2.
一个依赖搞定 Spring Boot 反爬虫,防止接口盗刷!
3.
Java8 Stream 极大简化了代码,它是如何实现的?
4.
马上大四了,秋招还是春招好?先找工作还是找实习?
5.
万字详解 Linux 常用指令(值得收藏)
6.
4年工作经验,多线程间的5种通信方式都说不出来,你敢信?
最新评论
部署文档没有了,您能提供下吗
部署文档没有了,能提供下吗
我测你的🐎
源码从哪里获取请问
想学
那篇石墨文档 没有权限查看哇