概述
介绍
作为一个配置HttpSecurity
的SecurityConfigurer
,ExceptionHandlingConfigurer
的配置任务如下 :
- 配置如下安全过滤器
Filter
ExceptionTranslationFilter
ExceptionHandlingConfigurer
配置过程中使用到了如下共享对象 :
RequestCache
如果该共享对象不存在,则缺省使用一个
HttpSessionRequestCache
AuthenticationEntryPoint
继承关系
使用
例子 1 : ExceptionHandlingConfigurer
对象的创建
// HttpSecurity 代码片段
public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
return getOrApply(new ExceptionHandlingConfigurer<>());
}
例子 2 : 通过ExceptionHandlingConfigurer
配置httpSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// ...
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new MyLoginUrlAuthenticationEntryPoint("/custom-login"))
.accessDeniedHandler(new MyAccessDeniedHandler());
}
// ...
}
源代码
源代码版本 Spring Security Config 5.1.4.RELEASE
package org.springframework.security.config.annotation.web.configurers;
// 省略 imports
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<>();
/**
* Creates a new instance
* @see HttpSecurity#exceptionHandling()
*/
public ExceptionHandlingConfigurer() {
}
/**
* Shortcut to specify the AccessDeniedHandler to be used is a specific error
* page
* 快捷方式,设置访问被拒绝使用的 AccessDeniedHandler 是一个 使用 AccessDeniedHandlerImpl ,
* 并且指向设定的错误页面(比如 /errors/401) accessDeniedUrl :
* 1. 如果 accessDeniedUrl 为 null,则返回 403 给浏览器端
* 2. 如果 accessDeniedUrl 不为 null,是某个 / 开头的有效路径,则 foward 用户到相应的错误页面
* @param accessDeniedUrl the URL to the access denied page (i.e. /errors/401)
* @return the ExceptionHandlingConfigurer for further customization
* @see AccessDeniedHandlerImpl
* @see #accessDeniedHandler(org.springframework.security.web.access.AccessDeniedHandler)
*/
public ExceptionHandlingConfigurer<H> accessDeniedPage(String accessDeniedUrl) {
AccessDeniedHandlerImpl accessDeniedHandler = new AccessDeniedHandlerImpl();
accessDeniedHandler.setErrorPage(accessDeniedUrl);
return accessDeniedHandler(accessDeniedHandler);
}
/**
* Specifies the AccessDeniedHandler to be used
* 设置访问被拒绝时使用的 AccessDeniedHandler
*
* @param accessDeniedHandler the AccessDeniedHandler to be used
* @return the ExceptionHandlingConfigurer for further customization
*/
public ExceptionHandlingConfigurer<H> accessDeniedHandler(
AccessDeniedHandler accessDeniedHandler) {
this.accessDeniedHandler = accessDeniedHandler;
return this;
}
/**
* Sets a default AccessDeniedHandler to be used which prefers being
* invoked for the provided RequestMatcher. If only a single default
* AccessDeniedHandler is specified, it will be what is used for the
* default AccessDeniedHandler. If multiple default
* AccessDeniedHandler instances are configured, then a
* RequestMatcherDelegatingAccessDeniedHandler will be used.
*
* @param deniedHandler the AccessDeniedHandler to use
* @param preferredMatcher the RequestMatcher for this default
* AccessDeniedHandler
* @return the ExceptionHandlingConfigurer for further customizations
* @since 5.1
*/
public ExceptionHandlingConfigurer<H> defaultAccessDeniedHandlerFor(
AccessDeniedHandler deniedHandler, RequestMatcher preferredMatcher) {
this.defaultDeniedHandlerMappings.put(preferredMatcher, deniedHandler);
return this;
}
/**
* Sets the AuthenticationEntryPoint to be used.
*
*
* If no #authenticationEntryPoint(AuthenticationEntryPoint) is specified,
* then
* #defaultAuthenticationEntryPointFor(AuthenticationEntryPoint, RequestMatcher)
* will be used. The first AuthenticationEntryPoint will be used as the
* default is no matches were found.
*
*
*
* If that is not provided defaults to Http403ForbiddenEntryPoint.
*
*
* @param authenticationEntryPoint the AuthenticationEntryPoint to use
* @return the ExceptionHandlingConfigurer for further customizations
*/
public ExceptionHandlingConfigurer<H> authenticationEntryPoint(
AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
return this;
}
/**
* Sets a default AuthenticationEntryPoint to be used which prefers being
* invoked for the provided RequestMatcher. If only a single default
* AuthenticationEntryPoint is specified, it will be what is used for the
* default AuthenticationEntryPoint. If multiple default
* AuthenticationEntryPoint instances are configured, then a
* DelegatingAuthenticationEntryPoint will be used.
*
* @param entryPoint the AuthenticationEntryPoint to use
* @param preferredMatcher the RequestMatcher for this default
* AuthenticationEntryPoint
* @return the ExceptionHandlingConfigurer for further customizations
*/
public ExceptionHandlingConfigurer<H> defaultAuthenticationEntryPointFor(
AuthenticationEntryPoint entryPoint, RequestMatcher preferredMatcher) {
this.defaultEntryPointMappings.put(preferredMatcher, entryPoint);
return this;
}
/**
* Gets any explicitly configured AuthenticationEntryPoint
* @return
*/
AuthenticationEntryPoint getAuthenticationEntryPoint() {
return this.authenticationEntryPoint;
}
/**
* Gets the AccessDeniedHandler that is configured.
*
* @return the AccessDeniedHandler
*/
AccessDeniedHandler getAccessDeniedHandler() {
return this.accessDeniedHandler;
}
// SecurityConfigurer 所定义的配置方法:生成一个 ExceptionTranslationFilter 配置到目标安全构建器
@Override
public void configure(H http) throws Exception {
// 准备最终要应用到目标 ExceptionTranslationFilter 的 AuthenticationEntryPoint,
// 先尝试使用外部指定值,未指定的话使用缺省值
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
// 创建目标 ExceptionTranslationFilter
// 这里使用到了 RequestCache, 获取该 RequestCache 对象的过程是 :
// 先尝试使用 http 中的共享对象,如果没有找到,则使用缺省值, 新建一个 HttpSessionRequestCache 对象
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
entryPoint, getRequestCache(http));
// 准备访问被拒绝时要是用的处理器 AccessDeniedHandler
// 先尝试使用外部指定值,未指定的话使用缺省值
AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
// 后置处理目标 ExceptionTranslationFilter
exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
// 将目标 ExceptionTranslationFilter 添加到目标安全构建器 http
http.addFilter(exceptionTranslationFilter);
}
/**
* Gets the AccessDeniedHandler according to the rules specified by
* #accessDeniedHandler(AccessDeniedHandler)
* @param http the HttpSecurity used to look up shared
* AccessDeniedHandler
* @return the AccessDeniedHandler to use
*/
AccessDeniedHandler getAccessDeniedHandler(H http) {
AccessDeniedHandler deniedHandler = this.accessDeniedHandler;
if (deniedHandler == null) {
deniedHandler = createDefaultDeniedHandler(http);
}
return deniedHandler;
}
/**
* Gets the AuthenticationEntryPoint according to the rules specified by
* #authenticationEntryPoint(AuthenticationEntryPoint)
* @param http the HttpSecurity used to look up shared
* AuthenticationEntryPoint
* @return the AuthenticationEntryPoint to use
*/
AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;
if (entryPoint == null) {
entryPoint = createDefaultEntryPoint(http);
}
return entryPoint;
}
// 创建缺省使用的 AccessDeniedHandler : 访问被拒绝时的处理器 :
// 1. 如果 this.defaultDeniedHandlerMappings 为空,则是用一个新的 AccessDeniedHandlerImpl
// 对象:访问被拒绝时想浏览器返回状态字 403
// 2. 如果 this.defaultDeniedHandlerMappings 包含一个元素,这是用该元素;
// 3. 如果 this.defaultDeniedHandlerMappings 包含多个元素,则构造一个
// RequestMatcherDelegatingAccessDeniedHandler 对象包装和代理
// this.defaultDeniedHandlerMappings 中的这组元素,此 RequestMatcherDelegatingAccessDeniedHandler
// 缺省的 AccessDeniedHandler 则是一个新的 AccessDeniedHandlerImpl
// 对象:访问被拒绝时想浏览器返回状态字 403
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 :
// 1. 如果 this.defaultEntryPointMappings 为空,则使用一个 Http403ForbiddenEntryPoint 实例
// 2. 如果 this.defaultEntryPointMappings 只包含一个元素,直接使用该元素
// 3. 如果 this.defaultEntryPointMappings 有多个元素,构建一个 DelegatingAuthenticationEntryPoint
// 代理对象供使用,该代理对象也是一个 AuthenticationEntryPoint,它将任务代理给
// this.defaultEntryPointMappings 中的各个 AuthenticationEntryPoint 对象,并将其中第一个设置为
// 缺省
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;
}
/**
* Gets the RequestCache to use. If one is defined using
* #requestCache(org.springframework.security.web.savedrequest.RequestCache),
* then it is used. Otherwise, an attempt to find a RequestCache shared object
* is made. If that fails, an HttpSessionRequestCache is used
*
* @param http the HttpSecurity to attempt to fined the shared object
* @return the RequestCache to use
*/
private RequestCache getRequestCache(H http) {
RequestCache result = http.getSharedObject(RequestCache.class);
if (result != null) {
return result;
}
return new HttpSessionRequestCache();
}
}