zoukankan      html  css  js  c++  java
  • Shiro

    一、Shiro简介

    1.简介

      Apache Shiro是Java的一个安全框架。它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。 Apache Shiro相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,能更简单的解决项目问题就好了。Shiro不仅可以用在JavaSE环境,也可以用在JavaEE环境帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。而且Shiro的API也是非常简单。

    2.主要API

    可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject,其每个API的含义:

    • Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

    • SecurityManager :安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

    • Realm :域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

    也就是说对于我们而言,最简单的一个Shiro应用:

    1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;

    2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

    从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。

    二、Shiro的使用

    1.第一步:导入jar包或Maven坐标

    <!--shiro-->
    <!--shiro和spring整合-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.3.2</version>
    </dependency>
    <!--shiro核心包-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.3.2</version>
    </dependency>
    <!--shiro缓存包-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <version>1.3.2</version>
    </dependency>

    2.第二步:在web.xml中配置过滤器

    <!-- Shiro Security filter  filter-name这个名字的值将来还会在spring中用到-->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    Tips: 此过滤器要配置在springmvc的过滤器之前。

    3.第三步:创建shiro的spring配置文件

    applicationContext-shiro.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <description>Shiro与Spring整合</description>
    
        <!--使用redis作为缓存的配置-->
        <!--<bean id="redisManager" class="org.crazycake.shiro.RedisManager">
             <property name="host" value="127.0.0.1:6379"></property>
         </bean>
         <bean id="cacheManager" class="org.crazycake.shiro.RedisCacheManager">
             <property name="redisManager" ref="redisManager"></property>
         </bean>-->
        <!--<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean>-->
    
        <!--使用内存作为缓存的配置:ehcache-->
        <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManagerConfigFile" value="classpath:cache/ehcache-shiro.xml"></property>
        </bean>
    
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
            <property name="cacheManager" ref="cacheManager"></property>
        </bean>
    
        <!-- 自定义Realm域的编写 -->
        <bean id="authRealm" class="cn.dintalk.web.shiro.AuthRealm">
            <!-- 注入自定义的密码比较器-->
            <property name="credentialsMatcher" ref="customerCredentialsMatcher"></property>
        </bean>
    
        <!-- 自定义的密码比较器 -->
        <bean id="customerCredentialsMatcher" class="cn.dintalk.web.shiro.CustomCredentialsMatcher"></bean>
    
        <!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager"/>
            <!--登录页面  -->
            <property name="loginUrl" value="/login.jsp"></property>
            <!-- 无权访问页面 -->
            <property name="unauthorizedUrl" value="/unauthorized.jsp"></property>
    
            <property name="filterChainDefinitions">
                <!-- /**代表下面的多级目录也过滤 -->
                <value>
                    <!--权限访问规则
                    /company/list.do = perms["企业管理"]-->
                    <!--匿名访问规则-->
                    /index.jsp* = anon
                    /login.jsp* = anon
                    /login* = anon
                    /logout* = anon
                    /css/** = anon
                    /img/** = anon
                    /plugins/** = anon
                    /make/** = anon
                    <!--认证访问规则-->
                    /** = authc,user
                    /*.* = authc
                </value>
            </property>
        </bean>
    
        <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    
        <!-- 生成代理,通过代理进行控制
             depends-on:指定当前类的创建时必须在指定bean的id后面创建
                         lifecycleBeanPostProcessor的创建要在DefaultAdvisorAutoProxyCreator之前
        -->
        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
              depends-on="lifecycleBeanPostProcessor">
            <property name="proxyTargetClass" value="true"/>
        </bean>
    
        <!-- 安全管理器 -->
        <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
            <property name="securityManager" ref="securityManager"/>
        </bean>
    
        <!--动态代理的代理规则:使用目标类的子类创建代理对象,使用cglib-->
        <aop:aspectj-autoproxy proxy-target-class="true"/>
    </beans>

    Tips : 权限访问控制也可在Controller中的每个方法加入注解进行限制:

    @RequiresPermissions("企业管理")
    @RequestMapping(value = "/list",name = "查看企业列表")
    public String findAll(@RequestParam(defaultValue = "1") int page,@RequestParam(defaultValue = "5") int size)  {
        PageInfo pageInfo = companyService.findPageByHelper(page, size);
        request.setAttribute("page", pageInfo);
        return "company/company-list";
    }

    这时,若无权限访问则会报错,因此我们在自定义的异常处理器中加入此异常的判断:

    CustomerExceptionResolver
    else if (e instanceof UnauthorizedException){
      mv.setViewName("forward:/unauthorized.jsp");//无权访问页面路径 return mv

    4.第四步:编写缓存的配置文件

    cache/ehcache-shiro.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache updateCheck="false" name="shiroCache">
        <defaultCache
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                overflowToDisk="false"
                diskPersistent="false"
                diskExpiryThreadIntervalSeconds="120"
                />
    </ehcache>

    5.第五步:自定义Realm域

    /**
     * 自定义realm域,进行认证:用户名和密码的校验
     *              和授权  :获取用户的权限和每次方法的权限鉴定(鉴权)
     *
     * @author Mr.song
     * @date 2019/05/11 16:27
     */
    public class AuthRealm extends AuthorizingRealm {
    
        @Autowired
        private UserService userService;
    
        /**
         * 授权的方法   还可以配合标签进行鉴权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //1.取出认证成功的用户(这里就是User,所以直接强转)
            User user = (User)principalCollection.getPrimaryPrincipal();
            //2.查询该用户具备的功能
            List<Module> moduleList = userService.findUserMenus(user.getId());
            //3.将功能模块的名称发放入set集合,将来通过功能名称与配置的访问规则匹配
            Set<String> moduleSet = new HashSet<>();
            for (Module module : moduleList) {
                moduleSet.add(module.getName());
            }
            //4.创建返回值对象
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            //5.按照返回值要求填充对象并返回
            info.setStringPermissions(moduleSet);
            return info;
        }
    
        /**
         * 认证的方法,使用用户名和密码到数据库进行查询
         * @param authenticationToken 令牌(及包装后的登录信息)
         * @return 若此方法返回null,则subject的login方法就会抛出异常
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //1.把参数转成UsernamePasswordToken
            UsernamePasswordToken uToken = (UsernamePasswordToken)authenticationToken;
            //2.获取用户输入的登录名和密码
            String email = uToken.getUsername();
            String password = new String(uToken.getPassword(),0,uToken.getPassword().length);
            //3.使用邮箱到数据库查询
            User user = userService.findByEmail(email);
            //4.当user不为null时,按照要求创建返回值
            if (user != null){
                //按照返回值要求创建对象即可,构造方法内部会调用我们自定义的密码比较器
                //构造函数:传三个参数,当前用户,当前用户数据库密码,及当前的realm域的名称
                SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
                return info;
            }
            //没查到null,直接返回null,controller捕获后处理
            return null;
        }
    }

    6.第六步:自定义密码比较器

    /**
     * 自定义的密码校验器
     *
     * @author Mr.song
     * @date 2019/05/11 16:35
     */
    public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
    
        /**
         * 做密码比较
         * @param token 认证令牌: 即包装过后的登录信息(登录名和密码)
         * @param info  认证信息: 存着密文密码(即数据库中的密码)
         * @return
         */
        @Override
        public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
            //1.取出明文密码
            UsernamePasswordToken uToken = (UsernamePasswordToken)token;
            String email = uToken.getUsername();
            String password = new String(uToken.getPassword(),0,uToken.getPassword().length);
            //2.取出密文密码
            String dbPassword = (String) info.getCredentials();
            //3.将明文密码加密后进行比较
            String md5Password = Encrypt.md5(password,email);
            //4.返回比较结果,返回false时会抛出异常,处理器捕获进行处理
            return md5Password.equals(dbPassword);
        }
    }

    7.第七步:修改登录方法

    //登录
    @RequestMapping(value = "/login",name = "登录")
    public String login(String email,String password){
        try {
            //1.获取主体信息
            Subject subject = SecurityUtils.getSubject();
            //2.获取令牌,将登录信息套个壳子
            UsernamePasswordToken uToken = new UsernamePasswordToken(email,password);
            //3.调用subject的登录方法,将uToken交给shiro的核心
            subject.login(uToken);
            //4.获取认证的结果(这里其实就是User对象)
            User user = (User)subject.getPrincipal();//若没有获取到,shiro会报错,即认证失败
            //5.匹配成功,可以登录( 根据用户的角色信息动态展示菜单 )
            session.setAttribute("user",user);
            //6.查询当前用户角色的权限菜单
            List<Module> moduleList = userService.findUserMenus(user.getId());
            session.setAttribute("modules",moduleList);
            return "home/main";
        } catch (AuthenticationException e) {// 认证失败
            //无此用户,或密码不对
            request.setAttribute("error","邮箱或密码不匹配! 请重试");
            return "forward:/login.jsp";
        }
    }

    8.第八步:页面使用shiro标签

    引入标签库
    <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    使用标签包裹功能按钮(无此权限则不显示)
    <shiro:hasPermission name="新建企业">
         <button type="button" class="btn btn-default" title="新建"
                 onclick='location.href="${ctx}/company/toAdd.do"'>
             <i class="fa fa-file-o"></i> 新建
         </button>
     </shiro:hasPermission>
    shiro常用标签
    标签名称标签条件(均是显示标签内容)
    shiro:authenticated 登录之后
    shiro:notAuthenticated 不在登录状态时
    shiro:guest 用户在没有RememberMe时
    shiro:user 用户在RememberMe时
    <shiro:hasAnyRoles name="abc,123" > 在有abc或者123角色时
    <shiro:hasRole name="abc"> 拥有角色abc
    <shiro:lacksRole name="abc"> 没有角色abc
    <shiro:hasPermission name="abc"> 拥有权限资源abc
    <shiro:lacksPermission name="abc"> 没有abc权限资源
    shiro:principal 默认显示用户名称

    关注微信公众号,随时随地学习

  • 相关阅读:
    P2015 二叉苹果树(树形DP)
    Treats for the Cows (区间DP)
    You Are the One(区间DP 好题)
    Palindrome subsequence (区间DP)
    Cutting Sticks(区间DP)
    L2-013 红色警报 (dfs判断图连通性)
    L2-001 紧急救援 (dijkstra+dfs回溯路径)
    多线程 -- JMM、volatile关键字、内存屏障、happens-before原则、缓存一致性
    多线程 -- 各种锁的概念
    Spring Boot 学习笔记(十六)启动原理、运行流程、自动配置原理
  • 原文地址:https://www.cnblogs.com/dintalk/p/10852343.html
Copyright © 2011-2022 走看看