HttpSecurity的默认配置如下:
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
http.
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
HttpSecurity的exceptionHandling() 方法定义如下:
public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
return getOrApply(new ExceptionHandlingConfigurer<>());
}
这里的配置主要是为了统一处理Spring Security的异常,其主要处理Security的两大类异常,分别是AuthenticationException与AccessDeniedException。
其配置类ExceptionHandlingConfigurer的部分定义如下:
public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractHttpConfigurer<ExceptionHandlingConfigurer<H>, H> {
private AuthenticationEntryPoint authenticationEntryPoint;
private AccessDeniedHandler accessDeniedHandler;
private LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> defaultEntryPointMappings = new LinkedHashMap<>();
private LinkedHashMap<RequestMatcher, AccessDeniedHandler> defaultDeniedHandlerMappings = new LinkedHashMap<>();
public ExceptionHandlingConfigurer() {
}
@Override
public void configure(H http) {
//身份验证入口点(驱动应用开始进行身份验证),用于启动身份验证方案(默认:Http403ForbiddenEntryPoint)
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
//处理Filter链中抛出的AccessDeniedException与AuthenticationException类型的异常;
//它提供了Java异常和HTTP响应之间的桥梁
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
entryPoint, getRequestCache(http));
//默认为AccessDeniedHandlerImpl【响应403】
AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
http.addFilter(exceptionTranslationFilter);
}
AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;
if (entryPoint == null) {
entryPoint = createDefaultEntryPoint(http);
}
return entryPoint;
}
private AuthenticationEntryPoint createDefaultEntryPoint(H http) {
if (this.defaultEntryPointMappings.isEmpty()) {
return new Http403ForbiddenEntryPoint();
}
if (this.defaultEntryPointMappings.size() == 1) {
return this.defaultEntryPointMappings.values().iterator().next();
}
DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(
this.defaultEntryPointMappings);
entryPoint.setDefaultEntryPoint(this.defaultEntryPointMappings.values().iterator()
.next());
return entryPoint;
}
AccessDeniedHandler getAccessDeniedHandler(H http) {
AccessDeniedHandler deniedHandler = this.accessDeniedHandler;
if (deniedHandler == null) {
deniedHandler = createDefaultDeniedHandler(http);
}
return deniedHandler;
}
private AccessDeniedHandler createDefaultDeniedHandler(H http) {
if (this.defaultDeniedHandlerMappings.isEmpty()) {
return new AccessDeniedHandlerImpl();
}
if (this.defaultDeniedHandlerMappings.size() == 1) {
return this.defaultDeniedHandlerMappings.values().iterator().next();
}
return new RequestMatcherDelegatingAccessDeniedHandler(
this.defaultDeniedHandlerMappings,
new AccessDeniedHandlerImpl());
}
}
AuthenticationEntryPoint | 说明 | 验证过滤器 |
---|---|---|
LoginUrlAuthenticationEntryPoint | 可以对请求进行重定向,例如将http转换成https请求;将请求重定向到配置的登录界面(可以服务器端或客户端重定向); | UsernamePasswordAuthenticationFilter |
DigestAuthenticationEntryPoint | 摘要式身份验证入口点,添加响应头“WWW-Authenticate”并响应401【未经授权】 | DigestAuthenticationFilter |
BasicAuthenticationEntryPoint | 添加响应头“WWW-Authenticate”并响应401【未经授权】 | BasicAuthenticationFilter |
ExceptionTranslationFilter
该过滤器的作用是捕获Spring Security的Filter执行时抛出来的异常,然后根据异常类型来发起验证的流程。
public class ExceptionTranslationFilter extends GenericFilterBean {
// ~ Instance fields
// ================================================================================================
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
private RequestCache requestCache = new HttpSessionRequestCache();
private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint,
RequestCache requestCache) {
Assert.notNull(authenticationEntryPoint,
"authenticationEntryPoint cannot be null");
Assert.notNull(requestCache, "requestCache cannot be null");
this.authenticationEntryPoint = authenticationEntryPoint;
this.requestCache = requestCache;
}
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) {
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
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(
messages.getMessage(
"ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
logger.debug(
"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
exception);
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
protected void sendStartAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContextHolder.getContext().setAuthentication(null);
//保存当前Request的请求信息,例如:header、cookie、path等,以便在用户进行身份验证后可以检索和重用该请求
requestCache.saveRequest(request, response);
logger.debug("Calling Authentication entry point.");
//开始启动验证(例如通过重定向到登录界面)
authenticationEntryPoint.commence(request, response, reason);
}
}