zoukankan      html  css  js  c++  java
  • Shiro 安全框架

    1. Shiro安全框架简介

      1. Shiro概述

    Shiro是apache旗下一个开源安全框架(http://shiro.apache.org/),它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。

    用户在进行资源访问时,要求系统要对用户进行权限控制,其具体流程如图-1所示:

     

    1. Shiro概要架构

    在概念层面,Shiro 架构包含三个主要的理念,如图-2所示:

     

    其中:

    1. Subject :主体对象,负责提交用户认证和授权信息。

    2. SecurityManager:安全管理器,负责认证,授权等业务实现。

    3. Realm:领域对象,负责从数据层获取业务数据。

      1. Shiro详细架构

      Shiro框架进行权限管理时,要涉及到的一些核心对象,主要包括:认证管理对象,授权管理对象,会话管理对象,缓存管理对象,加密管理对象以及Realm管理对象(领域对象:负责处理认证和授权领域的数据访问题)等,其具体架构如图-3所示:

       

      其中:

      1. Subject(主体):与软件交互的一个特定的实体(用户、第三方服务等)。

      2. SecurityManager(安全管理器) :Shiro 的核心,用来协调管理组件工作。

      3. Authenticator(认证管理器):负责执行认证操作。

      4. Authorizer(授权管理器):负责授权检测。

      5. SessionManager(会话管理):负责创建并管理用户 Session 生命周期,提供一个强有力的 Session 体验。

      6. SessionDAO:代表 SessionManager 执行 Session 持久(CRUD)动作,它允许任何存储的数据挂接到 session 管理基础上。

      7. CacheManager(缓存管理器):提供创建缓存实例和管理缓存生命周期的功能。

      8. Cryptography(加密管理器):提供了加密方式的设计及管理。

      9. Realms(领域对象):是shiro和你的应用程序安全数据之间的桥梁。

        1. Shiro框架认证拦截实现(filter)

          1. Shiro基本环境配置

            1. 添加shiro依赖

        实用spring整合shiro时,需要在pom.xml中添加如下依赖:

        <dependency>
           <groupId>org.apache.shiro</groupId>
           <artifactId>shiro-spring</artifactId>
           <version>1.5.2</version>
        </dependency>
        1. Shiro核心对象配置

        基于SpringBoot 实现的项目中,没有提供shiro的自动化配置,需要我们自己配置。

        第一步:创建SpringShiroConfig类。关键代码如下:

        package com.cy.pj.common.config;
        /**@Configuration 注解描述的类为一个配置对象,
         * 此对象也会交给spring管理
         */
        @Configuration
        public class SpringShiroConfig {
         
        }

        第二步:在Shiro配置类中添加SecurityManager配置(这里一定要使用org.apache.shiro.mgt.SecurityManager这个接口对象),关键代码如下:

        @Bean
        public SecurityManager securityManager() {
                 DefaultWebSecurityManager sManager=
                 new DefaultWebSecurityManager();
                 return sManager;
        }

        第三步: 在Shiro配置类中添加ShiroFilterFactoryBean对象的配置。通过此对象设置资源匿名访问、认证访问。关键代码如下

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory (
                 SecurityManager securityManager,) {
             ShiroFilterFactoryBean sfBean=
             new ShiroFilterFactoryBean();
             sfBean.setSecurityManager(securityManager);
             //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)
             LinkedHashMap<String,String> map= new LinkedHashMap<>();
             //静态资源允许匿名访问:"anon"
             map.put("/bower_components/**","anon");
             map.put("/build/**","anon");
             map.put("/dist/**","anon");
             map.put("/plugins/**","anon");
             //除了匿名访问的资源,其它都要认证("authc")后访问
             map.put("/**","authc");
             sfBean.setFilterChainDefinitionMap(map);
             return sfBean;
         }

    其配置过程中,对象关系如下图-4所示:

     

    1. Shiro登陆页面呈现

      1. 服务端Controller实现

    • 业务描述及设计实现

    当服务端拦截到用户请求以后,判定此请求是否已经被认证,假如没有认证应该先跳转到登录页面。

    • 关键代码分析及实现.

    第一步:在PageController中添加一个呈现登录页面的方法,关键代码如下:

    @RequestMapping("doLoginUI")
    public String doLoginUI(){
            return "login";
    }

    第二步:修改SpringShiroConfig类中shiroFilterFactorybean的配置,添加登陆url的设置。关键代码见sfBean.setLoginUrl("/doLoginUI")   第7行  部分。

     1 @Bean
     2 public ShiroFilterFactoryBean shiroFilterFactory (
     3              @Autowired SecurityManager securityManager) {
     4          ShiroFilterFactoryBean sfBean=
     5          new ShiroFilterFactoryBean();
     6          sfBean.setSecurityManager(securityManager);
     7  sfBean.setLoginUrl("/doLoginUI");
     8 //定义map指定请求过滤规则(哪些资源允许匿名访问,
     9 哪些必须认证访问)
    10          LinkedHashMap<String,String> map=
    11                  new LinkedHashMap<>();
    12          //静态资源允许匿名访问:"anon"
    13          map.put("/bower_components/**","anon");
    14          map.put("/modules/**","anon");
    15          map.put("/dist/**","anon");
    16          map.put("/plugins/**","anon");
    17          //除了匿名访问的资源,其它都要认证("authc")后访问
    18          map.put("/**","authc");
    19          sfBean.setFilterChainDefinitionMap(map);
    20          return sfBean;
    21 }
    1. Shiro框架认证业务实现

      1. 认证流程分析

    身份认证即判定用户是否是系统的合法用户,用户访问系统资源时的认证(对用户身份信息的认证)流程图-5所示:

     

    步骤1:应用程序代码调用该Subject.login方法,并传入AuthenticationToken表示最终用户的主体和凭据的构造实例。

    第2步Subject实例(通常是一个DelegatingSubject(或子类))SecurityManager通过调用来委托应用程序实例securityManager.login(token)从此处开始实际的身份验证工作。

    步骤3SecurityManager作为基本的“伞”组件,接收令牌并Authenticator通过调用来简单地委派给其内部实例authenticator.authenticate(token)这几乎总是一个ModularRealmAuthenticator实例,它支持Realm在身份验证期间协调一个或多个实例。ModularRealmAuthenticator本质上提供一个PAM为Apache四郎(其中每个样式的范例Realm是在PAM术语一个“模块”)。

    步骤4:如果Realm为该应用程序配置了多个,则该ModularRealmAuthenticator实例将Realm使用其configureed 发起多身份验证尝试AuthenticationStrategyRealms调用进行身份验证之前,期间和之后,将调用,AuthenticationStrategy以允许它对每个Realm的结果做出反应。我们将AuthenticationStrategies尽快覆盖

    第5步Realm咨询每个配置,以查看是否supports已提交AuthenticationToken如果是这样,支持的Realm getAuthenticationInfo方法将与Submitted一起调用tokengetAuthenticationInfo方法有效地表示针对该特定对象的单个身份验证尝试Realm我们将Realm很快介绍身份验证行为。

    简单说就是

    1. 系统调用subject的login方法将用户信息提交给SecurityManager

    2. SecurityManager将认证操作委托给认证器对象Authenticator

    3. Authenticator将用户输入的身份信息传递给Realm。 

    4. Realm访问数据库获取用户信息然后对信息进行封装并返回。

    5. Authenticator 对realm返回的信息进行身份认证。


      1. 认证服务端实现

        1. 核心业务分析

      认证业务API处理流程分析,如图-6所示:

       

      • 业务描述及设计实现。

      在用户数据层对象SysUserDao中,按特定条件查询用户信息,并对其进行封装。

      • 关键代码分析及实现。

      在SysUserDao接口中,添加根据用户名获取用户对象的方法,关键代码如下:

      SysUser findUserByUserName(String username)。
      1. Mapper元素定义

      • 业务描述及设计实现。

      根据SysUserDao中定义的方法,在SysUserMapper文件中添加元素定义。

      • 关键代码分析及实现。

      基于用户名获取用户对象的方法,关键代码如下:

      <select id="findUserByUserName"
                 resultType="com.cy.pj.sys.entity.SysUser">
            select *
            from sys_users  
            where username=#{username}
         </select>
      1. Service接口及实现

      • 业务描述及设计实现。

      本模块的业务在Realm类型的对象中进行实现,我们编写realm时,要继承

      AuthorizingRealm并重写相关方法,完成认证及授权业务数据的获取及封装。

      • 关键代码分析及实现。

      第一步:定义ShiroUserRealm类,关键代码如下:

      package com.cy.pj.sys.service.realm;
      @Service
      public class ShiroUserRealm extends AuthorizingRealm {
       
          @Autowired
          private SysUserDao sysUserDao;
              
          /**
           * 设置凭证匹配器(与用户添加操作使用相同的加密算法)
           */
          @Override
          public void setCredentialsMatcher(
                CredentialsMatcher credentialsMatcher) {
              //构建凭证匹配对象 
              HashedCredentialsMatcher cMatcher=
              new HashedCredentialsMatcher();
              //设置加密算法
              cMatcher.setHashAlgorithmName("MD5");
              //设置加密次数
              cMatcher.setHashIterations(1);
              super.setCredentialsMatcher(cMatcher);
          }
          /**
           * 通过此方法完成认证数据的获取及封装,系统
           * 底层会将认证数据传递认证管理器,由认证
           * 管理器完成认证操作。
           */
          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(
                  AuthenticationToken token) 
                  throws AuthenticationException {
              //1.获取用户名(用户页面输入)
              UsernamePasswordToken upToken=
              (UsernamePasswordToken)token;
              String username=upToken.getUsername();
              //2.基于用户名查询用户信息
              SysUser user=
              sysUserDao.findUserByUserName(username);
              //3.判定用户是否存在
              if(user==null)
              throw new UnknownAccountException();
              //4.判定用户是否已被禁用。
              if(user.getValid()==0)
              throw new LockedAccountException();
              
              //5.封装用户信息
              ByteSource credentialsSalt=
              ByteSource.Util.bytes(user.getSalt());
              //记住:构建什么对象要看方法的返回值
              SimpleAuthenticationInfo info=
              new SimpleAuthenticationInfo(
                      user,//principal (身份)
                      user.getPassword(),//hashedCredentials
                      credentialsSalt, //credentialsSalt
                      getName());//realName
              //6.返回封装结果
              return info;//返回值会传递给认证管理器(后续
              //认证管理器会通过此信息完成认证操作)
          }
          ....
      }

      第二步:对此realm,需要在SpringShiroConfig配置类中,注入给SecurityManager对象,修改securityManager方法,见黄色背景部分,例如:

      1 @Bean
      2 public SecurityManager securityManager(Realm realm) {
      3          DefaultWebSecurityManager sManager=
      4          new DefaultWebSecurityManager();
      5          sManager.setRealm(realm);
      6          return sManager;
      7 }
      1. Controller 类实现

      • 业务描述及设计实现。

      在此对象中定义相关方法,处理客户端的登陆请求,例如获取用户名,密码等然后提交该shiro框架进行认证。

      • 关键代码分析及实现。

      第一步:在SysUserController中添加处理登陆的方法。关键代码如下:

       1    @RequestMapping("doLogin")
       2        public JsonResult doLogin(String username,String password){
       3            //1.获取Subject对象
       4            Subject subject=SecurityUtils.getSubject();
       5            //2.通过Subject提交用户信息,交给shiro框架进行认证操作
       6            //2.1对用户进行封装
       7            UsernamePasswordToken token=
       8            new UsernamePasswordToken(
       9                    username,//身份信息
      10                    password);//凭证信息
      11            //2.2对用户信息进行身份认证
      12            subject.login(token);
      13            //分析:
      14            //1)token会传给shiro的SecurityManager
      15            //2)SecurityManager将token传递给认证管理器
      16            //3)认证管理器会将token传递给realm
      17            return new JsonResult("login ok");
      18        }

      第二步:修改shiroFilterFactory的配置,对/user/doLogin这个路径进行匿名访问的配置,查看如下黄色标记部分的代码:

      @Bean
      public ShiroFilterFactoryBean shiroFilterFactory (
                   SecurityManager securityManager) {
               ShiroFilterFactoryBean sfBean=
               new ShiroFilterFactoryBean();
               sfBean.setSecurityManager(securityManager);
               //假如没有认证请求先访问此认证的url
               sfBean.setLoginUrl("/doLoginUI");
               //定义map指定请求过滤规则(哪些资源允许匿名访问,哪些必须认证访问)
               LinkedHashMap<String,String> map=
                       new LinkedHashMap<>();
               //静态资源允许匿名访问:"anon"
               map.put("/bower_components/**","anon");
               map.put("/build/**","anon");
               map.put("/dist/**","anon");
               map.put("/plugins/**","anon");
       
        map.put("/user/doLogin","anon");                       //authc表示,除了匿名访问的资源,其它都要认证("authc")后才能访问访问
               map.put("/**","authc");
               sfBean.setFilterChainDefinitionMap(map);
               return sfBean;
           }

      第三步:当我们在执行登录操作时,为了提高用户体验,可对系统中的异常信息进行处理,例如,在统一异常处理类中添加如下方法:

      @ExceptionHandler(ShiroException.class) 
         @ResponseBody
          public JsonResult doHandleShiroException(
                  ShiroException e) {
              JsonResult r=new JsonResult();
              r.setState(0);
              if(e instanceof UnknownAccountException) {
                  r.setMessage("账户不存在");
              }else if(e instanceof LockedAccountException) {
                  r.setMessage("账户已被禁用");
              }else if(e instanceof IncorrectCredentialsException) {
                  r.setMessage("密码不正确");
              }else if(e instanceof AuthorizationException) {
                  r.setMessage("没有此操作权限");
              }else {
                  r.setMessage("系统维护中");
              }
              e.printStackTrace();
              return r;
          }
  • 相关阅读:
    简单说说数据库表设计的三种范式
    存储过程简单的动态订单号
    Asp.Net页面生命周期
    jq 小笔记,上传判断其格式
    吃一垫长一智
    离散事件模拟
    二叉树查找树
    冷暖自知
    基督徒的人生箴言
    迷宫寻路
  • 原文地址:https://www.cnblogs.com/wangjincai/p/13380687.html
Copyright © 2011-2022 走看看