一 web.xml配置文件
- 配置2個(gè)filter
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- spring security是在DelegatingFilterProxy中進(jìn)行處理
- tomcat初始化DelegatingFilterProxy類(lèi)對(duì)象
- 從spring管理的bean中獲取
FilterChainProxy
,賦值為Filter delegate
-
FilterChainProxy
類(lèi)型bean通過(guò)xml標(biāo)簽定義腊瑟,解析后交給spring容器管理嫩痰。
二 spring 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"
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
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.2.xsd">
<http pattern="/" security="none"/>
<http auto-config="false" use-expressions="true" entry-point-ref="ssoAuthenticationEntryPoint"
security-context-repository-ref="myCookieSecurityContextRepository" create-session="stateless">
<intercept-url pattern="/index.html" access="hasRole('ROLE_USER')"/>
<!-- 管理員 -->
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
<custom-filter ref="ssoLogoutFilter" before="LOGOUT_FILTER"/>
<access-denied-handler ref="accessDeniedHandler"/>
</http>
<authentication-manager alias="authenticationManager"/>
</beans:beans>
- org.springframework.security.config.SecurityNamespaceHandler 注冊(cè)xml標(biāo)簽解析函數(shù)
- HttpSecurityBeanDefinitionParser http標(biāo)簽解析函數(shù)
- 一個(gè)http標(biāo)簽解析成一個(gè)DefaultSecurityFilterChain類(lèi)型過(guò)濾串,按匹配規(guī)則
RequestMatcher requestMatcher
對(duì)目標(biāo)url執(zhí)行一組List<Filter> filters
過(guò)濾操作。 - 所有的DefaultSecurityFilterChain用于初始化
filterChainProxy
屬性List<SecurityFilterChain> filterChains
矗蕊。filterChainProxy對(duì)象交給spring容器管理 - 第二個(gè)http標(biāo)簽初始化了一組filter疙驾,當(dāng)有請(qǐng)求時(shí)依次調(diào)用各filter的doFilter()函數(shù)處理請(qǐng)求。
三 filter
3.1 SecurityContextPersistenceFilter
- 實(shí)例化
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
this.forceEagerSessionCreation = false;
this.repo = repo;
}
- http標(biāo)簽配置
security-context-repository-ref="myCookieSecurityContextRepository"
為構(gòu)造函數(shù)參數(shù)repo - 過(guò)濾處理函數(shù)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if(request.getAttribute("__spring_security_scpf_applied") != null) {//只處理一次
chain.doFilter(request, response);
} else {
boolean debug = this.logger.isDebugEnabled();
request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
if(this.forceEagerSessionCreation) {
HttpSession session = request.getSession();
if(debug && session.isNew()) {
this.logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
//從請(qǐng)求中獲取用戶認(rèn)證信息昏翰,cookie或session中保存的認(rèn)證信息
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
boolean var13 = false;
try {
var13 = true;
//默認(rèn)threadlocal方式保存變量苍匆,保存filter處理前的認(rèn)證信息 SecurityContextHolder.setContext(contextBeforeChainExecution);
//執(zhí)行后續(xù)filter處理
chain.doFilter(holder.getRequest(), holder.getResponse());
var13 = false;
} finally {
if(var13) {//異常發(fā)生
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if(debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
//清理緩存數(shù)據(jù)
SecurityContextHolder.clearContext();
//保存認(rèn)證信息
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
//刪除處理標(biāo)記 request.removeAttribute("__spring_security_scpf_applied");
if(debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
3.2 WebAsyncManagerIntegrationFilter
- 支持異步處理
- 只調(diào)用一次處理
- 過(guò)濾處理
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);//初始化并注冊(cè)到request中
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor)asyncManager.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
if(securityProcessingInterceptor == null) {//初始化注冊(cè)到asyncManager中
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY, new SecurityContextCallableProcessingInterceptor());
}
filterChain.doFilter(request, response);
}
3.3 LogoutFilter
- 實(shí)例化
public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
Assert.notEmpty(handlers, "LogoutHandlers are required");
//退出處理函數(shù),清理session及cookie及緩存中的認(rèn)證信息
this.handlers = Arrays.asList(handlers);
Assert.isTrue(!StringUtils.hasLength(logoutSuccessUrl) || UrlUtils.isValidRedirectUrl(logoutSuccessUrl), logoutSuccessUrl + " isn't a valid redirect URL");
SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
if(StringUtils.hasText(logoutSuccessUrl)) {
//指定退出后的跳轉(zhuǎn)url urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
}
this.logoutSuccessHandler = urlLogoutSuccessHandler;
this.setFilterProcessesUrl("/j_spring_security_logout");
}
- 過(guò)濾處理
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if(!this.requiresLogout(request, response)) {//匹配函數(shù)未匹配
chain.doFilter(request, response);
} else {
//獲取認(rèn)證信息
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Logging out user '" + auth + "' and transferring to logout destination");
}
Iterator i$ = this.handlers.iterator();
while(i$.hasNext()) {//遍歷退出處理函數(shù)棚菊,進(jìn)行處理
LogoutHandler handler = (LogoutHandler)i$.next();
handler.logout(request, response, auth);
}
//使用sendRedirect()浸踩,跳轉(zhuǎn)到退出url
this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
}
}
- sendRedirect()與forward()
sendRedirect就是服務(wù)端根據(jù)邏輯,發(fā)送一個(gè)狀態(tài)碼(Location ,狀態(tài)碼320),告訴瀏覽器重新去請(qǐng)求那個(gè)地址统求,一般來(lái)說(shuō)瀏覽器會(huì)用剛才請(qǐng)求的所有參數(shù)重新請(qǐng)求检碗,所以session,request參數(shù)都可以獲取据块。
forward是服務(wù)器請(qǐng)求資源,服務(wù)器直接訪問(wèn)目標(biāo)地址的URL折剃,把那個(gè)URL的響應(yīng)內(nèi)容讀取過(guò)來(lái)另假,然后把這些內(nèi)容再發(fā)給瀏覽器,瀏覽器根本不知道服務(wù)器發(fā)送的內(nèi)容是從哪兒來(lái)的怕犁,所以它的地址欄中還是原來(lái)的地址边篮。
3.4 SecurityContextHolderAwareRequestFilter
- 獲取servlet版本信息
private boolean isServlet3() {
return ClassUtils.hasMethod(ServletRequest.class, "startAsync", new Class[0]);
}
- 根據(jù)servlet版本信息初始化請(qǐng)求工廠
this.requestFactory = (HttpServletRequestFactory)(this.isServlet3()?this.createServlet3Factory(this.rolePrefix):new HttpServlet25RequestFactory(this.trustResolver, this.rolePrefix));
- servlet3工廠
private HttpServletRequestFactory createServlet3Factory(String rolePrefix) {
HttpServlet3RequestFactory factory = new HttpServlet3RequestFactory(rolePrefix);
factory.setTrustResolver(this.trustResolver);
factory.setAuthenticationEntryPoint(this.authenticationEntryPoint);
factory.setAuthenticationManager(this.authenticationManager);
factory.setLogoutHandlers(this.logoutHandlers);
return factory;
}
- 注入認(rèn)證管理器AuthenticationManager
- 注入認(rèn)證入口函數(shù)AuthenticationEntryPoint
- 注入logout處理函數(shù)
- 過(guò)濾處理
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
//請(qǐng)求工廠創(chuàng)建一個(gè)請(qǐng)求 chain.doFilter(this.requestFactory.create((HttpServletRequest)req, (HttpServletResponse)res), res);
}
-
請(qǐng)求的類(lèi)關(guān)系圖
image.png
3.5 AnonymousAuthenticationFilter
- 支持匿名用戶認(rèn)證,用戶名 anonymousUser因苹,角色ROLE_ANONYMOUS
- 過(guò)濾處理
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
if(this.applyAnonymousForThisRequest((HttpServletRequest)req)) {
if(SecurityContextHolder.getContext().getAuthentication() == null) {//無(wú)認(rèn)證信息
SecurityContextHolder.getContext().setAuthentication(this.createAuthentication((HttpServletRequest)req));//創(chuàng)建匿名用戶認(rèn)證
if(this.logger.isDebugEnabled()) {
this.logger.debug("Populated SecurityContextHolder with anonymous token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
} else if(this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
//使用匿名用戶進(jìn)行后續(xù)處理
chain.doFilter(req, res);
}
3.6 ExceptionTranslationFilter
- 異常處理
- 實(shí)例化
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint, RequestCache requestCache) {
this.accessDeniedHandler = new AccessDeniedHandlerImpl();//禁止訪問(wèn)處理
this.authenticationTrustResolver = new AuthenticationTrustResolverImpl();
this.throwableAnalyzer = new ExceptionTranslationFilter.DefaultThrowableAnalyzer();//異常解析
this.requestCache = new HttpSessionRequestCache();
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
Assert.notNull(requestCache, "requestCache cannot be null");
//認(rèn)證入口苟耻,異常發(fā)生時(shí),調(diào)用認(rèn)證入口函數(shù)
this.authenticationEntryPoint = authenticationEntryPoint;
this.requestCache = requestCache;
}
- 過(guò)濾處理
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
try {
chain.doFilter(request, response);
this.logger.debug("Chain processed normally");
} catch (IOException var9) {//io異常不處理
throw var9;
} catch (Exception var10) {
//解析異常
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if(ase == null) {
ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if(ase == null) {//解析失敗扶檐,拋出異常
if(var10 instanceof ServletException) {
throw (ServletException)var10;
}
if(var10 instanceof RuntimeException) {
throw (RuntimeException)var10;
}
throw new RuntimeException(var10);
}
//解析成功凶杖,進(jìn)行處理
this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
}
}
- 異常解析結(jié)果處理
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
if(exception instanceof AuthenticationException) {
this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
//認(rèn)證異常,重新認(rèn)證
this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);
} else if(exception instanceof AccessDeniedException) {//權(quán)限異常
if(this.authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {//匿名用戶款筑,重新認(rèn)證
this.logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point", exception);
this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException("Full authentication is required to access this resource"));
} else {//調(diào)用認(rèn)證失敗處理函數(shù)處理
this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
}
}
}
3.7 FilterSecurityInterceptor
- doFilter處理
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);//初始化
this.invoke(fi);
}
- invoke()
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if(fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
if(fi.getRequest() != null) {//只處理一次
fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
//調(diào)用各模版函數(shù)
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
- beforeInvocation()
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
boolean debug = this.logger.isDebugEnabled();
if(!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass());
} else {
//獲取url匹配pattern規(guī)則的`intercept-url`配置
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if(attributes != null && !attributes.isEmpty()) {
if(debug) {
this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
//未認(rèn)證會(huì)創(chuàng)建匿名認(rèn)證信息 if(SecurityContextHolder.getContext().getAuthentication() == null) {
this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
}
//檢查是否需要進(jìn)行認(rèn)證智蝠,一種是匿名認(rèn)證,一種是配置了重復(fù)認(rèn)證奈梳。
Authentication authenticated = this.authenticateIfRequired();
try {//授權(quán)校驗(yàn)處理杈湾,失敗則拋AccessDeniedException
this.accessDecisionManager.decide(authenticated, object, attributes);
} catch (AccessDeniedException var7) {
this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));//發(fā)送通知事件
throw var7;//異常處理函數(shù)中處理
}
if(debug) {
this.logger.debug("Authorization successful");
}
//通知授權(quán)結(jié)果
if(this.publishAuthorizationSuccess) {
this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if(runAs == null) {
if(debug) {
this.logger.debug("RunAsManager did not change Authentication object");
}
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
} else {
if(debug) {
this.logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
} else if(this.rejectPublicInvocations) {
throw new IllegalArgumentException("Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. " + "This indicates a configuration error because the " + "rejectPublicInvocations property is set to 'true'");
} else {
if(debug) {
this.logger.debug("Public object - authentication not attempted");
}
this.publishEvent(new PublicInvocationEvent(object));
return null;
}
}
}
- finallyInvocation
protected void finallyInvocation(InterceptorStatusToken token) {
if(token != null && token.isContextHolderRefreshRequired()) {//配置每次都刷新緩存的認(rèn)證信息,默認(rèn)配置為否
if(this.logger.isDebugEnabled()) {
this.logger.debug("Reverting to original Authentication: " + token.getSecurityContext().getAuthentication());
}
SecurityContextHolder.setContext(token.getSecurityContext());
}
}
- afterInvocation
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
if(token == null) {
return returnedObject;
} else {
this.finallyInvocation(token);
if(this.afterInvocationManager != null) {
try {
//處理后校驗(yàn)
returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject);
} catch (AccessDeniedException var5) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(), var5);
this.publishEvent(event);
throw var5;
}
}
return returnedObject;
}
}