Shiro
三个核心组件:Subject, SecurityManager 和 Realms. Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。 Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。 SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。 Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。 从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。 Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
也就是说对于我们而言,最简单的一个 Shiro 应用: 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager; 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”; SecurityManager : 相 当 于 SpringMVC 中 的 DispatcherServlet 或 者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。 Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了; Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能; Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm; SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器); SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO中可以使用 Cache 进行缓存,以提高性能; CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能 Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密
shiro认证功能(Authentication)流程
@Test public void testHelloworld() { //1、获取 SecurityManager 工厂,此处使用 Ini 配置文件初始化 SecurityManager Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2、得到 SecurityManager 实例 并绑定给 SecurityUtils org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3、得到 Subject 及创建用户名/密码身份验证 Token(即用户身份/凭证) Subject subject = SecurityUtils.getSubject(); // UsernamePasswordToken 继承 UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123"); try { //4、登录,即身份验证 subject.login(token); } catch (AuthenticationException e) { //5、身份验证失败 } Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录 //6、退出 subject.logout();
shiro授权实现的方式以及流程
-
基于角色的访问控制(隐式角色)
-
基于资源的访问控制(显示角色) 实现方式:
// 1. 编程式(单个和多个角色) subject.hasRole(“admin”), Array.aslist("admin1","admin2"); //2. 注解 @RequiresRoles("admin") //3. 页面控制 <shiro:hasRole name="admin"> //4. shiro配置文件中配置 <property name="filterChainDefinitions"> <value> /commons/** = anon /plugins/** = anon /assets/** = anon /css/** = anon /js/** = anon /img/** = anon /fonts/** = anon /bootstrap/** = anon /login = anon /interface/** = anon /** = user </value> </property>
shiro 拦截器规则:
/** * 自定义Realm * (1)AuthenticatingRealm:shiro中的用于进行认证的领域,实现doGetAuthentcationInfo方法实现用户登录时的认证逻辑; * (2)AuthorizingRealm:shiro中用于授权的领域,实现doGetAuthrozitionInfo方法实现用户的授权逻辑,AuthorizingRealm继承了AuthenticatingRealm, * 所以在实际使用中主要用到的就是这个AuthenticatingRealm类; * (3)AuthenticatingRealm、AuthorizingRealm这两个类都是shiro中提供了一些线程的realm接口 * (4)在与spring整合项目中,shiro的SecurityManager会自动调用这两个方法,从而实现认证和授权,可以结合shiro的CacheManager将认证和授权信息保存在缓存中, * 这样可以提高系统的处理效率。 * */ public class UserRealm extends AuthorizingRealm{ @Autowired private UserService userSerivce; /** * 执行认证逻辑 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException { System.out.println("执行认证逻辑"); //编写shiro判断逻辑,判断用户名和密码 //1.判断用户名 token中的用户信息是登录时候传进来的 UsernamePasswordToken token = (UsernamePasswordToken)arg0; User user = userSerivce.findByName(token.getUsername()); if(user==null){ //用户名不存在 return null;//shiro底层会抛出UnKnowAccountException } //2.判断密码 //第二个字段是user.getPassword(),注意这里是指从数据库中获取的password。第三个字段是realm,即当前realm的名称。 //这块对比逻辑是先对比username,但是username肯定是相等的,所以真正对比的是password。 //从这里传入的password(这里是从数据库获取的)和token(filter中登录时生成的)中的password做对比,如果相同就允许登录, // 不相同就抛出IncorrectCredentialsException异常。 //如果认证不通过,就不会执行下面的授权方法了 return new SimpleAuthenticationInfo(user,user.getPassword(),""); } /** * 执行授权逻辑 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { //doGetAuthorizationInfo方法可能会执行多次,权限判断次数多少,就会执行多少次 System.out.println("执行授权逻辑1"); System.out.println("执行授权逻辑2"); //给资源进行授权 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //添加资源的授权字符串 //info.addStringPermission("user:add"); //到数据库查询当前登录用户的授权字符串 //获取当前登录用户 Subject subject = SecurityUtils.getSubject(); User user = (User)subject.getPrincipal(); User dbUser = userSerivce.findById(user.getId()); info.addStringPermission(dbUser.getPerms()); return info; } }