對於這種需求,首先想到的當然是 Spring-boot 提供的 AOP 接口,只需要在 Controller 方法前添加切點,然後再對切點進行處理即可。
其使用步驟如下:
#使用@Aspect 宣告切面類別WhitelistAspect;
#在切面類別內加入一個切點whitelistPointcut(),為了實現此切點靈活可組裝的能力,這裡不使用execution 全部攔截,而是添加一個註解@Whitelist,被註解的方法才會校驗白名單。
在切面類別中使用 spring 的 AOP 註解 @Before 宣告一個通知方法 checkWhitelist() 在 Controller 方法被執行之前校驗白名單。
切面類別偽代碼如下:
@Aspect public class WhitelistAspect { @Before(value = "whitelistPointcut() && @annotation(whitelist)") public void checkAppkeyWhitelist(JoinPoint joinPoint, Whitelist whitelist) { checkWhitelist(); // 可使用 joinPoint.getArgs() 获取Controller方法的参数 // 可以使用 whitelist 变量获取注解参数 } @Pointcut("@annotation(com.zhenbianshu.Whitelist)") public void whitelistPointCut() { } }
在Controller方法上新增 @Whitelist 註解實作功能。
本例中使用了註解來宣告切點,並且我實作了透過註解參數來宣告要校驗的白名單,如果之後還需要新增其他白名單的話,如透過UID 來校驗,則可以為此註解添加uid() 等方法,實現自訂校驗。
此外,spring 的AOP 還支援execution(執行方法)、bean(符合特定名稱的Bean 物件的執行方法)等切點宣告方法和 @Around(在目標函數執行中執行) 、@After(方法執行後) 等通知方法。
如此,功能已經實現了,但領導並不滿意=_=,原因是專案中 AOP 用得太多了,都用濫了,建議我換個方式。嗯,只好搞起。另外關注:碼猿技術專欄,在後台回覆:「面試寶典」可以獲取,高清PDF最新版3625頁互聯網大廠面試題。
Spring 的 攔截器(Interceptor) 實作這個功能也非常適合。顧名思義,攔截器用於在 Controller 內 Action 被執行前透過一些參數判斷是否要執行此方法,要實作一個攔截器,可以實作 Spring 的 HandlerInterceptor 介面。
實作步驟如下:
#定義攔截器類別 AppkeyInterceptor 類別並實作 HandlerInterceptor 介面。
實作其preHandle() 方法;
#在preHandle 方法內透過註解和參數判斷是否需要攔截請求,攔截請求時介面返回false;
在自訂的WebMvcConfigurerAdapter 類別內註冊此攔截器;
@Component public class WhitelistInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Whitelist whitelist = ((HandlerMethod) handler).getMethodAnnotation(Whitelist.class); // whitelist.values(); 通过 request 获取请求参数,通过 whitelist 变量获取注解参数 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 方法在Controller方法执行结束后执行 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 在view视图渲染完成后执行 } }
@Configuration public class MvcConfiguration extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new WhitelistInterceptor()).addPathPatterns("/*").order(1); // 这里可以配置拦截器启用的 path 的顺序,在有多个拦截器存在时,任一拦截器返回 false 都会使后续的请求方法不再执行 } }
當使用攔截器實現功能後,領導終於祭出大招了:我們已經有一個Auth 參數了,appkey 可以從Auth 參數裡取到,可以把在不在白名單作為Auth 的一種方式,為什麼不在Auth 時校驗? emmm… 吐血中。
@Component public class AuthParamResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(AuthParam.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Whitelist whitelist = parameter.getMethodAnnotation(Whitelist.class); // 通过 webRequest 和 whitelist 校验白名单 return new AuthParam(); } }
@Configuration public class MvcConfiguration extends WebMvcConfigurerAdapter { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new AuthParamResolver()); } }
Filter 并不是 Spring 提供的,它是在 Servlet 规范中定义的,是 Servlet 容器支持的。被 Filter 过滤的请求,不会派发到 Spring 容器中。它的实现也比较简单,实现 javax.servlet.Filter接口即可。
由于不在 Spring 容器中,Filter 获取不到 Spring 容器的资源,只能使用原生 Java 的 ServletRequest 和 ServletResponse 来获取请求参数。
另外,在一个 Filter 中要显示调用 FilterChain 的 doFilter 方法,不然认为请求被拦截。实现类似:
public class WhitelistFilter implements javax.servlet.Filter {
@Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化后被调用一次 } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 判断是否需要拦截 chain.doFilter(request, response); // 请求通过要显示调用 } @Override public void destroy() { // 被销毁时调用一次 } }
Filter 也需要显示配置:
@Configuration public class FilterConfiguration { @Bean public FilterRegistrationBean someFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new WhitelistFilter()); registration.addUrlPatterns("/*"); registration.setName("whitelistFilter"); registration.setOrder(1); // 设置过滤器被调用的顺序 return registration; } }
以上是Springboot怎麼實現通用Auth認證的詳細內容。更多資訊請關注PHP中文網其他相關文章!