Spring Security 原理:
Spring Security架構中有很多攔截器,這裡就不一一贅述,後面文章會較長的描述,現在來看看最核心的幾個。
首先來看看Spring Security 架構工作流程圖

Spring Security 核心流程分為使用者認證和授權管理兩部分。
使用者認證
用戶端發出登入請求,被AuthenticationProcessingFilter攔截器攔截,先判斷通路的url是否需要權限驗證,若不需要,則放行,繼續調用下一個攔截器,若需要權限,則調用AuthenticationManager的實作
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
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);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
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 failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
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);
}
AuthenticationManager調用ProviderManager來擷取使用者的認證資訊,ProviderManager存在多種不同的provider,因為使用者的認證資訊可以在xml配置檔案上,可以在LADP伺服器上,可以在資料庫上。
驗證通過後,将使用者的認證資訊封裝成UserDetails對象放在全局緩存SecuriyContextHolder中,以備後面通路資源時使用。
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 event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
授權管理
通路url時,會通過AbstractSecurityInterceptor攔截器攔截,這時會調用FilterInvocationSecurityMetadataSource的方法來擷取該url所需要的全部的權限
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
再調用授權管理器AccessDecisionManager,并傳遞權限清單
this.accessDecisionManager.decide(authenticated, object, attributes);
AccessDecisionManager到全局緩存SecurityContextHolder中拿到使用者認證資訊
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
再根據AccessDecisionManager的授權對策(一票決定、一票否定、少數服從多數)進行判斷,如果權限足夠,則傳回,否則抛出權限不足異常,調用權限不足頁面。
try {
returnedObject = afterInvocationManager.decide(token.getSecurityContext()
.getAuthentication(), token.getSecureObject(), token
.getAttributes(), returnedObject);
}
catch (AccessDeniedException accessDeniedException) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(
token.getSecureObject(), token.getAttributes(), token
.getSecurityContext().getAuthentication(),
accessDeniedException);
publishEvent(event);
throw accessDeniedException;
}