Spring Security3.1 最新配置實例

轉至http://blog.csdn.net/k10509806/article/details/6369131

這幾天學習了一下Spring Security3.1,從官網下載了Spring Security3.1版本進行練習,經過多次嘗試才摸清了其中的一些原理。本人不才,希望能幫助大家。還有,這次我第二次寫博客啊,文體不是很行。希望能讓觀看者不產生疲憊的感覺,我已經心滿意足了。

一、數據庫結構
先來看一下數據庫結構,採用的是基於角色-資源-用戶的權限管理設計。(MySql數據庫)
爲了節省篇章,只對比較重要的字段進行註釋。
1.用戶表Users
CREATE TABLE `users` (
-- 賬號是否有限 1. 是 0.否
`enable` int(11) default NULL,
`password` varchar(255) default NULL,
`account` varchar(255) default NULL,
`id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`id`)
)

2.角色表Roles
CREATE TABLE `roles` (
`enable` int(11) default NULL,
`name` varchar(255) default NULL,
`id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`id`)
)

3 用戶_角色表users_roles
CREATE TABLE `users_roles` (
--用戶表的外鍵
`uid` int(11) default NULL,
--角色表的外鍵
`rid` int(11) default NULL,
`urId` int(11) NOT NULL auto_increment,
PRIMARY KEY (`urId`),
KEY `rid` (`rid`),
KEY `uid` (`uid`),
CONSTRAINT `users_roles_ibfk_1` FOREIGN KEY (`rid`) REFERENCES `roles` (`id`),
CONSTRAINT `users_roles_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `users` (`id`)
)

4.資源表resources
CREATE TABLE `resources` (
`memo` varchar(255) default NULL,
-- 權限所對應的url地址
`url` varchar(255) default NULL,
--優先權
`priority` int(11) default NULL,
--類型
`type` int(11) default NULL,
--權限所對應的編碼,例201代表發表文章
`name` varchar(255) default NULL,
`id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`id`)
)

5.角色_資源表roles_resources
CREATE TABLE `roles_resources` (
`rsid` int(11) default NULL,
`rid` int(11) default NULL,
`rrId` int(11) NOT NULL auto_increment,
PRIMARY KEY (`rrId`),
KEY `rid` (`rid`),
KEY `roles_resources_ibfk_2` (`rsid`),
CONSTRAINT `roles_resources_ibfk_2` FOREIGN KEY (`rsid`) REFERENCES `resources` (`id`),
CONSTRAINT `roles_resources_ibfk_1` FOREIGN KEY (`rid`) REFERENCES `roles` (`id`)
)

二、系統配置
所需要的jar包,請自行到官網下載,我用的是Spring Security3.1.0.RC1版的。把dist下的除了源碼件包導入就行了。還有那些零零碎的 數據庫驅動啊,log4j.jar等等,我相信在用Spring Security之前,大家已經會的了。
1) web.xml

<!-- Spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml,classpath:applicationContext-security.xml</param-value>
</context-param>


<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 權限 -->
<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>


這裏主要是配置了讓容器啓動的時候加載application-security.xml和Spring Security的權限過濾器代理,讓其過濾所有的客服請求。
2)application-security.xml


<?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-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

<global-method-security pre-post-annotations="enabled" />
<!-- 該路徑下的資源不用過濾 -->
<http pattern="/js/**" security="none"/>
<http use-expressions="true" auto-config="true">

<form-login />
<logout/>
<!-- 實現免登陸驗證 -->
<remember-me />
<session-management invalid-session-url="/timeout.jsp">
<concurrency-control max-sessions="10" error-if-maximum-exceeded="true" />
</session-management>
<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
</http>
<!-- 配置過濾器 -->
<beans:bean id="myFilter" class="com.huaxin.security.MySecurityFilter">
<!-- 用戶擁有的權限 -->
<beans:property name="authenticationManager" ref="myAuthenticationManager" />
<!-- 用戶是否擁有所請求資源的權限 -->
<beans:property name="accessDecisionManager" ref="myAccessDecisionManager" />
<!-- 資源與權限對應關係 -->
<beans:property name="securityMetadataSource" ref="mySecurityMetadataSource" />
</beans:bean>
<!-- 實現了UserDetailsService的Bean -->
<authentication-manager alias="myAuthenticationManager">
<authentication-provider user-service-ref="myUserDetailServiceImpl" />
</authentication-manager>
<beans:bean id="myAccessDecisionManager" class="com.huaxin.security.MyAccessDecisionManager"></beans:bean>
<beans:bean id="mySecurityMetadataSource" class="com.huaxin.security.MySecurityMetadataSource">
<beans:constructor-arg name="resourcesDao" ref="resourcesDao"></beans:constructor-arg>
</beans:bean>
<beans:bean id="myUserDetailServiceImpl" class="com.huaxin.security.MyUserDetailServiceImpl">
<beans:property name="usersDao" ref="usersDao"></beans:property>
</beans:bean>
</beans:beans>



我們在第二個http標籤下配置一個我們自定義的繼承了org.springframework.security.access.intercept.AbstractSecurityInterceptor的Filter,並注入其
必須的3個組件authenticationManager、accessDecisionManager和securityMetadataSource。其作用上面已經註釋了。

<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/> 這裏的FILTER_SECURITY_INTERCEPTOR是Spring Security默認的Filter,
我們自定義的Filter必須在它之前,過濾客服請求。接下來看下我們最主要的myFilter吧。

