为什么80%的码农都做不了架构师?>>>   

1. 为什么要在运行时动态的开关 Spring Security?

考虑这样一个场景,当我们构建了一整套微服务架构的系统后,公司某个内部的老系统也感受到了微服务架构的好处,包括实时监控,限流,熔断,高可用的机制等等,老系统的开发人员也希望能减少自己的一些工作量,所以他们系统将老系统加入到我们的微服务架构体系中来。这样就产生了一些适配,兼容性问题,如果让老系统来完全适配已经构建好的微服务架构体系那么老系统改动的代价就比较大,包括技术的升级,开发人员的学习成本提高,测试问题,还有老系统还有一些不断的新需求要开发。比较理想的解决方案是对老系统的改动越小越好,最好能做到无缝集成,已经构建好的微服务架构来为老系统的集成提供支持。比如说老系统原本有自己的认证,授权控制,使用了 Spring Security ,在微服务架构中我们将认证,授权的工作统一放在了 API 网关层去处理。这样就和老系统的集成产生了冲突。于是我就需要让 API 网关路由到老系统上的请求不经过老系统自身的认证、授权流程,也可以正常访问。同时也不能破坏当不通过 API 网关访问时老系统的认证、授权流程也要能正常工作。所以这是我要达到的目的。

2. Spring Security 在 Web 项目中是如何工作的

这是我在网上找的一张图,目的就是为了大概说明问题。 Spring Web Security 的核心功能都是在这一条过滤器链上完成的。具体可以参考这个类 :

org.springframework.security.config.annotation.web.builders.FilterComparator , 这个类中定义了所有的 Spring Security 的过滤器以及他们的顺序。

搞清楚这一点,我就有一个想法,既然我想要关闭 Spring Security 不让他起作用 ,那我不让请求经过这些过滤器不就可以了么。

FilterComparator 源码 :

private static final int STEP = 100;private Map<String, Integer> filterToOrder = new HashMap<String, Integer>();FilterComparator() {int order = 100;put(ChannelProcessingFilter.class, order);order += STEP;put(ConcurrentSessionFilter.class, order);order += STEP;put(WebAsyncManagerIntegrationFilter.class, order);order += STEP;put(SecurityContextPersistenceFilter.class, order);order += STEP;put(HeaderWriterFilter.class, order);order += STEP;put(CorsFilter.class, order);order += STEP;put(CsrfFilter.class, order);order += STEP;put(LogoutFilter.class, order);order += STEP;put(X509AuthenticationFilter.class, order);order += STEP;put(AbstractPreAuthenticatedProcessingFilter.class, order);order += STEP;filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",order);order += STEP;put(UsernamePasswordAuthenticationFilter.class, order);order += STEP;put(ConcurrentSessionFilter.class, order);order += STEP;filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order);order += STEP;put(DefaultLoginPageGeneratingFilter.class, order);order += STEP;put(ConcurrentSessionFilter.class, order);order += STEP;put(DigestAuthenticationFilter.class, order);order += STEP;put(BasicAuthenticationFilter.class, order);order += STEP;put(RequestCacheAwareFilter.class, order);order += STEP;put(SecurityContextHolderAwareRequestFilter.class, order);order += STEP;put(JaasApiIntegrationFilter.class, order);order += STEP;put(RememberMeAuthenticationFilter.class, order);order += STEP;put(AnonymousAuthenticationFilter.class, order);order += STEP;put(SessionManagementFilter.class, order);order += STEP;put(ExceptionTranslationFilter.class, order);order += STEP;put(FilterSecurityInterceptor.class, order);order += STEP;put(SwitchUserFilter.class, order);}

3. Spring Security 的过滤器链是如何工作的

3.1 Spring Security 过滤器链是什么 ?

通过 debug 调试 可以发现在 Spring Security 提供的过滤器中使用的 FilterChain 的实际类型是这个类 : org.springframework.security.web.FilterChainProxy.VirtualFilterChain 。它实现了 FilterChain 接口。

3.2 Spring Security 过滤器链初始化

通过搜索可以找到过滤器链条是在这个函数中进行初始化的 : org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild 源码:

@Overrideprotected Filter performBuild() throws Exception {Assert.state(!securityFilterChainBuilders.isEmpty(),"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "+ WebSecurity.class.getSimpleName()+ ".addSecurityFilterChainBuilder directly");int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(chainSize);for (RequestMatcher ignoredRequest : ignoredRequests) {securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));}for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {securityFilterChains.add(securityFilterChainBuilder.build());}FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);if (httpFirewall != null) {filterChainProxy.setFirewall(httpFirewall);}filterChainProxy.afterPropertiesSet();Filter result = filterChainProxy;if (debugEnabled) {logger.warn("\n\n"+ "********************************************************************\n"+ "**********        Security debugging is enabled.       *************\n"+ "**********    This may include sensitive information.  *************\n"+ "**********      Do not use in a production system!     *************\n"+ "********************************************************************\n\n");result = new DebugFilter(filterChainProxy);}postBuildAction.run();return result;}

