Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。
Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。
Authentication:身份认证/登录,验证用户是不是拥有相应的身份。
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情。
Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的。
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
Web Support:Shiro 的 web 支持的 API 能够轻松地帮助保护 Web 应用程序。
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。
Concurrency:Apache Shiro 利用它的并发特性来支持多线程应用程序。
Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能
够如预期的一样安全。
"Run As":一个允许用户假设为另一个用户身份(如果允许)的功能。
"Remember Me":记住我。
Shiro就是一个最简单的应用:
1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
2、我们需要给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来管理主体与应用之间交互的数据;
SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可
以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的
Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到
缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-webartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-ehcacheartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-quartzartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-allartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
认证:身份认证/登录,验证用户是不是拥有相应的身份。基于shiro的认证,是通过subject的login方法完成用户认证工作的
创建shiro.ini配置文件,通过 [users] 指定了主体,将用户名和密码配置在shiro.ini配置文件中,以此模拟从数据库查询出的用户。
#数据格式 用户名=密码
[users]
admin=admin123
@Test
public void testLoginAndLogout() {
//获取SecurityManager工厂,使用Ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通过工厂创建SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//securityManager绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 创建Subject实例,该实例认证要使用上边创建的securityManager进行
Subject subject = SecurityUtils.getSubject();
// 构造主体登录的token凭证(即用户名/密码)
UsernamePasswordToken token = new UsernamePasswordToken("admin", "admin123");
try {
//登录,即身份验证
subject.login(token);
} catch (AuthenticationException e) {
// 身份验证失败
e.printStackTrace();
}
// 用户认证状态
Boolean isAuthenticated = subject.isAuthenticated();
System.out.println("用户认证状态:" + isAuthenticated);
// 用户退出
subject.logout();
isAuthenticated = subject.isAuthenticated();
System.out.println("用户认证状态:" + isAuthenticated);
}
授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限
创建存放权限的配置文件shiro-permission.ini
#用户、角色、权限的配置规则如下:
[users]
#模拟从数据库查询的用户
#数据格式 用户名=密码,角色1,角色2
#用户admin的密码是admin123,具有role1和role2两个角色
admin=admin123,role1,role2
#用户lisi的密码是lisi123,具有role1角色
lisi=lisi123,role1
[roles]
#模拟从数据库查询的角色和权限列表
#数据格式 角色名=权限1,权限2
#角色role1对资源user拥有add,select权限
role1=user:add,user:select
#角色role2对资源user拥有create、update权限
role2=user:create,user:update
@Test
public void testLoginAndLogout() {
//获取SecurityManager工厂,使用Ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");
//通过工厂创建SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//securityManager绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 创建Subject实例,该实例认证要使用上边创建的securityManager进行
Subject subject = SecurityUtils.getSubject();
// 创建token令牌,记录用户认证的身份和凭证
UsernamePasswordToken token = new UsernamePasswordToken("admin", "admin123");
try {
//登录,即身份验证
subject.login(token);
} catch (AuthenticationException e) {
// 身份验证失败
e.printStackTrace();
}
// 用户认证状态
Boolean isAuthenticated = subject.isAuthenticated();
System.out.println("用户认证状态:" + isAuthenticated);
// 基于角色的授权:
// 是否有某一个角色
System.out.println("用户是否拥有一个角色:" + subject.hasRole("role1"));
// 是否有多个角色
System.out.println("用户是否拥有多个角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));
//用户授权检测,失败抛出异常
//subject.checkRole("role1");
//subject.checkRoles(Arrays.asList("role1", "role2"));
// 基于资源授权:
System.out.println("是否拥有某一个权限:" + subject.isPermitted("user_add"));
System.out.println("是否拥有多个权限:" + subject.isPermittedAll("user_create","user_delete"));
//检查权限,失败抛出异常
// subject.checkPermission("sys:user:delete");
// subject.checkPermissions("user:create:1","user:delete");
// 用户退出
subject.logout();
isAuthenticated = subject.isAuthenticated();
System.out.println("用户认证状态:" + isAuthenticated);
}
}
Realm域:Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源
单Realm域
[main]
#自定义 realm
ShiroRealm=cn.ybzy.shiro.security.ShiroRealm
#将realm设置到securityManager
securityManager.realms=$ShiroRealm
多 Realm 配置
[main]
#自定义 realm
ShiroRealm=cn.ybzy.shiro.security.ShiroRealm1
ShiroRealm=cn.ybzy.shiro.security.ShiroRealm2
#将realm设置到securityManager
securityManager.realms=$ShiroRealm1,$ShiroRealm2
多realm域时securityManager 会按照 realms 指定的顺序进行身份认证
public class ShiroRealm extends AuthorizingRealm {
@Override
public String getName() {
return "ShiroRealm";
}
//登录认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException {
//从token中 获取用户身份信息
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
//用username与password 与数据库进行用户账号/密码校验 校验失败抛出异常
if(!username.equals("123")){
throw new UnknownAccountException("用户名或密码错误!");
}
//返回认证信息由父类AuthenticatingRealm进行认证
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
return simpleAuthenticationInfo;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//从principals获取已认证用户的信息
String username = (String) principalCollection.getPrimaryPrincipal();
//根据用户名去数据库查询该用户对应的角色以及权限
//角色授权,模拟数据库中查询的角色
Set<String> roles = new HashSet<>();
roles.add("role1");
roles.add("role2");
//资源授权,模拟数据库中查询的权限
Set<String> permission = new HashSet<>();
permission.add("user_add");
permission.add("user_select");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//将查询的角色、权限数据保存到simpleAuthorizationInfo
info.setStringPermissions(permission);
info.setRoles(roles);
return info;
}
}
@Test
public void testLoginAndLogout() {
//获取SecurityManager工厂,使用Ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
//通过工厂创建SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//securityManager绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 创建Subject实例,该实例认证要使用上边创建的securityManager进行
Subject subject = SecurityUtils.getSubject();
// 创建token令牌,记录用户认证的身份和凭证
UsernamePasswordToken token = new UsernamePasswordToken("admin", "admin123");
try {
//登录,即身份验证
subject.login(token);
} catch (AuthenticationException e) {
// 身份验证失败
e.printStackTrace();
}
// 用户认证状态
Boolean isAuthenticated = subject.isAuthenticated();
System.out.println("用户认证状态:" + isAuthenticated);
// 基于角色的授权:
// 是否有某一个角色
System.out.println("用户是否拥有一个角色:" + subject.hasRole("role1"));
// 是否有多个角色
System.out.println("用户是否拥有多个角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));
//用户授权检测,失败抛出异常
//subject.checkRole("role1");
//subject.checkRoles(Arrays.asList("role1", "role2"));
// 基于资源授权:
System.out.println("是否拥有某一个权限:" + subject.isPermitted("user_add"));
System.out.println("是否拥有多个权限:" + subject.isPermittedAll("user_create","user_delete"));
//检查权限,失败抛出异常
// subject.checkPermission("sys:user:delete");
// subject.checkPermissions("user:create:1","user:delete");
// 用户退出
subject.logout();
isAuthenticated = subject.isAuthenticated();
System.out.println("用户认证状态:" + isAuthenticated);
}
}
1. 首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;
2. SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
3. Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实
现;
4. Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
5. Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份
验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
1. 首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而SecurityManager接着会委托
给Authorizer;
2. Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver
把字符串转换成相应的Permission实例;
3. 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
4. Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted/hasRole会返回true,否则返回false表示
授权失败
授权自定义 realm
public class ShiroRealm extends AuthorizingRealm {
@Override
public String getName() {
return "ShiroRealm";
}
//登录认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//从token中 获取用户身份信息
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
//用username 与 password 进行用户账号/密码校验 校验失败抛出异常
if(!username.equals("123")){
throw new UnknownAccountException("用户名或密码错误!");
}
//返回认证信息由父类AuthenticatingRealm进行认证
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
username, password, getName());
return simpleAuthenticationInfo;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//角色授权
Set<String> roles = new HashSet<>();
roles.add("role1");
roles.add("role2");
//资源授权
Set<String> permission = new HashSet<>();
permission.add("user_add");
permission.add("user_select");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permission);
info.setRoles(roles);
return info;
}
}
散列算法
散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如 MD5、SHA 等。
@Test
public void testPwd(){
//明文密码:
String input="123456";
//加密:md5
Md5Hash md5Hash = new Md5Hash(input);
System.out.println("md5Hash = " + md5Hash);
//md5Hash = e10adc3949ba59abbe56e057f20f883e
//加密: MD5+盐
Md5Hash shiro = new Md5Hash(input, "shiro");
System.out.println("shiro = " + shiro);
// shiro = eef3a22a128d5adb5699e3c7da7a6fc8
//加密: MD5 + 盐 + 散列次数
Md5Hash shiro1 = new Md5Hash(input, "shiro", 2);
System.out.println("shiro1 = " + shiro1);
// shiro1 = b87dbd0bd4bcc6536a08d2027e329547
}
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=2
#将凭证匹配器设置到realm
myRealm.credentialsMatcher=$credentialsMatcher
#自定义 realm
ShiroRealm=cn.ybzy.shiro.security.ShiroRealm
#将realm设置到securityManager
securityManager.realms=$ShiroRealm1
public class ShiroRealm extends AuthorizingRealm {
@Override
public String getName() {
return "ShiroRealm";
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从token中 获取用户身份信息
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
//假设模拟从数据库中查询出的密码是加密后的密文 : 明文(123456) + 盐(shiro) + 散列次数(2)
String pwd ="b87dbd0bd4bcc6536a08d2027e329547";
//返回认证信息由父类AuthenticatingRealm进行认证
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
username, pwd, ByteSource.Util.bytes("shiro"),getName());
return simpleAuthenticationInfo;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
@Test
public void testLoginAndLogout() {
//获取SecurityManager工厂,使用Ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-password.ini");
//通过工厂创建SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//securityManager绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 创建Subject实例,该实例认证要使用上边创建的securityManager进行
Subject subject = SecurityUtils.getSubject();
// 创建token令牌,记录用户认证的身份和凭证
UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
try {
//登录,即身份验证
subject.login(token);
} catch (AuthenticationException e) {
// 身份验证失败
e.printStackTrace();
}
// 用户认证状态
Boolean isAuthenticated = subject.isAuthenticated();
System.out.println("用户认证状态:" + isAuthenticated);
// 基于角色的授权:
// 是否有某一个角色
System.out.println("用户是否拥有一个角色:" + subject.hasRole("role1"));
// 是否有多个角色
System.out.println("用户是否拥有多个角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));
// 基于资源授权:
System.out.println("是否拥有某一个权限:" + subject.isPermitted("user_add"));
System.out.println("是否拥有多个权限:" + subject.isPermittedAll("user_create","user_delete"));
// 用户退出
subject.logout();
isAuthenticated = subject.isAuthenticated();
System.out.println("用户认证状态:" + isAuthenticated);
}