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;
}