1、基础用法
- • ChannelProcessingFilter是SpringSecurity的第一个过滤器,具体排序规则见FilterComparator,
image.png
- • 主要作用:可限制服务端接受的安全协议,比如说仅支持Https或者Http
1.1 开启配置类:
- • 首先我们注册到容器中的WebSecurityConfigurerAdapter是针对于WebSecurity的配置类,但是像Cors、Csrf等等功能是靠HttpSecurity配置的, 我们可以通过重写此方法,并通过requiresChannel()注册一个ChannelSecurityConfigurer,这是用于配置通道的配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
......
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel();
}
......
}
1.2 使用
- • 注册通道处理器(ChannelProcessor)
http.requiresChannel().channelProcessors(list)
- • 通道处理器是用于用于判断是否满足某些安全条件,比如说
- • InsecureChannelProcessor:要求请求使用非安全通道
- • SecureChannelProcessor:要求请求使用安全通道
- • 针对于哪些请求需要进行安全通道的限制
http.requiresChannel().mvcMatchers("/*").requires("REQUIRES_SECURE_CHANNEL")
http.requiresChannel().mvcMatchers("/*").requires("REQUIRES_INSECURE_CHANNEL")
- • 注册一个ObjectPostProcessor,让过滤器在创建完成后执行的回调方法
http.requiresChannel().withObjectPostProcessor(new ObjectPostProcessor<ChannelProcessingFilter>() {
@Override
public <O extends ChannelProcessingFilter> O postProcess(O object) {
return object;
}
});
- • 这并不是当前介绍的这个过滤器独有,而应该是大部分过滤器都应该具备的,比如说ExceptionTranslationFilter,BasicAuthenticationFilter等等过滤器都具备的,都是在对应的配置类中负责执行的
2、ChannelSecurityConfigurer
- • 所有的配置类都是基于建造者模式进行的
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
- • ChannelSecurityConfigurer只重写了configure方法,这里也只讲这个,这里出现了两个新的类
- • ChannelDecisionManagerImpl:负责管理注册的ChannelProcessor
- • DefaultFilterInvocationSecurityMetadataSource:主要是根据请求确定本次请求需要具备哪些安全属性或者说权限
@Override
public void configure(H http) {
ChannelDecisionManagerImpl channelDecisionManager = new ChannelDecisionManagerImpl();
//设置通道处理器
channelDecisionManager.setChannelProcessors(getChannelProcessors(http));
channelDecisionManager = postProcess(channelDecisionManager);
//设置通道决策管理器
this.channelFilter.setChannelDecisionManager(channelDecisionManager);
//设置安全元数据源
DefaultFilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource = new DefaultFilterInvocationSecurityMetadataSource(
this.requestMap);
this.channelFilter.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
this.channelFilter = postProcess(this.channelFilter);
http.addFilter(this.channelFilter);
}
- • 第6行和14行代码就是要执行注册的ObjectPostProcessor
- • 接下来看getChannelProcessors(http)方法
/**
* 获得通道处理器
* @param http
* @return
*/
private List<ChannelProcessor> getChannelProcessors(H http) {
if (this.channelProcessors != null) {
return this.channelProcessors;
}
//创建安全通过处理器
InsecureChannelProcessor insecureChannelProcessor = new InsecureChannelProcessor();
SecureChannelProcessor secureChannelProcessor = new SecureChannelProcessor();
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if (portMapper != null) {
RetryWithHttpEntryPoint httpEntryPoint = new RetryWithHttpEntryPoint();
httpEntryPoint.setPortMapper(portMapper);
insecureChannelProcessor.setEntryPoint(httpEntryPoint);
//创建不安全通过处理器
RetryWithHttpsEntryPoint httpsEntryPoint = new RetryWithHttpsEntryPoint();
httpsEntryPoint.setPortMapper(portMapper);
secureChannelProcessor.setEntryPoint(httpsEntryPoint);
}
insecureChannelProcessor = postProcess(insecureChannelProcessor);
secureChannelProcessor = postProcess(secureChannelProcessor);
return Arrays.asList(insecureChannelProcessor, secureChannelProcessor);
}
- • 可以看到当我们没有为其注册通道处理器的时候,默认是注册两个,一个是要求安全的一个是非安全的
- • 这里出现了两个新类:PortMapper和RetryWithHttpEntryPoint
- • PortMapper:如果是系统要求使用安全的也就是Https那么就需要进行重定向,但是重定向后的端口怎么确定呢,就是靠这个类
- • 可以看出默认就只有两个端口的转换
public PortMapperImpl() {
this.httpsPortMappings = new HashMap<>();
this.httpsPortMappings.put(80, 443);
this.httpsPortMappings.put(8080, 8443);
}
- • RetryWithHttpEntryPoint:当请求的安全协议不支持的时候,进行重定向操作
@Override
public void commence(HttpServletRequest request, HttpServletResponse response) throws IOException {
String queryString = request.getQueryString();
String redirectUrl = request.getRequestURI() + ((queryString != null) ? ("?" + queryString) : "");
Integer currentPort = this.portResolver.getServerPort(request);
Integer redirectPort = getMappedPort(currentPort);
if (redirectPort != null) {
// http和https默认的端口不需要设置
boolean includePort = redirectPort != this.standardPort;
String port = (includePort) ? (":" + redirectPort) : "";
redirectUrl = this.scheme + request.getServerName() + port + redirectUrl;
}
this.logger.debug(LogMessage.format("Redirecting to: %s", redirectUrl));
// 设置重定向Url
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
}
3、ChannelProcessingFilter
- • 此过滤器就两个方法:afterPropertiesSet() 和 doFilter(......)
- • afterPropertiesSet():检查注册的安全属性是否合法
@Override
public void afterPropertiesSet() {
......
// 获取所有安全属性
Collection<ConfigAttribute> attrDefs = this.securityMetadataSource
.getAllConfigAttributes();
if (attrDefs == null) {
if (this.logger.isWarnEnabled()) {
this.logger
.warn("Could not validate configuration attributes as the FilterInvocationSecurityMetadataSource did "
+ "not return any attributes");
}
return;
}
Set<ConfigAttribute> unsupportedAttributes = new HashSet<>();
// 判断通道管理器中的通道处理器是否支持对于的安全属性(权限)
for (ConfigAttribute attr : attrDefs) {
if (!this.channelDecisionManager.supports(attr)) {
unsupportedAttributes.add(attr);
}
}
// 不为空就说明有安全属性不支持直接抛出异常
if (unsupportedAttributes.size() == 0) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Validated configuration attributes");
}
}
else {
throw new IllegalArgumentException(
"Unsupported configuration attributes: " + unsupportedAttributes);
}
}
- • doFilter(......):
- • 第一步:通过安全元数据源获得接口所需权限
- • 第二步:调用通道决策管理器判断请求是否合法,如果不合法就会在内部设置重定向的参数,然后这里就会直接返回了
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
//包装请求
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
//通过安全元数据源获得接口所需权限
Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(filterInvocation);
if (attributes != null) {
this.logger.debug(LogMessage.format("Request: %s; ConfigAttributes: %s", filterInvocation, attributes));
//调用通道决策管理器
this.channelDecisionManager.decide(filterInvocation, attributes);
//是否已经完成
if (filterInvocation.getResponse().isCommitted()) {
return;
}
}
chain.doFilter(request, response);
}