1. 自定义Realm基础

  • 步骤:

    • 创建一个类 ,继承AuthorizingRealm->AuthenticatingRealm->CachingRealm->Realm
    • 重写授权方法 doGetAuthorizationInfo
    • 重写认证方法 doGetAuthenticationInfo
  • 方法:
    • 当用户登陆的时候会调用 doGetAuthenticationInfo
    • 进行权限校验的时候会调用: doGetAuthorizationInfo
  • 对象介绍
    • UsernamePasswordToken : 对应就是 shiro的token中有Principal和Credential

      • UsernamePasswordToken-》HostAuthenticationToken-》AuthenticationToken
    • SimpleAuthorizationInfo:代表用户角色权限信息
    • SimpleAuthenticationInfo :代表该用户的认证信息

2. 自定义realm代码:

package net.xdclass.xdclassshiro;

import org.apache.commons.lang3.StringUtils;
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 java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set; /**
* 自定义realm:继承AuthorizingRealm,重写抽象方法
*/
public class CustomRealm extends AuthorizingRealm { private final Map<String, String> userInfoMap = new HashMap<>();
{
userInfoMap.put("jack", "");
userInfoMap.put("xdclass", "");
} //role->permission 模拟数据库角色与权限的关系
private final Map<String, Set<String>> permissonMap = new HashMap<>();
{
Set<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
set1.add("video:find");
set1.add("video:buy");
set2.add("video:add");
set2.add("video:delete");
permissonMap.put("jack", set1);
permissonMap.put("xdclass", set2);
} //user->role 模拟数据库角色与权限的关系
private final Map<String, Set<String>> roleMap = new HashMap<>();
{
Set<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
set1.add("role1");
set1.add("role2");
set2.add("root");
roleMap.put("jack", set1);
roleMap.put("xdclass", set2);
} /**
* 校验权限时会调用这个方法,原理就是把角色和权限封装到SimpleAuthorizationInfo的实体中,shiro框架会帮我们进行权限的校验
* 最终org.apache.shiro.realm.AuthorizingRealm#hasRole(org.apache.shiro.subject.PrincipalCollection, java.lang.String)
* 方法会调用这个返回的simpleAuthorizationInfo
* @param principalCollection
* @return simpleAuthorizationInfo
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("权限:doGetAuthorizationInfo");
//获取用户主账户名
String name = (String) principalCollection.getPrimaryPrincipal();
//通过名称查找权限,简化操作(正常是通过名称找角色,通过角色查询对应的权限)
Set<String> permissions = getPermissonsByNameFromDB(name);
Set<String> roles = getRolesByNameFromDB(name);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
} private Set<String> getRolesByNameFromDB(String name) {
return roleMap.get(name);
} private Set<String> getPermissonsByNameFromDB(String name) {
return permissonMap.get(name);
} //用户登录的时候会调用该方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证: doGetAuthenticationInfo");
//从token中获取身份信息,token代表用户输入的信息
String name = (String) authenticationToken.getPrincipal();
//模拟从数据库读密码
String pwd = getPwdByUserNameFromDB(name); if (StringUtils.isBlank(pwd)) {
//这里判断是必须要加的,返回null,即为认证不通过!!!详见源码
return null; //匹配失败
}
/*useNmaePasswordToken中有用户的Principal和Credential
SimpleAuthorizationInfo代表用户的角色权限信息
SimpleAuthenticationInfo 代表该用户的认证信息*/

// this.getName()是获取CachingRealm的名字
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, pwd, this.getName());
return
simpleAuthenticationInfo;
} private String getPwdByUserNameFromDB(String name) {
return userInfoMap.get(name);
}
}

代码测试:

