1.Shiro是什么
Shiro是一个非常强大的、易于使用的、开源的权限框架(安全框架)。它包括了权限校验、权限授予、会话管理、安全加密等组件。
2.为什么需要使用Shiro
在设计RBAC(Role Based Access Control)基础系统时,需要编写大量用于权限控制的代码。如果使用Shiro就可以大大减少我们的工作量。因为Shiro已经将RBAC系统大量的代码封装好。
如:页面的显示的HTML控件根据登录用户的权限不同而不同。使用Shiro可以轻松解决。
3.Shiro的下载
shiro的下载路径:http://shiro.apache.org/download.html
Shiro包说明
使用时根据列表的说明,下载需要的jar包即可
包 名 | Maven 坐标 | 说 明 |
shiro-all | 不推荐采用 | 包含Shiro的所有功能 |
shiro-core |
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
| 只要使用shiro必须的核心包,它依赖slf4j 和 commons-beanutils 以及还需要一个INI配置文件 |
shiro-web | <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
| 支持基于Web的应用 |
shiro-aspectj | <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-aspectj</artifactId>
<version>1.3.2</version>
</dependency>
| AspectJ对Shiro AOP和注释的支持 |
shiro-cas | <dependency> | 对cas单点登录框架的支持 |
shiro-ehcache | <dependency> | 对echche缓存框架的支持 |
shiro-hazelcast | <dependency> | 对hazelcast的famework缓存的支持 |
shiro-features |
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-features</artifactId>
<version>1.3.2</version>
</dependency>
| Karaf 的集成 |
shiro-guice | <dependency> | 对谷歌的guice框架的支持(类似spring的ioc框架) |
shiro-quartz | <dependency> | 对quartz定时任务调度框架的支持 |
shiro-spring |
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
| 支持Spring框架集成。 |
标红部分为常用包
4.Shiro结构图
Authentication:权限校验,每次操作校验用户是否有访问权限
Authorization:授权,用户登录时,授予用户对应的权限
Session Management:会话管理,用于记录用户的登录状态
Cryptography:加密,加密算法的实现(SHA、MD5)
web Support:对Web项目的支持,Shiro的标签!
5.Shiro入门
5.1 访问流程图
登录流程:
1.应用访问(如Web请求等)
2.Shiro根据访问请求创建一个Subject对象来标识当前访问的身份。
3.SecurityManger 加载配置文件进行身份校验(验证账号密码)
4.身份校验通过后SecurityManger 会根据配置文件授予当前Subject对象对应的用户权限
5.2 入门示例
配置步骤:
第一步:导入jar包
第二步:shiro.ini配置文件
创建一个shiro.ini配置文件,编写权限认证信息。
注:1.shiro.ini文件名可以任意编写,但后缀必须是ini
2.shiro.ini配置文件放在classpath根目录下
shiro.ini文件配置规则说明:
[main] #用于配置SecurityManager里面的对象 对象名=类全限制名 对象名.属性[.属性...] = 值 [users] #用于配置用户名信息 用户名= 密码, 角色1, 角色2, …, 角色N [roles] #用于配置角色信息 角色名= 权限1, 权限2, …, 权限N #全部权限使用 * (星号) [urls] #用于配置路径拦截规则
权限命名格式建议:权限:操作:操作
shiro.ini配置:
##用户信息 [users] admin=123456,role_admin,role_user ##角色信息 [roles] role_admin=* role_user=modular:to_add,modular:add
测试:
package com.gjs.shiro.test; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; public class ShiroTest { public static void main(String[] args) { //第一步:读取配置文件创建安全管理器 IniSecurityManagerFactory factory =new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.createInstance(); //第二步:设置SecurityUtils的安全管理器 SecurityUtils.setSecurityManager(securityManager); //第三步:获得一个没有权限身份对象 Subject subject = SecurityUtils.getSubject(); //第四步:构建验证信息token UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); //第四步:身份校验(验证账号密码) try { Subject resultSubject = securityManager.login(subject, token); System.out.println("校验通过"); System.out.println("用户名:"+resultSubject.getPrincipal()); System.out.println("验证授权:"+resultSubject.isPermitted("modular:add")); } catch (AuthenticationException e) { System.out.println("校验失败,用户名或者密码不正确"); e.printStackTrace(); } } }
API说明:
IniSecurityManagerFactory:作用加载ini配置文件获得SecurityManagerFactory对象
SecurityManager:安全管理容器,就是否则整个Shiro框架授权校验对象的管理
SecurityUtils :SecurityManager对象帮助类
Subject:验证通过后用于存储授权信息的身份对象
UsernamePasswordToken :用于设置校验信息的对象
IncorrectCredentialsException :密码出错异常
UnknownAccountException:用户名出错异常
6.Realm的使用
在入门示例中,用户验证信息来自于ini配置文,这样的难以符合我们实际的需求。而在实际开发中,我们的用户信息是存储在数据库里面,再从数据库里面读取出来。
Shiro是通过Realm机制,实现将配置文件的校验用户信息存放在数据库等数据存储系统里面。
6.1 访问流程图
如图所示,我们需要在ini配置文件中配置Realm对象,再在Realm中进行权限验证以及授权
6.2 示例:
配置步骤:
第一步:导入jar包
第二步:编写Realm
package com.gjs.shiro.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; /** * 我们自定义Realm继承授权的Realm AuthorizingRealm。因为授权包括校验。 * @author gjs * */ public class MyRealm extends AuthorizingRealm{ /** * 权限校验: 就是验证访问者(subject).是否使用有使用权限的身份,即验证账号密码。验证通过回AuthenticationInfo对象 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("权限校验"); System.out.println("用户名:" + token.getPrincipal()); if (token.getPrincipal().equals("admin")) { //参数1:用于存储用户信息,可以填充给Subject对象 //参数2:校验的密码。注意Shiro的校验是SimpleAuthenticationInfo内部完成的。 //参数3:Realm名字,用来标识Realm return new SimpleAuthenticationInfo(token.getPrincipal(), "123456", this.getName()); } return null; } /** * 权限授予:根据通过校验的身份(subject)将查询到的权限信息封装在AuthorizationInfo里面返回 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission("modular:add");//添加权限 info.addRole("RoleAdmin");//添加角色 return info; } }
第三步:创建shiro.ini配置文件
[main] ##声明Realm对象 myRealm=com.gjs.shiro.realm.MyRealm ##配置securityManager的realm对象。 对象引用需要在对象名前面加上 $ securityManager.realms=$myRealm
第四步:编写测试类
package com.gjs.shiro.test; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; public class ShiroTest { public static void main(String[] args) { //第一步:读取配置文件创建安全管理器 IniSecurityManagerFactory factory =new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.createInstance(); //第二步:设置SecurityUtils的安全管理器 SecurityUtils.setSecurityManager(securityManager); //第三步:获得一个没有权限身份对象 Subject subject = SecurityUtils.getSubject(); //第四步:构建验证信息token UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); //第四步:身份校验(验证账号密码) try { Subject resultSubject = securityManager.login(subject, token); System.out.println("校验通过"); System.out.println("用户名:"+resultSubject.getPrincipal()); System.out.println("验证授权:"+resultSubject.isPermitted("modular:add")); System.out.println("是否有RoleAdmin角色:"+resultSubject.hasRole("RoleAdmin")); } catch (AuthenticationException e) { System.out.println("校验失败,用户名或者密码不正确"); e.printStackTrace(); } } }
6.3 加密
在开发中我们需要对密码进行加密,而我们自己编写的加密工具类无法传递给SimpleAuthenticationInfo对象,作为密码校验。所以就要用到shiro框架自带的密码加密的功能。
SimpleHash类:用于生成指定的Hash算法。
HashedCredentialsMatcher类:用于让Realm校验时,校验指定的Hash算法
ByteSource 用于给Hash算法加盐的
示例:
生成md5密码
package com.gjs.shiro.test; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.util.ByteSource; /** * 用来创建加密后的密码,参数与校验器的参数保持一致 * @author gjs * */ public class Md5Util { public static void main(String[] args) { ByteSource salt = ByteSource.Util.bytes("gjs"); Md5Hash md5=new Md5Hash("123456", salt, 3); String password = md5.toString(); System.out.println(password); } }
修改ini配置文件
[main] ##声明Realm对象 myRealm=com.gjs.shiro.realm.MyRealm #加密的对象 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher ##指定加密算法. 属性对应的是set方法 credentialsMatcher.hashAlgorithmName=md5 ##算法是否加盐 credentialsMatcher.hashSalted=true ##加密次数 credentialsMatcher.hashIterations=3 ##指定加密的校验器给MyReam myRealm.credentialsMatcher=$credentialsMatcher ##配置securityManager的realm对象。 对象引用需要在对象名前面加上 $ securityManager.realms=$myRealm
修改Realm类:
package com.gjs.shiro.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; /** * 我们自定义Realm继承授权的Realm AuthorizingRealm。因为授权包括校验。 * @author gjs * */ public class MyRealm extends AuthorizingRealm{ /** * 权限校验: 就是验证访问者(subject).是否使用有使用权限的身份,即验证账号密码。验证通过回AuthenticationInfo对象 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("权限校验"); System.out.println("用户名:" + token.getPrincipal()); if (token.getPrincipal().equals("admin")) { ByteSource salt = ByteSource.Util.bytes("gjs"); //参数1:用于存储用户信息,可以填充给Subject对象 //参数2:校验的密码。注意Shiro的校验是SimpleAuthenticationInfo内部完成的。 //参数3:Realm名字,用来标识Realm return new SimpleAuthenticationInfo(token.getPrincipal(), "a0af233bfd499995a8c1bacc4f61c489",salt, this.getName()); } return null; } /** * 权限授予:根据通过校验的身份(subject)将查询到的权限信息封装在AuthorizationInfo里面返回 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission("modular:add");//添加权限 info.addRole("RoleAdmin");//添加角色 return info; } }
6.4 返回的认证信息为一个实体(JavaBean、Map)
上面代码校验后返回的认证信息是一个字符串的用户名,而我们如果将Shiro的校验功能用到登录的逻辑里面,明显需要返回的不是一个用户名,而是用户的信息。
用户的信息,我们需要用一个实体类来封装。可以是JavaBean或者是Map
我们上面写的校验方法返回的SimpleAuthenticationInfo的构建方法的第一个参数就是用于指定,返回的用户认证信息的。可以将用户名修改为一个我们指定的实体类对象就可以了
pojo实体类:
package com.gjs.shiro.pojo; import java.util.Date; public class User { private int id; private String name; private String password; private Date createDate; private int status; private Role role; public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", password=" + password + ", createDate=" + createDate + ", status=" + status + ", role=" + role + "]"; } }
package com.gjs.shiro.pojo; import java.util.List; public class Role { private int roleId; private String roleName; private List<Perm> rolePerms; public int getRoleId() { return roleId; } public void setRoleId(int roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public List<Perm> getRolePerms() { return rolePerms; } public void setRolePerms(List<Perm> rolePerms) { this.rolePerms = rolePerms; } @Override public String toString() { return "Role [roleId=" + roleId + ", roleName=" + roleName + ", rolePerms=" + rolePerms + "]"; } }
package com.gjs.shiro.pojo; public class Perm { private int permId; private String permName; private String permAction; private String permKey; public int getPermId() { return permId; } public void setPermId(int permId) { this.permId = permId; } public String getPermName() { return permName; } public void setPermName(String permName) { this.permName = permName; } public String getPermAction() { return permAction; } public void setPermAction(String permAction) { this.permAction = permAction; } public String getPermKey() { return permKey; } public void setPermKey(String permKey) { this.permKey = permKey; } @Override public String toString() { return "Perm [permId=" + permId + ", permName=" + permName + ", permAction=" + permAction + ", permKey=" + permKey + "]"; } }
修改Realm
package com.gjs.shiro.realm; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import com.gjs.shiro.pojo.Perm; import com.gjs.shiro.pojo.Role; import com.gjs.shiro.pojo.User; /** * 我们自定义Realm继承授权的Realm AuthorizingRealm。因为授权包括校验。 * @author gjs * */ public class MyRealm extends AuthorizingRealm{ /** * 权限校验: 就是验证访问者(subject).是否使用有使用权限的身份,即验证账号密码。验证通过回AuthenticationInfo对象 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("权限校验"); User user=new User(); //此处的数据应从数据库查出来 user.setId(1); user.setName((String)token.getPrincipal()); user.setStatus(0); user.setCreateDate(new Date()); if (token.getPrincipal().equals(user.getName())) { ByteSource salt = ByteSource.Util.bytes("gjs"); //参数1:用于设置认证信息,返回给调用对象的 //参数2:校验的密码。注意Shiro的校验是SimpleAuthenticationInfo内部完成的。 //参数3:密码的盐 //参数4:Realm名字,用来标识Realm return new SimpleAuthenticationInfo(user, "a0af233bfd499995a8c1bacc4f61c489",salt, this.getName()); } return null; } /** * 权限授予:根据通过校验的身份(subject)将查询到的权限信息封装在AuthorizationInfo里面返回 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //获取认证信息 User user = (User) principals.getPrimaryPrincipal(); //将授权信息也存到当前用户对象里面(值传递) //角色 Role role=new Role(); role.setRoleId(1); role.setRoleName("RoleAdmin"); user.setRole(role); //权限 List<Perm> perms=new ArrayList<>(); Perm perm1=new Perm(); perm1.setPermId(1); perm1.setPermName("用户管理"); perm1.setPermAction("/user/toUserList"); perm1.setPermKey("user:to_edit"); perms.add(perm1); role.setRolePerms(perms); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission(user.getRole().getRolePerms().get(0).getPermKey());//添加权限 info.addRole(user.getRole().getName());//添加角色 return info; } }
测试类:
package com.gjs.shiro.test; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import com.gjs.shiro.pojo.User; public class ShiroTest { public static void main(String[] args) { //第一步:读取配置文件创建安全管理器 IniSecurityManagerFactory factory =new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.createInstance(); //第二步:设置SecurityUtils的安全管理器 SecurityUtils.setSecurityManager(securityManager); //第三步:获得一个没有权限身份对象 Subject subject = SecurityUtils.getSubject(); //第四步:构建验证信息token UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); //第四步:身份校验(验证账号密码) try { Subject resultSubject = securityManager.login(subject, token); System.out.println("校验通过"); User user=(User) resultSubject.getPrincipal();//获取认证信息 System.out.println("用户名:"+user.getName()); System.out.println("验证授权:"+resultSubject.isPermitted("modular:add")); System.out.println("是否有RoleAdmin角色:"+resultSubject.hasRole("RoleAdmin")); System.out.println("获得角色:"+user.getRole()); System.out.println("获得第一个权限:"+user.getRole().getRolePerms().get(0)); } catch (AuthenticationException e) { System.out.println("校验失败,用户名或者密码不正确"); e.printStackTrace(); } } }
7.常用API:
IniSecurityManagerFactory : 用于加载配置文件,创建SecurityManager对象
SecurityManager :就是整个Shiro的控制对象
SecurityUtils :SecurityManager 工具类,用于获得Subject对象
Subject :身份类,存储返回的数据信息、提供了校验的权限的方法
UsernamePasswordToken 身份信息构建类 (Token 令牌,作用就是传入校验参数)
AuthorizingRealm 支持校验与授权的Realm
AuthenticationInfo 校验成功返回的信息的父接口
SimpleAuthenticationInfo 校验成功返回信息类
Md5Hash Md5加密类
ByteSource 字节码处理工具类,我们在构造Md5加盐时使用到。
HashedCredentialsMatcher Md5算法校验器,用于支持Md5校验
AuthorizationInfo 授权成功返回的信息类的父接口
PrincipalCollection 授予是获得验证信息的类
SimpleAuthorizationInfo 授权成功返回的信息类的实现类