3)myFilter
(1) MySecurityFilter.java 過濾用戶請求

public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter {
//與applicationContext-security.xml裏的myFilter的屬性securityMetadataSource對應,
//其他的兩個組件,已經在AbstractSecurityInterceptor定義
private FilterInvocationSecurityMetadataSource securityMetadataSource;

@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}

private void invoke(FilterInvocation fi) throws IOException, ServletException {
// object爲FilterInvocation對象
//super.beforeInvocation(fi);源碼
//1.獲取請求資源的權限
//執行Collection<ConfigAttribute> attributes = SecurityMetadataSource.getAttributes(object);
//2.是否擁有權限
//this.accessDecisionManager.decide(authenticated, object, attributes);
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}

public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return securityMetadataSource;
}

public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
}

public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}

public void destroy() {
// TODO Auto-generated method stub

}

@Override
public Class<? extends Object> getSecureObjectClass() {
//下面的MyAccessDecisionManager的supports方面必須放回true,否則會提醒類型錯誤
return FilterInvocation.class;
}
}


核心的InterceptorStatusToken token = super.beforeInvocation(fi);會調用我們定義的accessDecisionManager:decide(Object object)和securityMetadataSource
:getAttributes(Object object)方法。

(2)MySecurityMetadataSource.java

//1 加載資源與權限的對應關係
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
//由spring調用
public MySecurityMetadataSource(ResourcesDao resourcesDao) {
this.resourcesDao = resourcesDao;
loadResourceDefine();
}

private ResourcesDao resourcesDao;
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;

public ResourcesDao getResourcesDao() {
return resourcesDao;
}

public void setResourcesDao(ResourcesDao resourcesDao) {
this.resourcesDao = resourcesDao;
}

public Collection<ConfigAttribute> getAllConfigAttributes() {
// TODO Auto-generated method stub
return null;
}

public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
}
//加載所有資源與權限的關係
private void loadResourceDefine() {
if(resourceMap == null) {
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
List<Resources> resources = this.resourcesDao.findAll();
for (Resources resource : resources) {
Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
//以權限名封裝爲Spring的security Object
ConfigAttribute configAttribute = new SecurityConfig(resource.getName());
configAttributes.add(configAttribute);
resourceMap.put(resource.getUrl(), configAttributes);
}
}

Set<Entry<String, Collection<ConfigAttribute>>> resourceSet = resourceMap.entrySet();
Iterator<Entry<String, Collection<ConfigAttribute>>> iterator = resourceSet.iterator();

}
//返回所請求資源所需要的權限
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {

String requestUrl = ((FilterInvocation) object).getRequestUrl();
System.out.println("requestUrl is " + requestUrl);
if(resourceMap == null) {
loadResourceDefine();
}
return resourceMap.get(requestUrl);
}

}


這裏的resourcesDao,熟悉Dao設計模式和Spring 注入的朋友應該看得明白。

(3)MyUserDetailServiceImpl.java


public class MyUserDetailServiceImpl implements UserDetailsService {

private UsersDao usersDao;
public UsersDao getUsersDao() {
return usersDao;
}

public void setUsersDao(UsersDao usersDao) {
this.usersDao = usersDao;
}

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username is " + username);
Users users = this.usersDao.findByName(username);
if(users == null) {
throw new UsernameNotFoundException(username);
}
Collection<GrantedAuthority> grantedAuths = obtionGrantedAuthorities(users);

boolean enables = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;

User userdetail = new User(users.getAccount(), users.getPassword(), enables, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuths);
return userdetail;
}

//取得用戶的權限
private Set<GrantedAuthority> obtionGrantedAuthorities(Users user) {
Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
Set<Roles> roles = user.getRoles();

for(Roles role : roles) {
Set<Resources> tempRes = role.getResources();
for(Resources res : tempRes) {
authSet.add(new GrantedAuthorityImpl(res.getName()));
s }
}
return authSet;
}
}


(4) MyAccessDecisionManager.java


public class MyAccessDecisionManager implements AccessDecisionManager {

public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(configAttributes == null) {
return;
}
//所請求的資源擁有的權限(一個資源對多個權限)
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while(iterator.hasNext()) {
ConfigAttribute configAttribute = iterator.next();
//訪問所請求資源所需要的權限
String needPermission = configAttribute.getAttribute();
System.out.println("needPermission is " + needPermission);
//用戶所擁有的權限authentication
for(GrantedAuthority ga : authentication.getAuthorities()) {
if(needPermission.equals(ga.getAuthority())) {
return;
}
}
}
//沒有權限
throw new AccessDeniedException(" 沒有權限訪問! ");
}

public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return true;
}

public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
}

}


三、流程
1)容器啓動(MySecurityMetadataSource:loadResourceDefine加載系統資源與權限列表)
2)用戶發出請求
3)過濾器攔截(MySecurityFilter:doFilter)
4)取得請求資源所需權限(MySecurityMetadataSource:getAttributes)
5)匹配用戶擁有權限和請求權限(MyAccessDecisionManager:decide),如果用戶沒有相應的權限,
執行第6步,否則執行第7步。
6)登錄
7)驗證並授權(MyUserDetailServiceImpl:loadUserByUsername)
8)重複4,5

四、結束語
好了,終於寫完了,回頭看了一下,感覺不是怎麼行。等我弄明白Spring Security它的原理之後,再回頭修改下注釋吧。大家覺得不妥的地方,可以留言,我會回覆大家的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章