Shiro系列文章:
【Shiro】Apache Shiro架構之身份認證(Authentication)
【Shiro】Apache Shiro架構之權限認證(Authorization)
【Shiro】Apache Shiro架構之自定義realm
【Shiro】Apache Shiro架構之實際運用(整合到Spring中)
前面兩節內容介紹了Shiro中是如何進行身份和權限的認證,但是隻是單純的進行Shiro的驗證,簡單一點的話,用的是.ini配置文件,也舉了個使用jdbc realm的例子,這篇博文主要來總結一下Shiro是如何集成web的,即如何用在web工程中。
寫在前面:本文沒有使用web框架,比如springmvc或者struts2,用的是原始的servlet,使用的是.ini配置文件,旨在簡單粗暴,說明問題。後面會寫一些和框架整合的博文。
本文部分參考Apache Shiro的官方文檔,文檔地址:http://shiro.apache.org/web.html
新建一個基於maven的web工程,整個工程結構目錄如下:
下面來總結一下Shiro集成web的步驟。
1. 配置
1.1 web.xml配置
Shiro如果想要集成到web中,首先需要在web.xml中配置Shiro的監聽器和過濾器,如下:
<!-- 添加shiro支持 -->
<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>
<init-param>
<param-name>configPath</param-name>
<param-value>/WEB-INF/shiro.ini</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
由上面的配置可以看出,定義Shiro配置文件的路徑在/WEE-INF/shiro.ini文件,Shiro的過濾器將會攔截所有的請求。web.xml中其他配置就是servlet的映射配置了,不同的servlet對應不同的請求url,如下:
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>demo.shiro.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>AdminServlet</servlet-name>
<servlet-class>demo.shiro.servlet.AdminServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AdminServlet</servlet-name>
<url-pattern>/admin</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>TeacherRoleServlet</servlet-name>
<servlet-class>demo.shiro.servlet.TeacherRoleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TeacherRoleServlet</servlet-name>
<url-pattern>/student</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet>
<servlet-name>LogoutServlet</servlet-name>
<servlet-class>demo.shiro.servlet.LogoutServlet</servlet-class>
</servlet>
<servlet-name>LogoutServlet</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>TeacherPermsServlet</servlet-name>
<servlet-class>demo.shiro.servlet.TeacherPermsServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TeacherPermsServlet</servlet-name>
<url-pattern>/teacher</url-pattern>
</servlet-mapping>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
1.2 pom.xml配置
pom文件中需要引入相關的jar
<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>demo.shiro</groupId>
<artifactId>ShiroWeb</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- shiro核心包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.5</version>
</dependency>
<!-- 添加shiro web支持 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.5</version>
</dependency>
<!-- 添加sevlet支持 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 添加jsp支持 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<!-- 添加jstl支持 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- 添加log4j日誌 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>ShiroWeb</finalName>
</build>
</project>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
2. Shiro的內置過濾器(Default Filters)
當運行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即可。這裏我舉其中幾個(匿名,身份驗證,許可驗證,權限驗證)來說明如何使用,其他的類似。
3. Shiro的配置文件
針對上面我提到的幾個驗證,先把shiro.ini文件寫好:
[main]
#定義身份認證失敗後的請求url映射,loginUrl是身份認證過濾器中的一個屬性
authc.loginUrl=/login
#定義角色認證失敗後的請求url映射,unauthorizedUrl是角色認證過濾器中的一個屬性
roles.unauthorizedUrl=/unauthorized.jsp
#定義角色認證失敗後請求url映射,unauthorizedUrl是角色認證過濾器中的一個屬性
perms.unauthorizedUrl=/unauthorized.jsp
#定義幾個用戶和角色
[users]
csdn1=123,admin,teacher
csdn2=123,teacher
csdn3=123,student
csdn4=123
#定義不同角色的權限
[roles]
admin=user:*,student:*
teacher=student:*
#定義請求的地址需要做什麼驗證
[urls]
#請求login的時候不需要權限,遊客身份即可(anon)
/login=anon
#請求/admin的時候,需要身份認證(authc)
/admin=authc
#請求/student的時候,需要角色認證,必須是擁有teacher角色的用戶才行
/student=roles[teacher]
#請求/teacher的時候,需要權限認證,必須是擁有user:create權限的角色的用戶才行
/teacher=perms["user:create"]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
我簡單說明一下這個配置,比如[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權限的用戶纔可以通過驗證。
4. 測試程序
接下來一個個測試上面的這些認證。
4.1 測試authc身份認證
測試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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
認證成功跳轉到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>
- 1
- 2
- 3
- 4
- 5
因爲登錄提交請求的是/login,而/login=anon表示匿名驗證,所以不會攔截認證,直接進入LoginServlet,執行我們自己寫的認證代碼。下面是success.jsp,很簡單。
<body>
歡迎你${username }
<a href="${pageContext.request.contextPath }/logout">退出</a>
</body>
- 1
- 2
- 3
- 4
順帶把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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
然後就是/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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
寫好了之後,開啓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。這就是身份認證的過程。
4.2 測試roles角色認證
根據配置文件,我們需要請求/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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
上面程序的註釋中也提到了,其實可以不用再認證了,因爲進入該servlet之前,Shiro已經幫我們認證了,只有認證成功纔會進入到該servlet,否則會根據配置(roles.unauthorizedUrl=/unauthorized.jsp)會直接跳轉到unauthorized.jsp頁面顯示。
<body>
認證未通過,或者權限不足
<a href="${pageContext.request.contextPath }/logout">退出</a>
</body>
- 1
- 2
- 3
- 4
然後我們在瀏覽器中輸入http://localhost:8080/ShiroWeb/student來請求TeacherRoleServlet,這裏需要注意的是,剛剛測試過後需要點擊退出,否則還是當前用戶,會影響這次的測試。在測試角色認證的時候,它會先進行身份認證,再進行角色認證。也就是說,Shiro會先跳轉到登陸頁面讓我們登陸,我們可以嘗試兩個不同的用戶csdn1(有teacher角色)和csdn3(沒有teacher角色)來測試。
4.2 測試perms權限認證
和上面一樣,先寫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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
然後我們在瀏覽器中輸入http://localhost:8080/ShiroWeb/teacher來請求TeacherPermsServlet ,這裏同樣需要注意的是,剛剛測試過後需要點擊退出,否則還是當前用戶,會影響這次的測試。在測試權限認證的時候,它同樣會先進行身份認證,再進行權限認證。也就是說,Shiro會先跳轉到登陸頁面讓我們登陸,我們可以嘗試兩個不同的用戶csdn1(有admin角色,從而有user:create權限)和csdn3(沒有admin角色,從而沒有user:create權限)來測試。
5. url匹配方式
這裏提一下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這個就不多說了,結合上面兩個就知道了。