認證與授權(quán)
認證(Authentication):確定一個用戶的身份的過程政己。授權(quán)(Authorization):判斷一個用戶是否有訪問某個安全對象的權(quán)限峰锁。下面討論一下spring security中最基本的認證與授權(quán)顾瞪。
首先明確一下在認證與授權(quán)中關(guān)鍵的三個過濾器尼斧,其他過濾器不討論:
1. UsernamePasswordAuthenticationFilter:該過濾器用于攔截我們表單提交的請求(默認為/login)霜旧,進行用戶的認證過程吧冯吓。
2. ExceptionTranslationFilter:該過濾器主要用來捕獲處理spring security拋出的異常棚潦,異常主要來源于FilterSecurityInterceptor令漂。
3. FilterSecurityInterceptor:該過濾器主要用來進行授權(quán)判斷。
授權(quán)認證過程分析
訪問觸發(fā)/login
1.我們在瀏覽器中輸入http://localhost:8080/ 訪問應用丸边,因為我們的路徑被spring secuirty保護起來了叠必,我們是沒有權(quán)限訪問的,所以我們會被引導至登錄頁面進行登錄妹窖。
此路徑因為不是表單提交的路徑(/login)纬朝,該過程主要起作用的過濾器為FilterSecurityInterceptor。其部分源碼如下:
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) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//前處理
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
//使SecurityContextHolder中的Authentication保持原樣骄呼,因為RunAsManager會暫時改變
//其中的Authentication
super.finallyInvocation(token);
}
//調(diào)用后的處理
super.afterInvocation(token, null);
}
}
真正進行權(quán)限判斷的為beforeInvocation共苛,該方法定義在FilterSecurityInterceptor的父類AbstractSecurityInterceptor中,源碼如下:
FilterSecurityInterceptor->AbstractSecurityInterceptor
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();
//判斷object是否為過濾器支持的類型蜓萄,在這里是FilterInvocation(里面記錄包含了請求的request,response,FilterChain)
//這里可以把FilterInvocation看做是安全對象隅茎,因為通過它可以獲得request,通過request可以獲得請求的URI。
//而實際的安全對象就是URI
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());
}
//獲取安全對象所對應的ConfigAttribute嫉沽,ConfigAtrribute實際就是訪問安全所應該有的權(quán)限集辟犀。
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
//判斷安全對象是否擁有權(quán)限集,沒有的話說明所訪問的安全對象是一個公共對象绸硕,就是任何人都可以訪問的堂竟。
if (attributes == null || attributes.isEmpty()) {
//如果rejectPublicInvocations為true,說明不支持公共對象的訪問,此時會拋出異常玻佩。
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);
}
//判斷SecurityCntext中是否存在Authentication,不存在則說明訪問著根本沒登錄
//調(diào)用下面的credentialsNotFound()方法則會拋出一個AuthenticationException出嘹,
//該異常會被ExceptionTranslationFilter捕獲,并做出處理咬崔。
//不過默認情況下Authentication不會為null,因為AnonymouseFilter會默認注冊到
//過濾鏈中税稼,如果用戶沒登錄的話,會將其當做匿名用戶(Anonymouse User)來對待。
//除非你自己將AnonymouseFilter從過濾鏈中去掉娶聘。
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
//Autentication存在闻镶,則說明用戶已經(jīng)被認證(但是不表示已登錄,因為匿名用戶也是相當于被認證的)丸升,
//判斷用戶是否需要再次被認證铆农,如果你配置了每次訪問必須重新驗證,那么就會再次調(diào)用AuthenticationManager
//的authenticate方法進行驗證狡耻。
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
//判斷用戶是否有訪問被保護對象的權(quán)限墩剖。
//ed。默認的AccessDesicisonManager的實現(xiàn)類是AffirmativeBased
//AffirmativeBased采取投票的形式判斷用戶是否有訪問安全對象的權(quán)限
//票就是配置的Role夷狰。AffirmativeBased采用WebExpressionVoter進行投票
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 user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return 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-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
通過以上代碼得到以下結(jié)論
a). beforeInvocation(Object object)中的object為安全對象岭皂,類型為FilterInvocation。安全對象就是受spring security保護的對象沼头。雖然按道理來說安全對象應該是我們訪問的url爷绘,但是FilterInvocation中封裝了request,那么url也可以獲取到进倍。
b). Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object) 每個安全對象都會有對應的訪問權(quán)限集(Collection<ConfigAttribute>)土至,而且在容器啟動后所有安全對象的所有權(quán)限集就已經(jīng)被獲取到并被放在安全元數(shù)據(jù)中(SecurityMetadataSource中),通過安全元數(shù)據(jù)可以獲取到各個安全對象的權(quán)限集猾昆。因為我們每個安全對象都是登錄才可以訪問的(anyRequest().authenticated())陶因,這里我們只需要知道此時每個對象的權(quán)限集只有一個元素,并且是authenticated垂蜗。如果一個對象沒有權(quán)限集楷扬,說明它是一個公共對象,不受spring security保護贴见。
c). 當我們沒有登錄時烘苹,我們會被當做匿名用戶(Anonymouse)來看待。被當做匿名用戶對待是AnonymouseAuthenticationFilter來攔截封裝成一個Authentication對象蝇刀,當用戶被認證后就會被封裝成一個Authentication對象螟加。Authentication對象中封裝了用戶基本信息,該對象會在認證中做詳細介紹吞琐。AnonymouseAuthenticationFilter也是默認被注冊的捆探。
d). 最中進行授權(quán)判斷的是AccessDecisionManager的子類AffirmativeBased的decide方法。我在來看其decide的源碼:
AffirmativeBased-》decide
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
//根據(jù)用戶的authenticton和權(quán)限集得出能否訪問的結(jié)果
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) {
//如果deny>0說明沒有足夠的權(quán)限去訪問安全對象站粟,此時拋出的
//AccessDeniedException會被ExceptionTranslationFilter捕獲處理黍图。
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
因為我們首次登錄,所以會拋出AccessDeniedexception奴烙。此異常會被ExceptionTranslationFilter捕獲并進行處理的助被。其部分源碼如下:
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);
logger.debug("Chain processed normally");
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
}
if (ase != null) {
//真正處理異常的地方
handleSpringSecurityException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
logger.debug(
"Authentication exception occurred; redirecting to authentication entry point",
exception);
//未被認證剖张,引導去登錄
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
logger.debug(
"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
exception);
//如果為匿名用戶說明未登錄,引導去登錄
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
"Full authentication is required to access this resource"));
}
else {
logger.debug(
"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
exception);
//用戶已登錄揩环,但是沒有足夠權(quán)限去訪問安全對象搔弄,說明權(quán)限不足。進行
//權(quán)限不足的提醒
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
因為我們是以匿名用戶的身份進行登錄的丰滑,所以顾犹,會被引導去登錄頁面。登錄頁面的創(chuàng)建是由默認注冊的過濾器DefaultLoginPageGeneratingFilter產(chǎn)生的褒墨。具體怎么產(chǎn)生的這里不做分析炫刷。我們只需要是誰做的就可以了。實際在使用時我們也不大可能去用默認生成的登錄頁面郁妈,因為太丑了浑玛。。噩咪。
2.在被引導至登錄頁面后顾彰,我們將輸入用戶名和密碼,提交至應用剧腻。應用會校驗用戶名和密碼拘央,校驗成功后,我們成功訪問應用书在。
此時訪問的路徑為/login,這是UsernamePasswordAuthenticationFilter將攔截請求進行認證拆又。UsernamePasswordAuthenticationFilter的doFilter方法定義在其父類AbstractAuthenticationProcessingFilter中儒旬,源碼如下:
AbstractAuthenticationProcessingFilter->doFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//判斷請求是否需要進行驗證處理。默認對/login并且是POST請求的路徑進行攔截
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
//調(diào)用UsernamePasswordAuthenticationFilter的attemptAuthentication方法進行驗證帖族,并返回
//完整的被填充的Authentication對象
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
//進行session固定攻擊的處理
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) {
// 認證失敗后的處理
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//認證成功后的處理
successfulAuthentication(request, response, chain, authResult);
}
實際認證發(fā)生在UsernamePasswordAuthenticationFilter的attemptAuthentication中栈源,如果認證失敗,則會調(diào)用unsuccessfulAuthentication進行失敗后的處理竖般,一般是提示用戶認證失敗甚垦,要求重新輸入用戶名和密碼,如果認證成功涣雕,那么會調(diào)用successfulAuthentication進行成功后的處理艰亮,一般是將Authentication存進SecurityContext中并跳轉(zhuǎn)至之前訪問的頁面或者默認頁面(這部分在讀者讀完本節(jié)后自行去看源碼是怎么處理的,這里不做討論挣郭,現(xiàn)在只需知道會跳到一開始我們訪問的頁面中)迄埃。下面我們來看認證即attemptAuthentication的源碼:
attemptAuthentication
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();
//將用戶名和密碼封裝在Authentication的實現(xiàn)UsernamePasswordAuthenticationToken
//以便于AuthentictionManager進行認證
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//獲得AuthenticationManager進行認證
return this.getAuthenticationManager().authenticate(authRequest);
}
spring security在進行認證時,會將用戶名和密碼封裝成一個Authentication對象兑障,在進行認證后侄非,會將Authentication的權(quán)限等信息填充完全返回蕉汪。Authentication會被存在SecurityContext中,供應用之后的授權(quán)等操作使用逞怨。此處介紹下Authentication者疤,Authentication存儲的就是訪問應用的用戶的一些信息。下面是Authentication源碼:
Authentication
public interface Authentication extends Principal, Serializable {
//用戶的權(quán)限集合
Collection<? extends GrantedAuthority> getAuthorities();
//用戶登錄的憑證叠赦,一般指的就是密碼
Object getCredentials();
//用戶的一些額外的詳細信息宛渐,一般不用
Object getDetails();
//這里認為Principal就為登錄的用戶
Object getPrincipal();
//是否已經(jīng)被認證了
boolean isAuthenticated();
//設(shè)置認證的狀態(tài)
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
講解了Authentication后,我們回過頭來再看attemptAuthentication方法眯搭,該方法會調(diào)用AuthenticationManager的authenticate方法進行認證并返回一個填充完整的Authentication對象窥翩。
在這里我們又要講解一下認證的幾個核心的類,很重要鳞仙!
a). AuthenticationManager b).ProviderManager c).AuthenticationProvider d).UserDetailsService e).UserDetails
現(xiàn)在來說一下這幾個類的作用以及關(guān)聯(lián)關(guān)系寇蚊。
a). AuthenticationManager是一個接口,提供了authenticate方法用于認證棍好。
b). AuthenticationManager有一個默認的實現(xiàn)ProviderManager仗岸,其實現(xiàn)了authenticate方法。
c). ProviderManager內(nèi)部維護了一個存有AuthenticationProvider的集合借笙,ProviderManager實現(xiàn)的authenticate方法再調(diào)用這些AuthenticationProvider的authenticate方法去認證扒怖,表單提交默認用的AuthenticationProvider實現(xiàn)是DaoAuthenticationProvider。
d). AuthenticationProvider中維護了UserDetailsService业稼,我們使用內(nèi)存中的用戶盗痒,默認的實現(xiàn)是InMemoryUserDetailsManager。UserDetailsService用來查詢用戶的詳細信息低散,該詳細信息就是UserDetails俯邓。UserDetails的默認實現(xiàn)是User。查詢出來UserDetails后再對用戶輸入的密碼進行校驗熔号。校驗成功則將UserDetails中的信息填充進Authentication中返回稽鞭。校驗失敗則提醒用戶密碼錯誤。
以上說的這些接口的實現(xiàn)類是由我們在MySecurityConfig中配置時生成的引镊,即下面的代碼
configUser->AuthenticationManagerBuilder builder
@Autowired
public void configUser(AuthenticationManagerBuilder builder) throws Exception {
builder
.inMemoryAuthentication()
//創(chuàng)建用戶名為user朦蕴,密碼為password的用戶
.withUser("user").password("password").roles("USER");
}
這里不再討論具體是怎么生成的,記住即可弟头。因為我們實際在項目中一般都會用自定義的這些核心認證類吩抓。
下面我們來分析源碼,先來看ProviderManager的authenticate方法:
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();
//獲取所有AuthenticationProvider亮瓷,循環(huán)進行認證
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
//對authentication進行認證
result = provider.authenticate(authentication);
if (result != null) {
//填充成完整的Authentication
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 (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
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) {
//如果所有的AuthenticationProvider進行認證完result仍然為null
//此時表示為提供AuthenticationProvider琴拧,拋出ProviderNotFoundException異常
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
ProviderManager用AuthenticationProvider對authentication進行認證。如果沒有提供AuthenticationProvider嘱支,那么最終將拋出ProviderNotFoundException蚓胸。
我們表單提交認證時挣饥,AuthenticationProvider默認的實現(xiàn)是DaoAuthenticationProvider,DaoAuthenticationProvider的authenticate方法定義在其父類AbstractUserDetailsAuthenticationProvider中沛膳,其源碼如下:
AbstractUserDetailsAuthenticationProvider
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 {
//獲取UserDetails扔枫,即用戶詳細信息
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();
}
//認證成功,返回裝有用戶權(quán)限等信息的authentication對象
return createSuccessAuthentication(principalToReturn, authentication, user);
}
retrieveUser方法定義在DaoAuthenticationProvider中锹安,用來獲取UserDetails這里不再展示源碼短荐,請讀者自行去看。你會發(fā)現(xiàn)獲取獲取UserDetails正是由其中維護的UserDetailsService來完成的叹哭。獲取到UserDetails后再調(diào)用其
additionalAuthenticationChecks方法進行密碼的驗證忍宋。如果認證失敗,則拋出AuthenticationException风罩,如果認證成功則返回裝有權(quán)限等信息的Authentication對象糠排。
總結(jié)
到目前為止,我們結(jié)合我們創(chuàng)建的項目和spring security的源碼分析了web應用認證和授權(quán)的原理超升。內(nèi)容比較多入宦,現(xiàn)在理一下重點。
1.springSecurityFilterChain中各個過濾器怎么創(chuàng)建的只需了解即可室琢。不要太過關(guān)注乾闰。
2.重點記憶UsernamePasswordAuthenticationFilter,ExceptionTranslationFilter盈滴,F(xiàn)ilterSecurityInterceptor這三個過濾器的作用及源碼分析涯肩。
3.重要記憶認證中Authentication,AuthenticationManager雹熬,ProviderManager宽菜,AuthenticationProvider,UserDetailsService竿报,UserDetails這些類的作用及源碼分析。
4.重點記憶授權(quán)中FilterInvoction继谚,SecurityMetadataSource烈菌,AccessDecisionManager的作用。
5.將這些類理解的關(guān)鍵是建立起關(guān)聯(lián)花履,建立起關(guān)聯(lián)的方式就是跟著本節(jié)中的案例走下去芽世,一步步看代碼如何實現(xiàn)的。