Shiro的Web流程

Shiro 整合 Web-LMLPHP

Shiro整合SSM

  • 准备SSM的配置
  • 准备经典五张表(见Shiro基本使用),完成测试
  • 准备Shiro的配置
    • 核心过滤器

      <!--    配置Shiro整合web的过滤器-->
      <filter>
          <!--        默认情况下,请求到达这个过滤器,会去Spring容器中名字为filter-name的实例去处理-->
          <filter-name>shiroFilter</filter-name>
          <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      </filter>
      <filter-mapping>
          <filter-name>shiroFilter</filter-name>
          <url-pattern>/*</url-pattern>
      </filter-mapping>
      
    • 准备shiroFilter实例

      <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
      	.....
      </bean>
      
    • 注入SecurityManager,登录页面路径,过滤器链

      <!--    构建realm-->
      <bean id="realm" class="com.xxx.realm.ShiroRealm" />
      
      <!--    构建securityManager-->
      <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
          <property name="realm" ref="realm"/>
      </bean>
      
      <!--    构建ShiroFilter实例-->
      <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
          <property name="securityManager" ref="securityManager"/>
          <property name="loginUrl" value="/login.html" />
          <property name="filterChainDefinitionMap">
              <map>
                  <entry key="/login.html" value="anon" />
                  <entry key="/user/**" value="anon" />
                  <entry key="/**" value="authc" />
              </map>
          </property>
      </bean>
      
    • 将ShiroRealm的模拟数据库操作,修改为与数据库交互(见Shiro基本使用

    • 编写登录功能,并测试效果

      @PostMapping("/login")
      public String login(String username,String password){
          // 执行Shiro的认证操作
          //1. 直接基于SecurityUtils获取subject主体,不需要手动的将SecurityManager和SecurityUtils手动整合,Spring已经奥丁
          Subject subject = SecurityUtils.getSubject();
      
          //2. 发起认证
          try {
              subject.login(new UsernamePasswordToken(username,password));
              return "SUCCESS";
          } catch (UnknownAccountException exception){
              return "username fail!!!";
          } catch (IncorrectCredentialsException exception){
              return "password fail!!!";
          } catch (AuthenticationException e) {
              return "donot know...!!!";
          }
      }
      

Shiro整合SpringBoot

  • 搭建SpringBoot工程
  • 配置Shiro整合SpringBoot内容
    @Configuration
    public class ShiroConfig {
    
        @Bean
        public DefaultWebSecurityManager securityManager(ShiroRealm realm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(realm);
            return securityManager;
        }
    
        @Bean
        public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
            DefaultShiroFilterChainDefinition shiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
    
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
            filterChainDefinitionMap.put("/login.html","anon");
            filterChainDefinitionMap.put("/user/**","anon");
            filterChainDefinitionMap.put("/**","authc");
    
            shiroFilterChainDefinition.addPathDefinitions(filterChainDefinitionMap);
    
            return shiroFilterChainDefinition;
        }
    }
    

Shiro授权方式

过滤器链

public enum DefaultFilter {
	// ....
    perms(PermissionsAuthorizationFilter.class),
    roles(RolesAuthorizationFilter.class),
	// ....
}
filterChainDefinitionMap.put("/item/select","roles[超级管理员,运营]");
filterChainDefinitionMap.put("/item/delete","perms[item:delete,item:insert]");

Shiro 整合 Web-LMLPHP

自定义过滤器

  • 仿照RolesAuthorizationFilter实现自定义过滤器
    /**
     * 在要求的多个角色中,有一个满足要求,就放行
     * @author zjw
     * @description
     */
    public class RolesOrAuthorizationFilter extends AuthorizationFilter {
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
            // 获取主体subject
            Subject subject = getSubject(request, response);
            // 将传入的角色转成数组操作
            String[] rolesArray = (String[]) mappedValue;
            // 健壮性校验
            if (rolesArray == null || rolesArray.length == 0) {
                return true;
            }
            // 开始校验
            for (String role : rolesArray) {
                if(subject.hasRole(role)){
                    return true;
                }
            }
    
            return false;
        }
    }
    
  • 将自定义过滤器配置给Shiro
    @Configuration
    public class ShiroConfig {
    
        @Bean
        public DefaultWebSecurityManager securityManager(ShiroRealm realm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(realm);
            return securityManager;
        }
    
        @Bean
        public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
            DefaultShiroFilterChainDefinition shiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
    
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
            filterChainDefinitionMap.put("/login.html","anon");
            filterChainDefinitionMap.put("/user/**","anon");
            filterChainDefinitionMap.put("/item/select","rolesOr[超级管理员,运营]");
            filterChainDefinitionMap.put("/item/delete","perms[item:delete,item:insert]");
            filterChainDefinitionMap.put("/**","authc");
    
            shiroFilterChainDefinition.addPathDefinitions(filterChainDefinitionMap);
    
            return shiroFilterChainDefinition;
        }
    
        @Value("#{ @environment['shiro.loginUrl'] ?: '/login.jsp' }")
        protected String loginUrl;
    
        @Value("#{ @environment['shiro.successUrl'] ?: '/' }")
        protected String successUrl;
    
        @Value("#{ @environment['shiro.unauthorizedUrl'] ?: null }")
        protected String unauthorizedUrl;
    
    
        @Bean
        protected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {
            //1. 构建ShiroFilterFactoryBean工厂
            ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
    
            //2. 设置了大量的路径
            filterFactoryBean.setLoginUrl(loginUrl);
            filterFactoryBean.setSuccessUrl(successUrl);
            filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
    
            //3. 设置安全管理器
            filterFactoryBean.setSecurityManager(securityManager);
    
            //4. 设置过滤器链
            filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
    
            //5. 设置自定义过滤器 , 这里一定要手动的new出来这个自定义过滤器,如果使用Spring管理自定义过滤器,会造成无法获取到Subject
            filterFactoryBean.getFilters().put("rolesOr",new RolesOrAuthorizationFilter());
    
            //6. 返回工厂
            return filterFactoryBean;
        }
    }
    

