当前位置: 首页 > 后端技术 > Java

SpringBoot实现通用Auth认证的4种方式!

时间:2023-04-02 00:44:28 Java

来源:https://zhenbianshu.github.io/文章介绍了spring-boot中实现generalauth的四种方式,包括传统的AOP、拦截器、参数解析器和过滤器,并提供了相应的示例代码,最后是一个他们的执行顺序的简要总结。前言最近被无穷无尽的业务需求压得喘不过气来。我终于得到了一份让我突破代码舒适区的工作。解决的过程非常曲折。这让我一度怀疑人生,但收获也很大。从代码上看并不明显,但感觉已经揭开了java、Tomcat、Spring一直遮住眼睛的那层面纱。对它们的理解达到了一个新的水平。好久没有输出了,就挑一个方面来总结一下,希望在整理的过程中能学到一些其他的东西。由于Java生态的繁荣,以下每个模块都有大量的文章专门介绍。所以我选择了另外一个角度,从实际问题出发,把这些零散的知识联系起来,大家可以看成一个总结。对于每个模块的最终详细介绍,可以去官方文档或者阅读网上其他的博客。需求简单明了,完全不像产品提出的风骚需求:在我们的web框架中增加一个通用的appkey白名单验证功能,希望它的扩展性更好。这个web框架是本系的先行者基于spring-boot实现的。它介于业务和Spring框架之间,做一些偏向于业务的通用功能,比如日志输出、功能切换、通用参数分析等。它通常对业务是透明的。最近一直在忙着做需求,写好代码,根本没有注意到它的存在。对于传统AOP中的这种需求,首先想到的当然是Spring-boot提供的AOP接口。只需要在Controller方法前添加一个切点,然后对切点进行处理即可。其使用实现步骤如下:使用@Aspect声明切面类WhitelistAspect;在切点类中添加一个切点whitelistPointcut(),为了实现这个切点的灵活可组装能力,这里我们不使用execution来拦截所有,而是添加一个注解@Whitelist,注解的方法会验证白名单。在切面类中使用spring的AOP注解@Before声明一个通知方法checkWhitelist(),用于在Controller方法执行前验证白名单。切面类的伪代码如下//可以使用joinPoint.getArgs()获取Controller方法的参数//可以使用whitelist变量获取注解参数}@Pointcut("@annotation(com.zhenbianshu.Whitelist)")publicvoidwhitelistPointCut(){}}在Controller方法中添加@Whitelist注解实现功能。本例中使用注解来声明切点,我实现了注解参数来声明待验证的白名单。如果后面需要添加其他白名单,比如UID来校验,可以注解这个Add方法,比如uid()来实现自定义校验。另外,spring的AOP还支持execution(执行方法)、bean(匹配特定名称的Bean对象的执行方法)、@Around(在目标函数执行时执行)、@After等切入点声明方法(方法执行后)等通知方法。就这样,功能实现了,但是领导不满意=_=,原因是项目中AOP用的太多了,过度使用了。我建议我改变它。好吧,我不得不开始了。拦截器Spring的拦截器(Interceptor)也很适合实现这个功能。顾名思义,拦截器就是在Controller中的Action执行之前,通过一些参数来判断是否执行这个方法。要实现拦截器,可以实现Spring的HandlerInterceptor接口。实现步骤如下:定义拦截器类AppkeyInterceptor类,实现HandlerInterceptor接口。实现它的preHandle()方法;通过preHandle方法中的注解和参数判断是否拦截请求,拦截请求时接口返回false;在自定义WebMvcConfigurerAdapter类中注册此拦截器;AppkeyInterceptor类如下:@ComponentpublicclassWhitelistInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{Whitelistwhitelist=((HandlerMethod)handler).getMethodAnnotation(Whitelist.class);//白名单.values();通过白名单变量获取注解参数返回true;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{//该方法在Controller方法执行完之后执行}@OverridepublicvoidrestquestafterReplerv(Http,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{//视图视图渲染完成后执行}}要启用拦截器,必须显式配置启用它。这里我们使用WebMvcConfigurerAdapter来配置它。需要注意的是,继承它的MvcConfiguration需要在ComponentScan路径下。@ConfigurationpublicclassMvcConfigurationextendsWebMvcConfigurerAdapter{@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newWhitelistInterceptor()).addPathPatterns("/*").order(1);//这里可以配置拦截器启用的路径当有多个拦截器时,如果有一个拦截器返回false,后面的请求方法将不会被执行}}另外需要注意的是,拦截器执行成功后,响应码为200,但响应数据为空。在使用拦截器实现功能后,leader终于来了个大动作:我们已经有了一个Auth参数,appkey可以从Auth参数中获取,是否在白名单中可以作为Auth的一种方式,为什么不在Auth检查时?emmm……吐血。ArgumentResolver参数解析器是Spring提供的一个解析自定义参数的工具。我们常用的@RequestParam注解都有它的影子。使用它,我们可以在进入ControllerAction之前将参数组合成我们想要的。Spring会维护一个ResolverList。当请求到来时,Spring发现有自定义类型参数(非基本类型),会依次尝试这些Resolver,直到一个Resolver能够解析出需要的参数。要实现参数解析器,请实现HandlerMethodArgumentResolver接口。实现并定义自定义参数类型AuthParam,其中有appkey相关字段;定义AuthParamResolver并实现HandlerMethodArgumentResolver接口;实现supportsParameter()接口方法适配AuthParam和AuthParamResolver;实现resolveArgument()接口方法解析reqest对象生成AuthParam对象,并在此处查看AuthParam,确认appkey是否在白名单中;在ControllerAction方法的签名中添加AuthParam参数来开启这个Resolver;实现的AuthParamResolver类如下:}@OverridepublicObjectresolveArgument(MethodParameterparameter,ModelAndViewContainermavContainer,NativeWebRequestwebRequest,WebDataBinderFactorybinderFactory)throwsException{Whitelistwhitelist=parameter.class)//通过webRequest验证白名单,whitelistreturnnewAuthParam();}}Extension当然参数解析器的使用也需要单独配置,我们也在WebMvcConfigurerAdapter中的配置:@ConfigurationpublicclassMvcConfigurationextendsWebMvcConfigurerAdapter{@OverridepublicvoidaddArgumentResolvers(ListargumentResolvers){argumentResolvers.add(newAuthParamResolver());查找是否有其他方式实现该功能,发现常见的FilterFilterFilter并不是Spring提供的,它是在Servlet规范中定义的,由Servlet容器支持。经过Filter过滤的请求不会被分派到Spring容器中。它的实现也比较简单,只需要实现javax.servlet.Filter接口即可。由于不在Spring容器中,Filter无法获取Spring容器的资源,只能使用Java原生的ServletRequest和ServletResponse获取请求参数。另外,在一个Filter中,必须显式调用FilterChain的doFilter方法,否则认为请求被拦截。实现类似:publicclassWhitelistFilterimplementsjavax.servlet.Filter{@Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{//初始化后调用一次}@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChail)Ex,ServletException{//判断是否拦截chain.doFilter(request,response);//请求需要显示和调用}@Overridepublicvoiddestroy(){//销毁时调用一次}}ExtendedFilter也需要显示配置:@ConfigurationpublicclassFilterConfiguration{@BeanpublicFilterRegistrationBeansomeFilterRegistration(){FilterRegistrationBean注册=newFilterRegistrationBean();registration.setFilter(newWhitelistFilter());registration.addUrlPatterns("/*");registration.setName("white);listFilterregistration.setOrder(1);//设置filter的调用顺序returnregistration;}}总结四种实现各有适合的场景,那么它们之间的调用顺序是怎样的?Filter是Servlet实现的,自然先调用,然后是Interceptor被拦截了,所以不需要后续处理,然后是parameterparser,最后是aspect的切入点。在一个项目中实现所有四种方法后,输出日志也证明了这一结论。近期热点文章推荐:1.1,000+Java面试题及答案(2021最新版)2.终于通过开源项目拿到了IntelliJIDEA激活码,太贴心了!3、阿里Mock工具正式开源,秒杀市面上所有Mock工具!4、SpringCloud2020.0.0正式发布,全新颠覆版本!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!