zoukankan      html  css  js  c++  java
  • shiro框架学习-5-自定义Realm

    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", "123");
            userInfoMap.put("xdclass", "456");
        }
    
        //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()

     

  • 相关阅读:
    关于vue2.x使用axios以及http-proxy-middleware代理处理跨域的问题
    vue-resource的使用
    从头开始开发一个vue幻灯片组件
    图与例解读Async/Await
    浅谈web缓存
    APICloud框架——总结一下最近开发APP遇到的一些问题 (三)
    编写现代 CSS 代码的 20 个建议
    仿微信联系人列表滑动字母索引
    初来乍到,向各位大牛虚心学习
    转发80端口的脚本
  • 原文地址:https://www.cnblogs.com/enjoyjava/p/12080135.html
Copyright © 2011-2022 走看看