文章目录
shiro学习
简介
在以往的权限管理中,我们的权限管理通常是有以下几个步骤: 1.创建用户,分配权限。 2.用户登录,权限拦截器拦截请求,识别当前用户登录信息 3.从权限表中判断是否拥有权限
从以上步骤中可以提取到以下三个问题。 三个问题: > 1.如何让Shiro拦截请求。 > 在web开发中,Shiro会提供一个拦截器来对请求进行拦截。 > > 2.Shiro如何判断发起请求用户的身份? > 在web开发中,会借助session来判断,如果禁用了session,那么可能需要重写一些方法。 > > 3.如何判断权限? > Shiro使用realm来判断权限。
下面的也将以这三个问题为中心来描述Shiro。
功能
-
Authentication:身份认证/登录,验证用户是不是拥有相应的身份。
-
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限。
-
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的。
-
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
-
Web Support:Web支持,可以非常容易的集成到 web 环境。
-
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。
-
Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去。
-
Testing:提供测试支持。
-
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
-
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
(同类的比较知名的安全框架还有spring security,Shiro的优点是比较简洁,功能虽然比不上Spring Security多样,但对于安全需求不多的时候可以使用Shiro。)
运行原理
-
Application Code:代表着应用,应用使用Subject来标识自己的身份,以及使用Subject来进行认证和授权。
-
Subject:代表着“当前用户”。这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等。所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager。我们可以把 Subject 认为是一个门面,SecurityManager 才是实际的执行者。
包含 Principals 和 Credentials 两个信息。我们看下两者的具体含义。
Principals:代表身份。可以是用户名、邮件、手机号码等等,用来标识一个登录主体的身份。
Credentials:代表凭证。常见的有密码,数字证书等等。
说白了,两者代表了需要认证的内容,最常见的便是用户名、密码了。比如用户登录时,通过 Shiro 进行身份认证,其中就包括主体认证。
-
Shiro SecurityManager:应用使用Subject来进行认证和授权,实际上执行认证和授权的是SecurityManager。安全管理器。即所有与安全有关的操作都会与 SecurityManager 交互,且它管理着所有 Subject。可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,我们可以把它看成 DispatcherServlet 前端控制器。
-
Realm:SecurityManager的认证和授权需要使用Realm,Realm负责获取用户的权限和角色等信息,再返回给SecurityManager来进行判断。
-
Subject:主体,可以看到主体可以是任何与应用交互的“用户”。
-
SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher。它是 Shiro 的核心,所有具体的交互都通过 SecurityManager 进行控制。它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
-
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,我们可以自定义实现。其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了。
-
Authrizer:授权器,或者访问控制器。它用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能。
-
Realm:可以有1个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的。它可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等。
-
SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 需要有人去管理它的生命周期,这个组件就是 SessionManager。而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境。
-
SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD。我们可以自定义 SessionDAO 的实现,控制 session 存储的位置。如通过 JDBC 写到数据库或通过 jedis 写入 redis 中。另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能。
-
CacheManager:缓存管理器。它来管理如用户、角色、权限等的缓存的。因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能。
-
Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密的。
小demo
-
shiro.ini
# ----------------------------------------------------------------------------- # users用来定义用户 [users] # 用户名 = 密码,角色1,角色2... admin = secret, admin guest = guest, guest aa = 123456, guest # ----------------------------------------------------------------------------- # roles用来定义角色 [roles] # 角色 = 权限 (* 代表所有权限) admin = * # 角色 = 权限 (* 代表所有权限) guest = see aa = see
-
shiroDemo.java
package com.demo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroDemo { private static final Logger log = LoggerFactory.getLogger(ShiroDemo.class); public static void main(String[] args) { //1.创建SecurityManagerFactory IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2.获取SecurityManager,绑定到SecurityUtils中 SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3.获取一个用户识别信息 Subject currentUser = SecurityUtils.getSubject(); //4.判断是否已经身份验证 if (!currentUser.isAuthenticated()) { // 4.1把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken("guest", "guest"); // 4.2设置rememberme token.setRememberMe(true); try { // 4.3登录. currentUser.login(token); } catch (UnknownAccountException uae) { //用户不存在异常 log.info("****---->用户名不存在: " + token.getPrincipal()); return; } catch (IncorrectCredentialsException ice) { // 密码不匹配异常 log.info("****---->" + token.getPrincipal() + " 的密码错误!"); return; } catch (LockedAccountException lae) { // 用户被锁定 log.info("****---->用户 " + token.getPrincipal() + " 已被锁定"); } catch (AuthenticationException ae) { // 其他异常,认证异常的父类 log.info("****---->用户" + token.getPrincipal() + " 验证发生异常"); } } // 5.权限测试: //5.1判断用户是否有某个角色 if (currentUser.hasRole("guest")) { log.info("****---->用户拥有角色guest!"); } else { log.info("****---->用户没有拥有角色guest"); return; } //5.2判断用户是否执行某个操作的权限 if (currentUser.isPermitted("see")) { log.info("****----> 用户拥有执行此功能的权限"); } else { log.info("****---->用户没有拥有执行此功能的权限"); } //6.退出 System.out.println("****---->" + currentUser.isAuthenticated()); currentUser.logout(); System.out.println("****---->" + currentUser.isAuthenticated()); } }
解析一下上面的代码做了什么:
对于shiro.ini:
1.在
[users]
标签下以用户名 = 密码,角色1,角色2...
的格式创建了用户
2.在[roles]
标签下以角色 = 权限 (* 代表所有权限)
的格式为用户分配了角色对于ShiroDemo.java:
1.使用shiro.ini来获取了IniSecurityManagerFactory
2.通过IniSecurityManagerFactory获取SecurityManager,并绑定到SecurityUtils中
3.使用SecurityUtils获取一个用户识别信息Subject
4.对Subject对象判断是否已经身份验证(Authenticated)
5.将用户名和密码封装成UsernamePasswordToken对象,调用Subject对象的login方法来进行登录
6.登录成功后,调用Subject对象的hasRole方法来判断用户是否拥有某个角色
7.调用Subject对象的isPermitted方法来判断用户是否拥有某个行为
8.调用Subject对象的logout方法来退出。
Realm
- Realm是真正负责处理认证和授权的组件。
- SecurityManager要完成认证,需要Realm返回一个AuthenticationInfo,AuthenticationInfo会携带存储起来的正确的用户认证信息,用来与用户提交的信息进行比对,如果信息不匹配,那么会认证失败。
- SecurityManager要完成授权,需要Realm返回一个AuthorizationInfo
认证
下面的例子是以继承了AuthenticatingRealm的自定义Realm来实现自定义认证。
认证依赖于方法doGetAuthenticationInfo,需要返回一个AuthenticationInfo,通常返回一个他的子类SimpleAuthenticationInfo,构造方法的第一个参数是用户名,第二个是验证密码,第三个是当前realm的className。
package com.demo.realms;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
public class MyRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
System.out.println("MyRealm认证中---->用户:"+token.getPrincipal());
// 可以从token中获取用户名来从数据库中查询数据
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String password="123456";// 假设这是从数据库中查询到的用户密码
// 创建一个SimpleAuthenticationInfo,第一个参数是用户名,第二个是验证密码,第三个是当前realm的className
// 验证密码会与用户提交的密码进行比对
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(upToken.getUsername(),password,this.getName());
return info;
}
}
当创建了一个Realm之后,需要告诉SecurityManager,所以在shiro.ini中配置:
# -------------------------------------------------------------------
[main]
myRealm = com.demo.realms.MyRealm
# --------由于自定义认证,所以去除users,roles------------------------
这样子就可以进行自定义认证了,在上面的用户密码中都设置了"123456",所以如果输入的密码不正确都会认证失败。但上面没有设置授权,所以代码中要去掉授权的判断.
授权
下面的例子是以继承了AuthorizingRealm的自定义Realm来实现自定义认证和自定义授权。
授权依赖于方法doGetAuthorizationInfo,需要返回一个AuthorizationInfo,通常返回一个他的子类SimpleAuthorizationInfo。构造SimpleAuthorizationInfo可以空构造,也可以传入一个Set<String> roles
来构造。
package com.demo.realms