声明
博文的内容学习自B站UP主 编程不良人 (UID:352224540)的教程,感谢能有这么好的教程
https://www.bilibili.com/video/BV1uz4y197Zm?p=9
Shiro
shiro是一个基于java的安全框架,主要提供认证与授权服务。
认证——概念
在shiro框架中,系统的访问者被称为 Subject (主体)
- Subject 的用户名属于 Principal (身份信息)
- Subject 的密码属于 Credential(凭证信息)
- Token (令牌) = Principal + Credential + ...
每次访问系统时,Subject 都会带着它的令牌给 Shiro 进行 Authentication (身份认证),
Shiro 会派出 SecurityManager(安全管理器) 来处理 Subject 的令牌,
SecurityManager 会到 Realm(数据域)中核实令牌的信息,如果核实通过,意味着认证成功了,则被允许进入系统
授权——概念
当 Subject(主体) 通过了认证,进入到系统之后,在访问资源(比如说增删改查的功能)之前,还需要被 Shiro 验证 Subject 是否对某些资源拥有权限,即是否 Authorization(授权)。
授权的方式有两种:
- RBAC(Role-Based Access Control 基于角色的访问控制)
这个比较好理解,就是说什么样的角色拥有什么样的权限,例如:角色是admin时,拥有增删改查的资源的权限;而角色时user时,只有查的资源的权限。
- RBAC(Role-Based Access Control 基于资源的访问控制)
这个相对于上面的角色访问控制要细分一点(除了角色的概念),就是不仅仅要讨论资源,还有讨论什么样的资源实例。比如说查的资源有多种资源实例(查xx1,查xx2....),当用户user要查xx1时,Shiro会验证他是否拥有查权限中的查xx1的权限。在Shiro,会用 权限字符串 来表达用户与权限的关系。
"user:select:*" //拥有user的select的所有权限
"user:select:xx1" //拥有user的select xx1的权限
实操
工程沿用上一章做的自定义 MD5 Realm 例子
目录结构:
Shiro
--shiro-java
----src
------main
--------java
----------com
------------xilo
--------------realm
----------------CustomerRealm.java
----------------CustomerMD5Realm.java(MD5Realm)
--------------shiro
----------------TestAuthenticator.java
----------------TestCustomerRealmAuthenticator.java
----------------TestCustomerMD5RealmAuthenticator.java(使用自定义MD5Realm的shiro)
--------resources
----------shiro.ini
----pom.xml
--pom.xml
CustomerMD5Realm.java
package com.xilo.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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
public class CustomerMD5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String)token.getPrincipal();
System.out.println("doGetAuthenticationInfo---用户名:"+principal);
if("jack".equals(principal)){
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("jack","923391846b52b2391ba3a3cfca311ab9", ByteSource.Util.bytes("01*1334px"),this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
TestCustomerMD5RealmAuthenticator.java
package com.xilo.shiro;
import com.xilo.realm.CustomerMD5Realm;
import com.xilo.realm.CustomerRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import java.util.ArrayList;
import java.util.Arrays;
public class TestCustomerMD5RealmAuthenticator {
public static void main(String[] args) {
//1、创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2、创建Realm
CustomerMD5Realm realm = new CustomerMD5Realm();
//3、设置Realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");
//散列1024次
credentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(credentialsMatcher);
//4、给安全管理器设置【自定义realm】
securityManager.setRealm(realm);
//5、给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//6、关键对象 Subject 主体
Subject subject = SecurityUtils.getSubject();
//7、创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("jack","001");
//用户登录(进行验证)
try{
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
}catch (UnknownAccountException e){
System.out.println("用户"+token.getUsername()+"不存在");
}catch (IncorrectCredentialsException e){
System.out.println("用户"+token.getUsername()+"密码错误");
}catch (Exception e){
e.printStackTrace();
}
}
}
当我们在完成 Subject 身份验证之后,就要开始验证用户的授权情况了,首先谈基于角色的访问控制,我们先查一下当前 Subject 拥有那些角色
package com.xilo.shiro;
import com.xilo.realm.CustomerMD5Realm;
import com.xilo.realm.CustomerRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import java.util.ArrayList;
import java.util.Arrays;
public class TestCustomerMD5RealmAuthenticator {
public static void main(String[] args) {
//1、创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2、创建Realm
CustomerMD5Realm realm = new CustomerMD5Realm();
//3、设置Realm使用hash凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");
//散列1024次
credentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(credentialsMatcher);
//4、给安全管理器设置【自定义realm】
securityManager.setRealm(realm);
//5、给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//6、关键对象 Subject 主体
Subject subject = SecurityUtils.getSubject();
//7、创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("jack","001");
//用户登录(进行验证)
try{
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
}catch (UnknownAccountException e){
System.out.println("用户"+token.getUsername()+"不存在");
}catch (IncorrectCredentialsException e){
System.out.println("用户"+token.getUsername()+"密码错误");
}catch (Exception e){
e.printStackTrace();
}
//验证授权
if(subject.isAuthenticated()){
//查看 subject 是否有 admin 角色
System.out.println(subject.hasRole("admin"));
//查看 subject 是否有 user1 和 user2 角色
System.out.println(subject.hasAllRoles(Arrays.asList("user1","user2")));
//查看 subject 是否有 admin 或 user1 或 user2 角色
boolean[] booleans = subject.hasRoles(Arrays.asList("admin","user1","user2"));
for(boolean bo : booleans){
System.out.println(bo);
}
}
}
}
运行:
doGetAuthenticationInfo---用户名:jack
认证状态:true
false
false
false
false
false
什么角色都没有,因为当前 Subject 没有被授权任何角色,在上一章中我们知道当 Subject 进行认证时会进入到 Realm 中去调用 doGetAuthenticationInfo 进行身份认证相关的操作,类似的, Subject 要进行授权时,也要进入到 Realm 的 doGetAuthorizationInfo 中去进行授权认证的相关操作。下面我们可以验证一下
package com.xilo.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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
public class CustomerMD5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("doGetAuthorizationInfo---"+"进行认证:"+principalCollection.getPrimaryPrincipal());
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String)token.getPrincipal();
System.out.println("doGetAuthenticationInfo---用户名:"+principal);
if("jack".equals(principal)){
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("jack","923391846b52b2391ba3a3cfca311ab9", ByteSource.Util.bytes("01*1334px"),this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
运行之后,可以看到,每次进行角色验证时,都会调用到 Realm 中的 doGetAuthorizationInfo
doGetAuthenticationInfo---用户名:jack
认证状态:true
doGetAuthorizationInfo---进行认证:jack
false
doGetAuthorizationInfo---进行认证:jack
false
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
false
false
false
接下来,我们就模拟给 Subject 赋予角色
package com.xilo.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;
public class CustomerMD5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("doGetAuthorizationInfo---"+"进行认证:"+principalCollection.getPrimaryPrincipal());
//添加角色
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user1");
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String)token.getPrincipal();
System.out.println("doGetAuthenticationInfo---用户名:"+principal);
if("jack".equals(principal)){
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("jack","923391846b52b2391ba3a3cfca311ab9", ByteSource.Util.bytes("01*1334px"),this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
运行之后,可以查到当前 Subject 拥有 admin 和 user1的角色
doGetAuthenticationInfo---用户名:jack
认证状态:true
doGetAuthorizationInfo---进行认证:jack
true
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
false
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
true
true
false
讲完了 基于角色的访问控制 的设置,下面来说 基于角色的访问控制 的设置,也就是以 权限字符串(资源标识符:操作:资源类型) 的形式描述权限信息
我们先在 Realm 的 doGetAuthorizationInfo 给 Subject 赋值权限信息
//添加角色
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user1");
//将数据库中的查询权限信息赋值给权限对象
simpleAuthorizationInfo.addStringPermission("user1:select:xx1");
simpleAuthorizationInfo.addStringPermission("user1:create:xx2");
simpleAuthorizationInfo.addStringPermission("user1:delete:*");
然后我们在 TestCustomerMD5RealmAuthenticator 查询 Subject 是否拥有该资源权限
//基于权限字符串的访问控制(资源标识符:操作:资源类型)
System.out.println("权限:"+subject.isPermitted("user1:select:xx1"));
System.out.println("权限:"+subject.isPermittedAll("user1:select:xx1","user1:create:xx1"));
booleans = subject.isPermitted("user1:select:xx1","user1:create:xx1","user1:delete:*");
for(boolean bo : booleans){
System.out.println(bo);
}
运行结果:
doGetAuthenticationInfo---用户名:jack
认证状态:true
doGetAuthorizationInfo---进行认证:jack
true
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
false
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
true
true
false
doGetAuthorizationInfo---进行认证:jack
权限:true
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
权限:false
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
doGetAuthorizationInfo---进行认证:jack
true
false
true
Process finished with exit code 0
到此为止,基于javaSE使用Shiro的流程已经结束了,虽然还没用到数据库,只是模拟的数据,但是对于Shiro的运行基本流程有了个整体清晰的认识,后面将会整合到SpringBoot去做实操。