org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain 源码:FilterChainProxy 本身也是一个过滤器这个过滤器会被注册到过滤器链上。然后这个过滤器内部封装了 Spring Security 的过滤器链条。

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)public Filter springSecurityFilterChain() throws Exception {boolean hasConfigurers = webSecurityConfigurers != null&& !webSecurityConfigurers.isEmpty();if (!hasConfigurers) {WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {});webSecurity.apply(adapter);}return webSecurity.build();}

3.3 Spring Security 过滤器链的工作过程

org.springframework.security.web.FilterChainProxy#doFilter 源码 :

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);}}

org.springframework.security.web.FilterChainProxy#doFilterInternal 源码 : 这个函数是 Spring Security 过滤器链条的执行入口。每次请求都会 new 一个 VirtualFilterChain 的实例对象,然后调用该对象的 doFilter 函数,于是请求就进入到 Spring Security 的过滤器链处理中。

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);}

org.springframework.security.web.FilterChainProxy.VirtualFilterChain#doFilter 源码 :这里就是去挨个调用 Spring Security 的过滤器的过程 ,重点需要关注的是 originalChain (原始的过滤器链条也就是 servlet 容器的) , currentPosition  (spring security 过滤器链当前执行到的位置) , size (spring security 过滤器链中过滤器的个数) 。 当 currentPosition  == size 的时候也就意味着 spring security 的过滤器链条执行完了,于是就该使用原始的 originalChain 继续去调用 servlet 容器中注册的过滤器了。

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 chainthis.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);}}}

3.4 运行时跳过 Spring Security 过滤器链的思路

了解上面讲的 spring security 过滤器链的执行过程后如何跳过spring security 的过滤器链就显而易见了 , 只需要控制 org.springframework.security.web.FilterChainProxy.Virt    ualFilterChain 对象中的 currentPosition  == size 就可以了。但是 org.springframework.security.web.FilterChainProxy.VirtualFilterChain 这个类是内部私有的静态成员类。 Spring Security 的目的就是为了封装它将它隐藏起来,想想也可以理解毕竟这是它之所以能实现功能的核心,肯定不希望被乱动。但是没办法为了实现我的需求,我还是要乱动它,想要改变它的话就只有通过反射的方式才能实现。

3.4.1 关于反射的小技巧

Class.forName("org.springframework.security.web.FilterChainProxy.VirtualFilterChain"); 使用这行代码的时候是不能成功获取到 VirtualFilterChain 类的 Class 对象的。因为 Java 中内部类在编译成 .class 文件后名称是这样的 FilterChainProxy$VirtualFilterChain 。想要成功获取到这个内部类的 Class 对象的话需要这样写 Class.forName("org.springframework.security.web.FilterChainProxy$VirtualFilterChain");

3.5 运行时跳过 Spring Security 过滤器链的实现方式

首先我自定义了一个过滤器并且把它加入到 Spring Security 过滤器链条中的最前面,因为我的目的是完全的“关闭”掉spring security 的过滤器链。示例代码 :通过反射的方式改变 currentPosition 的值即可。

@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {if (! isSkipOver(request)) {filterChain.doFilter(request , response);return;}Class<? extends FilterChain> filterChainClass = filterChain.getClass();try {Class<?> virtualFilterChainClass =Class.forName("org.springframework.security.web.FilterChainProxy$VirtualFilterChain");if (virtualFilterChainClass.isAssignableFrom(filterChainClass)) {Reflect reflect = Reflect.on(filterChain);Object size = reflect.field("size").get();reflect.set("currentPosition" , size);}} catch (Throwable t) {throw new ApplicationRuntimeException(t);}filterChain.doFilter(request , response);}

4. 广告

如果你也面临到我所说的类似问题,需要编写一些代码来解决你的问题的话,那么上面这些代码已经不需要你再去花时间编写了,我已经写好了。你只需要通过 maven :

<dependency>
  <groupId>org.hepeng</groupId>
  <artifactId>hp-java-commons</artifactId>
  <version>1.1.3</version>
</dependency>

或者是 gradle :  implementation 'org.hepeng:hp-java-commons:1.1.3'

