javascript
Spring Web Application Security
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
基本流程
Spring Security認(rèn)證過程的發(fā)起
(引用 http://blog.csdn.net/kaikai8552/article/details/3932370)
發(fā)起的條件:
????? 用戶訪問資源時(shí),發(fā)生認(rèn)證異常(AuthenticationException)或授權(quán)異常(AccessDeniedException),ExceptionTranslationFilter通過調(diào)用AuthenticationEntryPoint的commence方法發(fā)起認(rèn)證過程。如果ExceptionTranslationFilter接收到的是授權(quán)異常,并且當(dāng)前認(rèn)證過的票據(jù)不是匿名票據(jù)(AnonymousAuthenticationToken),將不會發(fā)起認(rèn)證過程,而是交給AccessDeniedHandler處理(一般會直接提示用戶拒絕訪問)。
發(fā)起過程:
????? 發(fā)起認(rèn)證之前,Spring Security會將用戶之前的請求信息保存在session里。還會清空SecurityContextHolder中的票據(jù)。AuthenticationEntryPoint將用戶定向到認(rèn)證的入口,收集認(rèn)證信息。認(rèn)證入口一般是個(gè)頁面,需要用戶輸入用戶名和密碼,也有其他方式的入口。收集到認(rèn)證信息之后,重新提交認(rèn)證請求。認(rèn)證信息會再次通過過濾器,由AuthenticationManager認(rèn)證。AuthenticationEntryPoint是用戶提供憑證的入口,真正的認(rèn)證是由過濾器來完成。(但是DigestProcessingFilter的實(shí)現(xiàn),是直接使用了一個(gè)userDetailsService,這是個(gè)特例并且這個(gè)過濾器也沒有繼承SpringSecurityFilter)
下面是Spring Security提供的幾個(gè)AuthenticationEntryPoint的實(shí)現(xiàn)。
AuthenticationProcessingFilterEntryPoint
????? 會生成一個(gè)用于認(rèn)證的表單,收集認(rèn)證信息。生成的頁面的URL缺省值是/j_spring_security_check,可以配置自己指定的URL。只有以這個(gè)URL結(jié)尾的請求收集上來的認(rèn)證信息,才會被AuthenticationProcessingFilter用來認(rèn)證。用戶提交的用戶名和密碼之后轉(zhuǎn)交AuthenticationManager處理認(rèn)證。
BasicProcessingFilterEntryPoint
????? 將會向?yàn)g覽器發(fā)送一個(gè)RFC2616規(guī)定的basic認(rèn)證頭信息。瀏覽器在識別到認(rèn)證頭之后,會彈出對話框,收集用戶名和密碼。瀏覽器會將收集到的用戶名和密碼,打包成RFC標(biāo)準(zhǔn)的認(rèn)證響應(yīng),發(fā)送到服務(wù)器。在下一次請求到達(dá)BasicProcessingFilter時(shí),過濾器會提取認(rèn)證信息,并由AuthenticationManager處理認(rèn)證。
DigestProcessingFilterEntryPoint
????? 和Basic認(rèn)證類似,會向?yàn)g覽器發(fā)送一個(gè)RFC2616標(biāo)準(zhǔn)的digest認(rèn)證頭信息。瀏覽器在收到認(rèn)證頭后,會彈出對話框,
收集用戶名和密碼。瀏覽器會將收集到的用戶名和密碼,打包成RFC標(biāo)準(zhǔn)的認(rèn)證響應(yīng),發(fā)送到服務(wù)器。在下一次請求到達(dá)DigestProcessingFilter時(shí),過濾器會提取認(rèn)證信息,并由AuthenticationManager處理認(rèn)證。
PreAuthenticatedProcessingFilterEntryPoint
????? 簡單的向?yàn)g覽器發(fā)送一個(gè)錯(cuò)誤頁面,不做任何處理。
FilterChain Filter Order
The order that filters are defined in the chain is very important. Irrespective of which filters you are actually using, the order should be as follows:
ChannelProcessingFilter, because it might need to redirect to a different protocol
ConcurrentSessionFilter, because it doesn't use any SecurityContextHolder functionality but needs to update the SessionRegistry to reflect ongoing requests from the principal。檢查Session超時(shí),更新最近訪問信息。
SecurityContextPersistenceFilter, so a SecurityContext can be set up in the SecurityContextHolder at the beginning of a web request, and any changes to the SecurityContext can be copied to the HttpSession when the web request ends (ready for use with the next web request)。從Session中加載認(rèn)證信息,請求完成后,保存認(rèn)證信息。
Authentication processing mechanisms - UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter etc - so that theSecurityContextHolder can be modified to contain a valid Authentication request token
The SecurityContextHolderAwareRequestFilter, if you are using it to install a Spring Security aware HttpServletRequestWrapper into your servlet container
RememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, and the request presents a cookie that enables remember-me services to take place, a suitable remembered Authentication object will be put there
AnonymousAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, an anonymousAuthentication object will be put there
ExceptionTranslationFilter, to catch any Spring Security exceptions so that either an HTTP error response can be returned or an appropriateAuthenticationEntryPoint can be launched
FilterSecurityInterceptor, to protect web URIs and raise exceptions when access is denied
順序定義如下:
enum SecurityFilters {FIRST (Integer.MIN_VALUE),CHANNEL_FILTER,CONCURRENT_SESSION_FILTER,SECURITY_CONTEXT_FILTER,LOGOUT_FILTER,X509_FILTER,PRE_AUTH_FILTER,CAS_FILTER,FORM_LOGIN_FILTER,OPENID_FILTER,LOGIN_PAGE_FILTER,DIGEST_AUTH_FILTER,BASIC_AUTH_FILTER,REQUEST_CACHE_FILTER,SERVLET_API_SUPPORT_FILTER,JAAS_API_SUPPORT_FILTER,REMEMBER_ME_FILTER,ANONYMOUS_FILTER,SESSION_MANAGEMENT_FILTER,EXCEPTION_TRANSLATION_FILTER,FILTER_SECURITY_INTERCEPTOR,SWITCH_USER_FILTER,LAST (Integer.MAX_VALUE);private static final int INTERVAL = 100;private final int order;private SecurityFilters() {order = ordinal() * INTERVAL;}private SecurityFilters(int order) {this.order = order;}public int getOrder() {return order;} }一、The Security Namespace
1、<http … />
(1) 如果聲明了該元素,則會檢查是否創(chuàng)建了FilterChainProxy,如果沒有,則創(chuàng)建name為”springSecurityFilterChain”的FilterChainProxy Bean,并初始化FilterChainProxy.filterChains屬性。
(2) 對于每個(gè)http元素,會創(chuàng)建SecurityFilterChain對象。
a. 并會依次使用HttpConfigurationBuilder、AuthenticationConfigBuilder創(chuàng)建相關(guān)的Filter,如果子元素有custom-filter,則創(chuàng)建自定義Filter,將所有的filter添加到SecurityFilterChain對象中。
HttpConfigurationBuilder:
創(chuàng)建FilterSecurityInterceptor Bean。多個(gè)<interceptor-url …/>會為每個(gè)元素會注冊一個(gè)Map.Entry<RequestMatcher,SecurityConfig>。FilterSecurityInterceptor 默認(rèn)的access-decision-manager-ref 為AffirmativeBased實(shí)例,包含RoleVoter,AuthenticatedVoter兩個(gè)voter
b. 為SecurityFilterChain創(chuàng)建filterChainMatcher:
String requestMatcherRef = element.getAttribute(ATT_REQUEST_MATCHER_REF);String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN);if (StringUtils.hasText(requestMatcherRef)) {if (StringUtils.hasText(filterChainPattern)) {pc.getReaderContext().error("You can't define a pattern and a request-matcher-ref for the " +"same filter chain", pc.extractSource(element));}filterChainMatcher = new RuntimeBeanReference(requestMatcherRef);} else if (StringUtils.hasText(filterChainPattern)) {filterChainMatcher = MatcherType.fromElement(element).createMatcher(filterChainPattern, null);} else {filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class);}(3) 默認(rèn)情況下filter情況
a. ChannelProcessingFilter (CHANNEL_FILTER) ,根據(jù)intercept-url的requires-channel屬性,來建立matcher與channel關(guān)聯(lián)。如果有配置,則創(chuàng)建filter,否則不創(chuàng)建
b. ConcurrentSessionFilter( CONCURRENT_SESSION_FILTER),http元素配置了session-management子元素時(shí),且session-management配置了concurrency-control子元素時(shí)創(chuàng)建該filter
c. SecurityContextPersistenceFilter (SECURITY_CONTEXT_FILTER),如果sessionPolicy為stateless,則使用 NullSecurityContextRepository;否則使用HttpSessionSecurityContextRepository存儲認(rèn)證信息。默認(rèn)該filter是配置的
d. LogoutFilter (LOGOUT_FILTER),如果配置了子元素logout或者h(yuǎn)ttp設(shè)置了autoConfig為true,會創(chuàng)建該filter。當(dāng)用戶訪問logout url(如果不配置默認(rèn)為/j_spring_security_logout)時(shí),分別調(diào)用SecurityContextLogoutHandler、rememberMeServices(http子元素配置的remember-me元素)、CookieClearingLogoutHandler(配置了delete-cookie屬性)
e.J2eePreAuthenticatedProcessingFilter (PRE_AUTH_FILTER)。如果http配置了子元素jee
f. UsernamePasswordAuthenticationFilter (FORM_LOGIN_FILTER)。配置了form-login子元素或者authConfig為true時(shí)創(chuàng)建。默認(rèn)登錄處理的URL為/j_spring_security_check。如果請求的URI為/j_spring_security_check,則調(diào)用authenticationManager進(jìn)行認(rèn)證。
g. DefaultLoginPageGeneratingFilter (LOGIN_PAGE_FILTER),如果http配置了form-login,但form-login沒有提供login-page屬性,則會輸出spring實(shí)現(xiàn)的登陸頁面。
h. BasicAuthenticationFilter(BASIC_AUTH_FILTER),如果http配置了http-basic子元素或者autoConfig為true。從http header中讀取認(rèn)證信息,如果SecurityContextHolder已經(jīng)包含認(rèn)證完成的Authentication,則不需要進(jìn)行認(rèn)證。否則調(diào)用authenticationManager進(jìn)行認(rèn)證。
i. SecurityContextHolderAwareRequestFilter(SERVLET_API_FILTER)。如果配置了servlet-api-provision為false,則不會創(chuàng)建。否則創(chuàng)建該filter。該filter會使用SecurityContextHolderAwareRequestWrapper封裝request。
j. RememberMeAuthenticationFilter (REMEMBER_ME_FILTER)。如果配置了dataSource,則使用PersistentTokenBasedRememberMeServices,否則使用TokenBasedRememberMeServices。PersistentTokenBasedRememberMeServices從數(shù)據(jù)庫中,讀取remember me的認(rèn)證信息,AccountStatusUserDetailsChecker檢查賬戶狀態(tài);調(diào)用authenticationManager進(jìn)行認(rèn)證,認(rèn)證通過后,保存到SecurityContextHolder中。
k. AnonymousAuthenticationFilter (ANONYMOUS_FILTER)。 如果http配置了anonymous 子元素且enable為false,則不會創(chuàng)建該元素。如果SecurityContextHolder中沒有Authentication對象,則創(chuàng)建AnonymousAuthenticationToken,保存到SecurityContextHolder中。
l. SessionManagementFilter (SESSION_MANAGEMENT_FILTER) 。 http元素配置了session-management子元素時(shí)。且session-management配置了concurrency-control子元素;或session-fixation-protection(默認(rèn)為migrateSession)不為none;或配置了invalid-session-url;或配置了session-authentication-strategy-ref。concurrency-control使用ConcurrentSessionControlStrategy處理認(rèn)證成功后的操作,除了SessionFixationProtectionStrategy的功能,另外會注冊新生成的session到sessionRegister中。其他使用SessionFixationProtectionStrategy,每次創(chuàng)建新的session,復(fù)制數(shù)據(jù),用于更改sessionId,這樣保護(hù)sessionId被惡意使用:
/*** Called when a user is newly authenticated.* <p>* If a session already exists, and matches the session Id from the client, a new session will be created, and the* session attributes copied to it (if {@code migrateSessionAttributes} is set).* If the client's requested session Id is invalid, nothing will be done, since there is no need to change the* session Id if it doesn't match the current session.* <p>* If there is no session, no action is taken unless the {@code alwaysCreateSession} property is set, in which* case a session will be created if one doesn't already exist.*/public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {boolean hadSessionAlready = request.getSession(false) != null;if (!hadSessionAlready && !alwaysCreateSession) {// Session fixation isn't a problem if there's no sessionreturn;}// Create new session if necessaryHttpSession session = request.getSession();if (hadSessionAlready && request.isRequestedSessionIdValid()) {// We need to migrate to a new sessionString originalSessionId = session.getId();if (logger.isDebugEnabled()) {logger.debug("Invalidating session with Id '" + originalSessionId +"' " + (migrateSessionAttributes ?"and" : "without") + " migrating attributes.");}Map<String, Object> attributesToMigrate = extractAttributes(session);session.invalidate();session = request.getSession(true); // we now have a new sessionif (logger.isDebugEnabled()) {logger.debug("Started new session: " + session.getId());}if (originalSessionId.equals(session.getId())) {logger.warn("Your servlet container did not change the session ID when a new session was created. You will" +" not be adequately protected against session-fixation attacks");}transferAttributes(attributesToMigrate, session);onSessionChange(originalSessionId, session, authentication);}}
m. ExceptionTranslationFilter (EXCEPTION_TRANSLATION_FILTER),從exception cause chain中,提取AuthenticationException或AccessDeniedException。
n. FilterSecurityInterceptor? (FILTER_INTECEPTOR_FILTER) 。最重要的filter,用戶的認(rèn)證就是改filter實(shí)現(xiàn)的。進(jìn)行認(rèn)證與權(quán)限檢查:
Authentication authenticated = authenticateIfRequired();// Attempt authorizationtry {this.accessDecisionManager.decide(authenticated, object, attributes);}catch (AccessDeniedException accessDeniedException) {publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));throw accessDeniedException;}2、authentication-manager
默認(rèn)會創(chuàng)建ProviderManager Bean,包含了配置的AuthenticationProvider的Bean實(shí)例
3、authentication-provider
默認(rèn)創(chuàng)建DaoAuthenticationProvider bean
DaoAuthenticationProvider中,additionalAuthenticationChecks包含了對密碼的校驗(yàn)
protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {Object salt = null;if (this.saltSource != null) {salt = this.saltSource.getSalt(userDetails);}if (authentication.getCredentials() == null) {logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);}String presentedPassword = authentication.getCredentials().toString();if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);}}?
二、Spring Security OAuth2
Spring Security OAuth2 包含如下標(biāo)簽:
registerBeanDefinitionParser("authorization-server", new AuthorizationServerBeanDefinitionParser()); registerBeanDefinitionParser("resource-server", new ResourceServerBeanDefinitionParser()); registerBeanDefinitionParser("client-details-service", new ClientDetailsServiceBeanDefinitionParser()); registerBeanDefinitionParser("client", new ClientBeanDefinitionParser()); registerBeanDefinitionParser("resource", new ResourceBeanDefinitionParser()); registerBeanDefinitionParser("rest-template", new RestTemplateBeanDefinitionParser()); registerBeanDefinitionParser("expression-handler", new ExpressionHandlerBeanDefinitionParser()); registerBeanDefinitionParser("web-expression-handler", new WebExpressionHandlerBeanDefinitionParser());Spring Security OAuth2流程
(1) 在Spring 配置文件中,為 API資源添加http元素,該元素需要添加resourceServerFilter,同時(shí)必須設(shè)置create-session="never":
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />resourceServerFilter添加了OAuth2AuthenticationProcessingFilter,并使用OAuth2AuthenticationManager進(jìn)行認(rèn)證過程,如果resourceServerFilter不指定entry-point-ref屬性,則默認(rèn)使用OAuth2AuthenticationEntryPoint處理結(jié)果
OAuth2AuthenticationProcessingFilter負(fù)責(zé)從request中提取token信息,生成PreAuthenticatedAuthenticationToken,調(diào)用OAuth2AuthenticationManager.authentication進(jìn)行校驗(yàn)。
(2) Client訪問資源時(shí),resourceServer會進(jìn)行OAuth 2.0 規(guī)范中要求的認(rèn)證。如果token為空,則調(diào)用接下來的filter,不會在SecurityContextHolder中添加任何Authentication。如果使用token沒有查詢到對應(yīng)的OAuth2Authentication,則拋出InvalidTokenException;接著調(diào)用tokenService中的loadAuthentication(token),并檢查resourceId是否在允許的resourceIds中。
client與user之間的授權(quán)關(guān)聯(lián)是使用OAuth2Authentication表示的,其中分別包含了clientAuthentication(AuthorizationRequest)與userAuthentication(Authentication)。是通過指定的resource-server中配置的authentication-manager進(jìn)行校驗(yàn)的。
(3) 如果沒有關(guān)聯(lián)的accessToken,不會生成任何Authentication對象,則在FilterSecurityInterceptor 中,則會拋出AuthenticationCredentialsNotFoundException,通過配置的entry-point-ref,返回錯(cuò)誤信息。
(4) Client收到錯(cuò)誤信息后,會請求/oauth/authorize,獲取authorization code。獲取成功后,繼續(xù)請求/oauth/token獲取accessToken
?
(5) 訪問/oauth/token時(shí),需要驗(yàn)證client,使用ClientCredentialsTokenEndpointFilter。在經(jīng)過過濾器后,會到處理的Tokenpoint。
a.ClientCredentialsTokenEndpointFilter什么作用?
校驗(yàn)client_id與client_secret,并生成UsernamePasswordAuthenticationToken,并調(diào)用SecurityContextHolder.getContext().setAuthentication(authResult);保存認(rèn)證結(jié)果
b. 哪兒校驗(yàn)grant code?
在AuthorizationCodeTokenGranter類中:
@Overrideprotected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest authorizationRequest) {Map<String, String> parameters = authorizationRequest.getAuthorizationParameters();String authorizationCode = parameters.get("code");String redirectUri = parameters.get("redirect_uri");if (authorizationCode == null) {throw new OAuth2Exception("An authorization code must be supplied.");}AuthorizationRequestHolder storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);if (storedAuth == null) {throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);}AuthorizationRequest pendingAuthorizationRequest = storedAuth.getAuthenticationRequest();if (pendingAuthorizationRequest.getRedirectUri() != null&& !pendingAuthorizationRequest.getRedirectUri().equals(redirectUri)) {throw new RedirectMismatchException("Redirect URI mismatch.");}String pendingClientId = pendingAuthorizationRequest.getClientId();String clientId = authorizationRequest.getClientId();if (clientId != null && !clientId.equals(pendingClientId)) {// just a sanity check.throw new InvalidClientException("Client ID mismatch");}// Secret is not required in the authorization request, so it won't be available// in the pendingAuthorizationRequest. We do want to check that a secret is provided// in the token request, but that happens elsewhere.Map<String, String> combinedParameters = new HashMap<String, String>(storedAuth.getAuthenticationRequest().getAuthorizationParameters());// Combine the parameters adding the new ones last so they override if there are any clashescombinedParameters.putAll(parameters);// Similarly scopes are not required in the token request, so we don't make a comparison here, just// enforce validity through the AuthorizationRequestFactory.DefaultAuthorizationRequest outgoingRequest = new DefaultAuthorizationRequest(pendingAuthorizationRequest);outgoingRequest.setAuthorizationParameters(combinedParameters);Authentication userAuth = storedAuth.getUserAuthentication();return new OAuth2Authentication(outgoingRequest, userAuth);}?
?
1、authorization-server
該元素負(fù)責(zé)創(chuàng)建AuthorizationEndpoint與TokenEndpoint
?
首先設(shè)置authorizationEndpointUrl與tokenEndpointUrl,如果設(shè)置了這兩個(gè)屬性,則注冊EndpointValidationFilter Bean,該Bean會分別將設(shè)置的兩個(gè)URL重定向到AuthorizationEndpoint與TokenEndpoint。如果指定了這兩個(gè)屬性,則需要在web.xml中配置名稱為oauth2EndpointUrlFilter的DelegatingFilterProxy Filter
AuthorizationEndpoint
該類聲明如下
@FrameworkEndpoint @SessionAttributes("authorizationRequest") @RequestMapping(value = "/oauth/authorize") public class AuthorizationEndpoint extends AbstractEndpoint implements InitializingBean {服務(wù)于/oauth/authorize
默認(rèn)的TokenGranter為CompositeTokenGranter以下的每個(gè)元素會為AuthorizationEndpoint添加一種類型的TokenGranter
<oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices"user-approval-handler-ref="userApprovalHandler"><oauth:authorization-code /> <!--AuthorizationCodeTokenGranter 可以指定的屬性有:disabled: Boolean value specifying whether the authorization code mechanism is disabled. This effectively disables the authorization code grant mechanism.services-ref: The reference to the bean that defines the authorization code services (instance of org.springframework.security.oauth2.provider.code.AuthorizationCodeServices)user-approval-page: The URL of the page that handles the user approval form.approval-parameter-name: The name of the form parameter that is used to indicate user approval of the client authentication request.--><oauth:implicit /><oauth:refresh-token /><oauth:client-credentials /><oauth:password /></oauth:authorization-server>?
?
2、resource-server
定義OAuth2AuthenticationProcessingFilter,使用指定的authentication-manager進(jìn)行校驗(yàn)。可以指定resource-id屬性,用來標(biāo)識資源ID,對資源進(jìn)行分類。在用戶授權(quán)時(shí),會記錄資源ID,標(biāo)識此次授權(quán)可以訪問那些資源。
?
?
----------------------------------------------------------------
學(xué)習(xí)部分:
1、ListBeanFactory
使用BeanFactory機(jī)制,把bean作為List注入其他Bean
BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean, BeanIds.FILTER_CHAINS));2、HandlerMapping
在DispatcherServlet中,initHandlerMappings會讀取applicationContext中定義的所有的HandlerMapping及其子類型的Bean,注冊進(jìn)來,從而可以根據(jù)request獲得HandlerMapping。這樣就可以任意擴(kuò)展HandlerMapping,OAuth機(jī)制就擴(kuò)展了HandlerMapping (FrameworkEndpointHandlerMapping),通過@FrameworkEndpoint來標(biāo)識Bean為Handler
轉(zhuǎn)載于:https://my.oschina.net/guoxf1/blog/73121
總結(jié)
以上是生活随笔為你收集整理的Spring Web Application Security的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 加息意味什么
- 下一篇: awstats 安装与配置