首先導入spring security所需要的jar包
spring-security-core-2.0.5.RELEASE.jar
spring-security-core-tiger-2.0.5.RELEASE.jar
一.配置過濾器
在web.xml中定義如下過濾器
<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>
二.在spring配置文件中添加security的命名空間
<beans:beans xmlns="http://www.springframework.org/schema/security"
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-2.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
三.在spring配置文中中定義需要保護的資源
<http auto-config='true'>
<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
<intercept-url pattern="/**" access="ROLE_USER" />
</http>
注意intercept-url的先后順序,spring security使用第一個能匹配的intercept-url標簽進行權限控制。
四.使用數據庫獲取用戶權限
<!-- 數據源 -->
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<beans:property name="driverClassName" value="com.mysql.jdbc.Driver"></beans:property>
<beans:property name="url" value="jdbc:mysql://localhost:3306/sp"></beans:property>
<beans:property name="username" value="root"></beans:property>
<beans:property name="password" value="root"></beans:property>
</beans:bean>
<!-- 定義用戶的權限根據注入的數據源獲得 -->
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>
定義權限管理模塊的表結構(mysql數據庫)
alter table `t_account_role` drop foreign key `FK1C2BC93384B0A30E`;
alter table `t_account_role` drop foreign key `FK1C2BC9332D31C656`;
drop table if exists `t_account_role`;
drop table if exists `t_account`;
drop table if exists `t_role`;
/* 用戶表 */
CREATE TABLE `t_account` (
`id` int(11) NOT NULL,
`username` varchar(255) default NULL,
`password` varchar(255) default NULL,
`enabled` int default NULL, /* 用戶是否禁用 0:禁用 非0:可用*/
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* 角色表 */
CREATE TABLE `t_role` (
`id` int(11) NOT NULL,
`name` varchar(255) default NULL, /* 角色名 */
`descn` varchar(255) default NULL, /* 角色在spring配置文件中的名字 如ROLE_ADMIN,ROLE_USER*/
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* 用戶角色中間表 */
CREATE TABLE `t_account_role` (
`a_id` int(11) NOT NULL,
`r_id` int(11) NOT NULL,
PRIMARY KEY (`a_id`,`r_id`),
KEY `FK1C2BC9332D31C656` (`r_id`),
KEY `FK1C2BC93371CCC630` (`a_id`),
CONSTRAINT `FK1C2BC93384B0A30E` FOREIGN KEY (`a_id`) REFERENCES `t_account` (`id`),
CONSTRAINT `FK1C2BC9332D31C656` FOREIGN KEY (`r_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* 初始化數據 */
insert into t_account values(1,'zhangsan','123',1);
insert into t_account values(2,'lisi','321',1);
insert into t_role values(1,'系統管理員','ROLE_ADMIN');
insert into t_role values(2,'普通用戶','ROLE_USER');
insert into t_account_role values(1,2);
insert into t_account_role values(2,1);
當用戶登錄時,spring security首先判斷用戶是否可以登錄。用戶登錄后spring security獲得該用戶的
所有權限以判斷用戶是否可以訪問資源。
spring配置文件中定義
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password,enabled from t_account where username=?"
authorities-by-username-query="select r.descn from t_account_role ar join
t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/>
</authentication-provider>
users-by-username-query:根據用戶名查找用戶
authorities-by-username-query:根據用戶名查找這個用戶所有的角色名,將用戶訪問的URL地址和
查詢結果與<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />標簽進行匹配。
匹配成功就允許訪問,否則就返回到提示頁面。
在<http>標簽中添加登錄頁面等信息
<http auto-config='true'>
<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page="/login.jsp"
authentication-failure-url="/error.jsp"
default-target-url="/index.jsp" />
</http>
<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>讓沒有登錄的用戶也可以訪問登錄頁面
<form-login login-page="/login.jsp"
authentication-failure-url="/error.jsp"
default-target-url="/index.jsp" />
login-page:當用戶登錄時顯示自定義登錄頁面
authentication-failure-url:登錄失敗時跳轉到哪個頁面
default-target-url:登錄成功后跳轉到哪個頁面
注意:users-by-username-query指定的查詢,必須至少按順序返回3列,列名必須是username,password,enabled
authorities-by-username-query指定的查詢,必須至少按順序返回2列,第一列列名必須是username
第2列必須是權限的名字,與<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />中的
access匹配。
不能使用select *
完成的配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
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-2.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
<!-- 數據源 -->
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<beans:property name="driverClassName" value="com.mysql.jdbc.Driver"></beans:property>
<beans:property name="url" value="jdbc:mysql://localhost:3306/sp"></beans:property>
<beans:property name="username" value="root"></beans:property>
<beans:property name="password" value="root"></beans:property>
</beans:bean>
<http auto-config='true'>
<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
<intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" />
<form-login login-page="/login.jsp"
authentication-failure-url="/error.jsp"
default-target-url="/index.jsp" />
</http>
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password,enabled from t_account where username=?"
authorities-by-username-query="select a.username,r.descn from t_account_role ar join
t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/>
</authentication-provider>
</beans:beans>
登錄頁面
<form action="${pageContext.request.contextPath}/j_spring_security_check" method="post">
用戶: <input type="text" name="j_username" value="${SPRING_SECURITY_LAST_USERNAME}"/><br />
密碼: <input type="password" name="j_password"/><br />
<input type="checkbox" name="_spring_security_remember_me" />兩周之內不必登陸<br />
<input type="submit" value="登陸"/><input type="reset" value="重置"/>
</form>
頁面中輸入控件的name屬性和form的action地址必須符合spring security的規定
登錄失敗頁面
${SPRING_SECURITY_LAST_EXCEPTION.message} 獲取spring生成的異常
將資源信息放入數據庫中
表結構
/* 用戶表 */
CREATE TABLE `t_account` (
`id` int(11) NOT NULL,
`username` varchar(255) default NULL,
`password` varchar(255) default NULL,
`enabled` int default NULL, /* 用戶是否禁用 0:禁用 非0:可用*/
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* 角色表 */
CREATE TABLE `t_role` (
`id` int(11) NOT NULL,
`name` varchar(255) default NULL, /* 角色名 */
`descn` varchar(255) default NULL, /* 角色在spring配置文件中的名字 如ROLE_ADMIN,ROLE_USER*/
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* 用戶角色中間表 */
CREATE TABLE `t_account_role` (
`a_id` int(11) NOT NULL,
`r_id` int(11) NOT NULL,
PRIMARY KEY (`a_id`,`r_id`),
KEY `FK1C2BC9332D31C656` (`r_id`),
KEY `FK1C2BC93371CCC630` (`a_id`),
CONSTRAINT `FK1C2BC93384B0A30E` FOREIGN KEY (`a_id`) REFERENCES `t_account` (`id`),
CONSTRAINT `FK1C2BC9332D31C656` FOREIGN KEY (`r_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* 資源表 */
CREATE TABLE `t_module` (
`id` int(11) NOT NULL,
`name` varchar(255) default NULL,
`address` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* 資源角色的中間表 */
CREATE TABLE `t_module_role` (
`m_id` int(11) NOT NULL,
`r_id` int(11) NOT NULL,
PRIMARY KEY (`m_id`,`r_id`),
KEY `FKA713071E2D31C656` (`r_id`),
KEY `FKA713071ED78C9071` (`m_id`),
CONSTRAINT `FKA713071ED78C9071` FOREIGN KEY (`m_id`) REFERENCES `t_module` (`id`),
CONSTRAINT `FKA713071E2D31C656` FOREIGN KEY (`r_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* 初始化數據 */
insert into t_account values(1,'zhangsan','123',1);
insert into t_account values(2,'lisi','321',1);
insert into t_role values(1,'系統管理員','ROLE_ADMIN');
insert into t_role values(2,'普通用戶','ROLE_USER');
insert into t_account_role values(1,2);
insert into t_account_role values(2,1);
insert into t_module values(1,'部門管理','/dept.jsp');
insert into t_module values(2,'人員管理','/emp.jsp');
insert into `t_module_role` values(1,1);
insert into `t_module_role` values(1,2);
insert into `t_module_role` values(2,1);
1.在自定義的過濾器中獲取資源的URL地址和角色名以取代spring配置文件中原有的<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
過濾器代碼:
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.object.MappingSqlQuery;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.ConfigAttributeEditor;
import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.RequestKey;
import org.springframework.security.util.AntUrlPathMatcher;
import org.springframework.security.util.UrlMatcher;
public class JdbcFilterInvocationDefinitionSourceFactoryBean
extends JdbcDaoSupport implements FactoryBean {
private String resourceQuery;
public boolean isSingleton() {
return true;
}
public Class getObjectType() {
return FilterInvocationDefinitionSource.class;
}
public Object getObject() {
return new DefaultFilterInvocationDefinitionSource(this
.getUrlMatcher(), this.buildRequestMap());
}
protected Map<String, String> findResources() {
ResourceMapping resourceMapping = new ResourceMapping(getDataSource(),
resourceQuery);
Map<String, String> resourceMap = new LinkedHashMap<String, String>();
for (Resource resource : (List<Resource>) resourceMapping.execute()) {
String url = resource.getUrl();
String role = resource.getRole();
if (resourceMap.containsKey(url)) {
String value = resourceMap.get(url);
resourceMap.put(url, value + "," + role);
} else {
resourceMap.put(url, role);
}
}
return resourceMap;
}
protected LinkedHashMap<RequestKey, ConfigAttributeDefinition> buildRequestMap() {
LinkedHashMap<RequestKey, ConfigAttributeDefinition> requestMap = null;
requestMap = new LinkedHashMap<RequestKey, ConfigAttributeDefinition>();
ConfigAttributeEditor editor = new ConfigAttributeEditor();
Map<String, String> resourceMap = this.findResources();
for (Map.Entry<String, String> entry : resourceMap.entrySet()) {
RequestKey key = new RequestKey(entry.getKey(), null);
editor.setAsText(entry.getValue());
requestMap.put(key,
(ConfigAttributeDefinition) editor.getValue());
}
return requestMap;
}
protected UrlMatcher getUrlMatcher() {
return new AntUrlPathMatcher();
}
public void setResourceQuery(String resourceQuery) {
this.resourceQuery = resourceQuery;
}
private class Resource {
private String url;
private String role;
public Resource(String url, String role) {
this.url = url;
this.role = role;
}
public String getUrl() {
return url;
}
public String getRole() {
return role;
}
}
private class ResourceMapping extends MappingSqlQuery {
protected ResourceMapping(DataSource dataSource,
String resourceQuery) {
super(dataSource, resourceQuery);
compile();
}
protected Object mapRow(ResultSet rs, int rownum)
throws SQLException {
String url = rs.getString(1);
String role = rs.getString(2);
Resource resource = new Resource(url, role);
return resource;
}
}
}
將自定義的過濾器放入到原有的spring security過濾器鏈中(在spring配置文件中配置)
定義自定義過濾器(sql語句用於查詢資源的URL地址 如:/index.jsp 和角色名 如ROLE_USER)
<beans:bean id="filterInvocationDefinitionSource"
class="com.lovo.JdbcFilterInvocationDefinitionSourceFactoryBean">
<beans:property name="dataSource" ref="dataSource"/>
<beans:property name="resourceQuery" value="
select m.address,r.descn
from t_module_role mr
join t_module m on mr.m_id=m.id
join t_role r on mr.r_id=r.id;
"/>
</beans:bean>
將自定義過濾器放入過濾器鏈中
<beans:bean id="filterSecurityInterceptor"
class="org.springframework.security.intercept.web.FilterSecurityInterceptor" autowire="byType">
<custom-filter before="FILTER_SECURITY_INTERCEPTOR"/>
<beans:property name="objectDefinitionSource" ref="filterInvocationDefinitionSource" />
</beans:bean>
注意:FilterSecurityInterceptor過濾器會向request中寫入一個標記,用於標記是否已經控制了當前請求,以避免對同一請求多次處理,導致第2個FilterSecurityInterceptor不會再次執行。
在<http>中不需要再定義<intercept-url>,如下:
<http auto-config='true'>
<form-login login-page="/login.jsp"
authentication-failure-url="/error.jsp"
default-target-url="/index.jsp" />
</http>
自定義的過濾器就從配置文件中讀取sql,查詢結果就是角色和資源,用戶登錄時就在session中保存了用戶的角色。
注意:intercept-url的先后順序,spring security使用第一個能匹配的intercept-url標簽進行權限控制。
現在intercept-url來源於數據庫,所以在sql查詢時注意角色和資源的順序。
建議在角色和資源的中間表中添加1個字段用於標識順序,(按從嚴到寬的順序)
表結構修改如下:
/* 資源角色的中間表 */
CREATE TABLE `t_module_role` (
`m_id` int(11) NOT NULL,
`r_id` int(11) NOT NULL,
`priority` int(11) default NULL, /* 用於標識角色和資源的匹配順序 從嚴到寬 */
PRIMARY KEY (`m_id`,`r_id`),
KEY `FKA713071E2D31C656` (`r_id`),
KEY `FKA713071ED78C9071` (`m_id`),
CONSTRAINT `FKA713071ED78C9071` FOREIGN KEY (`m_id`) REFERENCES `t_module` (`id`),
CONSTRAINT `FKA713071E2D31C656` FOREIGN KEY (`r_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
數據如下:
insert into t_account values(1,'zhangsan','123',1);
insert into t_account values(2,'lisi','321',1);
insert into t_role values(1,'系統管理員','ROLE_ADMIN');
insert into t_role values(2,'普通用戶','ROLE_USER');
insert into t_account_role values(1,2);
insert into t_account_role values(2,1);
insert into t_module values(1,'部門管理','/dept.jsp');
insert into t_module values(2,'人員管理','/emp.jsp');
insert into `t_module_role` values(1,1,3);
insert into `t_module_role` values(1,2,2);
insert into `t_module_role` values(2,1,1);
自定義過濾器修改如下:
<beans:bean id="filterInvocationDefinitionSource"
class="com.lovo.JdbcFilterInvocationDefinitionSourceFactoryBean">
<beans:property name="dataSource" ref="dataSource"/>
<beans:property name="resourceQuery" value="
select m.address,r.descn
from t_module_role mr
join t_module m on mr.m_id=m.id
join t_role r on mr.r_id=r.id
order by mr.priority
"/>
</beans:bean>
如果持久層使用的是hibernate,那么只需要給自定義過濾器注入1個hibernateTemplate.
在自定義過濾器中就不需要再使用mapRow的方式。而是直接獲取url和角色名即可。
例如:
public class ModuleFilter implements FactoryBean {
private String resourceQuery;
public boolean isSingleton() {
return true;
}
public Class getObjectType() {
return FilterInvocationDefinitionSource.class;
}
public Object getObject() {
return new DefaultFilterInvocationDefinitionSource(this
.getUrlMatcher(), this.buildRequestMap());
}
protected Map<String, String> findResources() {
ResourceMapping resourceMapping = new ResourceMapping();
Map<String, String> resourceMap = new LinkedHashMap<String, String>();
for (Resource resource : (List<Resource>) resourceMapping.execute()) {
String url = resource.getUrl();
String role = resource.getRole();
if (resourceMap.containsKey(url)) {
String value = resourceMap.get(url);
resourceMap.put(url, value + "," + role);
} else {
resourceMap.put(url, role);
}
}
return resourceMap;
}
protected LinkedHashMap<RequestKey, ConfigAttributeDefinition> buildRequestMap() {
LinkedHashMap<RequestKey, ConfigAttributeDefinition> requestMap = null;
requestMap = new LinkedHashMap<RequestKey, ConfigAttributeDefinition>();
ConfigAttributeEditor editor = new ConfigAttributeEditor();
Map<String, String> resourceMap = this.findResources();
for (Map.Entry<String, String> entry : resourceMap.entrySet()) {
RequestKey key = new RequestKey(entry.getKey(), null);
editor.setAsText(entry.getValue());
requestMap.put(key,
(ConfigAttributeDefinition) editor.getValue());
}
return requestMap;
}
protected UrlMatcher getUrlMatcher() {
return new AntUrlPathMatcher();
}
public void setResourceQuery(String resourceQuery) {
this.resourceQuery = resourceQuery;
}
private class Resource {
private String url;
private String role;
public Resource(String url, String role) {
this.url = url;
this.role = role;
}
public String getUrl() {
return url;
}
public String getRole() {
return role;
}
}
private class ResourceMapping{
public List<Resource> execute(){
List<Resource> rlist = new ArrayList<Resource>();
List<Role> list = hibernateTemplate.find(resourceQuery);
for(int i=0;i<list.size();i++){
Role role = list.get(i);
Set<Module> set = role.getModuleSet();
Iterator<Module> it = set.iterator();
while(it.hasNext()){
Module m = it.next();
Resource re = new Resource(m.getUrl(),role.getDescn());
rlist.add(re);
}
}
return rlist;
}
}
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
private HibernateTemplate hibernateTemplate;
}
而從靈活性的角度考慮,把hql寫在配置文件中
<beans:bean id="moduleFilter" class="com.lovo.ModuleFilter">
<beans:property name="resourceQuery"
value="from com.lovo.po.Role order by ind">
</beans:property>
<beans:property name="hibernateTemplate" ref="hibernateTemplate">
</beans:property>
</beans:bean>
問題:系統只會在初始化的時候從數據庫中加載信息。無法識別數據庫中信息的改變。
解決:每個jsp頁面上重新內存
代碼:每個jsp頁面include如下代碼:
<%@page import="org.springframework.context.ApplicationContext"%>
<%@page import="org.springframework.web.context.support.WebApplicationContextUtils"%>
<%@page import="org.springframework.beans.factory.FactoryBean"%>
<%@page import="org.springframework.security.intercept.web.FilterSecurityInterceptor"%>
<%@page import="org.springframework.security.intercept.web.FilterInvocationDefinitionSource"%>
<%
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
FactoryBean factoryBean = (FactoryBean) ctx.getBean("&自定義過濾器的id");
FilterInvocationDefinitionSource fids = (FilterInvocationDefinitionSource) factoryBean.getObject();
FilterSecurityInterceptor filter = (FilterSecurityInterceptor) ctx.getBean("filterSecurityInterceptor");
filter.setObjectDefinitionSource(fids);
%>
控制用戶信息
用戶密碼MD5加密:
<authentication-provider>
<password-encoder hash="md5"/>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password,enabled from t_account where username=?"
authorities-by-username-query="select a.username,r.descn from t_account_role ar join
t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/>
</authentication-provider>
鹽值加密
<authentication-provider>
<password-encoder hash="md5">
<salt-source user-property="username"/>
</password-encoder>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password,enabled from t_account where username=?"
authorities-by-username-query="select a.username,r.descn from t_account_role ar join
t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/>
</authentication-provider>
用戶信息緩存,使用spring內置的ehCache實現
<beans:bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"></beans:bean>
<beans:bean id="userEhCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<beans:property name="cacheManager" ref="cacheManager"></beans:property>
<beans:property name="cacheName" value="userCache"></beans:property>
</beans:bean>
<beans:bean id="userCache" class="org.springframework.security.providers.dao.cache.EhCacheBasedUserCache">
<beans:property name="cache" ref="userEhCache"></beans:property>
</beans:bean>
在src目錄下新建ehcache.xml
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache
name="userCache"
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="3600"
overflowToDisk="true"
/>
</ehcache>
在程序中獲取用戶信息
UserDetails ud = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String name = ud.getUsername();
String pwd = ud.getPassword();
GrantedAuthority[] ga = ud.getAuthorities();
System.out.println(name + "," + pwd);
for(GrantedAuthority g : ga){
System.out.println(g.getAuthority());
}
自定義訪問拒絕頁面
<http auto-config='true' access-denied-page="/error.jsp">
訪問用戶以帶參形式訪問
在資源URL地址后加*
如
insert into t_module values(1,'部門管理','/dept.jsp*');
insert into t_module values(2,'人員管理','/emp.jsp*');
自定義用戶接口實現
由於將sql寫在配置文件中只適用於小型系統,而且不靈活,在大型系統或實體關系復雜時需要自定義用戶實現的接口。
自定義用戶實現需要實現2個接口。
UserDetails:實體類需要實現的接口。
UserDetailsService:實體管理類需要實現的接口。
配置:
<!-- 實體管理類 -->
<beans:bean id="userManager" class="com.lovo.UserManager"></beans:bean>
<authentication-provider user-service-ref="userManager">
<!--
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password,enabled from t_account where username=?"
authorities-by-username-query="select a.username,r.descn from t_account_role ar join
t_account a on ar.a_id=a.id join t_role r on ar.r_id=r.id where a.username=?"/>
-->
</authentication-provider>
原有的通過固定sql語句的獲取方式可以拋棄,改為使用user-service-ref屬性注入的bean來實現對用戶及用戶所
擁有的資源的查找。
用戶注銷
注銷功能由過濾器org.springframework.security.ui.logout.LogoutFilter負責完成。
<a href="${pageContext.request.contextPath}/j_spring_security_logout">注銷</a>
被注銷的用戶就是當前session中保存的用戶,注銷后頁面自動重定向到default-target-url所指定的頁面
spring security過濾器體系
spring security的一系列功能都是由一串過濾器來完成的。在spring配置文件中配置的<http>標簽實際上就是
起到默認的過濾器進行聲明和配置的作用。
管理會話
當項目中要求不能使用同一個賬號同時登陸,按以下步驟實施
1.web.xml添加1個監聽器
<listener>
<listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
2.spring配置文件中的<http>標簽添加子標簽<concurrent-session-control/>
<http auto-config='true' access-denied-page="/noview.jsp">
<form-login login-page="/login.jsp" authentication-failure-url="/error.jsp" default-target-url="/index.jsp"/>
<concurrent-session-control/>
</http>
<concurrent-session-control/>會產生1個org.springframework.security.concurrent.ConcurrentSessionFilter
並放在過濾器鏈的最前面。
默認情況下,使用同一賬號后登陸的用戶會踢出先登陸的用戶。
如果想禁止第2個用戶登錄,則設置
<concurrent-session-control exception-if-maximum-exceeded="true"/>
對方法級的權限控制
1.添加依賴包 cglib-nodep-2.1_3.jar aspectjweaver.jar aspectjrt.jar
2.利用<global-method-security>設置需要保護的方法及可以調用的權限
<global-method-security>
<protect-pointcut access="ROLE_ADMIN,ROLE_USER" expression="execution(* com.lovo.bo.AccountBo.get*(..))"/>
<protect-pointcut access="ROLE_ADMIN" expression="execution(* com.lovo.bo.AccountBo.create*(..))"/>
</global-method-security>
利用注解同樣可以實現方法級的保護
需要spring-security-core-tiger.jar包
啟用注解保護
<global-method-security secured-annotations="enabled"/>
@Secured({"ROLE_ADMIN","ROLE_USER"})
public void getOneAccount();
擁護ROLE_ADMIN或ROLE_USER權限的用戶可以調用該方法
SecurityContext安全上下文
SecurityContext securityContext = SecurityContextHolder.getContext();
SecurityContext中保存着實現了Authentication 接口的對象,如果用戶尚未通過
認證,那么SecurityContext.getAuthenticaiton()方法就會返回null。
注意,如果使用了匿名用戶,SecurityContext.getAuthenticaiton()返回的不是null.
只有在未啟用過濾器鏈的情況下,SecurityContext.getAuthenticaiton()才返回空。
驗證管理器
驗證管理器用來識別用戶的身份。使用命名空間會自動注冊一個驗證管理器的bean.
是org.springframework.security.providers.ProviderManager類的一個對象。
如果要在其他的bean中要引用這個驗證管理器,則給這個驗證管理器取一個別名。
<authentication-manager alias="authenticationManager"/>
如果要采用其他的類來完成驗證管理器的功能,可以使用以下標簽
<beans:bean id="abc" class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
<custom-authentication-provider/>
</beans:bean>
訪問決策管理器
當使用命名空間配置時,默認的AccessDecisionManager實例會自動注冊。
默認的策略是使用一個AffirmativeBased作為AccessDecisionManager(訪問決策管理器),投票者是RoleVoter和
AuthenticatedVote
<global-method-security secured-annotations="enabled" access-decision-manager-ref="accessDecisionManager">
</global-method-security>
利用訪問決策管理器對方法進行保護
<beans:bean id="filterSecurityInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor" autowire="byType">
<custom-filter before="FILTER_SECURITY_INTERCEPTOR"/>
<beans:property name="objectDefinitionSource" ref="moduleFilter" />
<beans:property name="accessDecisionManager" ref="accessDecisionManager"></beans:property>
</beans:bean>
向過濾器注入訪問決策管理器
不管是MethodSecurityInterceptor還是FilterSecurityInterceptor都使用 authenticationManager和accessDecisionManager屬性用於驗證用戶,並且都是通過使用 objectDefinitionSource屬性來定義受保護的資源。不同的是過濾器安全攔截器將URL資源與權限關聯,而方法安全攔截器將業務方法與權限關聯。
objectDefinitionSource屬性需要注入的就是org.springframework.security.intercept.ObjectDefinitionSource這個接口的實現類。
該接口中有一個方法是:
ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException;
參數實際類型是org.springframework.security.intercept.web.FilterInvocation
這個方法用於獲取保護資源對應的權限信息,返回一個ConfigAttributeDefinition對象。
ConfigAttributeDefinition對象內部維護1個列表,安全攔截器就通過調用getAttributes方法來獲取ConfigAttributeDefinition對象,並將該對象和當前用戶擁有的Authentication對象傳遞給accessDecisionManager(訪問決策管理器)
訪問決策管理器在將其傳遞給具體實現類維護的投票者,這些投票者從ConfigAttributeDefinition對象中獲取這個存放了訪問保護資源需要的權限信息的列表,然后遍歷這個列表並與 Authentication對象中GrantedAuthority[]數據中的用戶權限信息進行匹配,如果匹配成功,投票者就會投贊成票,否則就投反對票,最后訪問決策管理器來統計這些投票決定用戶是否能訪問該資源。
FilterInvocationDefinitionSource接口和MethodDefinitionSource接口繼承自ObjectDefinitionSource接口,並提供了2個默認實現類用以從配置文件讀取權限信息。
是DefaultFilterInvocationDefinitionSource和DelegatingMethodDefinitionSource兩個類,如果需要從其他數據來源讀取則需要實現FilterInvocationDefinitionSource接口和MethodDefinitionSource接口。
自定義的過濾器必須注入objectDefinitionSource,accessDecisionManager,authenticationManager3個屬性。
FilterInvocationDefinitionSource接口getAttributes實現思路
1.獲取客戶端訪問的url地址。
2.將該url地址和數據庫中存儲的url地址匹配,找到所有有權訪問該地址的權限(角色)名字。
3.把所有的權限名利用ConfigAttributeEditor類封裝成ConfigAttributeDefinition對象
簡單例子:
public class MyFilter extends HibernateDaoSupport implements FilterInvocationDefinitionSource,FactoryBean{
//保存權限信息(能夠訪問當前資源的角色名)
private List<String> roleNameList = new ArrayList<String>();
public ConfigAttributeDefinition getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation)object;
String requeatUrl = fi.getRequestUrl(); //獲取客戶端訪問的url地址
requeatUrl = requeatUrl.toLowerCase(); //將url地址全部轉換成小寫
if(requeatUrl.indexOf("?") != -1){ //過濾請求參數
requeatUrl = requeatUrl.substring(0,requeatUrl.indexOf("?"));
}
//在數據庫中查找能夠訪問此url地址的角色
List<Module> list = this.getHibernateTemplate().find("from com.lovo.po.Module where url like ?",requeatUrl+"%");
if(list.size() <= 0){
return null;
}
String s = "";
Module module = (Module)list.get(0);
Set<Role> set = module.getRoleSet();
roleNameList.clear();
for(Role role : set){
roleNameList.add(role.getDescn());
s += role.getDescn() + ",";
}
s = s.substring(0,(s.length() - 1)); //將所有的角色名(ROLE_開頭)拼接為一個字符串。
ConfigAttributeEditor editer = new ConfigAttributeEditor();
editer.setAsText(s);
return (ConfigAttributeDefinition) editer.getValue(); //將包含所有角色名(ROLE_開頭)的字符串轉換為ConfigAttributeDefinition對象。
}
public Collection getConfigAttributeDefinitions() {
return Collections.unmodifiableCollection(this.roleNameList);
}
public boolean supports(Class clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public Object getObject() throws Exception {
return this;
}
public Class getObjectType() {
return FilterInvocationDefinitionSource.class;
}
public boolean isSingleton() {
// TODO Auto-generated method stub
return true;
}
}
方法保護的原理是通過aop來實現的。
<aop:config>
<aop:pointcut expression="execution(* com.lovo.bo.face.*.*(..))" id="me"/>
<aop:advisor advice-ref="methodSecurityInterceptor" pointcut-ref="me"/>
</aop:config>
通知是1個實現了MethodSecurityInterceptor接口的類
methodSecurityInterceptor是spring中的一個類
<beans:bean id="methodSecurityInterceptor" class="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor">
<beans:property name="objectDefinitionSource" ref="methodFilter" />
<beans:property name="accessDecisionManager" ref="accessDecisionManager"></beans:property>
<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
</beans:bean>
accessDecisionManager屬性注入訪問決策管理器,authenticationManager屬性注入驗證管理器。
objectDefinitionSource屬性注入1個實現了MethodDefinitionSource接口的類。
<beans:bean id="methodFilter" class="com.lovo.method.MethodFilter">
<beans:property name="hibernateTemplate" ref="hibernateTemplate"></beans:property>
</beans:bean>
這個類的作用是根據用戶調用的方法,找到相應的角色,
public class MethodFilter extends HibernateDaoSupport implements MethodDefinitionSource{
private List<String> roleList = new ArrayList<String>();
public ConfigAttributeDefinition getAttributes(Method method, Class targetClass) {
return null;
}
public ConfigAttributeDefinition getAttributes(Object object)
throws IllegalArgumentException {
ConfigAttributeEditor editor = new ConfigAttributeEditor();
String s = "";
ReflectiveMethodInvocation rmi = (ReflectiveMethodInvocation)object;
String methodName = rmi.getThis().getClass().getName() + "." + rmi.getMethod().getName();
System.out.println(methodName);
List<Module> list = this.getHibernateTemplate().find("from com.lovo.po.Module where url like ?",methodName);
if(list.size() == 0){
return null;
}
roleList.clear();
for(int i=0;i<list.size();i++){
Module module = list.get(i);
Set<Role> roleSet = module.getRoleSet();
Iterator<Role> it = roleSet.iterator();
while(it.hasNext()){
Role role = it.next();
s += role.getDescn() + ",";
roleList.add(role.getDescn());
}
}
s = s.substring(0,(s.length() - 1));
editor.setAsText(s);
editor.getValue();
return (ConfigAttributeDefinition) editor.getValue();
}
public Collection getConfigAttributeDefinitions() {
return roleList;
}
public boolean supports(Class clazz) {
return true;
}
}
相應的方法名保存在數據庫中。
