zoukankan      html  css  js  c++  java
  • Shiro 中的 Realm

    前言

    之前写项目用了 Shiro 框架,来进行安全验证以及权限管理。当时项目赶得急,没怎么深入了解,只能说能跑能改,不过在使用的过程中发现 Shiro 确实很优秀。现在回过头来学习原理,读读源码,深入的学习下。·

    本篇博文主要写的是关于使用 Shiro 起步时最重要的一块,找了一些资料,力求写得简单明了。

    简介

    Realm:域,Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro 会从应用配置的 Realm 中查找用户及其权限信息。从这个意义上讲,Realm 实质上是一个安全相关的 DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给 Shiro 。当配置 Shiro时,你必须至少指定一个 Realm ,用于认证和(或)授权。配置多个 Realm 是可以的,但是至少需要一个。
    Shiro 内置了可以连接大量安全数据源(又名目录)的 Realm,如 LDAP、关系数据库(JDBC)、类似 INI 的文本配置资源以及属性文件等。如果缺省的 Realm 不能满足需求,你还可以插入代表自定义数据源的自己的 Realm 实现。

    功能

    Realm能做的工作主要有以下几个方面:

    • 身份验证(getAuthenticationInfo 方法)验证账户和密码,并返回相关信息

    • 权限获取(getAuthorizationInfo 方法) 获取指定身份的权限,并返回相关信息

    • 令牌支持(supports方法)判断该令牌(Token)是否被支持

      令牌有很多种类型,例如:HostAuthenticationToken(主机验证令牌),UsernamePasswordToken(账户密码验证令牌)

    这里主来说明一下关于前两点验证方面的逻辑,因为令牌一般用的都是 UsernamePasswordToken,哪怕用 HostAuthenticationToken,也没必要细讲,这个函数很少用到。

    身份验证

    我们看到第一个方法就是我们上面说的“验证账户和密码,并返回相关信息”的方法。从方法的名字上看,只有取得验证信息的意思,其实这里面还包括了进行验证的逻辑。
    看Javadoc,这个方法的作用是:根据传进来的 Token,返回用户的验证信息。下面说明一下 Token 和 用户验证信息 。

    • Token:就是要拿来进行验证的信息,例如:如果是 UsernamePasswordToken 的话,这个 Token 的内容就是“用户提交的用户名和密码”。

      来看下 UsernamePasswordToken 的属性。

      public class UsernamePasswordToken implements HostAuthenticationToken, RememberMeAuthenticationToken {
        private String username;
        private char[] password;
        private boolean rememberMe;
        private String host;
      ...

    • 用户验证信息:就是用户验证通过后,返回给系统的信息。例如:用户登录验证的话,一般来说,返回给系统的“用户验证信息”就应该是这个用户的“用户名和密码”。但也可以返回其它信息,例如返回用户的“邮箱地址和登录密码”信息,做为“用户验证信息”。 那么返回给谁呢,Shiro 中的三大组件之一的 Subject。

      不细谈,这么说吧,Subject:即“当前操作用户”。但是,在 Shiro 中,Subject 这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是 Shiro 的“用户”概念。

    上面说了“根据传进来的Token”和“返回用户的验证信息”,但没有说验证的过程,这个过程也是在这个方法中进行。我们看一下源码:

    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        // doGetAuthenticationInfo方法的内容,由各个子类来实现。
        // 主要是用来取得我们保存的“用户验证信息”,例如DB里保存的密码(具体看JdbcRealm的方法实现)
        if (info == null) {
            info = doGetAuthenticationInfo(token);
            ...
        }
        // 在这里,把用户提交的信息(Token)和我们保存的“用户验证信息”进行比较
        // 如果不通过,直接抛出定义好的异常。
        if (info != null) {
            assertCredentialsMatch(token, info);
        } else {
    
        return info;
    }

    权限获取

    “权限验证”的处理,是由接口定义的。但“验证是否有访问权限”的逻辑,则是由类定义的。定义的类为:AuthorizingRealm ,在这个类中有个getAuthorizationInfo 方法。这个方法和getAuthenticationInfo 方法的处理流程有点像:

    • 验证是否有指定的权限

    • 返回用户的权限信息

    调用时机

    下面看一个实际登录的 Controller 的例子:

    @Controller
    public class LoginController {
    
        //登录跳转
        @RequestMapping(value = "/login", method = {RequestMethod.GET})
        public String loginUI() throws Exception {
            return "../../login";
        }
    
        //登录跳转
        @RequestMapping(value = "/sxqy", method = {RequestMethod.GET})
        public String loginUI2() throws Exception {
            return "../../login";
        }
    
        //重点!!!!!!
        //登录表单处理
        @RequestMapping(value = "/login", method = {RequestMethod.POST})
        public String login(ViewEmployeeMiPsd viewEmployeeMiPsd) throws Exception {
    
            //Shiro实现登录
            UsernamePasswordToken token = new UsernamePasswordToken(viewEmployeeMiPsd.getCode(),
                    viewEmployeeMiPsd.getPsd());
            Subject subject = SecurityUtils.getSubject();
            //如果获取不到用户名就是登录失败,但登录失败的话,会直接抛出异常
            try{
                //重点!!!!!!
                //getAuthenticationInfo 执行时机
                subject.login(token);
            }catch (Exception e){
                e.printStackTrace();
            }
    
            //重点!!!!!!
             //getAuthorizationInfo  执行时机 -- subject.hasRole()
            if (subject.hasRole("admin")) {
                return "redirect:/admin/showComputerProblems";
            } else if (!subject.hasRole("admin")) {
                return "redirect:/normal/showComputerProblems";
            }
    
            return "/login";
        }
    
    }

    不过,getAuthorizationInfo 的执行调用方式包括上面的总共有三个:

    1. subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去调用这个是否有什么角色或者是否有什么权限的时候;
    2. @RequiresRoles(“admin”) :在方法上加注解的时候;
    3. [@shiro.hasPermission name = “admin”][/@shiro.hasPermission]:在页面上加shiro标签的时候,即进这个页面的时候扫描到有这个标签的时候。

    实现

    需要注意的是,在 Shiro 实际使用中,我们是肯定会自定义一个 Realm 类的。

    从上面的功能说明可以看出来,在权限控制中比较重要的验证(登录或权限)逻辑,都是在Realm中做的。Realm的类继承如下:

    Realm类继承图

    不同的继承,需要实现不同的方法。继承了 AuthorizingRealm 的类,都要实现上面说的 getAuthenticationInfogetAuthorizationInfo 方法,来完成身份验证和权限获取。但如果自定义的 Realm 类只实现 Realm 接口的话,只需要 getAuthenticationInfo 方法就可以。下面看一个只实现 Realm 接口的自定义 Realm:

    public class MyRealm1 implements Realm {  
        @Override  
        public String getName() {  
            return "myrealm1";  
        }  
        @Override  
        public boolean supports(AuthenticationToken token) {  
            //仅支持UsernamePasswordToken类型的Token  
            return token instanceof UsernamePasswordToken;   
        }  
        @Override  
        public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
            String username = (String)token.getPrincipal();  //得到用户名  
            String password = new String((char[])token.getCredentials()); //得到密码  
            if(!"zhang".equals(username)) {  
                throw new UnknownAccountException(); //如果用户名错误  
            }  
            if(!"123".equals(password)) {  
                throw new IncorrectCredentialsException(); //如果密码错误  
            }  
            //如果身份认证验证成功,返回一个AuthenticationInfo实现;  
            return new SimpleAuthenticationInfo(username, password, getName());  
        }  
    }   

    但是在使用中基本上都会对账户进行权限管理,下面看一个继承 AuthorizingRealm 的自定义 Realm:

    @Component
    public class LoginRealm extends AuthorizingRealm{
    
        @SuppressWarnings("SpringJavaAutowiringInspection")//忽略警告,下同
        @Resource(name = "roleServiceImpl")
        private RoleService roleService;
    
        @SuppressWarnings("SpringJavaAutowiringInspection")//忽略警告,下同
        @Resource(name = "viewEmployeeMiPsdServiceImpl")
        private ViewEmployeeMiPsdService viewEmployeeMiPsdService;
    
    
    
        /**
         *      获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息
         *      当调用权限验证时,就会调用此方法
         */
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
            String code = (String) getAvailablePrincipal(principalCollection);
    
            Role role = null;
            ViewEmployeeMiPsd viewEmployeeMiPsd = null;
            viewEmployeeMiPsd = viewEmployeeMiPsdService.findByCode(code);
            //通过用户名从数据库获取角色权限集
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            Set<String> r = new HashSet<>();
            if (role != null) {
                String[] roles = role.getRolename().split("\+");
                for(int i = 0;i < roles.length; i++){
                    r.add(roles[i].toString());
                }
                //放入该用户权限信息
                info.setRoles(r);
            }
    
            return info;
        }
    
        /**
         * 在这个方法中,进行身份验证
         * login时调用
         */
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //工号
            String code = (String) token.getPrincipal();
            //密码
            String password = new String((char[])token.getCredentials());
    
            ViewEmployeeMiPsd viewEmployeeMiPsd = null;
            viewEmployeeMiPsd = viewEmployeeMiPsdService.findByCode(code);
    
            if (viewEmployeeMiPsd == null) {
                //没有该用户
                throw new UnknownAccountException();
            } else if (!password.equals(viewEmployeeMiPsd.getPsd())) {
                //密码错误
                throw new IncorrectCredentialsException();
            }
    
            //身份验证通过,返回一个身份信息
            AuthenticationInfo aInfo = new SimpleAuthenticationInfo(code,password,getName());
    
            return aInfo;
        }
    }

    参考

    1. 关于Shiro中的Realmhotdust
    2. 关于何时执行shiro AuthorizingRealm 里的 doGetAuthenticationInfo与doGetAuthorizationInfofj200821
    3. shiro – 百度百科
  • 相关阅读:
    Maidsafe-去中心化互联网白皮书
    The Top 20 Cybersecurity Startups To Watch In 2021 Based On Crunchbase
    Top 10 Blockchain Security and Smart Contract Audit Companies
    The 20 Best Cybersecurity Startups To Watch In 2020
    Blockchain In Cybersecurity: 11 Startups To Watch In 2019
    004-STM32+BC26丨260Y基本控制篇(阿里云物联网平台)-在阿里云物联网平台上一型一密动态注册设备(Android)
    涂鸦开发-单片机+涂鸦模组开发+OTA
    000-ESP32学习开发-ESP32烧录板使用说明
    03-STM32+Air724UG远程升级篇OTA(阿里云物联网平台)-STM32+Air724UG使用阿里云物联网平台OTA远程更新STM32程序
    03-STM32+Air724UG远程升级篇OTA(自建物联网平台)-STM32+Air724UG实现利用http/https远程更新STM32程序(TCP指令,单片机程序检查更新)
  • 原文地址:https://www.cnblogs.com/Sherlock-J/p/12925964.html
Copyright © 2011-2022 走看看