搜索词>>shiro 耗时0.2250
  • Apache Shiro教程,您的第一个Apache Shiro应用程序(翻译)

    Apache Shiro教程,您的第一个Apache Shiro应用程序(翻译)-编程技术<h2>您的第一个Apache Shiro应用程序</h2> <p>如果你是Apache Shiro的新手,这个简短的教程将告诉你如何设置一个由Apache Shiro保护的初始和非常简单的应用程序。我们将讨论Shiro的核心概念,以帮助您熟悉Shiro的设计和API。</p> <p>如果您不想按照本教程实际编辑文件,则可以获取几乎相同的示例应用程序,并在您参考时进行参考。选择地点:</p> <ul> <li>在Apache Shiro的Git存储库中:https://github.com/apache/shiro/tree/master/samples/quickstart</li> <li>在Apache Shiro的源代码发布的<code>samples/quickstart</code>目录中。源分发可从下载页面获取。</li> </ul> <h3>建立</h3> <p>在这个简单的例子中,我们将创建一个非常简单的命令行应用程序,它将运行并快速退出,这样您可以感受到Shiro的API。</p>  <strong>任何应用程序</strong> <hr /> <p>Apache Shiro是从第一天开始设计的,以支持<em>任何应用</em>程序 - 从最小的命令行应用程序到最大的集群Web应用程序。即使我们为本教程创建了一个简单的应用程序,也知道无论应用程序如何创建或部署在其中,都会使用相同的使用模式。</p> <p>本教程需要Java 1.5或更高版本。我们还将使用Apache Maven作为我们的构建工具,但是当然这不需要使用Apache Shiro。您可以购买Shiro的.jars并以任何你喜欢的方式将它们合并到应用程序中,例如使用Apache Ant和Ivy。</p> <p>对于本教程,请确保您使用的是Maven 2.2.1或更高版本。您应该能够<code>mvn --version</code>在命令提示符中键入,并看到类似于以下内容:</p> <p><strong>测试Maven安装</strong></p> <pre> <code>hazlewood:~/shiro-tutorial$ mvn --version Apache Maven 2.2.1 (r801777; 2009-08-06 12:16:01-0700) Java version: 1.6.0_24 Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home Default locale: en_US, platform encoding: MacRoman OS name: "mac os x" version: "10.6.7" arch: "x86_64" Family: "mac" </code></pre> <p>现在,在文件系统上创建一个新目录,例如,<strong><code>shiro-tutorial</code></strong>并将以下Maven <strong><code>pom.xml</code></strong>文件保存在该目录中:</p> <p><strong>pom.xml</strong></p> <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.apache.shiro.tutorials</groupId> <artifactId>shiro-tutorial</artifactId> <version>1.0.0-SNAPSHOT</version> <name>First Apache Shiro Application</name> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>1.5</source> <target>1.5</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <!-- This plugin is only to test run our little application. It is not needed in most Shiro-enabled applications: --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.1</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <classpathScope>test</classpathScope> <mainClass>Tutorial</mainClass> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.1.0</version> </dependency> <!-- Shiro uses SLF4J for logging. We'll use the 'simple' binding in this example app. See http://www.slf4j.org for more info. --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.6.1</version> <scope>test</scope> </dependency> </dependencies> </project> </code></pre> <h4>Tutorial类</h4> <p>我们将运行一个简单的命令行应用程序,因此我们需要使用一个<code>public static void main(String[] args)</code>方法创建一个Java类。</p> <p>在包含您的<code>pom.xml</code>文件的同一目录中,创建一个* <code>src/main/java</code>子目录。在<code>src/main/java</code>创建一个<code>Tutorial.java</code>包含以下内容的文件:</p> <p><strong>src / main / java / Tutorial.java</strong></p> <pre> <code class="language-java">import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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"); System.exit(0); } } </code></pre> <p>不要担心import语句现在 - 我们会很快得到他们。但是现在,我们有一个典型的命令行程序“shell”。所有这个程序都会打印出文字“我的第一个Apache Shiro应用程序”并退出。</p> <h3>测试运行</h3> <p>要尝试我们的Tutorial应用程序,请在教程项目的根目录(例如<code>shiro-tutorial</code>)中的命令提示符中执行以下命令,然后键入以下内容:</p> <p><code>mvn compile exec:java</code></p> <p>你会看到我们的小教程'应用程序'运行和退出。您应该看到类似以下内容(注意粗体文本,表示我们的输出):</p> <p><strong>运行应用程序</strong></p> <code>lhazlewood:~/projects/shiro-tutorial$ mvn compile exec:java<br /> <br /> ... a bunch of Maven output ...<br /> <br /> <strong>1 [Tutorial.main()] INFO Tutorial - My First Apache Shiro Application</strong><br /> lhazlewood:~/projects/shiro-tutorial\$</code> <p>我们已经验证应用程序运行成功 - 现在让我们启用Apache Shiro。当我们继续教程,你可以运行<code>mvn compile exec:java</code>后,每次我们添加一些更多的代码,以查看我们的更改的结果。</p> <h3>启用S​​hiro</h3> <p>在应用程序中启用Shiro时,首先要了解的是,Shiro中的几乎所有内容都与称为的中央/核心组件相关<code>SecurityManager</code>。对于那些熟悉Java安全性的人,这是Shiro的一个SecurityManager的概念 - 它<em>不是</em>一样的东西<code>java.lang.SecurityManager</code>。</p> <p>虽然我们将在“ 架构”章节中详细介绍Shiro的设计,但现在已经足够了解Shiro <code>SecurityManager</code>是应用程序的Shiro环境的核心,而且每个应用程序<code>SecurityManager</code>都必须存在。因此,我们在Tutorial应用程序中必须做的第一件事是设置<code>SecurityManager</code>实例。</p> <h4>组态</h4> <p>虽然我们可以<code>SecurityManager</code>直接实例化一个类,但是Shiro的<code>SecurityManager</code>实现有足够的配置选项和内部组件,这使得在Java源代码中做这件事变得很困难 - <code>SecurityManager</code>使用灵活的基于文本的配置格式配置更容易。</p> <p>为此,Shiro通过基于文本的INI配置提供了默认的“公分母”解决方案。人们对这些天使用庞大的XML文件感到厌烦,INI很容易阅读,使用简单,而且只需要非常少的依赖。稍后还会看到,通过对对象图形导航的简单理解,INI可以有效地用于配置简单对象图形,如SecurityManager。</p>  <strong>许多配置选项</strong> <hr /> <p>Shiro的<code>SecurityManager</code>实现和所有支持组件都是JavaBeans兼容的。这允许Shiro几乎配置任何配置格式,如XML(Spring,JBoss,Guice等),YAML,JSON,Groovy Builder标记等。INI只是Shiro的“公分母”格式,允许在任何环境中进行配置,以防其他选项不可用。</p>   <h5>shiro.ini</h5> <p>因此,我们将使用一个INI文件来<code>SecurityManager</code>为这个简单的应用程序配置Shiro 。首先,创建一个<strong><code>src/main/resources</code></strong>目录,从同一目录开始,其中<code>pom.xml</code>。然后<code>shiro.ini</code>在该新目录中创建具有以下内容的文件:</p> <p><strong>src / main / resources / shiro.ini</strong></p> <pre> <code># ============================================================================= # Tutorial INI configuration # # Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :) # ============================================================================= # ----------------------------------------------------------------------------- # Users and their (optional) assigned roles # username = password, role1, role2, ..., roleN # ----------------------------------------------------------------------------- [users] root = secret, admin guest = guest, guest presidentskroob = 12345, president darkhelmet = ludicrousspeed, darklord, schwartz lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # roleName = perm1, perm2, ..., permN # ----------------------------------------------------------------------------- [roles] admin = * schwartz = lightsaber:* goodguy = winnebago:drive:eagle5 </code></pre> <p>正如你所看到的,这个配置基本上设置了一组小的静态用户帐户,足够用于我们的第一个应用程序。在后面的章节中,您将了解如何使用更复杂的用户数据源,如关系数据库,LDAP和ActiveDirectory等。</p> <h4>引用配置</h4> <p>现在我们已经定义了一个INI文件,我们可以<code>SecurityManager</code>在我们的Tutorial应用程序类中创建该实例。更改<code>main</code>方法以反映以下更新:</p> <pre> <code class="language-java">public static void main(String[] args) { log.info("My First Apache Shiro Application"); //1. Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //2. SecurityManager securityManager = factory.getInstance(); //3. SecurityUtils.setSecurityManager(securityManager); System.exit(0); } </code></pre> <p>我们去 - Shiro在我们的示例应用程序中启用,只添加了3行代码!这是多么容易?</p> <p>随意运行<code>mvn compile exec:java</code>,看到一切仍然运行成功(由于Shiro的默认日志记录调试或更低,你不会看到任何Shiro日志消息 - 如果它启动和运行没有错误,那么你知道一切仍然确定)。</p> <p>这里是上面添加的是做什么:</p> <ol> <li> <p>我们使用Shiro的<code>IniSecurityManagerFactory</code>实现来获取<code>shiro.ini</code>位于类路径根目录的文件。这个实现反映了Shiro对工厂方法设计模式的支持。该<code>classpath:</code>前缀是一个资源的指标,告诉四郎在哪里加载从ini文件(其它前缀,如<code>url:</code>和<code>file:</code>以及支持)。</p> </li> <li> <p>该<code>factory.getInstance()</code>方法被调用,它解析INI文件并返回一个<code>SecurityManager</code>反映配置的实例。</p> </li> <li> <p>在这个简单的示例中,我们将其设置<code>SecurityManager</code>为<em>静态</em>(内存)单例,可通过JVM访问。但请注意,如果您在单个JVM中将有多个启用Shiro的应用程序,这是不可取的。对于这个简单的例子,它是确定,但更复杂的应用程序环境通常会放置<code>SecurityManager</code>在应用程序特定的内存(如在Web应用程序<code>ServletContext</code>或Spring,Guice或JBoss DI容器实例)。</p> </li> </ol> <h3>使用Shiro</h3> <p>现在我们的SecurityManager已经设置好了,现在我们可以开始做我们真正关心的事情 - 执行安全操作。</p> <p>当保护我们的应用程序时,我们可能最相关的问题是“当前用户是谁”或“当前用户是否允许做X”?在我们编写代码或设计用户界面时,常常会提出这些问题:应用程序通常基于用户故事构建,并且您希望基于每个用户表示(和安全)的功能。因此,我们在应用程序中考虑安全性的最自然的方式是基于当前用户。Shiro的API从根本上代表了“当前用户”的<code>Subject</code>概念。</p> <p>在几乎所有环境中,您可以通过以下调用获取当前正在执行的用户:</p> <pre> <code>Subject currentUser = SecurityUtils.getSubject(); </code></pre> <p>使用<code>SecurityUtils</code>。getSubject(),我们可以获取当前正在执行的<code>Subject</code>。<em>主题</em>是一个安全术语,基本上意味着“当前正在执行的用户的安全特定视图”。它不被称为“用户”,因为“用户”这个词通常与一个人相关联。在安全世界中,术语“主题”可以指人,也可以是第三方进程,cron作业,守护进程帐户或任何类似的。它只是指“当前与软件交互的东西”。对于大多数意图和目的,你可以认为<code>Subject</code>是Shiro的'用户'概念。</p> <p>独立应用程序中的<code>getSubject()</code>调用可以<code>Subject</code>基于应用程序特定位置中的用户数据返回,并且在服务器环境(例如web应用程序)中,它<code>Subject</code>基于与当前线程或传入请求相关联的用户数据来获取。</p> <p>现在你有了<code>Subject</code>,你能用它做什么?</p> <p>如果您想在用户在应用程序的当前会话期间使用户可用,您可以获取其会话:</p> <pre> <code>Session session = currentUser.getSession(); session.setAttribute( "someKey", "aValue" ); </code></pre> <p>这<code>Session</code>是一个Shiro特定的实例,提供了大多数你习惯了与常规HttpSessions,但有一些额外的好处和一个<strong>巨大的</strong>区别:它不需要HTTP环境!</p> <p>如果在Web应用程序中部署,默认情况下<code>Session</code>将<code>HttpSession</code>基于。但是,在非Web环境中,像这个简单的教程应用程序,Shiro将默认自动使用其企业会话管理。这意味着您可以在任何层次的应用程序中使用相同的API,而不考虑部署环境!这打开了一个全新的应用程序世界,因为任何需要会话的应用程序不需要被强制使用<code>HttpSession</code>或EJB有状态会话Bean。而且,任何客户端技术现在都可以共享会话数据。</p> <p>所以现在你可以获得一个<code>Subject</code>和他们<code>Session</code>。怎么样<em>真正</em>像检查,如果他们被允许做的事情,比如对角色和权限检查有用的东西?</p> <p>好吧,我们只能为已知用户执行这些检查。我们的<code>Subject</code>上面的实例代表当前用户,但<em>谁</em>是当前用户?好吧,他们是匿名的 - 也就是说,直到他们至少登录一次。所以,让我们这样做:</p> <pre> <code class="language-java">if ( !currentUser.isAuthenticated() ) { //collect user principals and credentials in a gui specific manner //such as username/password html form, X509 certificate, OpenID, etc. //We'll use the username/password example here since it is the most common. UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); //this is all you have to do to support 'remember me' (no config - built in!): token.setRememberMe(true); currentUser.login(token); } </code></pre> <p>而已!这不容易。</p> <p>但是,如果他们的登录尝试失败怎么办?您可以捕获各种特定的异常,告诉您发生了什么,并允许您处理并做出相应的反应:</p> <pre> <code class="language-java">try { currentUser.login( token ); //if no exception, that's it, we're done! } catch ( UnknownAccountException uae ) { //username wasn't in the system, show them an error message? } catch ( IncorrectCredentialsException ice ) { //password didn't match, try again? } catch ( LockedAccountException lae ) { //account for that username is locked - can't login. Show them a message? } ... more types exceptions to check if you want ... } catch ( AuthenticationException ae ) { //unexpected condition - error? } </code></pre> <p>有很多不同类型的异常,你可以检查,或抛出自己的自定义条件Shiro可能不考虑。有关更多信息,请参阅AuthenticationException JavaDoc。</p>  <strong>方便的提示</strong> <hr /> <p>安全最佳做法是向用户提供通用登录失败消息,因为您不想帮助攻击者试图进入您的系统。</p> <p>好的,所以到现在为止,我们有一个登录用户。我们还能做什么?</p> <p>让我们说他们是谁:</p> <pre> <code>//print their identifying principal (in this case, a username): log.info( "User [" + currentUser.getPrincipal() + "] logged in successfully." ); </code></pre> <p>我们还可以测试他们是否有特定的作用:</p> <pre> <code class="language-java">if ( currentUser.hasRole( "schwartz" ) ) { log.info("May the Schwartz be with you!" ); } else { log.info( "Hello, mere mortal." ); } </code></pre> <p>我们还可以看到他们是否有权对某种类型的实体采取行动:</p> <pre> <code class="language-java">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."); } </code></pre> <p>此外,我们可以执行非常强大的<em>实例级</em>权限检查 - 查看用户是否能够访问类型的特定实例:</p> <pre> <code class="language-java">if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) { log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } </code></pre> <p>蛋糕,对吧?</p> <p>最后,当用户完成使用应用程序时,他们可以注销:</p> <pre> <code>currentUser.logout(); //removes all identifying information and invalidates their session too. </code></pre> <h4>最终教程类</h4> <p>在添加上面的代码示例之后,这里是我们最终的Tutorial类文件。随意编辑和玩它,并更改安全检查(和INI配置),你喜欢:</p> <p><strong>最终src / main / java / Tutorial.java</strong></p> <pre> <code class="language-java">import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 IniSecurityManagerFactory("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 correct value! [" + value + "]"); } // let's login the current user so we can check against roles and permissions: if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); try { currentUser.login(token); } catch (UnknownAccountException uae) { log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } // ... catch more exceptions here (maybe custom ones specific to your application? catch (AuthenticationException ae) { //unexpected condition? error? } } //say who they are: //print their identifying principal (in this case, a username): log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); //test a role: if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz 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 ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } //a (very powerful) Instance Level permission: if (currentUser.isPermitted("winnebago:drive:eagle5")) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } //all done - log out! currentUser.logout(); System.exit(0); } } </code></pre> <h3>概要</h3> <p>希望这个介绍教程帮助你了解如何设置Shiro在一个基本的应用程序,以及Shiro的主要设计概念,<code>Subject</code>和<code>SecurityManager</code>。</p> <p>但这是一个相当简单的应用程序。你可能会问自己,“如果我不想使用INI用户帐户而是想连接到更复杂的用户数据源怎么办?</p> <p>要回答这个问题,需要更深入地了解Shiro的架构和支持配置机制。接下来我们将介绍Shiro的建筑。</p>
  • Java编程之Apache Shiro Web支持

    组态,将Shiro集成到任何Web应用程序中的最简单的方法是在web.xml中配置Servlet ContextListener和Filter,了解如何读取Shiro的INI配置。<h1>组态</h1> <p>将Shiro集成到任何Web应用程序中的最简单的方法是在web.xml中配置Servlet ContextListener和Filter,了解如何读取Shiro的INI配置。大部分INI配置格式本身在配置页面的INI Sections部分中定义,但我们将在此处介绍一些其他特定于Web的部分。</p>  <strong>使用Spring?</strong> <hr /> <p>Spring Framework用户不会执行此设置。如果你使用Spring,你将需要阅读关于Spring特定的web配置。</p>   <h3>web.xml</h3> <h4>Shiro 1.2以后</h4> <p>在Shiro 1.2及更高版本中,标准Web应用程序通过添加以下XML块来初始化Shiro <code>web.xml</code>:</p> <pre> <code class="language-xml"><listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener> ... <filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> </filter> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping> </code></pre> <p>这假设一个Shiro INI 配置文件位于以下两个位置之一,使用以前找到的位置:</p> <ol> <li><code>/WEB-INF/shiro.ini</code></li> <li><code>shiro.ini</code> 文件在类路径的根。</li> </ol> <p>这里是上面的配置:</p> <ul> <li> <p>的<code>EnvironmentLoaderListener</code>初始化四郎<code>WebEnvironment</code>实例(包含一切四郎需要操作,包括<code>SecurityManager</code>),并使其在访问<code>ServletContext</code>。如果您需要随时获取此<code>WebEnvironment</code>实例,则可以调用<code>WebUtils.getRequiredWebEnvironment(servletContext)</code>。</p> </li> <li> <p>该<code>ShiroFilter</code>会利用这个<code>WebEnvironment</code>来执行所有必要的安全操作的任何过滤的要求。</p> </li> <li> <p>最后,该<code>filter-mapping</code>定义确保所有请求都被过滤<code>ShiroFilter</code>,建议大多数Web应用程序使用,以确保任何请求都是安全的。</p> </li> </ul>  <strong>ShiroFilter过滤器映射</strong> <hr /> <p>通常希望在任何其他“filter-mapping”声明之前定义“ShiroFilter filter-mapping”,以确保Shiro也能在这些过滤器中运行。</p>   <h5>自定义<code>WebEnvironment</code>类</h5> <p>默认情况下,<code>EnvironmentLoaderListener</code>将创建一个<code>IniWebEnvironment</code>实例,它承担Shiro的基于INI的配置。如果愿意,您可以<code>WebEnvironment</code>通过在<code>ServletContext</code> <code>context-param</code>中指定一个自定义实例来指定<code>web.xml</code>:</p> <pre> <code class="language-xml"><context-param> <param-name>shiroEnvironmentClass</param-name> <param-value>com.foo.bar.shiro.MyWebEnvironment</param-value> </context-param> </code></pre> <p>这允许您自定义如何解析配置格式并将其表示为<code>WebEnvironment</code>实例。您可以对现有<code>IniWebEnvironment</code>的自定义行为进行子类化,或者完全支持不同的配置格式。例如,如果有人想要在XML而不是INI中配置Shiro,他们可以创建一个基于XML的实现,例如<code>com.foo.bar.shiro.XmlWebEnvironment</code>。</p> <h5>自定义配置位置</h5> <p>本<code>IniWebEnvironment</code>类希望读取并加载INI配置文件。默认情况下,此类将自动查找以下两个位置的Shiro <code>.ini</code>配置(按顺序):</p> <ol> <li><code>/WEB-INF/shiro.ini</code></li> <li><code>classpath:shiro.ini</code></li> </ol> <p>它将使用先找到的。</p> <p>然而,如果你希望将你的配置在其他位置,则可能与另一指定位置<code>context-param</code>在<code>web.xml</code>:</p> <pre> <code class="language-xml"><context-param> <param-name>shiroConfigLocations</param-name> <param-value>YOUR_RESOURCE_LOCATION_HERE</param-value> </context-param> </code></pre> <p>默认情况下,<code>param-value</code>期望由<code>ServletContext.getResource</code>方法定义的规则可解析。例如,<code>/WEB-INF/some/path/shiro.ini</code></p> <p>但是,您也可以使用Shiro的ResourceUtils类支持的适当资源前缀来指定特定的文件系统,类路径或URL位置,例如:</p> <ul> <li><code>file:/home/foobar/myapp/shiro.ini</code></li> <li><code>classpath:com/foo/bar/shiro.ini</code></li> <li><code>url:http://confighost.mycompany.com/myapp/shiro.ini</code></li> </ul> <h4>Shiro 1.1和更早版本</h4> <p>在1.1或更早版本的Web应用程序中启用Shiro的最简单的方法是定义IniShiroFilter并指定<code>filter-mapping</code>:</p> <pre> <code class="language-xml"><filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class> </filter> ... <!-- Make sure any request you want accessible to Shiro is filtered. /* catches all --> <!-- requests. Usually this filter mapping is defined first (before all others) to --> <!-- ensure that Shiro works in subsequent filters in the filter chain: --> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping> </code></pre> <p>此定义要求您的INI配置位于类路径(例如<code>classpath:shiro.ini</code>)的根目录下的shiro.ini文件中。</p> <h5>自定义路径</h5> <p>如果不想将INI配置放入<code>/WEB-INF/shiro.ini</code>或<code>classpath:shiro.ini</code>,您可以根据需要指定自定义资源位置。添加<code>configPath init-param</code>并指定资源位置:</p> <pre> <code class="language-xml"><filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class> <init-param> <param-name>configPath</param-name> <param-value>/WEB-INF/anotherFile.ini</param-value> </init-param> </filter> ... </code></pre> <p>未限定(无限额或“非前缀”)<code>configPath</code>值被假定为<code>ServletContext</code>资源路径,可通过该<br /> <code>ServletContext.getResource</code>方法定义的规则解析。</p>  <strong>ServletContext资源路径 - Shiro 1.2+</strong> <hr /> <p>ServletContext资源路径在Shiro 1.2和更高版本中可用。在1.1和更早的版本中,所有<code>configPath</code>的定义必须指定<code>classpath:</code>,<code>file:</code>或<code>url:</code>前缀。</p> <p>你也可以指定其他非<code>ServletContext</code>使用资源位置<code>classpath:</code>,<code>url:</code>或<code>file:</code>前缀分别表示classpath中,URL或文件系统位置。例如:</p> <pre> <code class="language-xml">... <init-param> <param-name>configPath</param-name> <param-value>url:http://configHost/myApp/shiro.ini</param-value> </init-param> ... </code></pre> <h5>内联配置</h5> <p>最后,还可以将INI配置嵌入到web.xml中,而不使用INI文件。您可以使用<code>config init-param</code>而不是<code>configPath</code>:</p> <pre> <code class="language-xml"><filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class> <init-param><param-name>config</param-name><param-value> # INI Config Here </param-value></init-param> </filter> ... </code></pre> <p>直列配置往往细小型或简单的应用,但它通常是更方便的外部化它的原因如下专用shiro.ini文件:</p> <ul> <li>您可以编辑安全配置很多,并且不想将修订控制'noise'添加到web.xml文件</li> <li>您可能希望将安全配置与web.xml配置的其余部分分离</li> <li>您的安全配置可能会变大,您希望保持web.xml精简并更易于阅读</li> <li>你有一个复杂的构建系统,其中相同的shiro配置可能需要在多个地方引用</li> </ul> <p>这取决于你 - 使用什么对你的项目有意义。</p> <h3>Web INI配置</h3> <p>除了标准<code>[main]</code>,<code>[users]</code>并<code>[roles]</code>在主已经说明部分配置章节中,你还可以指定一个特定的网络<code>[urls]</code>在部分<code>shiro.ini</code>文件:</p> <pre> <code># [main], [users] and [roles] above here ... [urls] ... </code></pre> <p>该<code>[urls]</code>部分允许你做一些事情,不以任何Web框架存在,我们见过的:确定特设的过滤器链在应用程序中任何匹配的URL路径的能力!</p> <p>这是<em>远远</em>比你通常定义过滤链更灵活,功能强大,简洁的<code>web.xml</code>:即使你从未使用过任何其他的功能,该功能提供四郎也只有这个使用,仅此一项就使值得使用。</p> <h4>[urls]</h4> <p>该<code>urls</code>节中每行的格式如下:</p> <pre> <code>_URL_Ant_Path_Expression_ = _Path_Specific_Filter_Chain_ </code></pre> <p>例如:</p> <pre> <code>... [urls] /index.html = anon /user/create = anon /user/** = authc /admin/** = authc, roles[administrator] /rest/** = authc, rest /remoting/rpc/** = authc, perms["remote:invoke"] </code></pre> <p>接下来,我们将详细介绍这些行的含义。</p> <p>等号(=)左侧的令牌是相对于Web应用程序的上下文根的Ant样式路径表达式。</p> <p>例如,假设您有以下<code>[urls]</code>行:</p> <pre> <code>/account/** = ssl, authc </code></pre> <p>该行指出:“以我的应用程序的路径的任何请求<code>/account</code>或任何它的子路径(<code>/account/foo</code>,<code>/account/bar/baz</code>,等),将触发”SSL,authc'过滤器链“。我们将在下面介绍过滤链。</p> <p>请注意,所有路径表达式都与应用程序的上下文根相关。这意味着如果你部署你的应用程序一天,<code>www.somehost.com/myapp</code>然后,然后将其部署到<code>www.anotherhost.com</code>(没有'myapp'子路径),模式匹配仍然会工作。所有路径都是相对于HttpServletRequest.getContextPath()值。</p>  <strong>订单事项!</strong> <hr /> <p>URL路径表达式根据传入请求按照它们定义的顺序和<em>第一个匹配的WINS</em>进行评估。例如,让我们假设有以下链定义:</p> <pre> <code>/account/** = ssl, authc /account/signup = anon </code></pre> <p>如果传入的请求打算到达<code>/account/signup/index.html</code>(所有“anon'ymous用户可访问),<em>它将永远不会被处理!</em>。原因是该<code>/account/**</code>模式首先匹配传入请求,并将所有剩余定义“短路”。</p> <p>始终记住根据<em>FIRST MATCH WINS</em>策略定义您的过滤器链!</p> <p> </p>   <h5>过滤器链定义</h5> <p>等号(=)右侧的令牌是以逗号分隔的过滤器列表,以对匹配该路径的请求执行。它必须匹配以下格式:</p> <pre> <code>filter1[optional_config1], filter2[optional_config2], ..., filterN[optional_configN] </code></pre> <p>哪里:</p> <ul> <li><em>filterN</em>是在<code>[main]</code>部分中定义的过滤器bean的名称</li> <li><code>[optional_configN]</code>是一个可选的括号字符串,对于<em>该特定路径</em>(每个过滤器,<em>特定</em>于<em>路径的</em>配置!)<em>的特定</em>过滤器具有含义。如果过滤器不需要为该URL路径指定特定的配置,您可以丢弃括号,<code>filterN[]</code>只是变成了<code>filterN</code>。</li> </ul> <p>并且因为过滤器令牌定义链(aka List),记住顺序很重要!按您希望请求流过链的顺序定义逗号分隔的列表。</p> <p>最后,如果不满足其必要条件(例如,执行重定向,使用HTTP错误代码进行响应,直接呈现等),则每个过滤器都可以自由地处理响应。否则,预期允许请求通过链继续到最终目的地视图。</p>  <strong>小费</strong> <hr /> <p>能够对路径特定配置(即<code>[optional_configN]</code>过滤器令牌的一部分)做出反应是对于Shiro过滤器可用的独特特征。</p> <p>如果你想创建自己的<code>javax.servlet.Filter</code>实现,也可以这样做,请确保你的过滤器子类org.apache.shiro.web.filter.PathMatchingFilter</p> <p> </p>   <h6>可用过滤器</h6> <p>可用于过滤器链定义的“池”过滤器在此<code>[main]</code>部分中定义。在主节中分配给它们的名称是在过滤器链定义中使用的名称。例如:</p> <pre> <code>[main] ... myFilter = com.company.web.some.FilterImplementation myFilter.property1 = value1 ... [urls] ... /some/path/** = myFilter </code></pre> <h2>默认过滤器</h2> <p>当运行Web应用程序时,Shiro将创建一些有用的默认<code>Filter</code>实例,并使其在<code>[main]</code>部分自动可用。您可以像配置<code>main</code>其他bean一样配置它们,并在链定义中引用它们。例如:</p> <pre> <code>[main] ... # Notice how we didn't define the class for the FormAuthenticationFilter ('authc') - it is instantiated and available already: authc.loginUrl = /login.jsp ... [urls] ... # make sure the end-user is authenticated. If not, redirect to the 'authc.loginUrl' above, # and after successful authentication, redirect them back to the original account page they # were trying to view: /account/** = authc ... </code></pre> <p>自动可用的默认Filter实例由DefaultFilter枚举定义,枚举的<code>name</code>字段是可用于配置的名称。他们是:</p> <table> <thead> <tr> <th>过滤器名称</th> <th>类</th> </tr> </thead> <tbody> <tr> <td>anon</td> <td>org.apache.shiro.web.filter.authc.AnonymousFilter</td> </tr> <tr> <td>authc</td> <td>org.apache.shiro.web.filter.authc.FormAuthenticationFilter</td> </tr> <tr> <td>authcBasic</td> <td>org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter</td> </tr> <tr> <td>登出</td> <td>org.apache.shiro.web.filter.authc.LogoutFilter</td> </tr> <tr> <td>noSessionCreation</td> <td>org.apache.shiro.web.filter.session.NoSessionCreationFilter</td> </tr> <tr> <td>烫发</td> <td>org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter</td> </tr> <tr> <td>港口</td> <td>org.apache.shiro.web.filter.authz.PortFilter</td> </tr> <tr> <td>休息</td> <td>org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter</td> </tr> <tr> <td>角色</td> <td>org.apache.shiro.web.filter.authz.RolesAuthorizationFilter</td> </tr> <tr> <td>ssl</td> <td>org.apache.shiro.web.filter.authz.SslFilter</td> </tr> <tr> <td>用户</td> <td>org.apache.shiro.web.filter.authc.UserFilter</td> </tr> </tbody> </table> <h2>启用和禁用过滤器</h2> <p>与任何过滤器链定义机制(<code>web.xml</code>,Shiro的INI等)的情况一样,只需通过将过滤器包含在过滤器链定义中来启用它,并通过从链定义中删除它来禁用它。</p> <p>但是在Shiro 1.2中添加的一个新功能是能够启用或禁用过滤器,而不从过滤器链中删除它们。如果启用(默认设置),则会按预期对请求进行过滤。如果禁用,则过滤器将允许请求立即传递到中的下一个元素<code>FilterChain</code>。您可以通常基于配置属性触发过滤器的启用状态,或者甚至可以<em>根据请求</em>触发它。</p> <p>这是一个强大的概念,因为与更改静态过滤器链定义(这将是永久和不灵活的)相比,基于某些要求启用或禁用过滤器通常更方便。</p> <p>Shiro通过其OncePerRequestFilter抽象父类完成此操作。所有Shiro的开箱即用的Filter实现子类化这一个,因此可以启用或禁用,而不从过滤器链中删除它们。你可以为你自己的过滤器实现子类这个类,如果你也需要这个功能*。</p> <p>* SHIRO-224希望能为任何过滤器启用此功能,而不只是那些子类<code>OncePerRequestFilter</code>。如果这对您很重要,请投票支持此问题。</p> <h3>一般启用/禁用</h3> <p>该OncePerRequestFilter(及其所有子类)支持启用/所有的请求,以及在每个请求的基础禁用。</p> <p>对所有请求的一般启用或禁用过滤器是通过将其<code>enabled</code>属性设置为true或false来实现的。默认设置是<code>true</code>因为大多数过滤器本身需要执行,如果它们在链中配置。</p> <p>例如,在shiro.ini中:</p> <pre> <code>[main] ... # configure Shiro's default 'ssl' filter to be disabled while testing: ssl.enabled = false [urls] ... /some/path = ssl, authc /another/path = ssl, roles[admin] ... </code></pre> <p>此示例显示可能许多URL路径都可能要求请求必须由SSL连接保护。在开发过程中设置SSL可能会令人沮丧和耗时。在开发过程中,可以禁用ssl过滤器。部署到生产时,您可以使用一个配置属性启用它 - 这比手动更改所有URL路径或维护两个Shiro配置要容易得多。</p> <h3>请求特定的启用/禁用</h3> <p><code>OncePerRequestFilter</code>实际上根据其<code>isEnabled(request, response)</code>方法确定是否启用或禁用过滤器。</p> <p>此方法默认返回属性的值,<code>enabled</code>用于一般启用/禁用所有请求,如上所述。如果要根据<em>请求特定</em>条件启用或禁用过滤器,则可以覆盖该<code>OncePerRequestFilter</code><code>isEnabled(request,response)</code>方法以执行更具体的检查。</p> <h3>路径特定的启用/禁用</h3> <p>Shiro的PathMatchingFilter(一个子类)<code>OncePerRequestFilter</code>能够基于被过滤的<em>特定路径</em>对配置做出反应,这意味着除了传入的请求和响应之外,您还可以基于路径和特定于路径的配置启用或禁用过滤器。</p> <p>如果您需要能够对匹配路径和路径特定配置做出反应,以确定是启用还是禁用过滤器,而不是<code>OncePerRequestFilter</code> <code>isEnabled(request,response)</code>覆盖方法,那么您将覆盖该<code>PathMatchingFilter</code> <code>isEnabled(request,response,path,pathConfig)</code>方法。</p> <h2>会话管理</h2> <h3>Servlet容器会话</h3> <p>在Web环境中,Shiro的默认会话管理器<code>SessionManager</code>实现是<code>ServletContainerSessionManager</code>。这个非常简单的实现将所有会话管理职责(包括如果servlet容器支持它的会话群集)委托给运行时Servlet容器。它本质上是一个桥梁,用于Shiro的会话API到servlet容器,没有别的。</p> <p>使用此默认值的一个好处是,使用现有servlet容器会话配置(超时,任何容器特定的集群机制等)的应用程序将按预期工作。</p> <p>这个默认的缺点是,你绑定到servlet容器的特定会话行为。例如,如果您想集群会话,但在生产中使用Jetty进行测试和Tomcat,则容器特定的配置(或代码)将不可移植。</p> <h4>Servlet容器会话超时</h4> <p>如果使用默认的servlet容器支持,您可以在Web应用程序的<code>web.xml</code>文件中按预期配置会话超时。例如:</p> <pre> <code class="language-xml"><session-config> <!-- web.xml expects the session timeout in minutes: --> <session-timeout>30</session-timeout> </session-config> </code></pre> <h3>本地会话</h3> <p>如果希望您的会话配置设置和集群在servlet容器(例如Jetty在测试中,但是Tomcat或JBoss在生产中)是可移植的,或者您想要控制特定的会话/集群功能,您可以启用Shiro的本地会话管理。</p> <p>“Native”这个词意味着Shiro自己的企业会话管理实现将用于支持所有<code>Subject</code>和<code>HttpServletRequest</code>会话,并完全绕过servlet容器。但是放心 - Shiro直接实现了Servlet规范的相关部分,所以任何现有的web / http相关代码如预期一样工作,并且不需要“知道”Shiro是透明地管理会话。</p> <h4>DefaultWebSessionManager</h4> <p>要为Web应用程序启用本机会话管理,您需要配置一个本地的Web会话管理器来覆盖默认的基于servlet容器的会话管理器。你可以通过配置<code>DefaultWebSessionManager</code>Shiro的实例来实现<code>SecurityManager</code>。例如,在<code>shiro.ini</code>:</p> <p><strong>shiro.ini本地Web会话管理</strong></p> <pre> <code>[main] ... sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager # configure properties (like session timeout) here if desired # Use the configured native session manager: securityManager.sessionManager = $sessionManager </code></pre> <p>一旦声明,您可以配置<code>DefaultWebSessionManager</code>实例与本地会话选项,如会话超时和聚类配置,如会话管理部分所述。</p> <h5>本地会话超时</h5> <p>配置<code>DefaultWebSessionManager</code>实例后,按会话管理:会话超时中所述配置会话超时</p> <h5>会话Cookie</h5> <p>在<code>DefaultWebSessionManager</code>支持两个特定网络的配置属性:</p> <ul> <li><code>sessionIdCookieEnabled</code> (布尔)</li> <li><code>sessionIdCookie</code>,一个Cookie实例。</li> </ul>  <strong>Cookie作为模板</strong> <hr /> <p>该<code>sessionIdCookie</code>属性本质上是一个模板 - 您配置<code>Cookie</code>实例属性,并且此模板将用于在运行时使用适当的会话ID值设置实际的HTTP Cookie。</p>   <h6>会话Cookie配置</h6> <p>DefaultWebSessionManager的<code>sessionIdCookie</code>默认实例是a <code>SimpleCookie</code>。这个简单的实现允许对要在http Cookie上配置的所有相关属性进行JavaBeans风格的属性配置。</p> <p>例如,您可以设置Cookie域:</p> <pre> <code>[main] ... securityManager.sessionManager.sessionIdCookie.domain = foo.com </code></pre> <p>有关其他属性,请参阅SimpleCookie JavaDoc。</p> <p>cookie的默认名称<code>JSESSIONID</code>与servlet规范一致。此外,Shiro的cookie支持<code>HttpOnly</code>标志。该<code>sessionIdCookie</code>套<code>HttpOnly</code>到<code>true</code>默认情况下,额外的安全性。</p>  <strong>注意</strong> <hr /> <p>Shiro的<code>Cookie</code>概念<code>HttpOnly</code>甚至在Servlet 2.4和2.5环境中支持该标志(而Servlet API只在2.6或更高版本中支持它)。</p>   <h6>禁用会话Cookie</h6> <p>如果您不想使用会话Cookie,可以通过将<code>sessionIdCookieEnabled</code>属性配置为false 来禁用它们。例如:</p> <p><strong>禁用本机会话Cookie</strong></p> <pre> <code>[main] ... securityManager.sessionManager.sessionIdCookieEnabled = false </code></pre> <h2>记住我的服务</h2> <p>Shiro将执行'rememberMe'服务,如果<code>AuthenticationToken</code>实现的<code>org.apache.shiro.authc.RememberMeAuthenticationToken</code>接口。此接口指定一个方法:</p> <pre> <code>boolean isRememberMe(); </code></pre> <p>如果此方法返回<code>true</code>,则Shiro将记住会话中的最终用户身份。</p>  <strong>UsernamePasswordToken和RememberMe</strong> <hr /> <p>常用的<code>UsernamePasswordToken</code>已经实现了<code>RememberMeAuthenticationToken</code>接口并支持rememberMe登录。</p>   <h3>计划支持</h3> <p>要以程序方式使用rememberMe,可以将值设置为<code>true</code>支持此配置的类。例如,使用标准<code>UsernamePasswordToken</code>:</p> <pre> <code>UsernamePasswordToken token = new UsernamePasswordToken(username, password); token.setRememberMe(true); SecurityUtils.getSubject().login(token); ... </code></pre> <h3>基于表单的登录</h3> <p>对于Web应用程序,<code>authc</code>过滤器默认为a <code>FormAuthenticationFilter</code>。这支持将“rememberMe”布尔作为form / request参数读取。默认情况下,它期望请求参数被命名<code>rememberMe</code>。这里是一个示例shiro.ini配置支持这:</p> <pre> <code>[main] authc.loginUrl = /login.jsp [urls] # your login form page here: login.jsp = authc </code></pre> <p>在您的网络表单中,有一个名为“rememberMe”的复选框:</p> <pre> <code><form ...> Username: <input type="text" name="username"/> <br/> Password: <input type="password" name="password"/> ... <input type="checkbox" name="rememberMe" value="true"/>Remember Me? ... </form> </code></pre> <p>默认情况下,<code>FormAuthenticationFilter</code>会寻找名为请求参数<code>username</code>,<code>password</code>和<code>rememberMe</code>。如果这些不同于您在表单中使用的表单字段名称,则需要在上配置名称<code>FormAuthenticationFilter</code>。例如,在<code>shiro.ini</code>:</p> <pre> <code>[main] ... authc.loginUrl = /whatever.jsp authc.usernameParam = somethingOtherThanUsername authc.passwordParam = somethingOtherThanPassword authc.rememberMeParam = somethingOtherThanRememberMe ... </code></pre> <h3>Cookie配置</h3> <p>您可以<code>rememberMe</code>通过设置默认的{{RememberMeManager}}的各种cookie属性来配置cookie的功能。例如,在shiro.ini中:</p> <pre> <code>[main] ... securityManager.rememberMeManager.cookie.name = foo securityManager.rememberMeManager.cookie.maxAge = blah ... </code></pre> <p>请参阅<code>CookieRememberMeManager</code>和支持<code>SimpleCookie</code>JavaDoc的配置属性。</p> <h3>自定义 <code>RememberMeManager</code></h3> <p>应该注意的是,如果默认的基于cookie的实现<code>RememberMeManager</code>不能满足你的需要,你可以插入任何你喜欢的<code>securityManager</code>类似,你将配置任何其他对象引用:</p> <pre> <code>[main] ... rememberMeManager = com.my.impl.RememberMeManager securityManager.rememberMeManager = $rememberMeManager </code></pre> <h2>JSP / GSP标签库</h2> <p>Apache Shiro提供了一个<code>Subject</code>-aware JSP / GSP标签库,允许您根据当前主题的状态控制您的JSP,JSTL或GSP页面输出。这对于基于查看网页的当前用户的身份和授权状态来个性化视图是非常有用的。</p> <h3>标签库配置</h3> <p>标签库描述符(TLD)文件捆绑在<code>shiro-web.jar</code>中<code>META-INF/shiro.tld</code>文件。要使用任何标记,请将以下行添加到JSP页面的顶部(或在您定义页面指令的任何位置):</p> <pre> <code><%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> </code></pre> <p>我们使用<code>shiro</code>前缀来指示shiro标签库命名空间,但是您可以分配任何您喜欢的名称。</p> <p>现在,我们将介绍每个标记,并说明如何使用它来呈现网页。</p> <h3>该<code>guest</code>标签</h3> <p><code>guest</code>只有当前<code>Subject</code>被视为“访客”时,标记才会显示其包装内容。客人是<code>Subject</code>没有身份的任何人。也就是说,我们不知道用户是谁,因为他们没有登录,他们不记得(从记住我服务)从以前的网站访问。</p> <p>例:</p> <pre> <code class="language-xml"><shiro:guest> Hi there! Please <a href="login.jsp">Login</a> or <a href="signup.jsp">Signup</a> today! </shiro:guest> </code></pre> <p>该<code>guest</code>标签是的逻辑相反的<code>user</code>标记。</p> <h3>该<code>user</code>标签</h3> <p><code>user</code>仅当当前<code>Subject</code>被视为“用户”时,标记才会显示其包装内容。在此上下文中的“用户”被定义为<code>Subject</code>具有已知标识,或者来自成功认证或者来自“记住我”服务。请注意,此标记在语义上与已认证的标记不同,后者比此标记更受限制。</p> <p>例:</p> <pre> <code class="language-xml"><shiro:user> Welcome back John! Not John? Click <a href="login.jsp">here<a> to login. </shiro:user> </code></pre> <p>该<code>user</code>标签是的逻辑相反的<code>guest</code>标记。</p> <h3>该<code>authenticated</code>标签</h3> <p>仅当当前用户<em>在其当前会话期间</em>已成功通过身份验证<em>时,</em>才显示主体内容。它比“用户”标签限制性更强。它在逻辑上与“notAuthenticated”标签相反。</p> <p>该<code>authenticated</code>标签将显示其包裹内容仅在当前<code>Subject</code>已成功验证<em>了当前会话中</em>。这是一个比用户更严格的标签,用于保证敏感工作流中的身份。</p> <p>例:</p> <pre> <code class="language-xml"><shiro:authenticated> <a href="updateAccount.jsp">Update your contact information</a>. </shiro:authenticated> </code></pre> <p>该<code>authenticated</code>标签是的逻辑相反的<code>notAuthenticated</code>标记。</p> <h3>该<code>notAuthenticated</code>标签</h3> <p>该<code>notAuthenticated</code>如果当前标签将显示其包裹内容<code>Subject</code>已<strong>不</strong>还成功地在本届会议期间进行身份验证。</p> <p>例:</p> <pre> <code class="language-xml"><shiro:notAuthenticated> Please <a href="login.jsp">login</a> in order to update your credit card information. </shiro:notAuthenticated> </code></pre> <p>该<code>notAuthenticated</code>标签是的逻辑相反的<code>authenticated</code>标记。</p> <h3>该<code>principal</code>标签</h3> <p>该<code>principal</code>标签将输出主题的<code>principal</code>(标识属性)或本金的属性。</p> <p>没有任何标记属性,标记将呈现<code>toString()</code>主体的值。例如(假设主体是字符串用户名):</p> <pre> <code class="language-xml">Hello, <shiro:principal/>, how are you today? </code></pre> <p>这是(大多数)等价于以下:</p> <pre> <code>Hello, <%= SecurityUtils.getSubject().getPrincipal().toString() %>, how are you today? </code></pre> <h4>类型主体</h4> <p>该<code>principal</code>标签假设默认情况下,该校长印的是<code>subject.getPrincipal()</code>价值。但是如果要打印一个<em>不是</em>主要主体的值,而是在主体的{ principal集合中另一个值,那么您可以通过类型获取该主体,并打印该值。</p> <p>例如,打印主题的用户ID(而不是用户名),假设ID在主体集合中:</p> <pre> <code>User ID: <principal type="java.lang.Integer"/> </code></pre> <p>这是(大多数)等价于以下:</p> <pre> <code>User ID: <%= SecurityUtils.getSubject().getPrincipals().oneByType(Integer.class).toString() %> </code></pre> <h4>主要财产</h4> <p>但是如果主体(上面的默认主要主体或者“类型化”主体)是一个复杂对象而不是一个简单的字符串,并且你想引用该主体上的一个属性呢?您可以使用该<code>property</code>属性指示要读取的属性的名称(必须通过JavaBeans兼容的getter方法访问)。例如(假设主要主体是User对象):</p> <pre> <code>Hello, <shiro:principal property="firstName"/>, how are you today? </code></pre> <p>这是(大多数)等价于以下:</p> <pre> <code>Hello, <%= SecurityUtils.getSubject().getPrincipal().getFirstName().toString() %>, how are you today? </code></pre> <p>或者,结合type属性:</p> <pre> <code>Hello, <shiro:principal type="com.foo.User" property="firstName"/>, how are you today? </code></pre> <p>这在很大程度上等同于以下:</p> <pre> <code>Hello, <%= SecurityUtils.getSubject().getPrincipals().oneByType(com.foo.User.class).getFirstName().toString() %>, how are you today? </code></pre> <h3>该<code>hasRole</code>标签</h3> <p><code>hasRole</code>仅当当前<code>Subject</code>分配了指定的角色时,标记才会显示其包装内容。</p> <p>例如:</p> <pre> <code><shiro:hasRole name="administrator"> <a href="admin.jsp">Administer the system</a> </shiro:hasRole> </code></pre> <p>该<code>hasRole</code>标签是的逻辑相反lacksRole标记。</p> <h3>该<code>lacksRole</code>标签</h3> <p><code>lacksRole</code>仅当当前<code>Subject</code> <strong>未</strong>分配指定角色时,标记才会显示其包装内容。</p> <p>例如:</p> <pre> <code class="language-xml"><shiro:lacksRole name="administrator"> Sorry, you are not allowed to administer the system. </shiro:lacksRole> </code></pre> <p>该<code>lacksRole</code>标签是的逻辑相反hasRole标记。</p> <h3>该<code>hasAnyRole</code>标签</h3> <p>该<code>hasAnyRole</code>如果当前标签将显示其包裹内容<code>Subject</code>被分配<em>任何</em>指定的角色从一个逗号分隔的角色名称列表。</p> <p>例如:</p> <pre> <code class="language-xml"><shiro:hasAnyRoles name="developer, project manager, administrator"> You are either a developer, project manager, or administrator. </shiro:lacksRole> </code></pre> <p>该<code>hasAnyRole</code>标签目前还没有一个逻辑相反的标记。</p> <h3>该<code>hasPermission</code>标签</h3> <p><code>hasPermission</code>只有当前的<code>Subject</code>“has”(暗示)指定的权限,标签才会显示其包装的内容。也就是说,用户具有指定的能力。</p> <p>例如:</p> <pre> <code class="language-xml"><shiro:hasPermission name="user:create"> <a href="createUser.jsp">Create a new User</a> </shiro:hasPermission> </code></pre> <p>该<code>hasPermission</code>标签是的逻辑相反lacksPermission标记。</p> <h3>该<code>lacksPermission</code>标签</h3> <p><code>lacksPermission</code>只有当前的<code>Subject</code> <strong>DOES没有</strong>(暗示)指定的权限,标签才会显示其包装的内容。也就是说,用户<strong>没有</strong>指定的能力。</p> <p>例如:</p> <pre> <code class="language-xml"><shiro:lacksPermission name="user:delete"> Sorry, you are not allowed to delete user accounts. </shiro:hasPermission> </code></pre> <p>该<code>lacksPermission</code>标签是的逻辑相反hasPermission标记。</p> <h2>借给文档</h2> <p>虽然我们希望本文档帮助您与Apache Shiro正在进行的工作,社区正在不断改进和扩展文档。如果您希望帮助Shiro项目,请考虑更正,扩展或添加您需要的文档。你提供的每一点帮助扩大了社区,反过来又改善了Shiro。</p> <p>提交文档的最简单方法是通过点击以下<code>Edit</code>链接提交拉取请求,将其发送到用户论坛或用户邮件列表。</p>
  • spring boot shiro 无状态token认证

    spring boot shiro 无状态token认证spring boot shiro 无状态token认证
  • spring boot shiro redis整合基于角色和权限的安全管理-Java编程

    Java编程之spring boot shiro redis整合基于角色和权限的安全管理,Java编程,spring boot,shiro,权限控制<h2>一、概述</h2>   本博客主要讲解spring boot整合Apache的shiro框架,实现基于角色的安全访问控制或者基于权限的访问安全控制,其中还使用到分布式缓存redis进行用户认证信息的缓存,减少数据库查询的开销。Apache shiro与spring security的作用几乎一样都是简化了Java程序的权限控制开发。 <h2>二、项目</h2> <h3>2.1首先是通过eclipse创建一个最新的spring boot项目,并添加以下依赖:</h3> <strong>pom.xml</strong> <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xqlee.project.demo.shiro</groupId> <artifactId>demo-springboot-shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-springboot-shiro-hello</name> <description>demo-springboot-shiro-hello</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- shiro权限控制框架 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <!--缓存暂时用简单的 https://mvnrepository.com/artifact/org.ehcache/ehcache --> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/net.sf.json-lib/json-lib --> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> <classifier>jdk15</classifier> </dependency> <!-- redis缓存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> <h3>2.2配置redis</h3> application.properties文件中添加配置: <pre> <code>####################Redis 配置信息 ########################## # Redis数据库分片索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=10.1.1.2 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=0 #测试redis的缓存日志 logging.level.net.xqlee.project.demo.shiro.config.shiro.cache=DEBUG</code></pre> <br /> Java config的redis配置<br /> <strong>RedisConfig</strong> <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.redis; import java.lang.reflect.Method; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @Configuration @EnableCaching // 继承CachingConfigurerSupport并重写方法,配合该注解实现spring缓存框架的启用 public class RedisConfig extends CachingConfigurerSupport { /** 载入通过配置文件配置的连接工场 **/ @Autowired RedisConnectionFactory redisConnectionFactory; @SuppressWarnings("rawtypes") @Autowired RedisTemplate redisTemplate; @Bean RedisTemplate<String, Object> objRedisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } /* * (non-Javadoc) * * @see org.springframework.cache.annotation.CachingConfigurerSupport# * cacheManager() */ @Bean // 必须添加此注解 @Override public CacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate); // 设置缓存过期时间 // redisCacheManager.setDefaultExpiration(60);//秒 return redisCacheManager; } /** * 重写缓存的key生成策略,可根据自身业务需要进行自己的配置生成条件 * * @see org.springframework.cache.annotation.CachingConfigurerSupport# * keyGenerator() */ @Bean // 必须项 @Override public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } } </code></pre> <h3><br /> 2.3创建shiro需要的 redis缓存器和缓存管理实现类</h3> 首先是缓存器cache的实现<br /> RedisCache.java <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.shiro.cache; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; /** * Redis的Shiro缓存对象实现 * * @author xq * * @param <K> * @param <V> */ public class RedisCache<K, V> implements Cache<K, V> { private static final Logger logger = LoggerFactory.getLogger(RedisCache.class); private RedisTemplate<K, V> redisTemplate; private final static String PREFIX = "shiro-cache:"; private String cacheKey; private long globExpire = 30; @SuppressWarnings({ "rawtypes", "unchecked" }) public RedisCache(final String name, final RedisTemplate redisTemplate) { this.cacheKey = PREFIX + name + ":"; this.redisTemplate = redisTemplate; } @Override public V get(K key) throws CacheException { logger.debug("Shiro从缓存中获取数据KEY值["+key+"]"); redisTemplate.boundValueOps(getCacheKey(key)).expire(globExpire, TimeUnit.MINUTES); return redisTemplate.boundValueOps(getCacheKey(key)).get(); } @Override public V put(K key, V value) throws CacheException { V old = get(key); redisTemplate.boundValueOps(getCacheKey(key)).set(value); return old; } @Override public V remove(K key) throws CacheException { V old = get(key); redisTemplate.delete(getCacheKey(key)); return old; } @Override public void clear() throws CacheException { redisTemplate.delete(keys()); } @Override public int size() { return keys().size(); } @Override public Set<K> keys() { return redisTemplate.keys(getCacheKey("*")); } @Override public Collection<V> values() { Set<K> set = keys(); List<V> list = new ArrayList<>(); for (K s : set) { list.add(get(s)); } return list; } @SuppressWarnings("unchecked") private K getCacheKey(Object k) { return (K) (this.cacheKey + k); } } </code></pre> 还有缓存管理器:<br /> <strong>RedisCacheManager.java</strong> <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.shiro.cache; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; /** * Redis的Shiro缓存管理器实现 * * @author xq * */ public class RedisCacheManager implements CacheManager { @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public <K, V> Cache<K, V> getCache(String name) throws CacheException { return new RedisCache<>(name, redisTemplate); } } </code></pre> <h3>2.4自定义一个shiro的realm实现</h3> <h3>创建realm之前需要编写一个模拟数据库查询的用户业务处理类,提供给上面的自定义realm使用</h3> 简单的用户登录对象: <pre> <code class="language-java">package net.xqlee.project.demo.shiro.pojo; import java.util.Date; import java.util.List; /** * 用户信息 * * @author xqlee * */ public class LoginAccount { /** 用户名 */ String loginName; List<String> roles;// 测试用 List<String> permissions;// 测试用直接放用户登录对象里面 /** 用户密码 **/ String password; boolean enabled; Date createDate; boolean isExpired; public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } public boolean isExpired() { return isExpired; } public void setExpired(boolean isExpired) { this.isExpired = isExpired; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } public List<String> getPermissions() { return permissions; } public void setPermissions(List<String> permissions) { this.permissions = permissions; } } </code></pre> 用户模拟业务处理 <pre> <code class="language-java">package net.xqlee.project.demo.shiro.service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.stereotype.Component; import net.xqlee.project.demo.shiro.pojo.LoginAccount; /** * 用户业务服务类 * * @author xqlee * */ @Component("userService") public class UserService { /** 由于重点不在数据库,这里需要使用数据库的地方全部用map代替 **/ /** 用户信息 **/ static Map<String, LoginAccount> users = new HashMap<>(); static { // 创建一个用户 LoginAccount account = new LoginAccount(); account.setLoginName("leftso"); account.setPassword("123456"); account.setEnabled(true); account.setExpired(false); // 角色添加 List<String> roles = new ArrayList<>(); roles.add("ROLE_USER"); account.setRoles(roles); List<String> permissions = new ArrayList<>(); permissions.add("query"); permissions.add("delete"); account.setPermissions(permissions); users.put(account.getLoginName(), account); // 创建一个用户 LoginAccount admin = new LoginAccount(); admin.setLoginName("admin"); admin.setPassword("123456"); admin.setEnabled(true); admin.setExpired(false); // 角色添加 roles = new ArrayList<>(); roles.add("ROLE_ADMIN"); admin.setRoles(roles); permissions = new ArrayList<>(); permissions.add("query"); permissions.add("delete"); admin.setPermissions(permissions); users.put("admin", admin); } /** * 通过用户名获取用户权限集合 * * @param loginName * 用户名 * @return 用户的权限集合 */ public List<String> getPermissionsByLoginName(String loginName) { if (users.containsKey(loginName)) { return users.get(loginName).getPermissions(); } return new ArrayList<>(); } /** * 通过用户名获取用户信息 * * @param loginName * 用户名 * @return 用户信息 */ public LoginAccount getLoginAccountByLoginName(String loginName) { if (users.containsKey(loginName)) { return users.get(loginName); } return null; } } </code></pre>   <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.shiro; import java.util.List; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import net.xqlee.project.demo.shiro.pojo.LoginAccount; import net.xqlee.project.demo.shiro.service.UserService; /** * 实现一个基于JDBC的Realm,继承AuthorizingRealm可以看见需要重写两个方法,doGetAuthorizationInfo和doGetAuthenticationInfo * * @author xqlee * */ @Component("JDBCShiroRealm") public class JDBCShiroRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(JDBCShiroRealm.class); /*** 用户业务处理类,用来查询数据库中用户相关信息 ***/ @Autowired UserService userService; /*** * 获取用户授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("##################执行Shiro权限认证##################"); // 获取用户名 String loginName = (String) principalCollection.fromRealm(getName()).iterator().next(); // 判断用户名是否存在 if (StringUtils.isEmpty(loginName)) { return null; } // 查询登录用户信息 LoginAccount account = userService.getLoginAccountByLoginName(loginName); if (account == null) { logger.warn("用户[" + loginName + "]信息不存在"); return null; } // 创建一个授权对象 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 进行权限设置 List<String> permissions = account.getPermissions(); if (permissions != null && !permissions.isEmpty()) { info.addStringPermissions(permissions); } // 角色设置 List<String> roles = account.getRoles(); if (roles != null) { info.addRoles(roles); } return info; } /** * 获取用户认证信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("##################执行Shiro登陆认证##################"); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; // 通过表单接收的用户名 String loginName = token.getUsername(); if (loginName != null && !"".equals(loginName)) { // 模拟数据库查询用户信息 LoginAccount account = userService.getLoginAccountByLoginName(loginName); if (account != null) { // 登陆的主要信息: 可以是一个实体类的对象, 但该实体类的对象一定是根据 token 的 username 查询得到的. Object principal = token.getPrincipal(); // 创建shiro的用户认证对象 // 注意该对象的密码将会传递至后续步骤与前面登陆的subject的密码进行比对。 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, account.getPassword(), getName()); return authenticationInfo; } } return null; } } </code></pre> <h3><br /> <br /> 2.5shiro的核心配置文件</h3> <strong>ShiroConfig.java</strong> <pre> <code class="language-java">package net.xqlee.project.demo.shiro.config.shiro; import java.util.LinkedHashMap; import java.util.Map; import org.apache.shiro.SecurityUtils; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import net.xqlee.project.demo.shiro.config.shiro.cache.RedisCacheManager; /*** * shiro权限管理配置 * * @author xqlee * */ @Configuration public class ShiroConfig { /** * ehcache缓存方案<br/> * 简单的缓存,后续可更换为redis缓存,通过自己实现shiro的CacheManager接口和Cache接口 * * @return */ @Bean public CacheManager shiroEhCacheManager() { EhCacheManager cacheManager = new EhCacheManager(); cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return cacheManager; } /** * redis缓存方案 * * @return */ @Bean public CacheManager shiroRedisCacheManager() { return new RedisCacheManager(); } /**** * 自定义Real * * @return */ @Bean public JDBCShiroRealm jdbcShiroRealm() { JDBCShiroRealm realm = new JDBCShiroRealm(); // 根据情况使用缓存器 realm.setCacheManager(shiroRedisCacheManager());//shiroEhCacheManager() return realm; } /*** * 安全管理配置 * * @return */ @Bean public SecurityManager defaultWebSecurityManager() { // DefaultSecurityManager defaultSecurityManager = new // DefaultSecurityManager(); DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 注意:!!!初始化成这个将会报错java.lang.IllegalArgumentException: // SessionContext must be an HTTP compatible // implementation.:模块化本地测试shiro的一些总结 // 配置 securityManager.setRealm(jdbcShiroRealm()); // 注意这里必须配置securityManager SecurityUtils.setSecurityManager(securityManager); // 根据情况选择缓存器 securityManager.setCacheManager(shiroRedisCacheManager());//shiroEhCacheManager() return securityManager; } /** * 配置shiro的拦截器链工厂,默认会拦截所有请求,并且不可配置 * * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); // 配置安全管理(必须) filterFactoryBean.setSecurityManager(defaultWebSecurityManager()); // 配置登陆的地址 filterFactoryBean.setLoginUrl("/userNoLogin.do");// 未登录时候跳转URL,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 filterFactoryBean.setSuccessUrl("/welcome.do");// 成功后欢迎页面 filterFactoryBean.setUnauthorizedUrl("/403.do");// 未认证页面 // 配置拦截地址和拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 必须使用LinkedHashMap,因为拦截有先后顺序 // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 filterChainDefinitionMap.put("/userNoLogin.do*", "anon");// 未登录跳转页面不设权限认证 filterChainDefinitionMap.put("/login.do*", "anon");// 登录接口不设置权限认真 filterChainDefinitionMap.put("/logout.do*", "anon");// 登出不需要认证 // 以下配置同样可以通过注解 // @RequiresPermissions("user:edit")来配置访问权限和角色注解@RequiresRoles(value={"ROLE_USER"})方式定义 // 权限配置示例,这里的配置理论来自数据库查询 filterChainDefinitionMap.put("/user/**", "roles[ROLE_USER],perms[query]");// /user/下面的需要ROLE_USER角色或者query权限才能访问 filterChainDefinitionMap.put("/admin/**", "perms[ROLE_ADMIN]");// /admin/下面的所有需要ROLE_ADMIN的角色才能访问 // 剩下的其他资源地址全部需要用户认证后才能访问 filterChainDefinitionMap.put("/**", "authc"); filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // 全部配置 // anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名访问 // // authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter // 需要登录,不需要权限和角色可访问 // // authcBasic // org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter // // perms // org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter // 需要给定的权限值才能访问 // // port org.apache.shiro.web.filter.authz.PortFilter // // rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter // // roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter // 需要给定的角色才能访问 // // ssl org.apache.shiro.web.filter.authz.SslFilter // // user org.apache.shiro.web.filter.authc.UserFilter // // logout org.apache.shiro.web.filter.authc.LogoutFilter return filterFactoryBean; } } </code></pre> 上面配置中有两个缓存器可以选择,一个是简单的ehcache,一个是redis,大型项目推荐使用redis <h3><br /> 2.6编写一个测试的controller</h3> <pre> <code class="language-java">package net.xqlee.project.demo.shiro.controller; import javax.servlet.http.HttpServletResponse; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.ExcessiveAttemptsException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import net.sf.json.JSONObject; /** * 用户登录用 * * @author xqlee * */ @RestController public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); /**** * 用户未登录 * * @return */ @GetMapping("userNoLogin.do") public Object noLogin() { JSONObject object = new JSONObject(); object.put("message", "用户未登录"); return object; } @GetMapping(value = "/login.do") public String login(String loginName, String password) { try { // 创建shiro需要的token UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginName, password.toCharArray()); usernamePasswordToken.setRememberMe(true);// 记住 try { SecurityUtils.getSubject().login(usernamePasswordToken); } catch (UnknownAccountException uae) { logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,未知账户"); return "对用户[" + loginName + "]进行登录验证..验证未通过,未知账户"; } catch (IncorrectCredentialsException ice) { logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,错误的凭证"); ice.printStackTrace(); return "对用户[" + loginName + "]进行登录验证..验证未通过,错误的凭证"; } catch (LockedAccountException lae) { logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,账户已锁定"); return "对用户[" + loginName + "]进行登录验证..验证未通过,账户已锁定"; } catch (ExcessiveAttemptsException eae) { logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,错误次数过多"); return "对用户[" + loginName + "]进行登录验证..验证未通过,错误次数过多"; } catch (AuthenticationException ae) { // 通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 logger.info("对用户[" + loginName + "]进行登录验证..验证未通过,堆栈轨迹如下"); ae.printStackTrace(); return "用户名或密码不正确"; } return "Login Success!"; } catch (Exception e) { return "登陆时候发生异常," + e.getMessage(); } } @GetMapping("/user/hello.do") public String hello() { return "Hello User, From Server"; } @GetMapping("/admin/hello.do") public String helloAdmin() { return "Hello Admin, From Server"; } @GetMapping("/welcome.do") public String loginSuccess() { return "welcome"; } @GetMapping("/403.do") public Object error403(HttpServletResponse response) { response.setStatus(403); JSONObject object = new JSONObject(); object.put("message", "用户权限不够"); return object; } } </code></pre> <h3>2.7测试</h3> 1.通过spring bootapplication方式启动项目,项目默认监听在8080端口<br /> <br /> 首先访问需要权限的url  http://localhost:8080/user/hello.do<br /> <br /> <img srcset="" width="" size="" class="img-thumbnail" alt="访问需要权限的url" src="/resources/assist/images/blog/9399b39fad254f35b9f4ec1d6ec9d671.jpg" /><br /> 可以看到返回的是用户未登录提示,也就是我们在controller中写的未登录时候调用的方法返回值。<br /> <br /> 现在我们先通过用户名leftso和密码123456进行登录  localhost:8080/login.do?loginName=leftso&password=123456<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="登录" src="/resources/assist/images/blog/3281336d3f4844fd8164a2768271cbaa.png" /><br /> <br /> 可以看到登录成功,这个时候我们再次打开最初访问的:http://localhost:8080/user/hello.do<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="成功访问" src="/resources/assist/images/blog/51efce91cb62475bbd70a0554b8b8323.png" /><br /> 可以看到这次我们成功访问了需要ROLE_USER角色的url,<br /> <br /> 现在用这个登录的信息访问需要ROLE_ADMIN权限的地址http://localhost:8080/admin/hello.do<br /> <br /> <img srcset="" width="" size="" class="img-thumbnail" alt="提示权限不足" src="/resources/assist/images/blog/42e8ee00ac544ebebccb3f95fd157a5a.png" /><br /> 上面可以看到,无法访问,返回的提示是权限不足<br /> <br /> 切换为admin的用户登录,然后访问地址http://localhost:8080/admin/hello.do<br /> <br /> 首先是admin登录<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="admin登录" src="/resources/assist/images/blog/a470702a46cd4f5d901c4689594df9d3.png" /><br /> 然后访问admin权限的地址<br /> <br /> <img srcset="" width="" size="" class="img-thumbnail" alt="admin资源访问成功" src="/resources/assist/images/blog/e16a663b41db45fb887fddd5ede6f3a9.png" /><br /> 上面看到admin也对应的访问成功了。<br /> <br /> 并且,切回eclipse的控制台可以看到:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="redis" src="/resources/assist/images/blog/df831fee0fb74c02aece3421c25b8949.png" /><br /> 我们的redis缓存也起作用了。<br /> <br /> 至此spring boot shiro redis的整合角色和权限控制讲解完毕。<br /> <br /> 留下思考:如何实现spring boot shiro的无状态认证呢?也就是当我们编写一个既要web前后端分离使用也同时需要满足app的接口需求。只能通过无状态的token实现。<br /> <br /> <a target="_blank" href="http://www.leftso.com/blog/593.html">spring boot shiro无状态化配置使用教程点击前往</a>