javascript
Spring Security源码解析(一)——认证和鉴权
目錄
認證過程
AuthenticationManager
Authentication
AbstractAuthenticationToken
UsernamePasswordAuthenticationToken
?RememberMeAuthenticationToken
?AbstractOAuth2TokenAuthenticationToken
ProviderManager
AuthenticationProvider
DaoAuthenticationProvider
AbstractUserDetailsAuthenticationProvider?
UserDetails
UserDetailsChecker?
DaoAuthenticationProvider屬性
DaoAuthenticationProvider.retrieveUser()
?createSuccessAuthentication()
additionalAuthenticationChecks()
UserDetailsService
?JdbcUserDetailsManager
InMemoryUserDetailsManager
總結
引入
AuthenticationManagerBuilder
performBuild()
Filters
UsernamePasswordAuthenticationFilter
AbstractAuthenticationProcessingFilter
attemptAuthentication
AnonymousAuthenticationFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
AbstractSecurityInterceptor
AccessDecisionManager?
?AbstractAccessDecisionManager?
AccessDecisionVoter
?WebExpressionVoter
RoleVoter?
AuthenticatedVoter
AffirmativeBased
ConsensusBased
UnanimousBased
密碼驗證時序圖
Spring Security解決用戶認證(Authentication)和用戶授權(Authorization)2個問題。
源碼地址:https://github.com/spring-projects/spring-security/tree/5.2.1.RELEASE
認證過程
AuthenticationManager
該對象提供了認證方法的入口,接收一個Authentiaton對象作為參數;
public interface AuthenticationManager {Authentication authenticate(Authentication authentication) throws AuthenticationException; }Authentication
Authentication封裝了驗證請求信息。
public interface Authentication extends Principal, Serializable {//獲取 授權信息Collection<? extends GrantedAuthority> getAuthorities();//憑據Object getCredentials();//用戶信息Object getDetails();//主體,可以理解驗證時的用戶名。Object getPrincipal();//是否被認證。boolean isAuthenticated();//認證結果設置。void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }?授權信息:
public interface GrantedAuthority extends Serializable {String getAuthority(); }?根據不同的認證類型,定義了多種實現:
AbstractAuthenticationToken
private final Collection<GrantedAuthority> authorities;private Object details;private boolean authenticated = false;UsernamePasswordAuthenticationToken
private final Object principal;private Object credentials;?RememberMeAuthenticationToken
private final Object principal;private final int keyHash;?AbstractOAuth2TokenAuthenticationToken
private Object principal;private Object credentials;private T token;ProviderManager
它是?AuthenticationManager?的一個實現類,提供了基本的認證邏輯和方法;它包含了一個?List<AuthenticationProvider>?對象,通過 AuthenticationProvider 接口來擴展出不同的認證提供者(當Spring Security默認提供的實現類不能滿足需求的時候可以擴展AuthenticationProvider?覆蓋supports(Class<?> authentication)方法);
?
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher(); //驗證者 列表private List<AuthenticationProvider> providers = Collections.emptyList();protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private AuthenticationManager parent;private boolean eraseCredentialsAfterAuthentication = true;AuthenticationManager?通過?authenticate(Authentication)?方法對其進行驗證;AuthenticationProvider實現類用來支撐對?Authentication?對象的驗證動作;UsernamePasswordAuthenticationToken實現了?Authentication主要是將用戶輸入的用戶名和密碼進行封裝,并供給?AuthenticationManager?進行驗證;驗證完成以后將返回一個認證成功的?Authentication?對象;
public Authentication authenticate(Authentication authentication)throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;AuthenticationException parentException = null;Authentication result = null;Authentication parentResult = null;boolean debug = logger.isDebugEnabled();//循環每個AuthenticationProvider for (AuthenticationProvider provider : getProviders()) {//如果不支持Authentication,則繼續下一個if (!provider.supports(toTest)) {continue;}if (debug) {logger.debug("Authentication attempt using "+ provider.getClass().getName());}try {//驗證result = provider.authenticate(authentication);//如果驗證不為null,則表示驗證成功。if (result != null) {copyDetails(authentication, result);break;}}catch (AccountStatusException | InternalAuthenticationServiceException e) {prepareException(e, authentication);// SEC-546: Avoid polling additional providers if auth failure is due to// invalid account statusthrow e;} catch (AuthenticationException e) {lastException = e;}}//如果所有provider都不支持驗證,則使用parent驗證。if (result == null && parent != null) {// Allow the parent to try.try {result = parentResult = 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 = parentException = e;}}//擦除敏感信息。if (result != null) {if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {((CredentialsContainer) result).eraseCredentials();}// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published itif (parentResult == null) {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}"));}// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published itif (parentException == null) {prepareException(lastException, authentication);}throw lastException;}AuthenticationProvider
AuthenticationProvider提供用戶認證。
public interface AuthenticationProvider {Authentication authenticate(Authentication authentication) throws AuthenticationException;boolean supports(Class<?> authentication); }?
?Spring Security提供了以下認證方式:
- 用戶名,密碼
- LDAP
- 匿名方式
- RememberMe
- JWT
- OAuth2
- ... ...
DaoAuthenticationProvider
DaoAuthenticationProvider支持Authentication為UsernamePasswordAuthenticationToken的認證。
//AbstractUserDetailsAuthenticationProvider方法public boolean supports(Class<?> authentication) {return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));}AbstractUserDetailsAuthenticationProvider?
AbstractUserDetailsAuthenticationProvider為DaoAuthenticationProvider提供了基本的認證方法。
public abstract class AbstractUserDetailsAuthenticationProvider implementsAuthenticationProvider, InitializingBean, MessageSourceAware {protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private UserCache userCache = new NullUserCache();private boolean forcePrincipalAsString = false;protected boolean hideUserNotFoundExceptions = true;private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); }?認證過程如下:
public Authentication authenticate(Authentication authentication)throws AuthenticationException { //T0:Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));//T1:String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName(); //T2:boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username); //T3:if (user == null) {cacheWasUsed = false;try { //T4: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 { //T5:preAuthenticationChecks.check(user); //T6: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;}} //T7:postAuthenticationChecks.check(user);if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (forcePrincipalAsString) {principalToReturn = user.getUsername();} //T8:return createSuccessAuthentication(principalToReturn, authentication, user);}T0:判斷是否是UsernamePasswordAuthenticationToken。
T1:獲取userName,默認:NONE_PROVIDED
T2:從緩存中獲取用戶信息。
T3:如果緩存中沒有用戶信息,則通過retrieveUser獲取(由子類實現)。
T4:通過retrieveUser獲取用戶信息。
T5:驗證前check。
T6:附加check(由子類實現)。
T7:驗證
T8:驗證成功,創建Authentication。重新 new 了一個?UsernamePasswordAuthenticationToken,因為到這里認證已經通過了,所以將 authorities 注入進去,并設置 authenticated 為 true,即不再需要認證。
?
protected abstract UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException;protected abstract void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException;UserDetails
UserDetails封裝認證用戶的詳細信息。
public interface UserDetails extends Serializable {//權限Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername(); //是否賬戶過期boolean isAccountNonExpired(); //是否lockedboolean isAccountNonLocked(); //憑據過期boolean isCredentialsNonExpired(); //賬戶是否可用boolean isEnabled(); }UserDetailsChecker?
驗證用戶。
- AccountStatusUserDetailsChecker
驗證用戶狀態。是否可用,過期,鎖定,憑據是否過期
- DefaultPostAuthenticationChecks
驗證用戶狀態。憑據是否過期。
- DefaultPreAuthenticationChecks
驗證用戶狀態。是否可用,過期,鎖定
DaoAuthenticationProvider屬性
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";private PasswordEncoder passwordEncoder;private volatile String userNotFoundEncodedPassword;private UserDetailsService userDetailsService;private UserDetailsPasswordService userDetailsPasswordService;DaoAuthenticationProvider.retrieveUser()
protected final UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try { //T1:UserDetailsService加載用戶信息UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}catch (UsernameNotFoundException ex) {mitigateAgainstTimingAttack(authentication);throw ex;}catch (InternalAuthenticationServiceException ex) {throw ex;}catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);}}?createSuccessAuthentication()
驗證后對密碼進行加密。
@Overrideprotected Authentication createSuccessAuthentication(Object principal,Authentication authentication, UserDetails user) {boolean upgradeEncoding = this.userDetailsPasswordService != null&& this.passwordEncoder.upgradeEncoding(user.getPassword());if (upgradeEncoding) {String presentedPassword = authentication.getCredentials().toString();String newPassword = this.passwordEncoder.encode(presentedPassword);user = this.userDetailsPasswordService.updatePassword(user, newPassword);}return super.createSuccessAuthentication(principal, authentication, user);}additionalAuthenticationChecks()
通過用戶名,密碼進行驗證。
@SuppressWarnings("deprecation")protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {if (authentication.getCredentials() == null) {logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}String presentedPassword = authentication.getCredentials().toString();if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}}UserDetailsService
通過用戶名獲取用戶信息。
public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }?JdbcUserDetailsManager
基于JDBC訪問UserDetail。默認需要包括以下表結構:
users (username, password, enabled)
authorities (username, authority)?
group_authorities (group_id, authority)
groups (id,group_name)
group_members (group_id, username)
InMemoryUserDetailsManager
基于內存訪問UserDetails
?
總結
UserDetailsService接口作為橋梁,是DaoAuthenticationProvier與特定用戶信息來源進行解耦的地方,UserDetailsService由UserDetails和UserDetailsManager所構成;UserDetails和UserDetailsManager各司其責,一個是對基本用戶信息進行封裝,一個是對基本用戶信息進行管理;
特別注意,UserDetailsService、UserDetails以及UserDetailsManager都是可被用戶自定義的擴展點,我們可以繼承這些接口提供自己的讀取用戶來源和管理用戶的方法,比如我們可以自己實現一個 與特定 ORM 框架,比如 Mybatis 或者 Hibernate,相關的UserDetailsService和UserDetailsManager;
引入
@Import({ WebSecurityConfiguration.class,SpringWebMvcImportSelector.class,OAuth2ImportSelector.class }) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity {boolean debug() default false; }?
@Import(AuthenticationConfiguration.class) @Configuration public @interface EnableGlobalAuthentication { }?
public class AuthenticationConfiguration {private ApplicationContext applicationContext;private AuthenticationManager authenticationManager;private boolean authenticationManagerInitialized;private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections.emptyList();private ObjectPostProcessor<Object> objectPostProcessor; }?AuthenticationManagerBuilder的實現類DefaultPasswordEncoderAuthenticationManagerBuilder?
@Beanpublic AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);if (authenticationEventPublisher != null) {result.authenticationEventPublisher(authenticationEventPublisher);}return result;}AuthenticationManagerBuilder
private AuthenticationManager parentAuthenticationManager;private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();private UserDetailsService defaultUserDetailsService;private Boolean eraseCredentials;private AuthenticationEventPublisher eventPublisher;performBuild()
構造ProviderManager
protected ProviderManager performBuild() throws Exception {if (!isConfigured()) {logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");return null;}ProviderManager providerManager = new ProviderManager(authenticationProviders,parentAuthenticationManager);if (eraseCredentials != null) {providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);}if (eventPublisher != null) {providerManager.setAuthenticationEventPublisher(eventPublisher);}providerManager = postProcess(providerManager);return providerManager;}Filters
登錄過程中會提供很多Filter用于處理登錄請求。認證相關的主要包含后面描述的幾個
UsernamePasswordAuthenticationFilter
基于用戶名和密碼的認證Filter。
AbstractAuthenticationProcessingFilter
屬性
protected ApplicationEventPublisher eventPublisher;protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource(); //private AuthenticationManager authenticationManager;protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); //RememberMeServices private RememberMeServices rememberMeServices = new NullRememberMeServices(); //請求匹配器private RequestMatcher requiresAuthenticationRequestMatcher;private boolean continueChainBeforeSuccessfulAuthentication = false;private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();private boolean allowSessionCreation = true; //認證成功handlerprivate AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); //認證失敗handlerprivate AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();doFilter()
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res; //如果請求不匹配Filter,則使用原始chain。if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}if (logger.isDebugEnabled()) {logger.debug("Request is to process authentication");}Authentication authResult;try { //嘗試認證authResult = attemptAuthentication(request, response); //結果為null,表示認證失敗。if (authResult == null) {// return immediately as subclass has indicated that it hasn't completed// authenticationreturn;} //sessionStrategy.onAuthentication(authResult, request, response);}catch (InternalAuthenticationServiceException failed) {logger.error("An internal error occurred while trying to authenticate the user.",failed);unsuccessfulAuthentication(request, response, failed);return;}catch (AuthenticationException failed) {// Authentication failedunsuccessfulAuthentication(request, response, failed);return;}// 如果允許驗證繼續其他認真。if (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//認證成功,回調successfulAuthentication(request, response, chain, authResult);}?真實驗證由子類實現。
public abstract Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException, IOException,ServletException;驗證成功或失敗回調
protected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response, FilterChain chain, 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 eventif (this.eventPublisher != null) {eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));} //successHandler回調successHandler.onAuthenticationSuccess(request, response, authResult);}protected void unsuccessfulAuthentication(HttpServletRequest request,HttpServletResponse response, AuthenticationException failed)throws IOException, ServletException {SecurityContextHolder.clearContext();if (logger.isDebugEnabled()) {logger.debug("Authentication request failed: " + failed.toString(), failed);logger.debug("Updated SecurityContextHolder to contain null Authentication");logger.debug("Delegating to authentication failure handler " + failureHandler);}rememberMeServices.loginFail(request, response);failureHandler.onAuthenticationFailure(request, response, failed);}attemptAuthentication
public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException { //必須是POST方法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" propertysetDetails(request, authRequest); //調用AuthenticationManager()驗證return this.getAuthenticationManager().authenticate(authRequest);}AnonymousAuthenticationFilter
AnonymousAuthenticationFilter過濾器是在UsernamePasswordAuthenticationFilter等過濾器之后,如果它前面的過濾器都沒有認證成功,Spring Security則為當前的SecurityContextHolder中添加一個Authenticaiton?的匿名實現類AnonymousAuthenticationToken;
public AnonymousAuthenticationFilter(String key) {this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));}doFilter方法,直接觸發原始chain過濾。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {if (SecurityContextHolder.getContext().getAuthentication() == null) {SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req)); ... ... }else {... ... }}chain.doFilter(req, res);}//設置匿名token protected Authentication createAuthentication(HttpServletRequest request) {AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,principal, authorities);auth.setDetails(authenticationDetailsSource.buildDetails(request));return auth;}ExceptionTranslationFilter
ExceptionTranslationFilter?異常處理過濾器,該過濾器用來處理在系統認證授權過程中拋出的異常(也就是下一個過濾器FilterSecurityInterceptor),主要是 處理?AuthenticationException?和?AccessDeniedException?。
FilterSecurityInterceptor
此過濾器為認證授權過濾器鏈中最后一個過濾器,該過濾器之后就是請求真正的處理邏輯。
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {FilterInvocation fi = new FilterInvocation(request, response, chain);invoke(fi);}public void invoke(FilterInvocation fi) throws IOException, ServletException {if ((fi.getRequest() != null)&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)&& observeOncePerRequest) {//已經處理。//直接調用真實服務。fi.getChain().doFilter(fi.getRequest(), fi.getResponse());}else {// 第一次調用,處理checking。if (fi.getRequest() != null && observeOncePerRequest) {//設置為true。fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}//前置處理InterceptorStatusToken token = super.beforeInvocation(fi);try {//真實業務邏輯。fi.getChain().doFilter(fi.getRequest(), fi.getResponse());}finally {super.finallyInvocation(token);}//后置處理。super.afterInvocation(token, null);}}AbstractSecurityInterceptor
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private ApplicationEventPublisher eventPublisher;private AccessDecisionManager accessDecisionManager;private AfterInvocationManager afterInvocationManager;private AuthenticationManager authenticationManager = new NoOpAuthenticationManager();private RunAsManager runAsManager = new NullRunAsManager();beforeInvocation
//object:請求的url。 protected InterceptorStatusToken beforeInvocation(Object object) {Assert.notNull(object, "Object was null");final boolean debug = logger.isDebugEnabled();if (!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: "+ getSecureObjectClass());} //使用當前的訪問資源路徑去匹配自定義的匹配規則。Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object); //如果沒有,則返回null。if (attributes == null || attributes.isEmpty()) {if (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'");}if (debug) {logger.debug("Public object - authentication not attempted");}publishEvent(new PublicInvocationEvent(object));return null; // no further work post-invocation}if (debug) {logger.debug("Secure object: " + object + "; Attributes: " + attributes);} //沒有驗證if (SecurityContextHolder.getContext().getAuthentication() == null) {credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound","An Authentication object was not found in the SecurityContext"),object, attributes);} //驗證過的信息。Authentication authenticated = authenticateIfRequired();// Attempt authorizationtry { //鑒權。this.accessDecisionManager.decide(authenticated, object, attributes);}catch (AccessDeniedException accessDeniedException) {publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,accessDeniedException));throw accessDeniedException;}if (debug) {logger.debug("Authorization successful");}if (publishAuthorizationSuccess) {publishEvent(new AuthorizedEvent(object, attributes, authenticated));}// Attempt to run as a different userAuthentication runAs = this.runAsManager.buildRunAs(authenticated, object,attributes);if (runAs == null) {if (debug) {logger.debug("RunAsManager did not change Authentication object");}// no further work post-invocationreturn new InterceptorStatusToken(SecurityContextHolder.getContext(), false,attributes, object);}else {if (debug) {logger.debug("Switching to RunAs Authentication: " + runAs);}SecurityContext origCtx = SecurityContextHolder.getContext();SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());SecurityContextHolder.getContext().setAuthentication(runAs);// need to revert to token.Authenticated post-invocationreturn new InterceptorStatusToken(origCtx, true, attributes, object);}} protected void finallyInvocation(InterceptorStatusToken token) {if (token != null && token.isContextHolderRefreshRequired()) {if (logger.isDebugEnabled()) {logger.debug("Reverting to original Authentication: "+ token.getSecurityContext().getAuthentication());}SecurityContextHolder.setContext(token.getSecurityContext());}}?
?
AccessDecisionManager?
AccessDecisionManager 用于鑒權。
public interface AccessDecisionManager {void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,InsufficientAuthenticationException;boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz); }?AbstractAccessDecisionManager?
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager,InitializingBean, MessageSourceAware {private List<AccessDecisionVoter<?>> decisionVoters;protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private boolean allowIfAllAbstainDecisions = false; } public boolean supports(ConfigAttribute attribute) {for (AccessDecisionVoter voter : this.decisionVoters) {if (voter.supports(attribute)) {return true;}}return false;}public boolean supports(Class<?> clazz) {for (AccessDecisionVoter voter : this.decisionVoters) {if (!voter.supports(clazz)) {return false;}}return true;}AccessDecisionVoter
投票。即權限判斷。
public interface AccessDecisionVoter<S> {//投票結果:int ACCESS_GRANTED = 1;//通過。int ACCESS_ABSTAIN = 0;//棄權。int ACCESS_DENIED = -1;//反對boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes); }?WebExpressionVoter
WebExpressionVoter ,它能理解怎樣解析 SpEL 表達式。 WebExpressionVoter 借助于WebSecurityExpressionHandler 接口的一個實現類來達到這個目的。
public int vote(Authentication authentication, FilterInvocation fi,Collection<ConfigAttribute> attributes) {assert authentication != null;assert fi != null;assert attributes != null;WebExpressionConfigAttribute weca = findConfigAttribute(attributes);if (weca == null) {return ACCESS_ABSTAIN;}EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,fi);ctx = weca.postProcess(ctx, fi);return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED: ACCESS_DENIED;}RoleVoter?
基于角色的鑒權,權限以ROLE_開頭。
AuthenticatedVoter
根據認證結果鑒權。RememberMe會使用到。
public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY";public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED";public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY";?
AffirmativeBased
AccessDecisionManager的默認實現。只要有一個通過則通過。沒有反對的也通過。
public void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {int deny = 0;for (AccessDecisionVoter voter : getDecisionVoters()) {int result = voter.vote(authentication, object, configAttributes);if (logger.isDebugEnabled()) {logger.debug("Voter: " + voter + ", returned: " + result);}switch (result) {case AccessDecisionVoter.ACCESS_GRANTED:return;case AccessDecisionVoter.ACCESS_DENIED:deny++;break;default:break;}}if (deny > 0) {throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}// To get this far, every AccessDecisionVoter abstainedcheckAllowIfAllAbstainDecisions();}
ConsensusBased
同意大于反對,或者同意等于反對(如果允許)則通過。
UnanimousBased
只要有一個不通過,就拒絕。
?
密碼驗證時序圖
?
總結
以上是生活随笔為你收集整理的Spring Security源码解析(一)——认证和鉴权的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Security源码解析(
- 下一篇: OAuth2(一)——核心概念