1、數據庫結構html
先來看一下數據庫結構,採用的是基於角色-資源-用戶的權限管理設計。(MySql數據庫)java
爲了節省篇章,只對比較重要的字段進行註釋。web
1.用戶表Usersspring
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`)
)express
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`)
)session
3 用戶_角色表users_rolesapp
CREATE TABLE `users_roles` (jsp
--用戶表的外鍵
`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`)
)
2、系統配置
所須要的jar包,請自行到官網下載,我用的是Spring Security3.1.0.RC1版的。把dist下的除了源碼件包導入就好了。還有那些零零碎的 數據庫驅動啊,log4j.jar等等,我相信在用Spring Security以前,你們已經會的了。
1) web.xml
[xhtml] view plaincopy
<!-- 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
[xhtml] view plaincopy
<?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 過濾用戶請求
[java] view plaincopy
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
[java] view plaincopy
//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
[java] view plaincopy
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
[java] view plaincopy
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;
}
}
3、流程
1)容器啓動(MySecurityMetadataSource:loadResourceDefine加載系統資源與權限列表)
2)用戶發出請求
3)過濾器攔截(MySecurityFilter:doFilter)
4)取得請求資源所需權限(MySecurityMetadataSource:getAttributes)
5)匹配用戶擁有權限和請求權限(MyAccessDecisionManager:decide),若是用戶沒有相應的權限,
執行第6步,不然執行第7步。 6)登陸 7)驗證並受權(MyUserDetailServiceImpl:loadUserByUsername) 8)重複4,5