Shiro系列文章:
【Shiro】Apache Shiro架構之身份認證(Authentication)
【Shiro】Apache Shiro架構之權限認證(Authorization)
【Shiro】Apache Shiro架構之自定義realm
【Shiro】Apache Shiro架構之實際運用(整合到Spring中)html
前面兩節內容介紹了Shiro中是如何進行身份和權限的認證,可是隻是單純的進行Shiro的驗證,簡單一點的話,用的是.ini配置文件,也舉了個使用jdbc realm的例子,這篇博文主要來總結一下Shiro是如何集成web的,即如何用在web工程中。java
寫在前面:本文沒有使用web框架,好比springmvc或者struts2,用的是原始的servlet,使用的是.ini配置文件,旨在簡單粗暴,說明問題。後面會寫一些和框架整合的博文。
本文部分參考Apache Shiro的官方文檔,文檔地址:http://shiro.apache.org/web.htmlweb
新建一個基於maven的web工程,整個工程結構目錄以下:
下面來總結一下Shiro集成web的步驟。spring
Shiro若是想要集成到web中,首先須要在web.xml中配置Shiro的監聽器和過濾器,以下:apache
1 <!-- 添加shiro支持 --> 2 <listener> 3 <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> 4 </listener> 5 6 <filter> 7 <filter-name>ShiroFilter</filter-name> 8 <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> 9 <init-param> 10 <param-name>configPath</param-name> 11 <param-value>/WEB-INF/shiro.ini</param-value> 12 </init-param> 13 </filter> 14 15 <filter-mapping> 16 <filter-name>ShiroFilter</filter-name> 17 <url-pattern>/*</url-pattern> 18 </filter-mapping>
由上面的配置能夠看出,定義Shiro配置文件的路徑在/WEB-INF/shiro.ini文件,Shiro的過濾器將會攔截全部的請求。web.xml中其餘配置就是servlet的映射配置了,不一樣的servlet對應不一樣的請求url,以下:api
1 <servlet> 2 <servlet-name>LoginServlet</servlet-name> 3 <servlet-class>demo.shiro.servlet.LoginServlet</servlet-class> 4 </servlet> 5 <servlet-mapping> 6 <servlet-name>LoginServlet</servlet-name> 7 <url-pattern>/login</url-pattern> 8 </servlet-mapping> 9 10 <servlet> 11 <servlet-name>AdminServlet</servlet-name> 12 <servlet-class>demo.shiro.servlet.AdminServlet</servlet-class> 13 </servlet> 14 <servlet-mapping> 15 <servlet-name>AdminServlet</servlet-name> 16 <url-pattern>/admin</url-pattern> 17 </servlet-mapping> 18 19 <servlet> 20 <servlet-name>TeacherRoleServlet</servlet-name> 21 <servlet-class>demo.shiro.servlet.TeacherRoleServlet</servlet-class> 22 </servlet> 23 <servlet-mapping> 24 <servlet-name>TeacherRoleServlet</servlet-name> 25 <url-pattern>/student</url-pattern> 26 </servlet-mapping> 27 <servlet-mapping> 28 29 <servlet> 30 <servlet-name>LogoutServlet</servlet-name> 31 <servlet-class>demo.shiro.servlet.LogoutServlet</servlet-class> 32 </servlet> 33 <servlet-name>LogoutServlet</servlet-name> 34 <url-pattern>/logout</url-pattern> 35 </servlet-mapping> 36 37 <servlet> 38 <servlet-name>TeacherPermsServlet</servlet-name> 39 <servlet-class>demo.shiro.servlet.TeacherPermsServlet</servlet-class> 40 </servlet> 41 <servlet-mapping> 42 <servlet-name>TeacherPermsServlet</servlet-name> 43 <url-pattern>/teacher</url-pattern> 44 </servlet-mapping>
pom文件中須要引入相關的jar瀏覽器
1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 3 <modelVersion>4.0.0</modelVersion> 4 <groupId>demo.shiro</groupId> 5 <artifactId>ShiroWeb</artifactId> 6 <packaging>war</packaging> 7 <version>0.0.1-SNAPSHOT</version> 8 9 <dependencies> 10 11 <!-- shiro核心包 --> 12 <dependency> 13 <groupId>org.apache.shiro</groupId> 14 <artifactId>shiro-core</artifactId> 15 <version>1.2.5</version> 16 </dependency> 17 <!-- 添加shiro web支持 --> 18 <dependency> 19 <groupId>org.apache.shiro</groupId> 20 <artifactId>shiro-web</artifactId> 21 <version>1.2.5</version> 22 </dependency> 23 24 <!-- 添加sevlet支持 --> 25 <dependency> 26 <groupId>javax.servlet</groupId> 27 <artifactId>javax.servlet-api</artifactId> 28 <version>3.1.0</version> 29 </dependency> 30 <!-- 添加jsp支持 --> 31 <dependency> 32 <groupId>javax.servlet.jsp</groupId> 33 <artifactId>javax.servlet.jsp-api</artifactId> 34 <version>2.3.1</version> 35 </dependency> 36 <!-- 添加jstl支持 --> 37 <dependency> 38 <groupId>javax.servlet</groupId> 39 <artifactId>jstl</artifactId> 40 <version>1.2</version> 41 </dependency> 42 <!-- 添加log4j日誌 --> 43 <dependency> 44 <groupId>log4j</groupId> 45 <artifactId>log4j</artifactId> 46 <version>1.2.17</version> 47 </dependency> 48 <dependency> 49 <groupId>commons-logging</groupId> 50 <artifactId>commons-logging</artifactId> 51 <version>1.2</version> 52 </dependency> 53 <dependency> 54 <groupId>org.slf4j</groupId> 55 <artifactId>slf4j-api</artifactId> 56 <version>1.7.21</version> 57 </dependency> 58 <dependency> 59 <groupId>junit</groupId> 60 <artifactId>junit</artifactId> 61 <version>4.12</version> 62 <scope>test</scope> 63 </dependency> 64 </dependencies> 65 <build> 66 <finalName>ShiroWeb</finalName> 67 </build> 68 </project>
當運行Web應用程序時,Shiro將建立一些有用的內置過濾器實例,而且自動的在[main]部分使用。咱們能夠人爲配置它們,就比如spring中的bean同樣,而且在本身定義的url中引用它們。Shiro中內置的過濾器有如下幾個:緩存
過濾器名 | 對應的類 |
---|---|
anon(匿名) | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc(身份驗證) | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic(http基本驗證) | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
logout(退出) | org.apache.shiro.web.filter.authc.LogoutFilter |
noSessionCreation(不建立session) | org.apache.shiro.web.filter.session.NoSessionCreationFilter |
perms(許可驗證) | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port(端口驗證) | org.apache.shiro.web.filter.authz.PortFilter |
rest(rest方面) | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles(權限驗證) | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl(ssl方面) | org.apache.shiro.web.filter.authz.SslFilter |
user(用戶方面) | org.apache.shiro.web.filter.authc.UserFilter |
這些內置的過濾器使用方法是這樣的,通常先指定一個請求url對應一個過濾器名,因爲該過濾器對應一個類,而這個類中會有一個屬性,這個屬性是當驗證失敗的時候指定要跳轉到那個url的,因此將這個屬性配置成驗證失敗後你要請求的url便可。這裏我舉其中幾個(匿名,身份驗證,許可驗證,權限驗證)來講明如何使用,其餘的相似。tomcat
針對上面我提到的幾個驗證,先把shiro.ini文件寫好:session
1 [main] 2 #定義身份認證失敗後的請求url映射,loginUrl是身份認證過濾器中的一個屬性 3 authc.loginUrl=/login 4 #定義角色認證失敗後的請求url映射,unauthorizedUrl是角色認證過濾器中的一個屬性 5 roles.unauthorizedUrl=/unauthorized.jsp 6 #定義角色認證失敗後請求url映射,unauthorizedUrl是角色認證過濾器中的一個屬性 7 perms.unauthorizedUrl=/unauthorized.jsp 8 9 #定義幾個用戶和角色 10 [users] 11 csdn1=123,admin,teacher 12 csdn2=123,teacher 13 csdn3=123,student 14 csdn4=123 15 16 #定義不一樣角色的權限 17 [roles] 18 admin=user:*,student:* 19 teacher=student:* 20 21 #定義請求的地址須要作什麼驗證 22 [urls] 23 #請求login的時候不須要權限,遊客身份便可(anon) 24 /login=anon 25 #請求/admin的時候,須要身份認證(authc) 26 /admin=authc 27 #請求/student的時候,須要角色認證,必須是擁有teacher角色的用戶才行 28 /student=roles[teacher] 29 #請求/teacher的時候,須要權限認證,必須是擁有user:create權限的角色的用戶才行 30 /teacher=perms["user:create"]
我簡單說明一下這個配置,好比[main]中定義的authc.loginUrl=/login,authc是一個內置過濾器,它對應org.apache.shiro.web.filter.authc.AnonymousFilter類,而這個類中有個loginUrl屬性,表示驗證失敗後將要請求的url映射,這個url映射與一個具體的servlet對應,讀到這裏,能夠和上面的servlet配置對比一下就知道了。roles.unauthorizedUrl也是同樣的道理,只不過這裏驗證失敗直接請求一個具體的jsp頁面而已。
[users]中是Subject認證主體,就再也不贅述了,[urls]中定義須要攔截哪些請求,並作什麼驗證。好比/login的url映射請求不攔截,由於是登錄的,請求/admin的url映射的話作身份認證,請求/student的url映射的話要作角色認證,且必須是teacher角色才行,請求/teacher的url映射的話須要進行權限認證,必須有user:create權限的用戶才能夠經過驗證。
接下來一個個測試上面的這些認證。
測試authc,得有/admin請求,加上上面的servlet配置可知,首先完成LoginServlet和AdminServlet。
//LoginServlet.java public class LoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("login doGet"); String username = request.getParameter("username"); String password = request.getParameter("password"); Subject currentUser = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { currentUser.login(token); System.out.println("認證成功"); request.getSession().setAttribute("username", username); request.getRequestDispatcher("/success.jsp").forward(request, response); } catch (AuthenticationException e) { e.printStackTrace(); System.out.println("認證失敗"); request.getRequestDispatcher("/login.jsp").forward(request, response); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
認證成功跳轉到success.jsp頁面,認證失敗的話就進入登陸頁面,讓用戶先登陸,看一下success.jsp和login.jsp
<form action="${pageContext.request.contextPath }/login" method="post"> username:<input type="text" name="username"/><br> password:<input type="password" name="password"/><br> <input type="submit" value="登錄"> </form>
由於登陸提交請求的是/login,而/login=anon表示匿名驗證,因此不會攔截認證,直接進入LoginServlet,執行咱們本身寫的認證代碼。下面是success.jsp,很簡單。
<body> 歡迎你${username } <a href="${pageContext.request.contextPath }/logout">退出</a> </body>
順帶把LogoutServlet寫了:
public class LogoutServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getSession().invalidate(); //直接讓session失效 } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
而後就是/admin請求了,請求的是AdminServlet.jsp,若是認證成功就會進入該servlet:
public class AdminServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("admin doGet"); request.getRequestDispatcher("/success.jsp").forward(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
寫好了以後,開啓tomcat,在瀏覽器中訪問:http://localhost:8080/ShiroWeb/admin來請求AdminServlet.Java,Shiro根據配置(/admin=authc)會去進行身份認證,可是此時確定是認證失敗,因此根據配置(authc.loginUrl=/login)去請求LoginServlet.java,Shiro不會去驗證,由於/login=anon,可是LoginServlet.java程序中會驗證,咱們本身寫的。由於沒有用戶名和密碼,因此認證失敗,會跳轉到login.jsp登錄頁面,輸入用戶名和密碼後,再次請求/login,此時再次進入LoginServlet.java,認證成功,進入success.jsp頁面。
由於Shiro中會默認有30分鐘緩存時間,因此此時咱們再次去訪問http://localhost:8080/ShiroWeb/admin的時候,就能夠訪問到AdminServlet.java中了,能夠在裏面打斷點驗證,而後掉轉到success.jsp。這就是身份認證的過程。
根據配置文件,咱們須要請求/student才能觸發roles角色認證,因此根據servlet的映射,咱們完成TeacherRoleServlet.java代碼。
public class TeacherRoleServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("role deget"); Subject currentUser = SecurityUtils.getSubject(); //實際上是不用判斷了,由於只要進來了,確定角色是對的,不然進不來 //判斷當前用戶是否具備teacher角色 if(currentUser.hasRole("teacher")) { request.getRequestDispatcher("/success.jsp").forward(request, response); } else { request.getRequestDispatcher("/unauthorized.jsp").forward(request, response); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
上面程序的註釋中也提到了,其實能夠不用再認證了,由於進入該servlet以前,Shiro已經幫咱們認證了,只有認證成功纔會進入到該servlet,不然會根據配置(roles.unauthorizedUrl=/unauthorized.jsp)會直接跳轉到unauthorized.jsp頁面顯示。
<body> 認證未經過,或者權限不足 <a href="${pageContext.request.contextPath }/logout">退出</a> </body>
而後咱們在瀏覽器中輸入http://localhost:8080/ShiroWeb/student來請求TeacherRoleServlet,這裏須要注意的是,剛剛測試事後須要點擊退出,不然仍是當前用戶,會影響此次的測試。在測試角色認證的時候,它會先進行身份認證,再進行角色認證。也就是說,Shiro會先跳轉到登錄頁面讓咱們登錄,咱們能夠嘗試兩個不一樣的用戶csdn1(有teacher角色)和csdn3(沒有teacher角色)來測試。
和上面同樣,先寫TeacherPermsServlet.java:
public class TeacherPermsServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("perms doget"); Subject currentUser = SecurityUtils.getSubject(); // 實際上是不用判斷了,由於只要進來了,確定角色是對的,不然進不來 // 判斷當前用戶是否具備teacher角色 if (currentUser.isPermitted("user:create")) { request.getRequestDispatcher("/success.jsp").forward(request, response); } else { request.getRequestDispatcher("/unauthorized.jsp").forward(request, response); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
而後咱們在瀏覽器中輸入http://localhost:8080/ShiroWeb/teacher來請求TeacherPermsServlet ,這裏一樣須要注意的是,剛剛測試事後須要點擊退出,不然仍是當前用戶,會影響此次的測試。在測試權限認證的時候,它一樣會先進行身份認證,再進行權限認證。也就是說,Shiro會先跳轉到登錄頁面讓咱們登錄,咱們能夠嘗試兩個不一樣的用戶csdn1(有admin角色,從而有user:create權限)和csdn3(沒有admin角色,從而沒有user:create權限)來測試。
這裏提一下Shiro在集成web的時候,url能夠不用向上面那樣寫的很死,它能夠匹配的,好比:
/admin?=authc,表示能夠請求以admin開頭的字符串,如xxx/adminfefe,但沒法匹配多個,即xxx/admindf/admin是不行的 /admin*=authc表示能夠匹配零個或者多個字符,如/admin,/admin1,/admin123,可是不能匹配/admin/abc這種 /admin/**=authc表示能夠匹配零個或者多個路徑,如/admin,/admin/ad/adfdf等。 /admin*/**=authc這個就很少說了,結合上面兩個就知道了。