??想要深入spring security的authentication (身份驗證)和access-control(訪問權(quán)限控制)工作流程桩撮,必須清楚spring security的主要技術(shù)點包括關(guān)鍵接口、類以及抽象類如何協(xié)同工作進(jìn)行authentication 和access-control的實現(xiàn)峰弹。
1.spring security 認(rèn)證和授權(quán)流程
常見認(rèn)證和授權(quán)流程可以分成:
- A user is prompted to log in with a username and password (用戶用賬密碼登錄)
- The system (successfully) verifies that the password is correct for the username(校驗密碼正確性)
- The context information for that user is obtained (their list of roles and so on).(獲取用戶信息context店量,如權(quán)限)
- A security context is established for the user(為用戶創(chuàng)建security context)
- The user proceeds, potentially to perform some operation which is potentially protected by an access control mechanism which checks the required permissions for the operation against the current security context information.(訪問權(quán)限控制,是否具有訪問權(quán)限)
1.1 spring security 認(rèn)證
上述前三點為spring security認(rèn)證驗證環(huán)節(jié):
- 通常通過AbstractAuthenticationProcessingFilter過濾器將賬號密碼組裝成Authentication實現(xiàn)類UsernamePasswordAuthenticationToken鞠呈;
- 將token傳遞給AuthenticationManager驗證是否有效融师,而AuthenticationManager通常使用ProviderManager實現(xiàn)類來檢驗;
- AuthenticationManager認(rèn)證成功后將返回一個擁有詳細(xì)信息的Authentication object(包括權(quán)限信息蚁吝,身份信息旱爆,細(xì)節(jié)信息,但密碼通常會被移除)窘茁;
- 通過SecurityContextHolder.getContext().getAuthentication().getPrincipal()將Authentication設(shè)置到security context中怀伦。
1.2 spring security訪問授權(quán)
- 通過FilterSecurityInterceptor過濾器入口進(jìn)入;
- FilterSecurityInterceptor通過其繼承的抽象類的AbstractSecurityInterceptor.beforeInvocation(Object object)方法進(jìn)行訪問授權(quán)山林,其中涉及了類AuthenticationManager房待、AccessDecisionManager、SecurityMetadataSource等驼抹。
根據(jù)上述描述的過程桑孩,我們接下來主要去分析其中涉及的一下Component、Service砂蔽、Filter洼怔。
2.核心組件(Core Component )
2.1 SecurityContextHolder
??SecurityContextHolder提供對SecurityContext的訪問,存儲security context(用戶信息左驾、角色權(quán)限等)镣隶,而且其具有下列儲存策略即工作模式:
SecurityContextHolder.MODE_THREADLOCAL(默認(rèn)):使用ThreadLocal,信息可供此線程下的所有的方法使用诡右,一種與線程綁定的策略安岂,此天然很適合Servlet Web應(yīng)用。
SecurityContextHolder.MODE_GLOBAL:使用于獨立應(yīng)用
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL:具有相同安全標(biāo)示的線程
修改SecurityContextHolder的工作模式有兩種方法 :
- 設(shè)置一個系統(tǒng)屬性(system.properties) : spring.security.strategy;
- 調(diào)用SecurityContextHolder靜態(tài)方法setStrategyName()
在默認(rèn)ThreadLocal策略中帆吻,SecurityContextHolder為靜態(tài)方法獲取用戶信息為:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
但是一般不需要自身去獲取域那。
其中g(shù)etAuthentication()返回一個Authentication認(rèn)證主體,接下來分析Authentication、UserDetails細(xì)節(jié)次员。
2.2 Authentication
??Spring Security使用一個Authentication對象來描述當(dāng)前用戶的相關(guān)信息,其包含用戶擁有的權(quán)限信息列表败许、用戶細(xì)節(jié)信息(身份信息、認(rèn)證信息)淑蔚。Authentication為認(rèn)證主體在spring security中時最高級別身份/認(rèn)證的抽象市殷,常見的實現(xiàn)類UsernamePasswordAuthenticationToken。Authentication接口源碼:
public interface Authentication extends Principal, Serializable {
//權(quán)限信息列表,默認(rèn)GrantedAuthority接口的一些實現(xiàn)類
Collection<? extends GrantedAuthority> getAuthorities();
//密碼信息
Object getCredentials();
//細(xì)節(jié)信息刹衫,web應(yīng)用中的實現(xiàn)接口通常為 WebAuthenticationDetails醋寝,它記錄了訪問者的ip地址和sessionId的值
Object getDetails();
//通常返回值為UserDetails實現(xiàn)類
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
前面兩個組件都涉及了UserDetails,以及GrantedAuthority其到底是什么呢带迟?2.3小節(jié)分析音羞。
2.3 UserDetails&GrantedAuthority
??UserDetails提供從應(yīng)用程序的DAO或其他安全數(shù)據(jù)源構(gòu)建Authentication對象所需的信息,包含GrantedAuthority仓犬。其官方實現(xiàn)類為User嗅绰,開發(fā)者可以實現(xiàn)其接口自定義UserDetails實現(xiàn)類。其接口源碼:
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
??UserDetails與Authentication接口功能類似婶肩,其實含義即是Authentication為用戶提交的認(rèn)證憑證(賬號密碼)办陷,UserDetails為系統(tǒng)中用戶正確認(rèn)證憑證,在UserDetailsService中的loadUserByUsername方法獲取正確的認(rèn)證憑證律歼。
??其中在getAuthorities()方法中獲取到GrantedAuthority列表是代表用戶訪問應(yīng)用程序權(quán)限范圍,此類權(quán)限通常是“role(角色)”啡专,例如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR险毁。GrantedAuthority接口常見的實現(xiàn)類SimpleGrantedAuthority。
3. 核心服務(wù)類(Core Services)
3.1 AuthenticationManager们童、ProviderManager以及AuthenticationProvider
??AuthenticationManager是認(rèn)證相關(guān)的核心接口畔况,是認(rèn)證一切的起點。但常見的認(rèn)證流程都是AuthenticationManager實現(xiàn)類ProviderManager處理慧库,而且ProviderManager實現(xiàn)類基于委托者模式維護(hù)AuthenticationProvider 列表用于不同的認(rèn)證方式跷跪。例如:
- 使用賬號密碼認(rèn)證方式DaoAuthenticationProvider實現(xiàn)類(繼承了AbstractUserDetailsAuthenticationProvide抽象類),其為默認(rèn)認(rèn)證方式齐板,進(jìn)行數(shù)據(jù)庫庫獲取認(rèn)證數(shù)據(jù)信息吵瞻。
- 游客身份登錄認(rèn)證方式AnonymousAuthenticationProvider實現(xiàn)類
- 從cookies獲取認(rèn)證方式RememberMeAuthenticationProvider實現(xiàn)類
??AuthenticationProvider為
ProviderManager源碼分析:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
//AuthenticationProvider列表依次認(rèn)證
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
//每個AuthenticationProvider進(jìn)行認(rèn)證
result = provider.authenticate(authentication)
if (result != null) {
copyDetails(authentication, result);
break;
}
}
....
catch (AuthenticationException e) {
lastException = e;
}
}
//進(jìn)行父類AuthenticationProvider進(jìn)行認(rèn)證
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
}
catch (AuthenticationException e) {
lastException = e;
}
}
// 如果有Authentication信息,則直接返回
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
//清除密碼
((CredentialsContainer) result).eraseCredentials();
}
//發(fā)布登錄成功事件
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
//如果都沒認(rèn)證成功甘磨,拋出異常
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
??ProviderManager 中的AuthenticationProvider列表橡羞,會依照次序去認(rèn)證,默認(rèn)策略下济舆,只需要通過一個AuthenticationProvider的認(rèn)證卿泽,即可被認(rèn)為是登錄成功,而且AuthenticationProvider認(rèn)證成功后返回一個Authentication實體滋觉,并為了安全會進(jìn)行清除密碼签夭。如果所有認(rèn)證器都無法認(rèn)證成功齐邦,則ProviderManager 會拋出一個ProviderNotFoundException異常。
3.2 UserDetailsService
??UserDetailsService接口作用是從特定的地方獲取認(rèn)證的數(shù)據(jù)源(賬號第租、密碼)侄旬。如何獲取到系統(tǒng)中正確的認(rèn)證憑證,通過loadUserByUsername(String username)獲取認(rèn)證信息煌妈,而且其只有一個方法:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
其常見的實現(xiàn)類從數(shù)據(jù)獲取的JdbcDaoImpl實現(xiàn)類儡羔,從內(nèi)存中獲取的InMemoryUserDetailsManager實現(xiàn)類,不過我們可以實現(xiàn)其接口自定義UserDetailsService實現(xiàn)類璧诵,如下:
public class CustomUserService implements UserDetailsService {
@Autowired
//用戶mapper
private UserInfoMapper userInfoMapper;
@Autowired
//用戶權(quán)限mapper
private PermissionInfoMapper permissionInfoMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfoDTO userInfo = userInfoMapper.getUserInfoByUserName(username);
if (userInfo != null) {
List<PermissionInfoDTO> permissionInfoDTOS = permissionInfoMapper.findByAdminUserId(userInfo.getId());
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
//組裝權(quán)限GrantedAuthority object
for (PermissionInfoDTO permissionInfoDTO : permissionInfoDTOS) {
if (permissionInfoDTO != null && permissionInfoDTO.getPermissionName() != null) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(
permissionInfoDTO.getPermissionName());
grantedAuthorityList.add(grantedAuthority);
}
}
//返回用戶信息
return new User(userInfo.getUserName(), userInfo.getPasswaord(), grantedAuthorityList);
}else {
//拋出用戶不存在異常
throw new UsernameNotFoundException("admin" + username + "do not exist");
}
}
}
3.3 AccessDecisionManager&SecurityMetadataSource
??AccessDecisionManager是由AbstractSecurityInterceptor調(diào)用汰蜘,負(fù)責(zé)做出最終的訪問控制決策。
AccessDecisionManager接口源碼:
//訪問控制決策
void decide(Authentication authentication, Object secureObject,Collection<ConfigAttribute> attrs)
throws AccessDeniedException;
//是否支持處理傳遞的ConfigAttribute
boolean supports(ConfigAttribute attribute);
//確認(rèn)class是否為AccessDecisionManager
boolean supports(Class clazz);
??SecurityMetadataSource包含著AbstractSecurityInterceptor訪問授權(quán)所需的元數(shù)據(jù)(動態(tài)url之宿、動態(tài)授權(quán)所需的數(shù)據(jù))族操,在AbstractSecurityInterceptor授權(quán)模塊中結(jié)合AccessDecisionManager進(jìn)行訪問授權(quán)。其涉及了ConfigAttribute比被。
SecurityMetadataSource接口:
Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException;
Collection<ConfigAttribute> getAllConfigAttributes();
boolean supports(Class<?> clazz);
我們還可以自定義SecurityMetadataSource數(shù)據(jù)源色难,實現(xiàn)接口FilterInvocationSecurityMetadataSource。例:
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
public List<ConfigAttribute> getAttributes(Object object) {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
String httpMethod = fi.getRequest().getMethod();
List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();
// Lookup your database (or other source) using this information and populate the
// list of attributes
return attributes;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
3.4 PasswordEncoder
??為了存儲安全等缀,一般要對密碼進(jìn)行算法加密枷莉,而spring security提供了加密PasswordEncoder接口。其實現(xiàn)類有使用BCrypt hash算法實現(xiàn)的BCryptPasswordEncoder尺迂,SCrypt hashing 算法實現(xiàn)的SCryptPasswordEncoder實現(xiàn)類笤妙,實現(xiàn)類內(nèi)部實現(xiàn)可看源碼分析。而PasswordEncoder接口只有兩個方法:
public interface PasswordEncoder {
//密碼加密
String encode(CharSequence rawPassword);
//密碼配對
boolean matches(CharSequence rawPassword, String encodedPassword);
}
4 核心 Security 過濾器(Core Security Filters)
4.1 FilterSecurityInterceptor
??FilterSecurityInterceptor是Spring security授權(quán)模塊入口噪裕,該類根據(jù)訪問的用戶的角色蹲盘,權(quán)限授權(quán)訪問那些資源(訪問特定路徑應(yīng)該具備的權(quán)限)。
??FilterSecurityInterceptor封裝FilterInvocation對象進(jìn)行操作膳音,所有的請求到了這一個filter召衔,如果這個filter之前沒有執(zhí)行過的話,那么首先執(zhí)行其父類AbstractSecurityInterceptor提供的InterceptorStatusToken token = super.beforeInvocation(fi)祭陷,在此方法中使用AuthenticationManager獲取Authentication中用戶詳情,使用ConfigAttribute封裝已定義好訪問權(quán)限詳情颗胡,并使用AccessDecisionManager.decide()方法進(jìn)行訪問權(quán)限控制。
FilterSecurityInterceptor源碼分析:
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 {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//回調(diào)其繼承的抽象類AbstractSecurityInterceptor的方法
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
AbstractSecurityInterceptor源碼分析:
protected InterceptorStatusToken beforeInvocation(Object object) {
....
//獲取所有訪問權(quán)限(url-role)屬性列表(已定義在數(shù)據(jù)庫或者其他地方)
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
....
//獲取該用戶訪問信息(包括url哑蔫,訪問權(quán)限)
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
//進(jìn)行授權(quán)訪問
this.accessDecisionManager.decide(authenticated, object, attributes);
}catch
....
}
4.2 UsernamePasswordAuthenticationFilter
??UsernamePasswordAuthenticationFilter使用username和password表單登錄使用的過濾器,也是最為常用的過濾器闸迷。其源碼:
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
//獲取表單中的用戶名和密碼
String username = obtainUsername(request);
String password = obtainPassword(request);
...
username = username.trim();
//組裝成username+password形式的token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//交給內(nèi)部的AuthenticationManager去認(rèn)證嵌纲,并返回認(rèn)證信息
return this.getAuthenticationManager().authenticate(authRequest);
}
??其主要代碼為創(chuàng)建UsernamePasswordAuthenticationToken的Authentication實體以及調(diào)用AuthenticationManager進(jìn)行authenticate認(rèn)證,根據(jù)認(rèn)證結(jié)果執(zhí)行successfulAuthentication或者unsuccessfulAuthentication腥沽,無論成功失敗逮走,一般的實現(xiàn)都是轉(zhuǎn)發(fā)或者重定向等處理,不再細(xì)究AuthenticationSuccessHandler和AuthenticationFailureHandle今阳。興趣的可以研究一下其父類AbstractAuthenticationProcessingFilter過濾器师溅。
4.3 AnonymousAuthenticationFilter
AnonymousAuthenticationFilter是匿名登錄過濾器,它位于常用的身份認(rèn)證過濾器(如UsernamePasswordAuthenticationFilter盾舌、BasicAuthenticationFilter墓臭、RememberMeAuthenticationFilter)之后,意味著只有在上述身份過濾器執(zhí)行完畢后妖谴,SecurityContext依舊沒有用戶信息窿锉,AnonymousAuthenticationFilter該過濾器才會有意義——基于用戶一個匿名身份。
AnonymousAuthenticationFilter源碼分析:
public class AnonymousAuthenticationFilter extends GenericFilterBean implements
InitializingBean {
...
public AnonymousAuthenticationFilter(String key) {
this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
}
...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
//創(chuàng)建匿名登錄Authentication的信息
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
...
}
chain.doFilter(req, res);
}
//創(chuàng)建匿名登錄Authentication的信息方法
protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
principal, authorities);
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
}
4.4 SecurityContextPersistenceFilter
??SecurityContextPersistenceFilter的兩個主要作用便是request來臨時膝舅,創(chuàng)建SecurityContext安全上下文信息和request結(jié)束時清空SecurityContextHolder嗡载。源碼后續(xù)分析。
小節(jié)總結(jié):
. AbstractAuthenticationProcessingFilter:主要處理登錄
. FilterSecurityInterceptor:主要處理鑒權(quán)
總結(jié)
??經(jīng)過上面對核心的Component仍稀、Service洼滚、Filter分析,初步了解了Spring Security工作原理以及認(rèn)證和授權(quán)工作流程琳轿。Spring Security認(rèn)證和授權(quán)還有很多負(fù)責(zé)的過程需要深入了解判沟,所以下次會對認(rèn)證模塊和授權(quán)模塊進(jìn)行更具體工作流程分析以及案例呈現(xiàn)。最后以上純粹個人結(jié)合博客和官方文檔總結(jié)崭篡,如有錯請指出!
最后可關(guān)注公眾號吧秕,一起學(xué)習(xí)琉闪。加群,每天會分享干貨砸彬,還有學(xué)習(xí)視頻領(lǐng)鹊弑小!