再自定义一个 Filter 并且继承 org.hepeng.commons.spring.security.web.filter.SkipOverSpringSecurityFilterChainFilter 即可。它已经经过了我的多次测试可以正常工作,如果你发现任何 bug 可以向我反馈,我会尽快修复。

转载于:https://my.oschina.net/j4love/blog/2989947

运行时动态的开关 Spring Security相关推荐

  1. LINQ to SQL 运行时动态构建查询条件

    原文地址:http://msdn.microsoft.com/zh-cn/dd567295.aspx 在进行数据查询时,经常碰到需要动态构建查询条件.使用LINQ实现这个需求可能会比以前拼接SQL语句 ...

  2. Java运行时动态加载类之URLClassLoader

    需求场景:通过URLClassLoader从jar文件中加载类并创建实例,可实现运行时动态加载 1.要加载的jar: 1)接口类IC package cn.fjs;public interface I ...

  3. SAP UI5 应用开发教程之五十八 - 使用工厂方法在运行时动态创建不同类型的列表行项目控件试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  4. SAP Spartacus table cell如何通过cxOutlet在运行时动态注入组件

    cxOutlet合集 SAP Spartacus自定义指令cxOutlet的工作原理 SAP Spartacus table cell如何通过cxOutlet在运行时动态注入组件 SAP Sparta ...

  5. 【java】Java运行时动态生成类几种方式

    1.概述 转载:Java运行时动态生成类几种方式 这里发现自己不知道的,原来Java 还能自己编译自己,学到了. 最近一个项目中利用规则引擎,提供用户拖拽式的灵活定义规则.这就要求根据数据库数据动态生 ...

  6. 动态生成java类_Java 运行时动态生成class

    Java是一门静态语言,通常,我们需要的class在编译的时候就已经生成了,为什么有时候我们还想在运行时动态生成class呢? 因为在有些时候,我们还真得在运行时为一个类动态创建子类.比如,编写一个O ...

  7. 运行时动态引入JS文件

    运行时动态引入JS文件(尚在开发环境) 1.添加方法 requireJSFiles export function requireJSFiles (target, pathArr) {return n ...

  8. android下运行时动态链接dlopen()和dlsym()的实现

    在android中,就如同在Linux下一样,我们也可以在app中,运行时动态加载一些动态链接库,执行调用其中的函数等操作.实现这一切最终依靠的就是dlopen()等几个函数.关于这几个函数的原型机这 ...

  9. Xilinx HLS FFT IP核运行时动态配置FFT长度

    如上图所示,xilinx hls的fft ip核不仅可以计算固定长度的FFT变换,还可以在运行时动态配置fft变换长度,但其可配置的长度仅限于小于等于最大长度的所有可能的2的幂,即若该fft ip可支 ...

最新文章

  1. do{}while(0)用法
  2. 浅谈MySQL架构体系
  3. 【荐】CSS多级导航菜单
  4. 自定义组件 点击空白处隐藏
  5. 查看文件more、less
  6. 迁移学习 简而言之_简而言之SPIFFE
  7. BGP——BGP优化技术(总结+配置)
  8. SQL-用JOIN连接多个表
  9. linux日志打印规则,Linux 打印简单日志(一)
  10. vsftp启用root用户
  11. html基础之 input:type
  12. Linux signal 那些事儿 (3)
  13. 数据结构:图的深度优先遍历和广度优先遍历
  14. 计算机管理储存u盘无法使用,小编教你无法格式化u盘怎么解决
  15. easyui-filebox java上传附件,在EasyUI项目中使用FileBox控件实现文件上传处理
  16. java中average方法_Java中的IntStream average()方法
  17. python:烤地瓜程序
  18. “互联网+”拯救了星巴克,出路是“第四空间
  19. 什么是ICP经营许可证?
  20. mac升级python版本_Mac上python如何升级?

热门文章

  1. C++STL笔记(十一):priority queue(带优先级的队列)详解
  2. C++新特性探究(13.6):右值引用再探究
  3. python获取eth0_详解 Python 获取网卡 IP 地址的黑魔法
  4. python哪些系统可以运行_python可以检测它在哪个操作系统下运行吗?
  5. matlab 柱状图_MATLAB作图实例:24:条形图
  6. mysql root账号_修改mysql root账号密码
  7. java匿名类对象的坏处_java匿名内部类的使用注意事项
  8. 云南大学软件学院计算机网络实验三,云南大学 软件学院 计网实验5
  9. java xml 表达式语言_中级Java开发工程师笔试题
  10. datetime类型的取年月日 sql_SQL2005怎么截取datetime类型字段的年月日,并以截取后的(年月日)字段排序...