package net.xdclass.xdclassshiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Before;
import org.junit.Test; /**
* 自定义Realm操作
*/
public class QuicksStratTest5_4 { private CustomRealm customRealm = new CustomRealm();
private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
@Before
public void inint(){
//构建环境
defaultSecurityManager.setRealm(customRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
} @Test
public void testAuthentication() {
//获取当前操作的主体
Subject subject = SecurityUtils.getSubject();
//模拟用户输入账号密码
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");
subject.login(usernamePasswordToken);
System.out.println("认证 结果:" + subject.isAuthenticated());
System.out.println("getPrincipal()=" + subject.getPrincipal()); System.out.println("调用权限校验:subject.hasRole方法会调用自定义realm重写的doGetAuthorizationInfo方法");
System.out.println("用户jack是否有root角色:" + subject.hasRole("root")); //false
// 权限校验
subject.checkRole("role1");
System.out.println("用户jack是否有role1角色:" + subject.hasRole("role1")); //true
System.out.println("用户jack是否有video:find权限:" + subject.isPermitted("video:find")); //true
} }

上面代码中 ,subject.login方法很关键,是所有认证授权逻辑的入口,跟踪一下代码,可以发现subject.login方法实际调用过程如下:

总结一下,shiro认证的主要流程就是

        subject.login(usernamePasswordToken);
DelegatingSubject->login()
DefaultSecurityManager->login()
AuthenticatingSecurityManager->authenticate()
AbstractAuthenticator->authenticate()
ModularRealmAuthenticator->doAuthenticate()
ModularRealmAuthenticator->doSingleRealmAuthentication()
AuthenticatingRealm->getAuthenticationInfo()
 

需要注意的是:org.apache.shiro.authc.pam.ModularRealmAuthenticator#doAuthenticate 这个方法中,一般只配置一个realm,因此走的是doSingleRealmAuthentication 这个分支

 protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}

doSingleRealmAuthentication 分支源码如下:

protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is " + "configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
} else {
// 这里实际上调用的就是自定义realmz中重写了的getAuthenticationInfo方法,如果返回null,抛出UnknownAccountException,认证不通过
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " + "submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
} else {
return
info;
}

}
}

在源码中,获取 AuthenticationInfo 认证信息,如果为空的话,抛出异常,这也是为什么自定义realm中 doGetAuthenticationInfo 方法中判断密码不正确时要返回null的原因;

doSingleRealmAuthentication 方法中的 AuthenticationInfo info = realm.getAuthenticationInfo(token) 调用了org.apache.shiro.realm.AuthenticatingRealm 类的getAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)方法,这个方法里面,调用了一行: info = this.doGetAuthenticationInfo(token),查找其实现类,就是我们自定义realm重写的的doGetAuthenticationInfo方法。

同理,shiro授权的主要流程如下:subject.checkRole方法是授权流程的入口

   subject.checkRole("admin")
DelegatingSubject->checkRole()
AuthorizingSecurityManager->checkRole()
ModularRealmAuthorizer->checkRole()
AuthorizingRealm->hasRole()
AuthorizingRealm->doGetAuthorizationInfo()

 

最新文章

  1. table中的td内容超出隐藏
  2. CodeForces 474B E(Contest #1)
  3. MAC 系统下使用 Sublime Text 2 直接编译运行 java 代码
  4. soj4271 Love Me, Love My Permutation (DFS)
  5. Android+Sqlite 实现古诗阅读应用(一)
  6. zxing-master core编译
  7. 面试题:实现LRUCache::Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法
  8. C#中静态构造函数含义及使用
  9. List Set Map用法和区别
  10. Misra-Gries 算法
  11. poj 1254 Hansel and Grethel
  12. UNIX网络编程——名字与地址转换(gethostbyname,gethostbyaddr,getservbyname,getservbyport,getaddrinfo,getnameinfo函数)
  13. CRM实施失败?请注意这6大问题及对策!
  14. 4 扩展库Scipy
  15. .htaccess FollowSymlinks影响rewrite功能
  16. mysql系统变量
  17. mysql查询高级用法
  18. JavaScript pop()函数弹出数组最后数据
  19. EF相同Context升级为分布式事务的原因
  20. struts2+websocket报错:failed: Error during WebSocket handshake: Unexpected response code:404

热门文章

  1. ansible自动化运维管理工具
  2. Luogu P3647 [APIO2014]连珠线
  3. Head First PHP&amp;MySQl第二章代码
  4. redis 列表 数据类型
  5. 会引起全表扫描的几种SQL 以及sql优化 (转)
  6. Leaving Auction CodeForces - 749D (set,贪心,模拟)
  7. [转载]Ubuntu环境下检查CPU 的温度
  8. SSH自动登录config文件配置
  9. H5 拍照图片旋转、压缩和上传
  10. vue点击出现蒙版