Shiro
介绍
它是一个功能强大且易于使用的Java安全框架,可以执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速且轻松地保护任何应用程序——从最小的移动应用程序到最大的web和企业应用程序。
核心概念
1、subject(主题):代表了正在操作的当前“用户”,这个用户并不一定是人也也可以指第三方进程、守护进程帐户或任何类似的东西。一旦您获得了主题,您就可以立即访问当前用户使用Shiro想要做的90%的事情,比如登录、注销、访问他们的会话、执行授权检查等。
2、securityManager(安全管理器):它是shiro的核心,所有与安全有关的操作都会与securityManager进行交互。
3、realm(数据域):充当Shiro和应用程序安全数据之间的“桥梁”,当实际需要与与安全相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从一个或多个为应用程序配置的领域中查找这些内容。realm可以有多个,但是至少得有一个。Shiro提供了开箱即用的realm,可以连接到许多安全数据源(即目录),如LDAP、关系数据库(JDBC)、文本配置源(如INI)和属性文件,等等。如果默认的realm不满足您的需求,可以添加您自己的领域实现来表示自定义数据源。
…
环境
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
以下例子 都是模拟的数据,并不是真正的数据库数据,和其他整合还需要其他的依赖,请前往官网查看
ini配置
ini 配置文件类似于 Java 中的 properties(key=value),不过提供了将 key/value 分类的特性,key 是每个部分不重复即可,而不是整个配置文件。如下是 INI 配置分类:
- [main] 部分
提供了对根对象 securityManager 及其依赖对象的配置。
- [users] 部分
配置用户名 / 密码及其角色,格式:“用户名 = 密码,角色 1,角色 2”,角色部分可省略。
- [roles] 部分
配置角色及权限之间的关系,格式:“角色 = 权限 1,权限 2”
- [urls] 部分
配置 url 及相应的拦截器之间的关系,格式:“url = 拦截器 [参数],拦截器 [参数]
例子:
[main]
\#提供了对根对象securityManager及其依赖的配置
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
…………
securityManager.realms=$jdbcRealm
[users]
\#提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
username=password,role1,role2
[roles]
\#提供了角色及权限之间关系的配置,角色=权限1,权限2
role1=permission1,permission2
[urls]
\#用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器
/index.html = anon
/admin/** = authc, roles[admin], perms["permission1"]
认证
1、认证流程
1.1、首先调用 Subject.login(token)进行登录,其会自动委托给 Security Manager,调用之前必 须通过 SecurityUtils. setSecurityManager()设置。
1.2、SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
1.3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,开发者可以自 定义插入自己的实现;
1.4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
1.5、Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返 回/抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进 行访问。
1.6、最后可以调用 subject.logout 退出,其会自动委托给 SecurityManager.logout 方法退出。
2、例子(没有使用ini 配置)
2.1、创建自定义realm
public class UserRealm extends AuthenticatingRealm {
//模拟service 层
private UserService userService =new UserService();
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从token 中获取用户名
String username= (String) authenticationToken.getPrincipal();
System.out.println("用户名: "+username);
//从数据库中获得user
User user = userService.findUserByUsername(username);
//如果存在该用户 才会进行认证
if(null!=user ){
AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"");
//认证成功
return info;
}
//返回空 认证失败
return null;
}
}
如果只是认证接需要继承 AuthenticatingRealm
AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"");
第一个参数 可以是任意类型 设置之后 可以通过
subject.getPrincipal()
获得这个参数第二个参数 原密码,数据库查询出的密码
它还有第三、第四个参数,在后面加密用例讲到
2.2、创建测试类
public class Shiro {
public static void main(String[] args) {
String username="xhb";
String password="123456";
Factory<SecurityManager> factory = new IniSecurityManagerFactory();
//得到securityManager 安全管理器
DefaultSecurityManager securityManager= (DefaultSecurityManager) factory.getInstance();
//将自定义的realm 给 securityManager 管理
securityManager.setRealm(new UserRealm());
//得到SecurityManager实例 并绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
//得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
//认证
try {
subject.login(token);
System.out.println("登录成功");
}catch (UnknownAccountException e){
System.out.println("用户不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
}
}
如果身份验证失败请捕获 AuthenticationException 或其子类
-
DisabledAccountException : 禁用的帐号
-
LockedAccountException : 锁定的帐号
-
UnknownAccountException : 错误的帐号
-
ExcessiveAttemptsException : 登录失败次数过多
-
IncorrectCredentialsException : 错误的凭证
-
ExpiredCredentialsException :过期的凭证
2.3、测试结果
用户名: xhb 登录成功
授权
1、授权流程
1.1、首先调用 Subject.isPermitted*/hasRole*接口,其会委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer
1.2、Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例
1.3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的 角色/权限
…
2、例子
2.1、创建自定义realm
public class UserRealm extends AuthorizingRealm {
private UserService userService =new UserService();
private RoleService roleService = new RoleService();
private PermissionService permissionService = new PermissionService();
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//认证例子中的代码
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//模拟从数据库中查询 角色
List roles=roleService.findRoles();
// info.addRole("role1");
// info.addRole1("role2");
//添加多个角色
info.addRoles(roles);
//模拟从数据库中查询 角色
List permissions = permissionService.findPermissions();
// info.addStringPermission("user:query");
// info.addStringPermission("user:update");
//添加多个权限
info.addStringPermissions(permissions);
return info;
}
}
授权需要继承 AuthorizingRealm 实现 doGetAuthorizationInfo 方法
AuthorizingRealm 是 AuthenticatingRealm 的子类,因为只有认证了才能授权
2.2、创建测试类
public class Shiro {
public static void main(String[] args) {
//认证中的代码。。。。
boolean hasRole = subject.hasRole("role1");
System.out.println("用户:"+username+" 拥有角色role1:"+hasRole);
boolean hasPermission = subject.isPermitted("content:query");
System.out.println(("用户:"+username+" 拥有角色permission:"+hasPermission));
}
}
2.3、测试结果
用户名: xhb
登录成功
doGetAuthorizationInfo
用户:xhb 拥有角色role1:true
doGetAuthorizationInfo
用户:xhb 拥有角色permission:false
在测试过程中,发现每次调用subject.hasRole("role1");
和 subject.isPermitted("content:query");
都会执行自定义 realm 中的doGetAuthorizationInfo(PrincipalCollection principalCollection)
授权的方法,而在自定义 realm 会从数据库中获得 角色列表 和 权限列表 ,这样会耗费性能,所以可以在认证的时候,就把 角色列表 和 权限列表 获取下来,因为一般登录只会认证一次
获取下来之后,创建一个对象ActiveUser
包含三个属性:user
数据库查询到的用户 roles
查询到的角色列表 permissions
查询到的权限列表,通过AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),salt,this.getName());
的第一个参数 ,通过subject.getPrincipal()
可以获得这个参数,并且也可以在doGetAuthorizationInfo(PrincipalCollection principalCollection)
方法中,可以通过principalCollection.getPrimaryPrincipal()
方法拿到这个参数
这样就不要每次判断 有无角色,有无权限 去查询数据库
编码加密
MD5加密
Md5Hash md5Hash=new Md5Hash("123456","cl",2);
System.out.println(md5Hash.toString());
// 5be1ac6418314812cc1763365618fb9f
Md5Hash md5Hash=new Md5Hash(“123456”,“cl”,2);
第一个参数 加密的字符串
第二个参数 salt 盐,用于混淆加密密码,让密文更加难以破解
第三个参数 散列次数,就是将加密的密文用同样的方法再加密
在存取用户密码的时候,一般使用密文,再shiro 认证的时候,用用户输入的原密码 和 数据库中密文比较
这个时候我们需要告诉shiro 加密规则
例子
注意:在模拟sevice 层中的获得用户 模拟的数据现在应该是已经加密的密文
自定义realm
//将认证例子代码中的认证方法中
AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"");
//替换为
ByteSource salt= ByteSource.Util.bytes("cl");
AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),salt,this.getName());
继续上面没说完的参数解释
AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),salt,this.getName());
第三个参数 salt 盐,用户密码加密 中用的盐
第四个参数 realmName 自定义realm 的名称
创建测试类
public class Shiro {
public static void main(String[] args) {
String username="xhb";
String password="123456";
Factory<SecurityManager> factory = new IniSecurityManagerFactory();
//得到securityManager 安全管理器
DefaultSecurityManager securityManager= (DefaultSecurityManager) factory.getInstance();
//创建自定义realm
UserRealm userRealm=new UserRealm();
*******************************重 要******************************************
//创建凭证匹配器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//设置加密方式
matcher.setHashAlgorithmName("md5");
//设置散列次数
matcher.setHashIterations(2);
//将凭证匹配器 注入自定义raalm
userRealm.setCredentialsMatcher(matcher);
*******************************************************************************
//将自定义realm 注入安全管理器
securityManager.setRealm(userRealm);
//得到SecurityManager实例 并绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
//得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
//认证
try {
subject.login(token);
System.out.println("登录成功");
User user = (User) subject.getPrincipal();
System.out.println(user.toString());
}catch (UnknownAccountException e){
System.out.println("用户不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
.....
}
}
测试结果
用户名: xhb
登录成功
User{username='xhb', password='5be1ac6418314812cc1763365618fb9f'}//数据库中的user
用户:xhb 拥有角色role1:true
用户:xhb 拥有角色permission:false
这样就完成了shiro 的基础入门
理解有限,有错的地方希望提出0.0