使用Spring Security實現權限管理
1、技術目標
- 瞭解並創建Security框架所需數據表
- 爲項目添加Spring Security框架
- 掌握Security框架配置
- 應用Security框架爲項目的CRUD操作綁定權限
注意:本文所用項目爲"影片管理",參看
http://hotstrong.iteye.com/blog/1156785
2、權限管理需求描述
-
爲系統中的每個操作定義權限,如定義4個權限:
1)超級權限,可以使用所有操作
2)添加影片權限
3)修改影片權限
4)刪除影片權限 - 爲系統設置管理員帳號、密碼
-
爲系統創建權限組,每個權限組可以配置多個操作權限,如創建2個權限組:
1)"Administrator"權限組,具有超級權限
2)"影片維護"權限組,具有添加影片、修改影片權限 - 可將管理員加入權限組,管理員登錄後具備權限組所對應操作權限
- 管理員可不屬於某權限組,可爲管理員直接分配權限
3、使用準備
3.1)在數據庫中創建6張表
t_admin 管理員帳號表
t_role權限表
t_group 權限組表
t_group_role權限組對應權限表
t_group_user管理員所屬權限組表
t_user_role管理員對應權限表
建表SQL語句如下:
SET FOREIGN_KEY_CHECKS=0;
------------------------------
-- 創建管理員帳號表t_admin
-- ----------------------------
CREATE TABLE `t_admin` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`passwd` varchar(12) NOT NULL DEFAULT '' COMMENT '用戶密碼',
`nickname` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶名字',
`phoneno` varchar(32) NOT NULL DEFAULT '' COMMENT '電話號碼',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- ----------------------------
-- 添加3個管理帳號
-- ----------------------------
INSERT INTO `t_admin` VALUES ('1', 'admin', 'admin', '');
INSERT INTO `t_admin` VALUES ('4', '123456', 'test', '');
INSERT INTO `t_admin` VALUES ('5', '111111', '111111', '');
-- ----------------------------
-- 創建權限表t_role
-- ----------------------------
CREATE TABLE `t_role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`role` varchar(40) NOT NULL DEFAULT '',
`descpt` varchar(40) NOT NULL DEFAULT '' COMMENT '角色描述',
`category` varchar(40) NOT NULL DEFAULT '' COMMENT '分類',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8;
-- ----------------------------
-- 加入4個操作權限
-- ----------------------------
INSERT INTO `t_role` VALUES ('1', 'ROLE_ADMIN', '系統管理員', '系統管理員');
INSERT INTO `t_role` VALUES ('2', 'ROLE_UPDATE_FILM', '修改', '影片管理');
INSERT INTO `t_role` VALUES ('3', 'ROLE_DELETE_FILM', '刪除', '影片管理');
INSERT INTO `t_role` VALUES ('4', 'ROLE_ADD_FILM', '添加', '影片管理');
-- ----------------------------
-- 創建權限組表
-- ----------------------------
CREATE TABLE `t_group` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`groupname` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- ----------------------------
-- 添加2個權限組
-- ----------------------------
INSERT INTO `t_group` VALUES ('1', 'Administrator');
INSERT INTO `t_group` VALUES ('2', '影片維護');
-- ----------------------------
-- 創建權限組對應權限表t_group_role
-- ----------------------------
CREATE TABLE `t_group_role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`groupid` bigint(20) unsigned NOT NULL,
`roleid` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `groupid2` (`groupid`,`roleid`),
KEY `roleid` (`roleid`),
CONSTRAINT `t_group_role_ibfk_1` FOREIGN KEY (`groupid`) REFERENCES `t_group` (`id`),
CONSTRAINT `t_group_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=83 DEFAULT CHARSET=utf8;
-- ----------------------------
-- 加入權限組與權限的對應關係
-- ----------------------------
INSERT INTO `t_group_role` VALUES ('1', '1', '1');
INSERT INTO `t_group_role` VALUES ('2', '2', '2');
INSERT INTO `t_group_role` VALUES ('4', '2', '4');
-- ----------------------------
-- 創建管理員所屬權限組表t_group_user
-- ----------------------------
CREATE TABLE `t_group_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`userid` bigint(20) unsigned NOT NULL,
`groupid` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `userid` (`userid`),
KEY `groupid` (`groupid`),
CONSTRAINT `t_group_user_ibfk_2` FOREIGN KEY (`groupid`) REFERENCES `t_group` (`id`),
CONSTRAINT `t_group_user_ibfk_3` FOREIGN KEY (`userid`) REFERENCES `t_admin` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
-- ----------------------------
-- 將管理員加入權限組
-- ----------------------------
INSERT INTO `t_group_user` VALUES ('1', '1', '1');
INSERT INTO `t_group_user` VALUES ('2', '4', '2');
-- ----------------------------
-- 創建管理員對應權限表t_user_role
-- 設置該表可跳過權限組,爲管理員直接分配權限
-- ----------------------------
CREATE TABLE `t_user_role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`userid` bigint(20) unsigned NOT NULL,
`roleid` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `userid` (`userid`),
KEY `roleid` (`roleid`),
CONSTRAINT `t_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `t_admin` (`id`),
CONSTRAINT `t_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
3.2)在項目中新增如下jar包(security框架所需jar包):
注意:以下jar包本文已提供下載
spring-security-config-3.1.0.RC2.jar
spring-security-core-3.1.0.RC2.jar
spring-security-taglibs-3.1.0.RC2.jar
spring-security-web-3.1.0.RC2.jar
3.3)創建如下包,放置登錄驗證過濾器代碼:
com.xxx.security
3.4)在src下創建Spring配置文件applicationContext-security.xml,內容如下:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:b="http://www.springframework.org/schema/beans" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <!-- 這裏進行配置 --> </beans:beans>
3.5)在web.xml中加入security配置,如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext-*.xml,classpath*:applicationContext-*.xml</param-value> </context-param> <!-- 配置Spring Security --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
4、站點根路徑下創建登錄頁面login.jsp,代碼如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path;
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>後臺登錄</title>
</head>
<body onload="document.loginForm.j_username.focus();">
<!-- 登錄表單 -->
<form name="loginForm" action="<c:url value='/j_spring_security_check'/>" method="post">
<!-- 登錄失敗後,顯示之前的登錄名 -->
用戶名:<input type='text' name='j_username' class="txtinput"
value='<c:if test="${not empty param.login_error}" >
<c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>' />
<br />
密碼:<input type='password' name='j_password' class="txtinput" />
<br />
<input type="checkbox" name="_spring_security_remember_me" />
保存登錄信息
<input name="submit" type="submit" value="提交" />
<input name="reset" type="reset" value="重置" />
</form>
<br />
<!-- 顯示登錄失敗原因 -->
<c:if test="${not empty param.error}">
<font color="red"> 登錄失敗<br />
<br />
原因: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />. </font>
</c:if>
</body>
</html>
5、站點根路徑下創建註銷頁面loggedout.jsp,代碼如下:
<%@page session="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path;
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>登出</title>
</head>
<body>
你已經退出。
<a href="<c:url value='/login.jsp'/>">點擊這裏登錄</a>
</body>
</html>
6、站點根路徑下創建HttpSession超時提示頁面timeout.jsp,代碼如下:
<%@page session="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path;
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>用戶失效</title>
</head>
<body>
你的登錄已經失效,請重新登錄。
<br />
<a href="<c:url value='/login.jsp'/>" >
點擊這裏登錄</a>
</body>
</html>
7、在com.xxx.security包下創建登錄驗證過濾器,該過濾器可用於在管理員登錄時進行日誌記錄等相關操作,包括兩個類:
- LoginUsernamePasswordAuthenticationFilter
- LoginSuccessHandler
7.1)LoginUsernamePasswordAuthenticationFilter代碼如下:
package com.xxx.security;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
public class LoginUsernamePasswordAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
}
7.2)LoginSuccessHandler代碼如下:
package com.xxx.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
/**
* 處理管理員登錄日誌
*
*/
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException,
ServletException {
UserDetails userDetails = (UserDetails)authentication.getPrincipal();
//輸出登錄提示信息
System.out.println("管理員 " + userDetails.getUsername() + " 登錄");
super.onAuthenticationSuccess(request, response, authentication);
}
}
8、在applicationContext-security.xml中加入權限管理配置,如下:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:b="http://www.springframework.org/schema/beans" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <http > <!-- 不攔截login.jsp --> <intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY" /> <!--僅攔截到manager下面的內容,具備access對應權限的--> <intercept-url pattern="/manager/**" access="ROLE_ADMIN,ROLE_UPDATE_FILM,ROLE_DELETE_FILM,ROLE_ADD_FILM" /> <!-- 設置登錄過濾器 --> <custom-filter before="FORM_LOGIN_FILTER" ref="authenticationProcessingFilter" /> <!-- 登錄表單設置 --> <form-login login-page="/login.jsp" default-target-url="/manager/films.jsp" authentication-failure-url="/login.jsp?error=true" /> <!-- 登出操作後跳轉到該頁面 --> <logout logout-success-url="/loggedout.jsp" delete-cookies="JSESSIONID" /> <remember-me /> <!-- SESSION超時後跳轉到該頁面 --> <session-management invalid-session-url="/timeout.jsp"> </session-management> </http> <authentication-manager alias="authenticationManager"> <authentication-provider> <!-- 直接使用SQL語句查詢登錄帳號對應權限, users-by-username-query:查詢登錄用戶是否存在 authorities-by-username-query:查詢登錄用戶權限(登錄用戶可以不屬於任何組,從t_user_role表中獲取權限) group-authorities-by-username-query:查詢登錄用戶所在組的權限 --> <jdbc-user-service data-source-ref="dataSource" group-authorities-by-username-query="SELECT g.id,g.groupname,role.role FROM t_group AS g LEFT OUTER JOIN t_group_role AS grouprole ON (g.id = grouprole.groupid) LEFT OUTER JOIN t_role AS role ON (role.id = grouprole.roleid) LEFT OUTER JOIN t_group_user AS groupuser on (g.id = groupuser.groupid) LEFT OUTER JOIN t_admin ON (t_admin.id = groupuser.userid) WHERE t_admin.nickname = ?" users-by-username-query="SELECT t_admin.nickname AS username,t_admin.passwd as password,'true' AS enabled FROM t_admin WHERE t_admin.nickname = ?" authorities-by-username-query="SELECT t_admin.nickname AS username,role.role as authorities FROM t_admin LEFT OUTER JOIN t_user_role AS userrole ON(t_admin.id = userrole.userid) LEFT OUTER JOIN t_role AS role ON (userrole.roleid = role.id) WHERE t_admin.nickname = ?" /> </authentication-provider> </authentication-manager> <!-- 自定義消息 --> <b:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <b:property name="basename" value="classpath:org/springframework/security/messages" /> </b:bean> <!-- 定製登錄過濾器 --> <beans:bean id="loginSuccessHandler" class="com.xxx.security.LoginSuccessHandler"> <b:property name="defaultTargetUrl"> <!-- 登錄成功後轉發到該頁面 --> <b:value>/manager/films.jsp</b:value> </b:property> </beans:bean> <beans:bean id="authenticationProcessingFilter" class="com.xxx.security.LoginUsernamePasswordAuthenticationFilter"> <beans:property name="authenticationSuccessHandler" ref="loginSuccessHandler"></beans:property> <beans:property name="authenticationFailureHandler" ref="authenticationFailureHandler"></beans:property> <beans:property name="authenticationManager" ref="authenticationManager"></beans:property> </beans:bean> <beans:bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <beans:property name="defaultFailureUrl"> <!-- 登錄失敗後轉發到該頁面 --> <beans:value>/login.jsp?error=true</beans:value> </beans:property> </beans:bean> </beans:beans>
9、爲影片頁面films.jsp定製操作權限,定製後,不同的帳號登錄會看到不同的操作,
比如,帳號"admin"屬於權限組"Administrator",具備權限"ROLE_ADMIN",登錄後
可以看到所有操作,帳號"test"屬於權限組"影片維護",具備權限"ROLE_UPDATE_FILM"
和"ROLE_ADD_FILM",登錄後只能看到"添加影片信息"和"修改"操作
films.jsp頁面權限分佈圖:
films.jsp代碼如下:
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8" %>
<%@taglib uri="/struts-tags" prefix="s" %>
<%@ taglib prefix="security"
uri="http://www.springframework.org/security/tags"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>信息操作</title>
</head>
<body>
<s:form action="/film/findFilm" method="post">
<s:submit value=" 獲取所有影片信息 "></s:submit>
</s:form>
<!-- 添加影片操作,登錄帳號具備ROLE_ADMIN權限或者ROLE_ADD_FILM權限可以執行 -->
<security:authorize ifAnyGranted="ROLE_ADMIN,ROLE_ADD_FILM">
<a href="<%=basePath %>manager/insertFilm.jsp">添加影片信息</a><br />
</security:authorize>
<s:if test="filmList != null">
<table border="1" width="40%">
<tr>
<th>序號</th><th>影片名</th><th>操作</th>
</tr>
<%-- 遍歷影片信息 --%>
<s:iterator var="film" value="filmList" status="st">
<tr>
<td><s:property value="#st.index+1" /></td>
<td><s:property value="fname" /></td>
<td>
<!-- 修改影片操作,登錄帳號具備ROLE_ADMIN權限或者ROLE_UPDATE_FILM權限可以執行 -->
<security:authorize ifAnyGranted="ROLE_ADMIN,ROLE_UPDATE_FILM">
<s:url id="detailUrl" value="/film/detailFilm">
<s:param name="id" value="%{id}"/>
</s:url>
<s:a href="%{detailUrl}">[修改]</s:a>
</security:authorize>
<!-- 刪除影片操作,登錄帳號具備ROLE_ADMIN權限或者ROLE_DELETE_FILM權限可以執行 -->
<security:authorize ifAnyGranted="ROLE_ADMIN,ROLE_DELETE_FILM">
<s:url id="deleteUrl" value="/film/deleteFilm">
<s:param name="id" value="%{id}"/>
</s:url>
<s:a href="%{deleteUrl}">[刪除]</s:a>
</security:authorize>
</td>
</tr>
</s:iterator>
</table>
</s:if>
</body>
</html>