首先提供一個總體的配置文件,再分析:css
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:s="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <!-- 不要過濾圖片等靜態資源,其中**表明能夠跨越目錄,*不能夠跨越目錄。 --> <s:http pattern="/**/*.jpg" security="none"/> <s:http pattern="/**/*.png" security="none"/> <s:http pattern="/**/*.gif" security="none"/> <s:http pattern="/**/*.css" security="none"/> <s:http pattern="/**/*.js" security="none"/> <s:http pattern="/login.jsp" security="none"/> <s:http pattern="/user/user!login.action" security="none"/> <s:http auto-config="false" use-expressions="true" entry-point-ref="loginUrlEntryPoint" access-denied-page="/error.jsp"> <!-- 處理用戶登錄時,用戶名和密碼判斷的filter(重要) --> <s:custom-filter position="FORM_LOGIN_FILTER" ref="loginFilter"/> <!-- 權限判斷、處理的filter鏈 --> <s:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myFilter"/> <!-- 檢測失效的sessionId,超時時定位到另一個URL --> <s:session-management invalid-session-url="/sessionTimeout.jsp" /> <!-- 登出 --> <s:logout logout-url="/j_spring_security_logout" invalidate-session="true" logout-success-url="/index.jsp" /> </s:http> <!-- 一個自定義的filter,必須包含authenticationManager, accessDecisionManager,securityMetadataSource三個屬性。 --> <bean id="myFilter" class="com.dtds.security.MyFilterSecurityInterceptor"> <!-- 資源數據源 --> <property name="securityMetadataSource" ref="mySecurityMetadataSource" /> <!-- 認證管理器 --> <property name="authenticationManager" ref="authenticationManager" /> <!-- 訪問決策器 --> <property name="accessDecisionManager" ref="accessDecisionManager" /> </bean> <!-- 資源源數據定義,將全部的資源和權限對應關係創建起來,即定義某一資源能夠被哪些角色去訪問。 --> <bean id="mySecurityMetadataSource" class="com.dtds.security.InvocationSecurityMetadataSourceServiceImpl"> <property name="userService" ref="userService"/> </bean> <!-- 認證配置, 使用userDetailsService提供的用戶信息 --> <s:authentication-manager alias="authenticationManager"> <s:authentication-provider ref="authenticationProvider"/> </s:authentication-manager> <!-- 能夠重寫 --> <!-- <bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> --> <bean id="authenticationProvider" class="com.dtds.security.SecurityAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService" /> <property name="hideUserNotFoundExceptions" value="false" /> <property name="passwordEncoder" ref="passwordEncoder"/> </bean> <!-- 登錄時查詢用戶、並加載用戶所擁有的權限等 --> <bean id="userDetailsService" class="com.dtds.security.UserDetailServiceImpl"> <property name="userService" ref="userService"/> </bean> <!-- 用戶的密碼加密或解密 --> <bean id="passwordEncoder" class="com.dtds.security.MyPasswordEncoder" /> <!-- 訪問決策器,決定某個用戶具備的角色,是否有足夠的權限去訪問某個資源。 --> <bean id="accessDecisionManager" class="com.dtds.security.MyAccessDecisionManager"/> <!-- 重寫登錄驗證 --> <bean id="loginFilter" class="com.dtds.security.MyUsernamePasswordAuthenticationFilter"> <!-- 認證管理 --> <property name="authenticationManager" ref="authenticationManager"></property> <!-- 驗證成功後的跳轉 --> <property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></property> <!-- 處理登錄的Action --> <property name="filterProcessesUrl" value="/j_spring_security_check"></property> <!-- 驗證失敗後的處理 --> <property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></property> </bean> <!-- 登錄成功 --> <!-- 這裏要實現自定義 --> <!-- <bean id="loginLogAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> --> <bean id="loginLogAuthenticationSuccessHandler" class="com.dtds.security.LoginAuthenticationSuccessHandler"> <property name="alwaysUseDefaultTargetUrl" value="true"/> <!-- 登錄成功時的頁面,這裏設定的頁面也會被spring security攔截 --> <!-- <property name="defaultTargetUrl" value="/content/select.jsp" /> <property name="targetUrlParameter" value="redirectTo" /> --> <property name="defaultTargetUrl" value="/user/user!login.action" /> </bean> <!-- 登錄失敗 --> <bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <!-- 能夠配置相應的跳轉方式。屬性forwardToDestination爲true採用forward false爲sendRedirect --> <property name="defaultFailureUrl" value="/login.jsp"></property> </bean> <!-- 未登陸的切入點 --> <bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <property name="loginFormUrl" value="/login.jsp"></property> </bean> <!-- Spring Security 認證切入點 --> <bean id="loginUrlEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <property name="loginFormUrl" value="/login.jsp"></property> </bean> </beans>
首先看到,用戶登錄的時候嗎請求會被這個配置攔截:java
<!-- 處理用戶登錄時,用戶名和密碼判斷的filter(重要) --> <s:custom-filter position="FORM_LOGIN_FILTER" ref="loginFilter"/>
能夠看到這個loginFilter是web
<!-- 重寫登錄驗證 --> <bean id="loginFilter" class="com.dtds.security.MyUsernamePasswordAuthenticationFilter">
它包含了四個屬性,首先看第一個spring
authenticationManager數據庫
它最終指向的是authenticationProvider,即一個權限判斷的提供者,它又包含了三個屬性,主要關注的是userDetailsService和passwordEncoderexpress
即用戶名和密碼的校驗,後面跟進代碼會再次回到這兩個屬性上~!(其實用戶名和密碼都正確的話,理論上就是經過了登錄權限的校驗)緩存
看看這個過濾器,它主要的功能是由方法attemptAuthentication提供,最終的目的返回一個權限對象Authentication,在該方法中:session
主要是這句:app
Authentication authentication = super.attemptAuthentication(request, response);jsp
因爲
MyUsernamePasswordAuthenticationFilter
是繼承自UsernamePasswordAuthenticationFilter過濾器,那麼這裏使用了super,即實際執行的是父類的方法,源碼以下:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
該方法的入參就是request和response,接着執行,就獲取到頁面輸入的用戶名和密碼,因爲這裏返回的是一個權限對象,能夠看到最終返回的便是這句:
this.getAuthenticationManager().authenticate(authRequest);
在返回前,並未看到SS對用戶名和密碼作任何驗證,那麼驗證確定在上述這句代碼中,跟進去,進入到ProviderManager的authenticate方法,源碼以下:
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } } catch (AccountStatusException e) { prepareException(e, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status throw e; } catch (AuthenticationException e) { lastException = e; } } if (result == null && parent != null) { // Allow the parent to try. try { result = parent.authenticate(authentication); } catch (ProviderNotFoundException e) { // ignore as we will throw below if no other exception occurred prior to calling parent and the parent // may throw ProviderNotFound even though a provider in the child already handled the request } catch (AuthenticationException e) { lastException = e; } } if (result != null) { if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data from authentication ((CredentialsContainer)result).eraseCredentials(); } eventPublisher.publishAuthenticationSuccess(result); return result; } // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) { lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound", new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}")); } prepareException(lastException, authentication); throw lastException; }
執行到這句:
result = provider.authenticate(authentication);
繼續跟進,進入:AbstractUserDetailsAuthenticationProvider的authenticate方法,源碼:
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // Determine username String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { logger.debug("User '" + username + "' not found"); if (hideUserNotFoundExceptions) { throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { throw notFound; } } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } try { preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) { // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } else { throw exception; } } postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (forcePrincipalAsString) { principalToReturn = user.getUsername(); } return createSuccessAuthentication(principalToReturn, authentication, user); }
這裏看出ss首先去緩存中獲取user對象,若是沒有獲取到,則繼續執行,到這句
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
跟進,則進入:
DaoAuthenticationProvider的retrieveUser方法,源碼:
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { loadedUser = this.getUserDetailsService().loadUserByUsername(username); } catch (UsernameNotFoundException notFound) { if(authentication.getCredentials() != null) { String presentedPassword = authentication.getCredentials().toString(); passwordEncoder.isPasswordValid(userNotFoundEncodedPassword, presentedPassword, null); } throw notFound; } catch (Exception repositoryProblem) { throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); } if (loadedUser == null) { throw new AuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; }
這裏關鍵代碼:
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
即便用UserDetailService的loadUserByUsername方法,而UserDetailService是一個接口,該接口正是須要咱們本身實現的關鍵點,它只有惟一的一個方法loadUserByUsername,該方法須要咱們本身實現,主要就是經過用戶名從數據庫中獲取用戶並判斷
跟進這麼多,最終獲得如同上面說的結果:回到userDetailsService和passwordEncoder
首先是userDetailsService
它主要是獲取用戶及權限,該方法返回的是一個Spring定義的User,按要求封裝便可,能夠本身建立一個MyUser類,繼承該User或者直接返回便可
返回以後,繼續在DaoAuthenticationProvider中執行,執行完畢以後繼續在AbstractUserDetailsAuthenticationProvider中執行
執行到這句:
preAuthenticationChecks.check(user);
源碼:
private class DefaultPreAuthenticationChecks implements UserDetailsChecker { public void check(UserDetails user) { if (!user.isAccountNonLocked()) { logger.debug("User account is locked"); throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"), user); } if (!user.isEnabled()) { logger.debug("User account is disabled"); throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"), user); } if (!user.isAccountNonExpired()) { logger.debug("User account is expired"); throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"), user); } } }
這是一個內部類,能夠看到是判斷user的一些其餘屬性
接着執行下面一句:
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
能夠看到這個方法最終會執行咱們本身繼承的類
SecurityAuthenticationProvider
方法:
@Override protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { logger.info("【3】:SecurityAuthenticationProvider"); //執行到這句話,下面就會執行MyPasswordEncoder super.additionalAuthenticationChecks(userDetails, authentication); WebAuthenticationDetails webDetail = (WebAuthenticationDetails) authentication.getDetails(); try { this.logger.warn("開始寫currentLogin表"); long start = System.currentTimeMillis(); //this.POService.additionalLoginCheck(peopleInfo); long end = System.currentTimeMillis(); this.logger.warn("寫currentLogin表完畢,用時:" + (end - start) + "ms"); } catch (Exception e) { this.logger.error("寫currentLogin表異常", e); throw new AuthenticationServiceException(e.getMessage()); } }
這裏能夠添加一些其餘的應用
該方法使用super又調用了父類的方法
DaoAuthenticationProvider
方法
@SuppressWarnings("deprecation") 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); } }
能夠看到繼續是一些驗證代碼
其中這句:
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); }
這裏能夠看到倒數第二個MyPassWordEncoder,便是咱們本身實現的密碼驗證類,以下:
@Override public boolean isPasswordValid(String npwd, String opwd, Object arg2) { System.out.println("3:MyPasswordEncoder.isPasswordValid().......validating password.........."); System.out.println("輸入的密碼:" + opwd + ",存儲的密碼" + npwd); if(npwd.equals(opwd)){ System.out.println("密碼正確"); } else { System.out.println("密碼錯誤"); } return npwd.equals(opwd); }
這裏只是簡單的驗證而已,能夠添加其餘的
自此,DaoAuthenticationProvider已經執行完畢,代碼繼續回到:SecurityAuthenticationProvider中,執行完畢,繼續執行AbstractUserDetailsAuthenticationProvider,知道執行最後一句返回代碼:
return createSuccessAuthentication(principalToReturn, authentication, user);
它是本類裏面的方法,跟進:
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { // Ensure we return the original credentials the user supplied, // so subsequent attempts are successful even with encoded passwords. // Also ensure we return the original getDetails(), so that future // authentication events after cache expiry contain the details UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; }
從名字能夠看出,如今已是符合權限的要求了create-success-Authentication
看這句
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities()));
這裏的user就是咱們自定義的繼承自spring User的MyUser,因爲重寫了
public Collection<GrantedAuthority> getAuthorities() { List list = new ArrayList(); for (String sid : this.roleInfos) { list.add(new GrantedAuthorityImpl(sid)); } return list; }
因此此時執行的是咱們本身的getAuthrities(),注意這裏返回的result
其中的principal存儲的是User,因此其餘方法調用getPrincipal()獲得的就是User
自此AbstractUserDetailsAuthenticationProvider執行完畢
繼續執行ProviderManager#authenticate()
因爲result不爲null,則執行:
if (result != null) {
copyDetails(authentication, result);
break;
}
繼續執行,則ProviderManager執行完畢,接着執行UsernamePasswordAuthenticationFilter,返回authentication,執行完畢。
再執行MyUsernamePasswordAuthenticationFilter
獲得了authentication對象並返回,MyUsernamePasswordAuthenticationFilter執行完畢。
自此這個過濾器AbstractAuthenticationProcessingFilter執行到doFilter,獲得正確的權限對象,最後執行:successfulAuthentication(request, response, chain, authResult);
即
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException{ successfulAuthentication(request, response, authResult); }
繼續執行
@Deprecated protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult); } SecurityContextHolder.getContext().setAuthentication(authResult); rememberMeServices.loginSuccess(request, response, authResult); // Fire event if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } successHandler.onAuthenticationSuccess(request, response, authResult); }
這裏能夠看出,將權限信息set到了SecurityContextHolder中,接着執行rememberMeService。。
最後一句:
successHandler.onAuthenticationSuccess(request, response, authResult);
因爲上述的權限認證已經經過,因此這裏就調用successHandler,即登陸成功後的處理類,這個類已經被咱們實現了,因此就執行LoginAuthenticationSuccessHandler的onAuthenticationSuccess方法,在這裏面就是執行咱們本身的流程,其中最重要的就是將用戶對象set到session中,供整個web使用
後面就返回了,繼續執行過濾器鏈