zoukankan      html  css  js  c++  java
  • Shiro【自定义Realm实战】

    一、前言

    虽然 Shiro 给我们提供了很多内置的 Realm,但在企业开发中,这往往不适用于项目中。

    所以,都需要自定义 Realm 来使用。

    二、步骤

    1. 创建一个类 ,继承 AuthorizingRealm

    ​ (继承关系:自定义Realm->AuthorizingRealm->AuthenticatingRealm->CachingRealm->Realm)

    1. 重写认证方法 doGetAuthenticationInfo

    ​ 重写授权方法 doGetAuthorizationInfo

    1. 在方法中分别返回对应的对象

    ​ SimpleAuthenticationInfo :代表该用户的认证信息
    ​ SimpleAuthorizationInfo:代表用户角色权限信息

    三、代码实现

    注:

    本文的案例中没有从数据库中查询真实的数据,所以使用集合模拟的数据。

    在实际开发中,只需要 service 层写好的方法从数据库查询数据即可。

    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;
    
    /**
     * 自定义的 Redlm 步骤:
     * 1.继承AuthorizingRealm
     * 2.重写授权方法 doGetAuthorizationInfo
     *   重写认证方法 doGetAuthenticationInfo
     * 3.在方法中分别返回对应的对象
     *   SimpleAuthorizationInfo:代表用户角色权限信息
         SimpleAuthenticationInfo :代表该用户的认证信息
     */
    public class CustomRealm extends AuthorizingRealm {
    
        private final Map<String,String> userInfoMap = new HashMap<>();
        {
            userInfoMap.put("jack","123");
            userInfoMap.put("xdclass","456");
        }
    
        //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);
        }
    
        //role -> permission
        private final Map<String,Set<String>> permissionMap = 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");
    
            permissionMap.put("jack",set1);
            permissionMap.put("xdclass",set2);
        }
    
        /**
         * 执行认证操作时,SecurityManager 会执行此方法
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            // UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken
            // 在实际开发的时候,authenticationToken是需要前端传递过来的用户输入的账号和密码
    
            // 获取用户唯一标识(俗称“账号”)
            String name = (String)authenticationToken.getPrincipal();
            // 根据账号从数据库中获取密码
            String pwd = getPwdByUserNameFromDB(name);
            if( pwd == null || "".equals(pwd)){
                return null;
            }
            // 将账号和密码封装到 SimpleAuthenticationInfo 对象中进行返回
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, pwd, this.getName());
    
            return simpleAuthenticationInfo;
        }
    
        /**
         * 执行授权操作时,SecurityManager 会执行此方法
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            // 获取“账号”
            String name = (String)principals.getPrimaryPrincipal();
    
            // 模拟从数据库中获取角色
            Set<String> roles = getRolesByNameFromDB(name);
    
            // 模拟从数据库中获取权限
            //(另外,此处本来是应该的根据角色获取权限的,但因为本来也只是模拟,所以为了简便,则直接根据账号获取的权限)
            Set<String> permissions = getPermissionsByNameFromDB(name);
    
            // 将角色和权限等信息封装到 SimpleAuthorizationInfo 进行返回
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.setRoles(roles);
            simpleAuthorizationInfo.setStringPermissions(permissions);
    
            return simpleAuthorizationInfo;
        }
    
        /**
         * 模拟从数据库中获取密码
         * @param name
         * @return
         */
        private String getPwdByUserNameFromDB(String name) {
            return userInfoMap.get(name);
        }
    
        /**
         * 模拟从数据库获取用户角色集合
         * @param name
         * @return
         */
        private Set<String> getRolesByNameFromDB(String name) {
            return roleMap.get(name);
    
        }
    
        /**
         *  模拟从数据库获取权限集合
         * @param name
         * @return
         */
        private Set<String> getPermissionsByNameFromDB(String name) {
            return permissionMap.get(name);
        }
    
    }
    

    测试代码如下:

    /**
     * 测试自定义的Realm
     */
    public class CustomRealmTest {
    
        private CustomRealm customRealm = new CustomRealm();
        private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    
        @Before
        public void init(){
            //构建环境
            defaultSecurityManager.setRealm(customRealm);
            SecurityUtils.setSecurityManager(defaultSecurityManager);
        }
    
        @Test
        public void testAuthentication() {
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");
    
            Subject subject = SecurityUtils.getSubject();
            subject.login(usernamePasswordToken);
    
            System.out.println(" 认证结果:"+subject.isAuthenticated());
            System.out.println(" getPrincipal=" + subject.getPrincipal());
            System.out.println("是否有对应的角色:"+subject.hasRole("role1"));
            System.out.println("是否有对应的权限:"+subject.isPermitted("video:add"));
        }
    }
    

    四、代码解析

    (一)方法

    当用户登陆的时候会调用 doGetAuthenticationInfo

    进行权限校验的时候会调用: doGetAuthorizationInfo

    (二)代码中的类(对象)
    1. UsernamePasswordToken :用来封装用户(Subject)传递过来的账号和密码

    ​ (继承体系:UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken)

    1. SimpleAuthenticationInfo :用来封装用户的认证信息

    2. SimpleAuthorizationInfo:用来封装用户的角色权限信息

    五、认证和授权流程源码分析

    在编辑器中通过 Debug 方式则能分析出认证和授权的大致流程如下:

    (一)认证
    认证流程解读:
        subject.login(usernamePasswordToken);
        DelegatingSubject->login()
        DefaultSecurityManager->login()
        AuthenticatingSecurityManager->authenticate()
        AbstractAuthenticator->authenticate()
        ModularRealmAuthenticator->doAuthenticate()
        ModularRealmAuthenticator->doSingleRealmAuthentication()
        AuthenticatingRealm->getAuthenticationInfo()
    
    (二)授权
    授权流程解读(查询角色):
        subject.checkRole("admin")
        DelegatingSubject->checkRole()
        AuthorizingSecurityManager->checkRole()
        ModularRealmAuthorizer->checkRole()
        AuthorizingRealm->hasRole()
        AuthorizingRealm->doGetAuthorizationInfo()
    
    授权流程解读(查询权限):
    subject.isPermitted("具体权限")
    	DelegatingSubject->isPermitted()
    	AuthorizingSecurityManager->isPermitted()
    	ModularRealmAuthorizer->isPermitted()
    	AuthorizingRealm->isPermitted()
    	AuthorizingRealm->getAuthorizationInfo()
    	CustomRealm->doGetAuthorizationInfo
    

    六、总结

    Realm 就是用来从数据库中获取用户的角色、权限等数据的,不要被这个名字给吓唬到。

    Java新手,若有错误,欢迎指正!

  • 相关阅读:
    IOC架构设计之ButterKnife源码&原理(二)下篇
    IOC架构设计之ButterKnife源码&原理(二)中篇
    IOC架构设计之ButterKnife源码&原理(二)上篇
    IOC架构设计之控制反转和依赖注入(一)
    RXJava之线程控制Scheduler(四)
    RXJava之变换(三)
    RXJava之扩展的观察者模式(二)
    微信小程序弹框提示绑定手环实例
    SpringBoot进阶教程 | 第四篇:整合Mybatis实现多数据源
    SpringCloud核心教程 | 第一篇: 使用Intellij中的Spring Initializr来快速构建Spring Cloud工程
  • 原文地址:https://www.cnblogs.com/Java-biao/p/14480684.html
Copyright © 2011-2022 走看看