shiro
shiro的作用
在我们日常创作的系统中总有些涉及到用户的内容需要登录,不允许随意访问,就像你访问淘宝的购物车首先需要你去登录,而这就属于权限控制。
权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。而shiro就是实现权限控制的一种工具
身份认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
shiro的核心框架
Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
SecurityManager
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。
通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进
行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
Authenticator
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
Authorizer
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
Realm
Realm即领域,相当于datasource数据源
securityManager进行安全认证需要通过Realm获取用户权限数据
比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
SessionManager
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
SessionDAO
SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会
话存储到数据库。
CacheManager
CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
Cryptography
Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
认证
认证中的关键对象
- Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体; - Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,
如用户名、手机号、邮箱地址等,一个主体可以有多个身份,
但是必须有一个主身份(Primary Principal)。 - credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
认证流程
认证的开发
引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
创建配置文件
创建.ini 文件用来模拟数据库中的数据(写死了数据)
ini文件放在主目录下
ini文件中内容如下
[users]
xiaochen=123
zhangsan=123456
lisi=789
开发认证
步骤:****
- 创建安全管理器
- 给安全管理器设置realm
- SecurityUtils 给全局安全工具类设置安全管理器
- 关键对象 subject 主体
- 创建令牌
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//括号中的内容应该是ini文件的地址
//3.SecurityUtils 给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4. 关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
//5. 创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen","123");
try {
subject.login(token); //用户认证
System.out.println("认证状态: "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:用户名不存在~");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码错误");
}
}
}
认证过程中的常见异常
- UnknownAccountException:用户名不存在
- IncorrectCredentialsException:密码错误
- DisabledAccountException(帐号被禁用)
- LockedAccountException(帐号被锁定)
- ExcessiveAttemptsException(登录失败次数过多)
- ExpiredCredentialsException(凭证过期)等
自定义realm
上边的程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。
shiro提供的realm
源码认证时使用的是SimpleAccountRealm
自定义的realm
/**
* 自定义realm 实现 将认证/授权数据的来源转化为数据库的实现
* */
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在token中获取用户名
String principal = (String) authenticationToken.getPrincipal();
System.out.println(principal);
//根据身份信息查询相关的数据库
if ("xiaochen".equals(principal)){
//参数一代表返回数据库中正确的名字;参数二是返回数据库中正确的密码;第三个参数是realm名字
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"123",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
自定义realm的测试
/**
* 使用自定义realm 将认证/授权的数据的来源转为数据库
* */
public class TestCustomerRealmAuthenticator {
public static void main(String[] args) {
//创建securityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//设置自定义realm
defaultSecurityManager.setRealm(new CustomerRealm());
//设置安全工具类
SecurityUtils.setSecurityManager(defaultSecurityManager);
//获取subject
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
try {
subject.login(token); //用户认证
System.out.println("认证状态: "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:用户名不存在~");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码错误");
}
}
}
加入md5算法加密
MD5主要特点:
不可逆,相同数据的MD5值肯定一样,不同数据的MD5值不一样
(一个MD5理论上的确是可能对应无数多个原文的,因为MD5是有限多个的而原文可以是无数多个。比如主流使用的MD5将任意长度的“字节串映射为一个128bit的大整数。也就是一共有2^128
种可能,大概是3.4*10^38,这个数字是有限多个的,而但是世界上可以被用来加密的原文则会有无数的可能性)
因为他加密时用的盐值不同,做hash排列的次数也不同,所以生成的MD5密码也不一样,同时因为每次调用时只有相同的盐值才能返回正确的密码,因此数据库中要多出来一列来存放salt
示例如下:
public class TestShiroMD5 {
public static void main(String[] args) {
Md5Hash md5Hash1 = new Md5Hash("123");
System.out.println(md5Hash1);
//使用MD5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash("123", "s6%*d", 1024);
System.out.println(md5Hash.toHex());
}
}
自定义md5+salt+hash的realm
/**
* 使用自定义realm 加入MD5 + salt + hash
* */
public class CustomerMd5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("身份信息:"+primaryPrincipal);
//根据身份信息 用户名 获取当前用户的角色信息,以及权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将数据库中查询的角色信息赋值给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
//将数据库中查询的权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:*");
simpleAuthorizationInfo.addStringPermission("product:create");
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取身份信息
String principal =(String) authenticationToken.getPrincipal();
//根据用户名查询数据库
if ("xiaochen".equals(principal)){
//参数1:数据库用户名 参数2:数据库MD5+salt之后的密码 参数3:注册的随机盐 参数4:realm的名字
return new SimpleAuthenticationInfo(principal,"2f3d8f691535a4fedc99dbc8309b6144", ByteSource.Util.bytes("s6%*d"),this.getName());
}
return null;
}
}
MD5+salt+hash的测试
public class TestCustomerMd5RealmAuthenicator {
public static void main(String[] args) {
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
CustomerMd5Realm realm = new CustomerMd5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5"); //使用的算法的名字
- List item
hashedCredent
- List item
ialsMatcher.setHashIterations(1024); //散列次数
realm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(realm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
try {
subject.login(token); //用户认证
System.out.println("认证状态: "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:用户名不存在~");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码错误");
}
//认证用户进行授权
if (subject.isAuthenticated()){
//1.基于角色的权限认证
System.out.println(subject.hasRole("super"));
//基于多角色权限控制
System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user")));
//是否具有其中一个角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));
for (boolean aBoolean : booleans) {
System.out.println(aBoolean);
}
System.out.println("============");
//基于权限字符串的访问控制 资源标识符:操作:资源类型
System.out.println("权限:"+subject.isPermitted("user:update:01")); //判断是否有某个权限
System.out.println("权限:"+subject.isPermitted("product:create:02")); //判断是否有某个权限
//分别具有那些权限
subject.isPermitted("user:*:01","order:*:10");
for (boolean aBoolean : booleans) {
System.out.println(aBoolean);
}
//同时具有哪些权限
boolean permittedAll = subject.isPermittedAll("user:*:01", "product:create:01");
System.out.println(permittedAll);
}
}
}
授权
简介
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
关键对象:
- Who,即主体(Subject),主体需要访问系统中的资源。
- What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
- How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
授权流程
授权方式
基于角色的访问控制:
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
if (subject.isAuthenticated()){
//1.基于角色的权限认证
if{
subject.hasRole("super")
}
//基于多角色权限控制
if{
subject.hasAllRoles(Arrays.asList("admin", "user"))
}
//是否具有其中一个角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));
for (boolean aBoolean : booleans) {
System.out.println(aBoolean);
}
}
基于资源的控制访问
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
if(subject.isPermission("user:update:01")){ //资源实例
//对01用户进行修改
}
if(subject.isPermission("user:update:*")){ //资源类型
//对所有用户进行修改
}
权限字符串
- 权限字符串的规则是:资源标识符:操作:资源实例标识符,
- 意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
- 用户创建权限:user:create,或user:create:*
- 用户修改实例001的权限:user:update:001
- 用户实例001的所有权限:user:*:001
shiro中授权编程实现方式
编程示
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
注解示
@RequiresRoles("admin")
public void hello() {
//有权限
}
//必须同时属于admin和user角色
@RequiresRoles({"admin","user"})
//
@RequiresRoles(value = {"admin","user"},logical=Logical.OR)
标签示
//JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
<shiro:hasRole name="admin">
<!— 有权限—>
</shiro:hasRole>
//注意: Thymeleaf 中使用shiro需要额外集成!
自定义授权
realm的实现
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("身份信息:"+primaryPrincipal);
//根据身份信息 用户名 获取当前用户的角色信息,以及权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将数据库中查询的角色信息赋值给权限对象
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
//将数据库中查询的权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user:*:*");
simpleAuthorizationInfo.addStringPermission("product:create");
return simpleAuthorizationInfo;
}
授权的实现
public class TestCustomerMd5RealmAuthenicator {
public static void main(String[] args) {
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
CustomerMd5Realm realm = new CustomerMd5Realm();
//设置realm使用hash凭证匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5"); //使用的算法的名字
hashedCredentialsMatcher.setHashIterations(1024); //散列次数
realm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(realm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen", "123");
try {
subject.login(token); //用户认证
System.out.println("认证状态: "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:用户名不存在~");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码错误");
}
//认证用户进行授权
if (subject.isAuthenticated()){
//1.基于角色的权限认证
System.out.println(subject.hasRole("super"));
//基于多角色权限控制
System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user")));
//是否具有其中一个角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));
for (boolean aBoolean : booleans) {
System.out.println(aBoolean);
}
System.out.println("============");
//基于权限字符串的访问控制 资源标识符:操作:资源类型
System.out.println("权限:"+subject.isPermitted("user:update:01")); //判断是否有某个权限
System.out.println("权限:"+subject.isPermitted("product:create:02")); //判断是否有某个权限
//分别具有那些权限
subject.isPermitted("user:*:01","order:*:10");
for (boolean aBoolean : booleans) {
System.out.println(aBoolean);
}
//同时具有哪些权限
boolean permittedAll = subject.isPermittedAll("user:*:01", "product:create:01");
System.out.println(permittedAll);
}
}
}
最后,我们在做项目的时候只要在写死数据的位置改为从数据库中调用即可完成登录时的授权认证