赞
踩
一、整理了解下Spring Security 的工作原理
如上图所示,spring security 的主要工作就是在原有的网络请求的过滤器链(ApplicationFilterChain)中额外添加一条过滤器链(FilterChainProxy),主要用于用户认证与授权。
请求进入web容器经一些容器自身的基础加工后,进入到servlet的滤器链中,spring security 使用 DelegatingFilterProxy 这个filter,将 targetBeanName 设置为 "springSecurityFilterChain" ,也就是security生成的 FilterChainProxy 这个类的bean的名字,使 servlet 过滤器链 指向了 spring security的过滤器链 ,同时也进入到spring 容器当中。经过一系列的认证工作,若认证成功后,会生成一个Authentication类型的对象(可以记录当前登录人的用户信息,权限信息),放入到SecurityContext中(默认以ThreadLocal的方式保存),然后返回servlet 过滤器链中,向控制层前行。
security的过滤器链会根据你所配置需要启停的功能增减,同时你也可以创建自己的过滤器添加进这个过滤器链中。
二、FilterChainProxy源码分析
- @Override
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- //此处是对请求是否第一次经过这个过滤器的判断,在spring security中的过滤器中这种用法是相当常见的
- boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
- if (clearContext) {
- try {
- request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
- doFilterInternal(request, response, chain);
- }
- finally {
- //清除security上下文中的保存的Authentication对象
- SecurityContextHolder.clearContext();
- request.removeAttribute(FILTER_APPLIED);
- }
- }
- else {
- doFilterInternal(request, response, chain);
- }
- }
-
-
- private void doFilterInternal(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- //对request进行一层包装,同时校验了一下请求的方式是否允许,以及请求地址中是否有非法字符,若不通过抛出异常
- FirewalledRequest fwRequest = firewall
- .getFirewalledRequest((HttpServletRequest) request);
- //对response进行了一层包装,若在response中添加header或cookie时,会对header和cookie的一些属性进行校验
- HttpServletResponse fwResponse = firewall
- .getFirewalledResponse((HttpServletResponse) response);
-
- //获取spring security中第一个匹配请求地址的过滤器列表
- 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;
- }
-
- //将过滤器列表和servlet过滤器链包装成一个新的过滤器链 VirtualFilterChain 是FilterChainProxy的内部类
- VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
- vfc.doFilter(fwRequest, fwResponse);
- }
-
- 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 {
- //security 过滤器执行结束后,继续执行servlet过滤器链
- 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() + "'");
- }
- //传入this,nextFilter中的会继续调用VirtualFilterChain.doFilter()直至所有过滤器执行结束或抛出异常
- nextFilter.doFilter(request, response, this);
- }
- }
- }
FilterChainProxy过滤器通过内部的FilterChain,将请求引入到了security的过滤器链中,上述对其主要的方法进行了简单描述。
三、基于SpringBoot,security的加载过程
springboot 启动时 有关spring security 主要加载的类有SecurityAutoConfiguration、SecurityRequestMatcherProviderAutoConfiguration、UserDetailsServiceAutoConfiguration、SecurityFilterAutoConfiguration , 其中需要讲一下SecurityAutoConfiguration 和SecurityFilterAutoConfiguration。
1、 SecurityAutoConfiguration 引入了SpringBootWebSecurityConfiguration、WebSecurityEnablerConfiguration、SecurityDataConfiguration 这三个配置类:
SpringBootWebSecurityConfiguration 当你没有主动配置security时,这个类的作用会生效,生一套默认的配置
WebSecurityEnablerConfiguration 的作用全在@EnableWebSecurity 这个注解上,使Security功能生效,并且承担了其中最主要的初始化工作。
@EnableWebSecurity 引入了 WebSecurityConfiguration类 ,在这个类中读取了所有的security配置(可以配置多个spring security 过滤器链),并根据这些配置生成一个name为“springSecurityFilterChain” 的bean,里边进行的动作相当多,有兴趣的同学可以研读一下这一块源码。
SecurityDataConfiguration 属于spring security 的一个扩展配置,此处省略。
2、SecurityFilterAutoConfiguration 的作用只有一个,就是创建一个DelegatingFilterProxyRegistrationBean,将spring security 的过滤器链springSecurityFilterChain 与servlet过滤器相连。
四、注意事项
1、当手动添加过滤器到spring security中时,如果这个过滤器添加到spring 容器中,spring 自动会将此过滤器添加到servlet过滤器链中,此时,两条过滤器链都会有这个过滤器,则会执行两次。
解决办法:
让这个过滤器继承OncePerRequestFilter ,这个过滤器内部保证只执行一次。
或者不要将这个过滤器添加到spring容器中。
2、在使用spring security 自带的csrf防护功能时,在前端并发请求会导致认证不通过,原因是前端同时需要发起多个请求时,这几个请求的发送总是会有一些先后顺序,当第n(n>1)个请求准备发起时,获取到的cookie中的csrfToken为token1, 此时第一个请求的已响应,cookie中的csrfToken随之替换成第一个请求中返回的cookie中的csrfToken2,这时第n个请求发出,header中携带的是token1,cookie中放的是token2,两者不相等,请求拒绝。 所以这个功能要视具体情况而用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。