当前位置: 首页 > 科技观察

盘点SpringSecurity框架中的八个经典设计模式

时间:2023-03-23 10:44:12 科技观察

上次有小伙伴提出源码分析太枯燥。如果能结合设计模式,对大家理解SpringSecurity源码会更有帮助,同时也可以复习一下。波设计模式。所以宋哥今天就尽量完成一篇文章,和大家聊一聊SpringSecurity中涉及到的设计模式。但是,SpringSecurity中涉及到的设计模式还是很多的。宋哥这里就说几个吧,剩下的欢迎小伙伴们留言补充。1、模板方法模式模板模式(templatemethodpattern)是一个抽象类,它公开定义了一个执行其方法的模板。它的子类可以根据需要重写方法实现,但是调用将按照抽象类中定义的方式进行,这是一种行为模式。模板方法的优点如下:代码中公共的部分提取到父类中,便于代码复用和扩展。有些方法由子类实现,子类可以通过扩展添加相应的功能,符合开闭原则。缺点是:需要为每个不同的实现定义一个子类,导致类的数量增加,系统更复杂,设计更抽象。父类中的抽象方法由子类实现,子类的执行结果会影响父类的结果,增加了代码理解的难度。介绍完模板方法模式之后,大家可能会猜到模板方法模式在SpringSecurity中用在了哪些地方。我举几个简单的例子。第一个例子是AbstractUserDetailsAuthenticationProvider类的设计。大家都知道这个类是用来做认证的,在这个方法中定义了认证逻辑,但是这个类定义了两个抽象方法:retrieveUser这个方法允许用户从数据源中获取用户对象。additionalAuthenticationChecks该方法用于附加验证(登录凭据的验证)。这两个抽象方法在DaoAuthenticationProvider中实现。DaoAuthenticationProvider的实现是从数据库中加载用户,默认的验证登录凭证也是验证密码。如果你的数据源来自其他地方,或者登录凭据不是密码,那么自定义类继承自AbstractUserDetailsAuthenticationProvider,并重写其中的这两个方法。2.责任链模式(ChainofResponsibilityPattern),在这种模式下,通常每个接收者都包含对另一个接收者的引用,如果一个对象不能处理请求,那么它会把同一个请求传递给下一个接收者,等等。在这个过程中,客户端只需要向责任链发送请求,不需要关心请求的处理细节和请求的传递过程,所以责任链解耦了请求的发送者来自请求的处理器。责任链模式的优点如下:减少对象之间的耦合。增强的系统可扩展性。当工作流发生变化时,链中的成员可以动态改变或调整它们的顺序。对象之间的连接被简化,每个对象只需要维护对其后继者的引用,而不需要维护对所有其他处理器的引用。职责分担,每个班只需要处理自己应该处理的工作,符合班级单一职责原则。缺点是:相对于较长的职责链,请求的处理可能涉及多个处理对象,系统性能会受到一定影响。建立责任链的合理性有赖委托方来保证,增加了委托方的复杂性。很明显,SpringSecurity中的过滤器链是一种责任链模式。请求到达后,由过滤器链中的过滤器逐一处理。过滤器链中的每个过滤器功能不同,互不干扰。我们还可以通过HttpSecurity过滤器动态配置过滤器链中的过滤(即在过滤器链中添加/删除过滤器)。具体的代码在FilterChainProxy$VirtualFilterChain中,如下:那么接下来我们就来看看VirtualFilterChain:privatestaticclassVirtualFilterChainimplementsFilterChain{privatefinalFilterChainoriginalChain;privatefinalListadditionalFilters;privatefinalFirewalledRequestfirewalledRequest;privatefinalintsize;privateintcurrentPosition=0;privateVirtualFilterChain(FirewalledRequestfirewalledRequest,FilterChainchain,ListadditionalFilters){this.originalChain=chain;this.additionalFilters=additionalFilters;this.size=additionalFilters.size();this.firewalledRequest=firewalledRequest;}@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse)throwsIOException,ServletException{if(currentPosition==size){if(logger.isDebugEnabled()){logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)+"reachedendofadditionalfilterchain;proceedingwithoriginalchain");}//当我们退出安全过滤器链时停用路径剥离this.firewalledRequest.reset();originalChain.doFilter(请求,响应);}else{currentPosition++;FilternextFilter=additionalFilters.get(currentPosition-1);if(logger.isDebugEnabled()){logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)+"atposition"+currentPosition+5一个全局属性,originalChain表示原始过滤器链,即WebFilter;additionalFilters表示SpringSecurity中的过滤器链;firewalledRequest表示当前请求;size表示过滤器链中过滤器的个数;currentPosition是过滤器链中的遍历时下标doFilter方法就是SpringSecurity中一个一个执行filter的过程,如果currentPosition==size,说明这个filter链已经执行完了,这时候调用originalChain.doFilter进入到原先的filter链方法中,同时退出SpringSecurity过滤器链,否则从additionalFilters中取出SpringSecurity过滤器链中的过滤器,调用doFilter方法一一个。nextFilter.doFilter就是沿着过滤器链一个接一个往下走。3.StrategyPattern策略模式(StrategyPattern),它定义了一系列算法,封装了每一个算法,并允许它们相互替换。策略模式让算法独立于使用它的客户端而变化,也称为策略模式(Policy)。策略模式的优点:策略模式为“开闭原则”提供了完善的支持。用户可以在不修改原有系统的情况下选择具体的策略,也可以灵活扩展新的策略。策略模式提供了一种管理相关策略的方法。策略模式提供了一种替代继承关系的方式。使用策略模式避免使用多个条件转移语句。策略模式的缺点:客户必须知道所有的策略类并决定使用哪一个。策略模式会导致策略类很多(使用享元模式可以在一定程度上减少对象的数量)。SpringSecurity中也有几个地方使用了策略模式。首先是用户登录信息存储。publicclassSecurityContextHolder{//~Staticfields/initializers//=========================================================================================publicstaticfinalStringMODE_THREADLOCAL="MODE_THREADLOCAL";publicstaticfinalStringMODE_INHERITABLETHREADLOCAL="MODE_INHERITABLETHREADLOCAL";publicstaticfinalStringMODE_GLOBAL="MODE_GLOBAL";publicstaticfinalStringSYSTEM_PROPERTY="spring.security.strategy";privatestaticStringstrategyName=System.getProperty(SYSTEM_PROPERTY);privatestaticSecurityContextHolderStrategystrategy;}用户可以自行选择使用哪一种策略!还有一个就是会话并发管理。在AbstractAuthenticationProcessingFilter#doFilter方法中,有如下代码:publicvoiddoFilter(ServletRequestreq,ServletResponseres,FilterChainchain)throwsIOException,ServletException{//省略sessionStrategy.onAuthentication(authResult,request,response);/Strategy/mode省略}当然,这样的例子还有很多,就不一一列举了。4.ProxyPatternProxyPattern(代理模式):为某个对象提供代理,代理对象控制对原对象的引用。它是一个对象结构模型。代理模式的优点:一定程度上降低了系统的耦合度。代理对象可以扩展目标对象的功能。代理对象保护目标对象。缺点:在客户端和真实对象之间增加了一个代理,可能会减慢请求的处理速度。增加了系统的复杂性。代理模式在SpringSecurity中最重要的应用就是将SpringSecurityfilter链接到WebFilter的过程,使用Spring提供的DelegatingFilterProxy,这是典型的代理模式:IOException{//Lazilyinitializethedelegateifnecessary.FilterdelegateToUse=this.delegate;if(delegateToUse==null){synchronized(this.delegateMonitor){delegateToUse=this.delegate;if(delegateToUse==null){WebApplicationContextwac=findWebApplicationContext();if(wac==null){thrownewIllegalStateException("NoWebApplicationContextfound:"+"noContextLoaderListenerorDispatcherServletregistered?");}delegateToUse=initDelegate(wac);}this.delegate=delegateToUse;}}//让delegate执行actualdoFilteroperation.invokeinvokeDelegate(Creasef,Usef,Usef)}}的当然还有很多地方也用到了代理模式,我就不说了一一列举。欢迎朋友们留言补充。5.AdapterModeAdapterPattern(适配器模式),大家平时使用的手机充电器学名叫做电源适配器,它的作用是将220V的电压转换为手机可用的5V电压。所以适配器模式其实也有类似的效果,将一个接口转换成客户想要的另一种接口,适配器模式使得接口不兼容的类可以协同工作。适配器模式分为类适配器模式、对象适配器模式和接口适配器模式。适配器模式的优点:解耦,通过引入一个适配器类来复用已有的适配器类,而不用修改原来的代码。提高了类的透明度和可重用性。它具有更好的灵活性和可扩展性。缺点:由于Java不支持多重继承,一次最多只能适配一个适配器类,目标抽象类只能是抽象类,不能是具体类,所以其使用有一定的局限性。SpringSecurity中也有很多适配器模式,比如我们最常见的WebSecurityConfigurerAdapter,它可以让两个原本不相关的WebSecurity和HttpSecurity协同工作。详见:深入理解WebSecurityConfigurerAdapter[源代码]6.构建器模式BuilderPattern(构建器模式)是将复杂对象的构建与其表示分离,使得同一个构建过程可以创建不同的对象。用户只需要指定复杂对象的类型和内容就可以构建对象,而无需了解里面的具体构建细节。建造者模式的优点:将产品本身与产品创建过程解耦,使得同一个创建过程可以创建不同的产品对象,客户端不需要知道产品的内部细节。每个产品对应一个构建器。用户可以使用不同的构建器来创建不同的产品,并且构建器本身可以很容易地修改或添加。可以更精细地控制产品创建过程。缺点:创建的产品需要有一定的相似性。如果差异太大,则不适合构建器模式。产品本身的复杂性增加了建造者的复杂性。SpringSecurity中构建器模式的使用也有很多,比如一个典型的AuthenticationManagerBuilder,它要构建的对象是AuthenticationManager,对应的构建方法是build。在一般的构建器模式下,构建器类的名称以builder结尾,构建方法命名为build()。关于AuthenticationManagerBuilder,参见:深入理解AuthenticationManagerBuilder【源码篇】一文。7、观察者模式观察者(observermode)是指多个对象之间存在一对多的依赖关系。当一个对象的状态发生变化时,所有依赖它的对象都会收到通知并自动更新。观察者模式也称为发布-订阅模式、模型-视图模式,它是一种对象-行为模式。观察者模式的优点:降低了目标和观察者之间的耦合关系,两者之间的耦合关系是抽象的。缺点:目标和观察者之间的依赖没有完全解决,可能会出现循环引用。当观察者对象较多时,程序执行效率会降低。在Spring框架中,观察者模式用于实现ApplicationContext的事件处理功能。Spring为我们提供了ApplicationEvent类和ApplicationListener接口来启用事件处理。Spring应用程序中任何实现ApplicationListener接口的bean都将作为事件发布者接收由ApplicationEvent推送的消息。这里,事件发布者是实现ApplicationListener的bean的主体(Subject)和观察者(Observer)。具体到SpringSecurity,比如登录成功事件的发布,session销毁事件等,都属于观察者模式。例如AbstractAuthenticationProcessingFilter#successfulAuthentication方法:protectedvoidsuccessfulAuthentication(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainchain,AuthenticationauthResult)throwsIOException,ServletException{if(logger.isDebugEnabled()){logger.debug("Authenticationsuccess.UpdatingSecurityContextHoldertocontain:"+authResult);}SecurityContextHolder.getContext().setAuthentication(authResult);rememberMeServices.loginSuccess(request,response,authResult);//Fireeventif(this.eventPublisher!=null){eventPublisher.publishEvent(newInteractiveAuthenticationSuccessEvent(authResult,this.getClass()));}successHandler.onAuthenticationSuccess(re,response,authResult);}8.Decorator模式Decorator(装饰模式)是指在不改变已有对象结构的情况下,动态地给对象增加一些额外功能的模式。装饰模式的优点:可以灵活地扩展一个类的功能。缺点:增加了很多子类,使得程序非常复杂。装饰模式在SpringSecurity中也有很多应用。最典型的就是一个request在经过filterchain的过程中会不断变化,其功能也会不断调整。很多类的请求都是通过装饰模式来设计的,例如:HeaderWriterRequestFirewalledRequestStrictHttpFirewallSaveToSessionRequestWrapper...等,类似的还有很多,就不一一赘述了。本文转载自微信公众号“江南的一场小雨”,可通过以下二维码关注。转载本文请联系江南一点鱼公众号。