注解

  • 注解进行授权时,是基于对Controller类进行代理,在前置增强中对请求进行权限校验

  • 因为咱们使用SpringBoot的测试方式,直接在Controller方法上添加注解即可

    @GetMapping("/update")
    @RequiresRoles(value = {"超级管理员","运营"})
    public String update(){
        return "item Update!!!";
    }
    
    @GetMapping("/insert")
    @RequiresRoles(value = {"超级管理员","运营"},logical = Logical.OR)
    public String insert(){
        return "item Update!!!";
    }
    
    //    @RequiresPermissions(value = "",logical = Logical.AND)
    
  • 在SpringBoot中注解默认就生效,是因为自动装配中,已经配置好了对注解的支持

    @Configuration
    @ConditionalOnProperty(name = "shiro.annotations.enabled", matchIfMissing = true)
    public class ShiroAnnotationProcessorAutoConfiguration extends AbstractShiroAnnotationProcessorConfiguration {
    
        @Bean
        @DependsOn("lifecycleBeanPostProcessor")
        @ConditionalOnMissingBean
        @Override
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            return super.defaultAdvisorAutoProxyCreator();
        }
    
        @Bean
        @ConditionalOnMissingBean
        @Override
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            return super.authorizationAttributeSourceAdvisor(securityManager);
        }
    }
    
  • 注解的形式无法将错误页面的信息定位到401.html,因为配置的这种路径,只针对过滤器链有效,注解无效。为了实现友好提示的效果,可以配置异常处理器,@RestControllerAdvice,@ControllerAdvice

记住我

记住我在开启后,可以针对一些安全级别相对更低的页面采用user过滤器拦截,只要登录过,不需要重新登录就可以访问。

准备两个接口

    @GetMapping("/rememberMe")
    public String rememberMe(){
        return "rememberMe!!!";
    }

    @GetMapping("/authentication")
    public String authentication(){
        return "authentication!!!";
    }

配置不同的过滤器

    filterChainDefinitionMap.put("/item/rememberMe","user");
    filterChainDefinitionMap.put("/item/authentication","authc");

在页面追加记住我按钮,并且在登录是,添加rememberMe效果

<form action="/user/login" method="post">
    用户名:<input  name="username" />  <br />
    密码:<input name="password" />  <br />
    记住我:<input type="checkbox" name="rememberMe" value="on" />  <br />
    <button type="submit">登录</button>
</form>
09-19 22:04