参考链接(主要参考此系列文章,截图和补充总结等由debug程序、阅读源码得出)
https://juejin.im/post/5d8d66aee51d45783f5aa49e
一、三句话解释框架原理
1、整个框架的核心是一个过滤器,这个过滤器名字叫springSecurityFilterChain类型是FilterChainProxy
2、核心过滤器里面是过滤器链(列表),过滤器链的每个元素都是一组URL对应一组过滤器
3、WebSecurity用来创建FilterChainProxy过滤器,
HttpSecurity用来创建过滤器链的每个元素。
补充解释:WebSecurity和HttpSecurity都是建造者
WebSecurity构建目标是FilterChainProxy对象
HttpSecurity的构建目标仅仅是FilterChainProxy中的一个SecurityFilterChain
@EnableWebSecurity注解,导入了WebSecurityConfiguration类
WebSecurityConfiguration中创建了建造者对象WebSecurity,和核心过滤器FilterChainProxy
二、框架接口设计
1、关注两个东西:建造者和适配器,框架的用法就是通过适配器对建造者进行配置
2、框架用法是写一个自定义配置类,继承WebSecurityConfigurerAdapter,重写几个configure()方法
WebSecurityConfigurerAdapter就是Web安全配置器的适配器对象。
// 安全建造者 // 顾名思义是一个builder构造器,创建并返回一个类型为O的对象
// 安全建造者
// 顾名思义是一个builder构造器,创建并返回一个类型为O的对象
public interface SecurityBuilder<O> {
O build() throws Exception;
}
// 抽象安全建造者
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
public final O build() throws Exception {
// 限定build()只会进行一次!
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
// 子类需要重写doBuild()方法
protected abstract O doBuild() throws Exception;
}
// 配置后的抽象安全建造者
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
// 实现了doBuild()方法,遍历configurers进行init()和configure()。
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;
}
}
// 它的子类HttpSecurity和WebSecurity都实现了它的performBuild()方法!!!
protected abstract O performBuild() throws Exception;
// 主要作用是将安全配置器SecurityConfigurer注入到属性configurers中,
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
}
// 安全配置器,配置建造者B,B可以建造O
// 初始化(init)SecurityBuilder,且配置(configure)SecurityBuilder
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
}
// Web安全配置器,配置建造者T,T可以建造web过滤器
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>>
extends SecurityConfigurer<Filter, T> {
}
// Web安全配置器的适配器
// 配置建造者WebSecurity,WebSecurity可以建造核心过滤器
public abstract class WebSecurityConfigurerAdapter
implements WebSecurityConfigurer<WebSecurity> {
}
// 用于构建FilterChainProxy的建造者
public final class WebSecurity
extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
implements
SecurityBuilder<Filter>, ApplicationContextAware {
}
// 用于构建SecurityFilterChain的建造者
public final class HttpSecurity
extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements
SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity> {
}
3、总结:
看到建造者去看他的方法:build(); doBuid(); init(); configure(); performBuilde();
看到配置器去看他的方法: init(); config();
三、从写MySecurityConfig时使用的@EnableWebSecurity注解开始看源码:
1、@EnableWebSecurity注解导入了三个类,重点关注WebSecurityConfiguration
2、WebSecurityConfiguration中需要关注两个方法:
setFilterChainProxySecurityConfigurer()方法:创建了WebSecurity建造者对象,用于后面建造FilterChainProxy过滤器。
springSecurityFilterChain()方法:调用WebSecurity.build(),建造出FilterChainProxy过滤器对象。
四、WebSecurity的创建过程(由WebSecurityConfiguration创建)
1、setFilterChainProxySecurityConfigurer()方法负责收集配置累对象列表webSecurityConfigurers,并创建WebSecurity:
/**
* Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>}
* instances used to create the web configuration.
*
* @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
* {@link WebSecurity} instance
* @param webSecurityConfigurers the
* {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
* create the web configuration
* @throws Exception
*/
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
2、下图是通过AutowiredWebSecurityConfigurersIgnoreParents的getWebSecurityConfigurers()方法,获取所有实现WebSecurityConfigurer的配置类。
五、FilterChainProxy的创建过程(由WebSecurityConfiguration创建)
1、在springSecurityFilterChain()方法中调用webSecurity.build()创建了FilterChainProxy。
PS:根据下面代码,我们可以知道如果创建的MySecurityConfig类没有被sping扫描到,
框架会新new 出一个WebSecurityConfigureAdapter对象,这会导致我们配置的用户名和密码失效。
2、WebSecurity是一个建造者,所以我们去看这些方法build(); doBuild(); init(); configure(); performBuild();
build()方法定义在WebSecurity对象的父类AbstractSecurityBuilder中:
build()方法会调用WebSecurity对象的父类AbstractConfiguredSecurityBuilder#doBuild():
doBuild()先调用init();configure();等方法
我们上面已经得知了configurersAddedInInitializing里是所有的配置类对象
如下图,这里会依次执行配置类的configure();init()方法
doBuild()最后调用了WebSecurity对象的perfomBuild(),来创建了FilterChainProxy对象
performBuild()里遍历securityFilterChainBuilders建造者列表
把每个SecurityBuilder建造者对象构建成SecurityFilterChain实例
最后创建并返回FilterChainProxy
3、securityFilterChainBuilders建造者列表的初始化
这时候要注意到WebSecurityConfigurerAdapter,这个类的创建了HttpSecurity并放入了securityFilterChainBuilders
WebSecurityConfigurerAdapter是一个安全配置器,我们知道建造者在performBuild()之前都会把循环调用安全配置器的init();configure();方法,然后创建HttpSecurity并放入自己的securityFilterChainBuilders里。
PS: 前面已经提到了,在WebSecurity初始化时,会依次将WebSecurityConfigurerAdapter的子类放入WebSecurity。
六、ServletContext如何拿到FilterChainProxy的过滤器对象
1、Bean都是存在Spring的Bean工厂里的,而且在Web项目中Servlet、Filter、Listener都要放入ServletContext中。
2、看下面这张图,ServletContainerInitializer接口提供了一个onStartup()方法,用于在Servlet容器启动时动态注册一些对象到ServletContext中。
官方的解释是:为了支持可以不使用web.xml。提供了ServletContainerInitializer,它可以通过SPI机制,当启动web容器的时候,会自动到添加的相应jar包下找到META-INF/services下以ServletContainerInitializer的全路径名称命名的文件,它的内容为ServletContainerInitializer实现类的全路径,将它们实例化。
Spring框架通过META-INF配置了SpringServletContainerInitializer类
SpringServletContainerInitializer实现了ServletContainerInitializer接口。
请注意该类上的@HandlesTypes(WebApplicationInitializer.class)注解
根据Sevlet3.0规范,Servlet容器在调用onStartup()方法时,会以Set集合的方式注入WebApplicationInitializer的子类(包括接口,抽象类)。然后会依次调用WebApplicationInitializer的实现类的onStartup方法,从而起到启动web.xml相同的作用(添加servlet,listener实例到ServletContext中)。
Spring Security中的AbstractSecurityWebApplicationInitializer就是WebApplicationInitializer的抽象子类.
当执行到下面的onStartup()方法时,会调用insertSpringSecurityFilterChain()
将类型为FilterChainProxy名称为springSecurityFilterChain的过滤器对象用DelegatingFilterProxy包装,然后注入ServletContext
七、FilterChainProxy运行过程
请求到达的时候,FilterChainProxy的dofilter()方法内部,会遍历所有的SecurityFilterChain,对匹配到的url,则一一调用SecurityFilterChain中的filter做认证或授权。
public class FilterChainProxy extends GenericFilterBean {
private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
private List<SecurityFilterChain> filterChains;
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
private HttpFirewall firewall = new StrictHttpFirewall();
public FilterChainProxy() {
}
public FilterChainProxy(SecurityFilterChain chain) {
this(Arrays.asList(chain));
}
public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChains = filterChains;
}
@Override
public void afterPropertiesSet() {
filterChainValidator.validate(this);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
doFilterInternal(request, response, chain);
}
}
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
// 根据当前请求,获得一组过滤器链
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters": " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
// 请求依次经过这组滤器链
vfc.doFilter(fwRequest, fwResponse);
}
/**
* 根据Request请求获得一个过滤器链
*/
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
/**
* 根据URL获得一个过滤器链
*/
public List<Filter> getFilters(String url) {
return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, null)
.getRequest())));
}
/**
* 返回一个过滤器链
*/
public List<SecurityFilterChain> getFilterChains() {
return Collections.unmodifiableList(filterChains);
}
// 过滤器链内部类
private static class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain;
private final List<Filter> additionalFilters;
private final FirewalledRequest firewalledRequest;
private final int size;
private int currentPosition = 0;
private VirtualFilterChain(FirewalledRequest firewalledRequest,
FilterChain chain, List<Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
this.size = additionalFilters.size();
this.firewalledRequest = firewalledRequest;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == size) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
originalChain.doFilter(request, response);
}
else {
currentPosition++;
Filter nextFilter = additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}
nextFilter.doFilter(request, response, this);
}
}
}
public interface FilterChainValidator {
void validate(FilterChainProxy filterChainProxy);
}
private static class NullFilterChainValidator implements FilterChainValidator {
@Override
public void validate(FilterChainProxy filterChainProxy) {
}
}
}
补充:WebSecurity在初始化的时候会扫描WebSecurityConfigurerAdapter配置器适配器的子类(即生成HttpSecurity配置器)。
所有的配置器会被调用init();configure();初始化配置,其中生成的每个HttpSecurity配置器都代表了一个过滤器链。
PS:如果有多个WebSecurityConfigurerAdapter配置器适配器的子类,会产生多个SecurityFilterChain过滤器链实例。Spring Security Oauth2的拓展就是这么做的。
八、spring security 怎么创建的过滤器
1、我们已经知道了springSecurityFilterChain(类型为FilterChainProxy)是实际起作用的过滤器链,DelegatingFilterProxy起到代理作用。
2、我们创建的MySecurityConfig继承了WebSecurityConfigurerAdapter。WebSecurityConfigurerAdapter就是用来创建过滤器链,重写的configure(HttpSecurity http)的方法就是用来配置HttpSecurity的。
protected void configure(HttpSecurity http) throws Exception { http .requestMatchers() // 指定当前`SecurityFilterChain`实例匹配哪些请求 .anyRequest().and() .authorizeRequests() // 拦截请求,创建FilterSecurityInterceptor .anyRequest().authenticated() // 在创建过滤器的基础上的一些自定义配置 .and() // 用and来表示配置过滤器结束,以便进行下一个过滤器的创建和配置 .formLogin().and() // 设置表单登录,创建UsernamePasswordAuthenticationFilter .httpBasic(); // basic验证,创建BasicAuthenticationFilter } 链接:https://juejin.im/post/5d9161ace51d4577ff0d9eb7 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3、http.authorizeRequests()、http.formLogin()、http.httpBasic()分别创建了ExpressionUrlAuthorizationConfigurer,FormLoginConfigurer,HttpBasicConfigurer。在三个类从父级一直往上找,会发现它们都是SecurityConfigurer建造器的子类。SecurityConfigurer中又有configure()方法。该方法被子类实现就用于创建各个过滤器,并将过滤器添加进HttpSecurity中维护的装有Filter的List中,比如HttpBasicConfigurer中的configure方法,源码如下:
HttpSecurity作为建造者会把根据api把这些配置器添加到实例中
这些配置器中大都是创建了相应的过滤器,并进行配置,最终在HttpSecurity建造SecurityFilterChain实例时放入过滤器链
九、认证过滤器 UsernamePasswordAuthenticationFilter
1、参数有username,password的,走UsernamePasswordAuthenticationFilter,提取参数构造UsernamePasswordAuthenticationToken进行认证,成功则填充SecurityContextHolder的Authentication
2、UsernamePasswordAuthenticationFilter实现了其父类AbstractAuthenticationProcessingFilter中的attemptAuthentication方法。这个方法会调用认证管理器AuthenticationManager去认证。
3、关闭formLogin()验证后则不通过UsernamePasswordAuthenticationFilter验证,可自定义拦截器进行验证拦截
十、默认验证流程
1、开启formLogin()验证后,请求会被UsernamePasswordAuthenticationFilter拦截,程序事先在InMemoryUserDetailsManager中保存了一个user在Map<String, MutableUserDetails> users 里面。拦截器会将两者进行比对。
2、默认用户信息是在类SecurityProperties中生成,程序启动时UserDetailsServiceAutoConfiguration调用inMemoryUserDetailsManager方法,内部调用getOrDeducePassword方法打印出默认的生成的密码。
十一、Spring组件@Scope
1、Bean的作用域
Singleton(单例式):在整个应用中,只创建bean的一个实例。
Prototype(原型式):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
Session(会话式):在Web应用中,为每个会话创建一个bean实例。(eg:电子商务应用中,一个bean代表一个用户的购物车,只要同一个session一个bean)。
Request(请求式):在Web应用中,为每个请求创建一个bean实例。
链接:https://www.cnblogs.com/MrSi/p/7932218.html
2、每个客户端登录都会产生一个session会话,它的生命周期 从登录系统到 session过期,期间session上存储的信息都是有效可用的,我习惯于叫它会话级的缓存,像用户登录的身份信息我们一般都会绑定到这个session上。这里我们要讲的@Scope("session"),就是spring提供出来的一个会话级bean方案,在这种模式下,用spring的DI功能来获取组件,可以做到在会话的生命周期中这个组件只有一个实例。
3、接下来再说请求(request),http协议的处理模型,从客户端发起request请求,到服务端的处理,最后response给客户端,我们称为一次完整的请求。在这样的一次请求过程中,我们的服务站可能要串行调用funcA->funcB->funcC·... 这样的一串函数,假如我们的系统需要做细致的权限校验(用户权限,数据权限),更可怕的是funcA,funcB,funcC是3个人分别实现的,而且都要做权限校验。那么极有可能会出现3个人各连接了一次数据库,读取了同一批权限数据。这里想象一下,假如一个数据读取要花2秒,那么3个方法就要花费6秒的处理时间。但实际上这些数据只用在这个请求过程中读取一次,缓存在request上下文环境中,我习惯称之为线程级缓存。关于线程级缓存java有ThreadLocal方案,像Hibernate的事务就是用这种方案维持一次执行过程中数据库连接的唯一。当然,今天要讲的@Scope("request")也可以做到这种线程级别的缓存。