登錄攔截
流程:
用戶登陸,會被AuthenticationProcessingFilter攔截,調用AuthenticationManager的實現,而且AuthenticationManager會調用ProviderManager來獲取用戶驗證信息(不同的Provider調用的服務不同,因爲這些信息可以是在數據庫上,可以是在LDAP服務器上,可以是xml配置文件上等),如果驗證通過後會將用戶的權限信息封裝一個User放到spring的全局緩存SecurityContextHolder中,以備後面訪問資源時使用。
ProvideManager 根據用戶名返回對應的角色權限信息
配置方式:
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUserDetailService">
<!--如果用戶的密碼採用加密的話 <password-encoder hash="md5" /> -->
</authentication-provider>
</authentication-manager>
代碼:
/**
* @author PL
* 驗證配置,認證管理器,實現用戶認證的入口
*/
public class MyUserDetailService implements UserDetailsService {
/**
* 登陸驗證時,通過username獲取用戶的所有權限信息,
* 並返回User放到spring的全局緩存SecurityContextHolder中,以供授權器使用
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();
//GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN");
GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_USER");
if(username.equals("admin")){
auths=new ArrayList<GrantedAuthority>();
auths.add(auth1);
// auths.add(auth2);
}
User user = new User(username, "admin", true, true, true, true, auths);
System.out.println("--------UserDetailService:"+JSONObject.toJSONString(user));
return user;
}
登錄資源訪問攔截
流程:
訪問url時,會通過AbstractSecurityInterceptor攔截器攔截,其中會調用FilterInvocationSecurityMetadataSource的方法來獲取被攔截url所需的全部權限,在調用授權管理器AccessDecisionManager,這個授權管理器會通過spring的全局緩存SecurityContextHolder獲取用戶的權限信息,還會獲取被攔截的url和被攔截url所需的全部權限,然後根據所配的策略(有:一票決定,一票否定,少數服從多數等),如果權限足夠,則返回,權限不夠則報錯並調用權限不足頁面。
加載所有URL對應的角色:
授權管理:
完整代碼:
SpringSecurity.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.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- 配置不過濾的資源(靜態資源及登錄相關) -->
<http pattern="/**/*.css" security="none"></http>
<http pattern="/**/*.jpg" security="none"></http>
<http pattern="/**/*.jpeg" security="none"></http>
<http pattern="/**/*.gif" security="none"></http>
<http pattern="/**/*.png" security="none"></http>
<http pattern="/js/*.js" security="none"></http>
<http pattern="/login.jsp" security="none"></http>
<http pattern="/getCode" security="none" /><!-- 不過濾驗證碼 -->
<http pattern="/test/**" security="none"></http><!-- 不過濾測試內容 -->
<http auto-config='true' >
<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/main.jsp" access="ROLE_ADMIN" />
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page="/login.jsp" />
<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR" />
</http>
<!--一個自定義的filter,必須包含 authenticationManager,accessDecisionManager,securityMetadataSource三個屬性,
我們的所有控制將在這三個類中實現,解釋詳見具體配置 -->
<beans:bean id="myFilter"
class="cn.panlei.springsecurity.service.MyFilterSecurityInterceptor">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />
<beans:property name="securityMetadataSource" ref="securityMetadataSource" />
</beans:bean>
<!--驗證配置,認證管理器,實現用戶認證的入口,主要實現UserDetailsService接口即可 -->
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUserDetailService">
<!--如果用戶的密碼採用加密的話 <password-encoder hash="md5" /> -->
</authentication-provider>
</authentication-manager>
<!--在這個類中,你就可以從數據庫中讀入用戶的密碼,角色信息,是否鎖定,賬號是否過期等 -->
<beans:bean id="myUserDetailService" class="cn.panlei.springsecurity.service.MyUserDetailService" />
<!--訪問決策器,決定某個用戶具有的角色,是否有足夠的權限去訪問某個資源 -->
<beans:bean id="myAccessDecisionManagerBean"
class="cn.panlei.springsecurity.service.MyAccessDecisionManager">
</beans:bean>
<!--資源源數據定義,將所有的資源和權限對應關係建立起來,即定義某一資源可以被哪些角色訪問 -->
<beans:bean id="securityMetadataSource"
class="cn.panlei.springsecurity.service.MyInvocationSecurityMetadataSource" />
</beans:beans>
MyUserDetailService.java
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.alibaba.fastjson.JSONObject;
/**
* @author PL
* 驗證配置,認證管理器,實現用戶認證的入口
*/
public class MyUserDetailService implements UserDetailsService {
/**
* 登陸驗證時,通過username獲取用戶的所有權限信息,
* 並返回User放到spring的全局緩存SecurityContextHolder中,以供授權器使用
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();
//GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN");
GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_USER");
if(username.equals("admin")){
auths=new ArrayList<GrantedAuthority>();
auths.add(auth1);
// auths.add(auth2);
}
User user = new User(username, "admin", true, true, true, true, auths);
System.out.println("--------UserDetailService:"+JSONObject.toJSONString(user));
return user;
}
}
MyFilterSecurityInterceptor.java
/**
*
* @author PL
*
* 資源訪問是進行攔截
*/
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
// 配置文件注入
private FilterInvocationSecurityMetadataSource securityMetadataSource;
// 登陸後,每次訪問資源都通過這個攔截器攔截
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public Class<? extends Object> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
// fi裏面有一個被攔截的url
// 裏面調用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取fi對應的所有權限
// object)這個方法獲取fi對應的所有權限
// 再調用MyAccessDecisionManager的decide方法來校驗用戶的權限是否足夠
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 執行下一個攔截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
public void destroy() {
}
public void init(FilterConfig arg0) throws ServletException {
}
}
MyAccessDecisionManager .java 表決器
public class MyAccessDecisionManager implements AccessDecisionManager {
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
System.out.println("進入AccessDecisionManager:"+authentication);
if (configAttributes == null) {
return;
}
Iterator<ConfigAttribute> ite = configAttributes.iterator();
while (ite.hasNext()) {
ConfigAttribute ca = ite.next();
String needRole = ((SecurityConfig) ca).getAttribute();
for (GrantedAuthority ga : authentication.getAuthorities()) {
if (needRole.equals(ga.getAuthority())) {
return;
}
}
}
// 注意:執行這裏,後臺是會拋異常的,但是界面會跳轉到所配的access-denied-page頁面
throw new AccessDeniedException("no right");
}
public boolean supports(ConfigAttribute attribute) {
// TODO Auto-generated method stub
return true;
}
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
}
}
MyInvocationSecurityMetadataSource.java
/**
*
* @author PL
*
* 資源源數據定義,將所有的資源和權限對應關係建立起來,即定義某一資源可以被哪些角色訪問
*
*/
public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
protected final Log logger = LogFactory.getLog(getClass());
//將所有的角色和url的對應關係緩存起來
//private static List<RoleUrlResource> rus = null;
private UrlMatcher urlMatcher = new AntUrlPathMatcher();
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
// tomcat啓動時實例化一次
public MyInvocationSecurityMetadataSource() {
loadResourceDefine();
}
// tomcat開啓時加載一次,加載所有url和權限(或角色)的對應關係
private void loadResourceDefine() {
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
ConfigAttribute ca = new SecurityConfig("ROLE_ADMIN");
atts.add(ca);
resourceMap.put("/index.jsp", atts);
Collection<ConfigAttribute> attsno = new ArrayList<ConfigAttribute>();
ConfigAttribute cano = new SecurityConfig("ROLE_NO");
attsno.add(cano);
resourceMap.put("/other.jsp", attsno);
System.out.println("---------FilterInvocationSecurityMetadataSource"+JSONObject.toJSONString(resourceMap));
}
// 參數是要訪問的url,返回這個url對於的所有權限(或角色)
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
/*String url = ((FilterInvocation) object).getRequestUrl();
// 查詢所有的url和角色的對應關係
if (rus == null) {
rus = roleUrlDao.findAll();
}
// 匹配所有的url,並對角色去重
Set<String> roles = new HashSet<String>();
for (RoleUrlResource ru : rus) {
if (urlMatcher.pathMatchesUrl(ru.getUrlResource().getUrl(), url)) {
roles.add(ru.getRole().getRoleName());
}
}
Collection<ConfigAttribute> cas = new ArrayList<ConfigAttribute>();
for (String role : roles) {
ConfigAttribute ca = new SecurityConfig(role);
cas.add(ca);
}
return cas;*/
// 將參數轉爲url
//logger.info("getAttributes"+JSONObject.toJSONString(object));
// 將參數轉爲url
String url = ((FilterInvocation)object).getRequestUrl();
Iterator<String>ite = resourceMap.keySet().iterator();
while (ite.hasNext()) {
String resURL = ite.next();
if (urlMatcher.pathMatchesUrl(resURL, url)) {
return resourceMap.get(resURL);
}
}
return null;
}
public boolean supports(Class<?> clazz) {
return true;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
}
UrlMatcher.java
public interface UrlMatcher {
Object compile(String paramString);
boolean pathMatchesUrl(Object paramObject, String paramString);
String getUniversalMatchPattern();
boolean requiresLowerCaseUrl();
}
獲取用戶輸入的密碼:UsernamePasswordAuthenticationFilter