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

码农架构-SpringBoot4种方式实现通用Auth认证

时间:2023-04-01 14:11:35 Java

最近被无穷无尽的业务需求压得喘不过气来。我终于得到了一份让我突破代码舒适区的工作。解决的过程很曲折,一度让我怀疑人生,但收获也很大。代码不明显,但感觉已经揭开了java、Tomcat、Spring一直遮住眼睛的那层面纱。对它们的理解达到了一个新的水平。1.前言好久没有输出了,所以挑一个方面来总结一下,希望在整理的过程中能学到一些其他的东西。由于Java生态的繁荣,以下每个模块都有大量的文章专门介绍。所以我选择了另外一个角度,从实际问题出发,把这些零散的知识联系起来,大家可以看成一个总结。对于每个模块的最终详细介绍,可以去官方文档或者阅读网上其他的博客。需求简单明了,完全不像产品提出的风骚需求:在我们的web框架中增加一个通用的appkey白名单验证功能,希望它的扩展性更好。这个web框架是本系的先行者基于spring-boot实现的。它介于业务和Spring框架之间,做一些偏向于业务的通用功能,比如日志输出、功能切换、通用参数分析等。它通常对业务是透明的。最近一直在忙着做需求,写好代码,根本没有注意到它的存在。2.传统AOP对于这个需求,首先想到的当然是Spring-boot提供的AOP接口。只需要在Controller方法前添加一个切点,然后对切点进行处理即可。?实现步骤如下:使用@Aspect声明切面类WhitelistAspect;在aspect类中添加切点whitelistPointcut()。为了实现这个切点的灵活和拼装能力,这里我们没有使用execution来拦截所有,而是添加一个注解@Whitelist,注解的方法会校验白名单。在切面类中使用spring的AOP注解@Before声明一个通知方法checkWhitelist(),用于在Controller方法执行前验证白名单。切面类的伪代码如下:@AspectpublicclassWhitelistAspect{@Before(value="whitelistPointcut()&&@annotation(whitelist)")publicvoidcheckAppkeyWhitelist(JoinPointjoinPoint,Whitelistwhitelist){checkWhitelist();//可以使用joinPoint.getArgs()获取Controller方法的参数//可以使用whitelist变量获取注解参数}@Pointcut("@annotation(com.zhenbianshu.Whitelist)")publicvoidwhitelistPointCut(){}}复制代码,在Controller方法中添加@Whitelist注解实现功能。?扩展本例中使用注解声明切点,我通过注解参数实现了白名单待校验。如果后面需要添加其他白名单,比如UID来验证,可以通过注解uid()等方法来实现自定义验证。另外,spring的AOP还支持execution(执行方法)、bean(匹配特定名称的Bean对象的执行方法)、@Around(在目标函数执行时执行)、@After等切入点声明方法(方法执行后)等通知方法。https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...https://zhuanlan.zhihu.com/p/...这样,功能实现了,但是领导不满意=_=,原因就是在项目中AOP用的太多了,被过度使用了。我建议我改变它。好吧,我不得不开始了。3.拦截器Spring的拦截器(Interceptor)也很适合实现这个功能。顾名思义,拦截器就是在Controller中的Action执行之前,通过一些参数来判断是否执行这个方法。要实现拦截器,可以实现Spring的HandlerInterceptor接口。?实现步骤如下:定义拦截器类AppkeyInterceptor类,实现HandlerInterceptor接口。实现它的preHandle()方法;通过preHandle方法中的注解和参数判断是否拦截请求,拦截请求时接口返回false;在自定义WebMvcConfigurerAdapter类中注册此拦截器;AppkeyInterceptor类如下://白名单.values();白名单变量获取注解参与数returntrue;}@OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{//方法在Controller方法执行结束后执行}@ServOverridepublicvoidServtrespontrequest,HttpServletRespontrequest,HttpServletRespontrequest,,Objecthandler,Exceptionex)throwsException{//视图视图渲染完成后执行}}复制代码?要启用拦截器,必须显式配置启用它。这里我们使用WebMvcConfigurerAdapter来配置它。需要注意的是,继承它的MvcConfiguration需要在ComponentScan路径下。@ConfigurationpublicclassMvcConfigurationextendsWebMvcConfigurerAdapter{@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newWhitelistInterceptor()).addPathPatterns("/*").order(1);//这里可以配置拦截器启用的路径顺序,当有多个拦截器时,任何一个返回false的拦截器都会让后面的请求方法不再执行}}复制代码也需要注意,拦截器成功后执行后,响应码为200,但响应数据为空。在使用拦截器实现功能后,leader终于来了个大动作:我们已经有了一个Auth参数,appkey可以从Auth参数中获取,是否在白名单中可以作为Auth的一种方式,为什么不在Auth检查时?emmm……吐血。4、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类如下webRequest,WebDataBinderFactorybinderFactory)throwsException{Whitelistwhitelist=parameter.getAnteration(Method;class)通过webRequest验证白名单,whitelistreturnnewAuthParam();}}jcopycode?Expansion当然使用parameterparser也需要单独配置,我们也在WebMvcConfigurerAdapter中配置:@Configurationpublic类MvcConfigurationextendsWebMvcConfigurerAdapter{@OverridepublicvoidaddArgumentResolvers(ListargumentResolvers){argumentResolvers.add(newAuthParamResolver());}}复制代码这次实现结束了,还是有点担心,于是上网搜索到看看有没有其他方法可以实现这个功能,发现Filter是通用的。第四,FilterFilter不是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,FilterChainchain)throwsIOException,ServletException{//判断是否需要拦截chain.doFilter(request,response);//请求传递过来显示调用}@Overridepublicvoiddestroy(){//销毁时调用一次}}复制代码复制代码