Apache Shiro

作者: 罗志贇 | 来源:发表于2017-04-12 00:35 被阅读0次

    Apache Shiro

    Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。

    Apache Shiro Features

    Shiro 把 Shiro 开发团队称为“应用程序的四大基石”——身份验证,授权,会话管理和加密作为其目标。

    • Authentication:简称为"登录",这是一个证明用户是他们所说的他们是谁的行为
    • Authorization:访问空值的过程,也就是"谁"去访问"什么";
    • Session Management:管理用户特定的会话, 即使在非 Web 或 EJB 应用程序
    • Cryptography:通过使用加密算法保持数据安全同时易于使用

    开始一个应用程序

    public static void main(String[] args){
      //1.
      Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
      //2.
      SecurityManager securityManager = factory.getInstance();
      //3.
      SecurityUtils.setSecurityManage(securityManger);
      
      System.exit(0);
    }
    

    在几乎所有环境中,都可以通过下面的调用获取当前正在执行的用户:

    Subject currentUser = SecurityUtils.getSubject();   
    

    subject指的是:"当前正在执行的用户的特定的安全视图",可以把Subject看成是shiro的"User"概念

    subject获取会话

    Session session = currentUser.getSession();
    session.setAttribute("someKye","aValue");
    

    为已知用户做检查

    if(!currentUser.isAuthenticated()){
      UsernamePasswordToken token = new UsernamePasswordToken("lonestarr","vespa");
      token.setRememberMe(true);
      currentUser.login(token);
    }
    

    如果登陆失败则可以进行异常捕获

    try{
      currentUser.login(token);
    }catch(UnknownAccountException u){
      //username wasn't in the system ,show them an error message?
    }catch(IncorrectCredentitalsException e){
      //password didn't match ,try again?
    }catch(LockedAccountException l){
      //account for that username is locked -can't login.
    }
    

    获取登陆的角色

    log.info("User ["+currentUser.getPrincipal()+"] logged in successfully.");
    

    测试是否有特定的角色:

    if(currentUser.hasRole("luoluo")){
      log.info("May the luoluo be with you ");
    }else{
      log.info("hello ,mere mortal.");      
    }
    

    判断是否有权限在一个确定类型实体上进行操作:

    if(currentUser.isPermitted("lightsaber:weild")){
      log.info("You may use a lightsaber ring. Use it wisely.");
    }else{
      log.info("Sorry,lightsaber rings are for schwartz masters only");
    }
    

    注销

    currentUser.logout();
    

    完整事例:

    public class Tutorial{
      private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
      
      public static void main(String[] args){
        log.info("My First Apache Shiro Application");
        //加载用户信息
        Factory<SecurityManager> factory = new IniSecurityManagerFacotry("classpath:shiro:ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        
        //get the currently executing user;
        Subject currentUser = SecurityUtils.getSubject();
        
        //Do some stuff with a session(no need for a web or EJB container!!!)
        Session session = currentUser.getSession();
        session.setAttribute("someKey","aValue");
        String value = (String)session.getAttribute("someKey");
        if(value.equals("aValue")){
          log.info("Retrieved the corrent value!["+value+"]");
        }
        
        //let's login the current user so we can check against and permissions;
        if(!currentUser.isAuthenticated()){
          UsernamePasswordToken token = new UsernamePasswordToken("lonestarr","vespa");
          token.setRememberMe(true);
          try{
            currentUser.login(token);
          }catch(UnknownAccountException u){
            log.info("There is no user with username of "+token.getPrincipal());
          }catch(IncorrentCredentitalsException i){
            log.info("Password for account "+token.getPrincipal()+" was incorrect!");
          }catch(LockedAccountException l){
            
          }
        }
        
        //sya who they are:
        //print their identitfying principal(in this case,a username)
        log.info("User["+currentUser.getPrincipal()+"]logged in successfully.");
        
        //test a role ;
        if(currentUser.hasRole("luoluo")){
          log.info("May the luoluo be with you ");
        }else{
          log.info("hello ,mere mortal");
        }
        
        //test a typed permission(not instance-level)
        if(currentUser.isPermitted("lightsaber:weild")){
          log.info("you may use a lightsaber:weild");
        }else{
          log.info("Sorry , lightsaber rings are for schwartz masters only.");
        }
        
        //log_out!
        currentUser.logout();
        
        System.exit(0);
      }
    }
    

    Apache Shiro Architecture

    shiro的架构有3个主要的概念:Subject ,SecurityManager 和 Realms

    • Subject:一个 Subject 可以是一个人,但它还可以代表第三方服务或其他类似的任何东西——基本上是当前正与软件进行交互的任何东西
    • SecurityManager:是Shiro架构的心脏,并作为一种"保护伞"对象来协调内部的安全组件共同构成一个对象图
    • Realms::Realms 担当 Shiro 和你的应用程序的安全数据之间的“桥梁”或“连接器”。当它实际上与安全相 关的数据如用来执行身份验证(登录)及授权(访问控制)的用户帐户交互时, Shiro 从一个或多个为应用程 序配置的 Realm 中寻找许多这样的东西。

    Apache Shiro Configuration

    首先需要创建一个SecurityManager

    Realm realm = //instantiate or acquire a Realm instance.
    
    SecurityManager securityManger = new DefaultSecurityManager(realm);
    
    //Make the SecurityManager instance available to the entire application via static memory;
    SecurityUtils.setSecurityManager(securityManager);
    

    Authenticating Subjects(验证Subjects)

    可以大致分为三步:

    1. 收集Subjects提交的Principals(身份)和Credentials(凭证)
    2. 提交Principals(身份)和Credentials(凭证)进行身份验证
    3. 如果提交成功,则允许访问,否则重新进行身份验证

    Set1:

    UsernamePasswordToken token = new UsernamePasswordToken(username,password);
    
    token.setRememberMe(true);
    

    Step2:

    Subject currentUser = SecurityUtils.getSubject();
    currentUser.login(token);
    

    通过login方法,有效的体现了身份验证

    Step3:处理成功或失败

    如果验证成功,则调用subject.isAuthenticated()的调用将返回true

    验证失败则运行AuthenticationException

    try{
      curretnUser.login(token);
    }catch(UnknownAccountException uae){
      
    }catch(IncorrectCredentialsException ice){
      
    }catch(LockedAccountException eas){
      
    }catch(ExcessiveAttemptsException eae){
      
    }catch(AuthenticationException ae){
      
    }
    

    AuthenticationStrategy

    当一个应用程序配置了两个或两个以上的 Realm 时,ModularRealmAuthenticator 依靠内部的 AuthenticationStrategy 组件来确定这些认证尝试的成功或失败条件

    AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这 4 次交互所需的任何必要的 状态将被作为方法参数):

    1. 在任何Realm被调用之前被询问
    2. 在一个单独的Realm的getAuthenticationInfo方法被调用之前立即被询问
    3. 在一个单独的Realm的getAuthenticationInfo方法被调用之后立即被询问
    4. 在所有Realm被调用之后被询问

    Shiro 有 3 个具体的 AuthenticationStrategy 实现:

    AtLeastOneSuccessfulStrategy:如果一个(或更多)Realm 验证成功,则整体的 尝试被认为是成功的。如果没有一个验证成功,则整体尝试失败。

    FirstSuccessfulStrategy:只有第一个成功地验证的 Realm 返回的信息将被 使用。所有进一步的 Realm 将被忽略。如果没有 一个验证成功,则整体尝试失败。

    AllSucessfulStrategy为了整体的尝试成功,所有配置的 Realm 必须验 证成功。如果没有一个验证成功,则整体尝试失败。

    Authorization(授权)

    控制谁有权限在应用程序中做什么

    在 Shiro 中执行授权可以有 3 种方式:

    • 编写代码——你可以在你的 Java 代码中用像 if 和 else 块的结构执行授权检查
    • JDK 的注解——你可以添加授权注解给你的 Java 方法
    • JSP/GSP 标签库——你可以控制基于角色和权限的 JSP 或者 GSP 页面输出

    Role-Based Authorization(基于角色的授权)

    Role checks

    Subject currentUser = SecurityUtils.getSubject();
    
    if(currentUser.hasRole("administrator")){
      //show the admin button
    }else{
      //don't show the button?Grey it out?
    }
    

    Role Assertions(角色断言)

    Subject currentUser = SecurityUtils.getSubject();
    //guarantee that the current user is a bank teller and therefore allowed to open the account:
    currentUser.checkRole("bankTeller");
    openBankAccount();
    

    通过使用 hasRole*方法的一个好处就是代码可以变得清洁,由于你不需要创建你自己的 AuthorizationException 如果 当前的 Subject 不符合预期条件

    Permission-Based Authorization(基于权限的授权)

    Permission Checks(权限检查)

    1.基于对象

    Permission printPermission = new PrinterPermission("laserjet4400h","print");
    Subject currentUser = SecurityUtils.getSubject();
    if(currentUser.isPermitted(printPermission)){
      //show the Print button
    }else{
      //don't show the button?
    }
    

    2.基于字符串

    Subject currentUser = SecurityUtils.getSubject();
    if(currentUser.isPermitted("printer:print:laserjet4400n")){
      //TODO
    }else{
      //TODO
    }
    

    基于字符串的权限是很有帮助的,由于你不必被迫实现一个接口,而且简单的字符串易于阅读。其缺点是,你不具 备类型安全,如果你需要更为复杂的行为将超出了字符串所能代表的范围,你就得实现你自己的基于权限接口的权 限对象。

    Permission Assertions(权限断言)

    Subject currentUser = SecurityUtils.getSubject();
    
    Permission p = new AccountPersion("open");
    currentUser.checkPermission(p);
    openBankAccount();
    

    或者,使用字符串权限:

    Subject currentUser = SecurityUtils.getSubject();
    
    currentUser.checkPermission("open");
    openBankAccount();
    

    Wildcard Permissions

    为了使用易于处理且仍然可读的权限语句,Shiro 提供了强大而直观的语法,我们称之为 WildcardPermission。

    一个极其简单的方法是授予用户"queryPrinter"权限。然后你可以检查用户是否具有 queryPrinter 权限通过调用:

    subject.isPermitted("queryPrinter");
    
    //上面的语法等同于
    subject.isPermitted(new WildcardPermission("queryPrinter"));
    

    Multiple Parts

    通配符权限支持多层次或部件(parts)的概念

    在该例中,第一部分是权限被操作的领域(打印机),第二部分是被执行的操作(查询)。上面其他的例子将被改 为:

    printer:print
    printer:manage
    

    Multiple Vaules

    printer:print,query
    

    它能够赋予用户 print 和 query 打印机的能力。由于他们被授予了这两个操作,你可以通过调用下面的语句来判断用 户是否有能力查询打印机:

    subject.isPermitted("print:query");//返回true
    

    All Values

    printer:query,print,manage
    

    简单点:

    printer:*
    

    最后,在一个通配符权限字符串中的任何部分使用通配符 token 也是可以的。例如,如果你想对某个用户在所有领 域(不仅仅是打印机)授予"view"权限,你可以这样做:

    *:view
    

    Instance-Level Access Control

    另一种常见的通配符权限用法是塑造实例级的访问控制列表。在这种情况下,你使用三个部件——第一个是域,第 二个是操作,第三个是被付诸实施的实例。

    printer:query:lp7200
    printer:print:epsoncolor
    

    第一个定义了查询拥有 ID lp7200 的打印机的行为。第二条权限定义了打印到拥有 ID epsoncolor 的打印机的行为。

    如果你授予这些权限给用户,那么他们能够在特定的实例上执行特定的行为。然后你可以在代码中做一个检查:

    if(SecurityUtils.getSubject().isPermitted("printer:query:lp7200")){
      //Return the current jobs on printer lp 7200
    }
    

    Missing Parts

    printer:print
    

    等价于

    printer:print:*
    
    printer
    

    等价于

    printer:*.*
    

    Apache Shiro Realms

    Realm 是一个能够访问应用程序特定的安全数据(如用户、角色及权限)的组件。

    Handling supported AuthenticationTokens

    若 Realm 支持一个提交的 AuthenticationToken,那么 Authenticator 将会调用该 Realm 的 getAuthenticationInfo(token) 方法。这有效地代表了一个与 Realm 的后备数据源的授权尝试。该方法按以下方法进行:

    1. 为主要的识别信息检查token
    2. 基于principal在数据源中寻找相吻合的账户数据
    3. 确保token支持的credentials匹配那些存储在数据源的
    4. 若credentitals匹配,返回一个封装了Shiro能够理解的账户数据格式的AuthenticationInfo实例
    5. 若credentials不匹配,则抛出AuthenticationException异常

    Session Management

    Apache Shiro 提供安全框架界独一无二的东西:一个完整的企业级 Session 解决方案,从最简单的命令行及智能手机 应用到最大的集群企业 Web 应用程序。

    即使你在一个 Servlet 或 EJB 容器中部署你的应用程序,仍然有令人信服的理由来使用 Shiro 的 Session 支持而不是容 器的。下面是一个 Shiro 的 Session 支持的最可取的功能列表:

    • pojo/j2sebased:Shiro 的一切(包括所有 Session 和 Session Management 方面)都是基于接口和 POJO 实现
    • Easy Custom Session Storage:因为 Shiro 的 Session 对象是基于 POJO 的,会话数据可以很容易地存储在任意 数量的数据源。
    • Container-Independent Clustering:Shiro 的会话可以很容易地聚集通过使用任何随手可用的网络缓存产品,像 Ehcache + Terracotta,Coherence,GigaSpaces, 等等。
    • Heterogeneous Client Access:与 EJB 或 web 会话不同, Shiro 会话可以被各种客户端技术“共享”。
    • Event Listeners:事件监听器允许你在会话生命周期监听生命周期事件。你可以侦听这些事件和对自定义应用 程序的行为作出反应——例如,更新用户记录当他们的会话过期时。
    • Host Address Retention:Shiro Sessions 从会话发起地方保留 IP 地址或主机名。这允许你确定用户所在,并作 出相应的反应(通常是在 IP 分配确定的企业内部网络环境)。
    • Inactivity/Expiration Support:由于不活动导致会话过期如预期的那样,但它们可以延续很久通过 touch()方法 来保持它们“活着”,如果你希望的话。
    • Transparent Web Use:Shiro的网络支持
    • Can be used for SSO:由于 Shiro 会话是基于 POJO 的,它们可以很容易地存储在任何数据源,而且它们可以跨 程序“共享”如果需要的话。

    使用方式:

    Subject currentUser = SecurityUtils.getSubject();
    Session session = currentUser.getSession();
    session.setAttribute("someKey","someValue");
    

    JSP Tag Library

    引入:

    <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    

    The guest tag

    guest 标签将显示它包含的内容,仅当当前的 Subject 被认为是'guest'时。'guest'是指没有身份 ID 的任何 Subject。也 就是说,我们并不知道用户是谁

    <shiro:guest>
        Hi there ! Please<a href = "login.jsp">Login</a>or<a href="signup.jsp">Signup</a>today!
    </shiro:guest>
    

    The user Tag

    user 标签将显示它包含的内容,仅当当前的 Subject 被认为是'user'时。'user'在上下文中被定义为一个已知身份 ID 的 Subject,或是成功通过身份验证及通过'RememberMe'服务的。请注意这个标签在语义上与 authenticated 标签是 不同的,authenticated 标签更为严格。

    <shiro:user>
      Welcome back luoluo ! NOt luoluo? Click<a href="login.jsp">here<a>to login.
    </shiro:user>  
    

    The authenticated tag

    仅仅只当当前用户在当前会话中成功地通过了身份验证 authenticated 标签才会显示包含的内容。它比'user'标签更 为严格。

    <shiro:authenticated>
      <a href="updateAccount.jsp">Update your contact information</a>
    </shiro:authenticated>
    

    The principal tag

    principal 标签将会输出 Subject 的主体(标识属性)或主要的属性。

    Hello,<shiro:principal />,how are you today?
    

    等价于

    Hello,<%=SecurityUtils.getSubject().getPrincipal().toString() %>,how are you today?
    

    Typed principal

    principal 标签默认情况下,假定该 principal 输出的是 subject.getPrincipal()的值。但若你想输出一个不是主要 principal 的值,而是属于另一个 Subject 的 principal collection,你可以通过类型来获取该 principal 并输出该值。

    User ID:<principal type="java.lang.Integer" />
    

    等价于

    User ID:<%=SecurityUtils.getSubject().getPrincipals().oneByType(Integer.class).toString()%>
    

    Principal property

    但如果该 principal(是默认主要的 principal 或是上面的'typed' principal)是一个复杂的对象而不是一个简单的字符串, 而且你希望引用该 principal 上的一个属性该怎么办呢?你可以使用 property 属性来来表示 property 的名称来理解 (必须通过 JavaBeans 兼容的 getter 方法访问)。

    Hello,<shiro:principal property="firstName" />,how are you today
    

    等价于

    Hello,<%=SecurityUtils.getSubject().getPrincipal().getFirstName().toString() %>,how are you today?
    

    或者结合属性

    Hello,<shiro:principal type="com.foo.User" property="firstName" /> ,how are you today?
    

    等价于

    Hello ,<%=SecurityUtils.getSubject().getPrincipals.oneByType(com.foo.User.class).getFirstName().toString() %>,how are you today?
    

    The hasRole tag

    <shiro:hasRole name="administrator">
      <a href="admin.jsp">Administer the system</a>
    </shiro:hasRole>
    

    The hasAnyRole tag

    <shiro:hasAnyRole name="developer,project manager , administrator">
        You are either a developer,project manager,or administrater.
    </shiro:hasAnyRole>
    

    The hasPermission tag

    hasPermission 标签将会显示它所包含的内容,仅当当前 Subject“拥有”(蕴含)特定的权限。也就是说,用户具 有特定的能力。

    <shiro:hasPermission name="user:create">
        <a href="createUser.jsp">Create a new User</a>
    </shiro:hasPermission>
    

    The lacksPermission tag

    lacksPermission 标签将会显示它所包含的内容,仅当当前 Subject 没有拥有(蕴含)特定的权限。也就是说,用户没 有特定的能力。

    <shiro:lacksPermission name="user:delete">
        Sorry,you are not allowed to deleted user accounts;
    </shiro:lacksPermission>
    

    Understanding Subjects in Apache Shiro

    一个 Shiro Subject 实例代表了一个单一应用程序用户的安全状态和操作

    这些操作包括:

    • authentication(login)
    • authorization(access control)
    • session access
    • logout

    The Currently Executing Subject

    getSubject()方法调用一个独立的应用程序,该应用程序可以返回一个在应用程序特有位置上基于用户数据的 Subject, 在服务器环境中(如,Web 应用程序),它基于与当前线程或传入的请求相关的用户数据上获得 Subject。

    Subject currentUser = SecurityUtils.getSubject();
    

    可得的他们的 session:

    Session session = currentUser.getSession();
    session.setAttribute("someKey","aValue");
    

    可以获取用户信息

    log.info("User["+currentUser.getPrincipal()+"]logged in successfully");
    

    可以测试是否有特定的角色

    if(currentUser.hasRole("schwartz")){
      log.info(".....");
    }else{
      log.info(".....");
    }
    

    可以判断否有权限

    if(currentUser.isPermitted("...")){
      
    }else{
      
    }
    

    可以注销

    currentUser.logout();
    

    Spring Framework

    这里是在 Spring 应用程序中启用应用程序单例 SecurityManager 的最简单的方法:

    web.xml

     <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>
    

    applicationContext.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:p="http://www.springframework.org/schema/p"  
        xmlns:context="http://www.springframework.org/schema/context"   
        xmlns:tx="http://www.springframework.org/schema/tx"  
        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    
        http://www.springframework.org/schema/tx    
        http://www.springframework.org/schema/tx/spring-tx.xsd    
        http://www.springframework.org/schema/context    
        http://www.springframework.org/schema/context/spring-context.xsd">
        
        <description>Shrio的配置文件</description>
        
        <!-- SecurityManager配置 -->
        <!-- 配置Realm域 -->
        <!-- 密码比较器 -->
        <!-- 代理如何生成? 用工厂来生成Shiro的相关过滤器-->
        <!-- 配置缓存:ehcache缓存 -->
        <!-- 安全管理 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
            <property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
            <!-- 缓存 -->
            <property name="cacheManager" ref="shiroEhcacheManager"/>
        </bean>
    
        <!-- 自定义权限认证 -->
        <bean id="authRealm" class="cn.itcast.jk.shiro.AuthRealm">
            <property name="userService" ref="userService"/>
            <!-- 自定义密码加密算法  -->
            <property name="credentialsMatcher" ref="passwordMatcher"/>
        </bean>
        
        <!-- 设置密码加密策略 md5hash -->
        <bean id="passwordMatcher" class="cn.itcast.jk.shiro.CustomCredentialsMatcher"/>
    
        <!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager"/>
            <!--登录页面  -->
            <property name="loginUrl" value="/index.jsp"></property>
            <!-- 登录成功后 -->      
            <property name="successUrl" value="/home.action"></property>
            
            
            <property name="unauthorizedUrl" value="/index.jsp"></property>
            
            <property name="filterChainDefinitions">
                <!-- /**代表下面的多级目录也过滤 -->
                <value>
                    /index.jsp* = anon
                    /home* = anon
                    /sysadmin/login/login.jsp* = anon
                    /sysadmin/login/logout.jsp* = anon
                    /login* = anon
                    /logout* = anon
                    /components/** = anon
                    /css/** = anon
                    /images/** = anon
                    /js/** = anon
                    /make/** = anon
                    /skin/** = anon
                    /stat/** = anon
                    /ufiles/** = anon
                    /validator/** = anon
                    /resource/** = anon
                    /sysadmin/deptAction_* = perms["部门管理"]
                    /** = authc
                    /*.* = authc
                </value>
            </property>
        </bean>
    
        <!-- 用户授权/认证信息Cache, 采用EhCache  缓存 -->
        <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
        </bean>
    
        <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    
        <!-- 生成代理,通过代理进行控制 -->
        <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>
    </beans>
    

    如果需要设置注解,则要添加如下配置

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" />
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>
    

    相关文章

      网友评论

        本文标题:Apache Shiro

        本文链接:https://www.haomeiwen.com/subject/uuepattx.html