SpringBoot使⽤过滤器和分别实现REST接⼝简易
安全认证⽰例代码详解
本⽂通过⼀个简易安全认证⽰例的开发实践,理解过滤器和的⼯作原理。
很多⽂章都将过滤器(Filter)、(Interceptor)和(Listener)这三者和Spring关联起来讲解,并认为过滤器(Filter)、(Interceptor)和(Listener)是Spring提供的应⽤⼴泛的组件功能。
但是严格来说,过滤器和属于Servlet范畴的API,和Spring没什么关系。
因为过滤器继承⾃javax.servlet.Filter接⼝,继承⾃javax.servlet.ServletContextListener接⼝,只有继承的是org.springframework.web.servlet.HandlerInterceptor接⼝。
上⾯的流程图参考⾃⽹上资料,⼀图胜千⾔。看完本⽂以后,将对过滤器和的调⽤过程会有更深刻理解。
⼀、安全认证设计思路
有时候内外⽹调⽤API,对安全性的要求不⼀样,很多情况下外⽹调⽤API的种种限制在内⽹根本没有必要,但是⽹关部署的时候,可能因为成本和复杂度等问题,内外⽹要调⽤的API会部署在⼀起。
实现REST接⼝的安全性,可以通过成熟框架如Spring Security或者 shiro 搞定。
但是因为安全框架往往实现复杂(我数了下Spring Security,洋洋洒洒⼤概有11个核⼼模块,shiro的源码代码量也⽐较惊⼈)同时可能要引⼊复杂配置(能不能让⼈痛快⼀点),不利于中⼩团队的灵活快速开发、部署及问题排查。
很多团队⾃⼰造轮⼦实现安全认证,本⽂这个简易认证⽰例参考⾃我所在的前⼚开发团队,可以认为是个基于token的安全认证服务。
⼤致设计思路如下:
1、⾃定义http请求头,每次调⽤API都在请求头⾥传⼈⼀个token值
2、token放在缓存(如redis)中,根据业务和API的不同设置不同策略的过期时间
3、token可以设置⽩名单和⿊名单,可以限制API调⽤频率,便于开发和测试,便于紧急处理异状,甚⾄临时关闭API
4、外⽹调⽤必须传⼈token,token可以和⽤户有关系,⽐如每次打开页⾯或者登录⽣成token写⼊请求头,页⾯验证cookie和token有效性等
在Spring Security框架⾥有两个概念,即认证和授权,认证指可以访问系统的⽤户,⽽授权则是⽤户可以访问的资源。
实现上述简易安全认证需求,你可能需要独⽴出⼀个token服务,保证⽣成token全局唯⼀,可能包含的模块有⾃定义流⽔⽣成器、CRM、加解密、⽇志、API统计、缓存等,但是和⽤户(CRM)其实是弱绑定关系。某些和⽤户有关系的公共服务,⽐如我们经常⽤到的SMS和邮件服务,也可以通过token机制解决安全调⽤问题。
综上,本⽂的简易安全认证其实和Spring Security框架提供的认证和授权有点不⼀样,当然,这种“安全”处理⽅式对专业⼈⼠没什么新意,但是可以对外挡掉很⼤⼀部分⼩⽩⽤户。
⼆、⾃定义Filter
和Spring MVC类似,Spring Boot提供了很多servlet过滤器(Filter)可使⽤,并且它⾃动添加了⼀些常⽤过滤器,⽐如CharacterEncodingFilter(⽤于处理编码问题)、HiddenHttpMethodFilter(隐藏HTTP函数)、HttpPutFormContentFilter(form表单处理)、RequestContextFilter(请求上下⽂)等。通常我们还会⾃定义Filter实现⼀些通⽤功能,⽐如记录⽇志、判断是否登录、权限验证等。
1、⾃定义请求头
很简单,在request header添加⾃定义请求头authtoken:
@RequestMapping(value = "/getinfobyid", method = RequestMethod.POST)
@ApiOperation("根据商品Id查询商品信息")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "header", name = "authtoken", required = true, value = "authtoken", dataType =
"String"),
})
public GetGoodsByGoodsIdResponse getGoodsByGoodsId(@RequestHeader String authtoken, @RequestBody GetGoodsByGoodsIdRequest request) {  return _GoodsByGoodsId(request);
}
getGoodsByGoodsId
加了@RequestHeader修饰的authtoken字段就可以在swagger这样的框架下显⽰出来。
调⽤后,可以根据http⼯具看到请求头,本⽂⽰例是authtoken(和某些框架的token区分开):
备注:很多httpclient⼯具都⽀持动态传⼈请求头,⽐如RestTemplate。
2、实现Filter
Filter接⼝共有三个⽅法,即init,doFilter和destory,看到名称就⼤概知道它们主要⽤途了,通常我们
只要在doFilter这个⽅法内,对Http请求进⾏处理:
package com.ller.filter;
import com.power.demomon.AppConst;
import com.power.demomon.BizResult;
import com.power.act.AuthTokenService;
import com.power.demo.util.PowerLogger;
import com.power.demo.util.SerializeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class AuthTokenFilter implements Filter {
@Autowired
private AuthTokenService authTokenService;
@Override
public void init(FilterConfig var1) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String token = Header(AppConst.AUTH_TOKEN);
BizResult<String> bizResult = authTokenService.powerCheck(token);
System.out.println(SerializeUtil.Serialize(bizResult));
if (IsOK() == true) {
PowerLogger.info("auth token filter passed");
chain.doFilter(request, response);
} else {
throw new Message());
}
}
@Override
public void destroy() {
}
}
AuthTokenFilter
注意,Filter这样的东西,我认为从实际分层⾓度,多数处理的还是表现层偏多,不建议直接在Filter中直接使⽤数据访问层Dao,虽然这样的代码⼀两年前我在很多⽼古董项⽬中看到过很多次,⽽且<<Spring实战>>的书⾥也有这样写的先例。
3、认证服务
这⾥就是主要业务逻辑了,⽰例代码只是简单写下思路,不要轻易就⽤于⽣产环境:
package com.power.demo.service.impl;
import com.power.demo.cache.PowerCacheBuilder;
import com.power.demomon.BizResult;
import com.power.act.AuthTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class AuthTokenServiceImpl implements AuthTokenService {
@Autowired
private PowerCacheBuilder cacheBuilder;
/*
* 验证请求头token是否合法
* */
@Override
public BizResult<String> powerCheck(String token) {
BizResult<String> bizResult = new BizResult<>(true, "验证通过");
System.out.println("token的值为:" + token);
if (StringUtils.isEmpty(token) == true) {
bizResult.setFail("authtoken为空");
return bizResult;
}
//处理⿊名单
bizResult = checkForbidList(token);
if (IsOK() == false) {
return bizResult;
}
//处理⽩名单
bizResult = checkAllowList(token);
if (IsOK() == false) {
return bizResult;
}
String key = String.format("Power.AuthTokenService.%s", token);
//cacheBuilder.set(key, token);
//cacheBuilder.set(key, UpperCase());
/springboot aop
/从缓存中取
String existToken = (key);
if (StringUtils.isEmpty(existToken) == true) {
bizResult.setFail(String.format("不存在此authtoken:%s", token));
return bizResult;
}
//⽐较token是否相同
Boolean isEqual = token.equals(existToken);
if (isEqual == false) {
bizResult.setFail(String.format("不合法的authtoken:%s", token));
return bizResult;
}
//do something
return bizResult;
}
}
AuthTokenServiceImpl
⽤到的缓存服务可以参考这⾥,这个也是我在前⼚的经验总结。
4、注册Filter
常见的有两种写法:
(1)、使⽤@WebFilter注解来标识Filter
@Order(1)
@WebFilter(urlPatterns = {"/api/v1/goods/*", "/api/v1/userinfo/*"})
public class AuthTokenFilter implements Filter {
使⽤@WebFilter注解,还可以配合使⽤@Order注解,@Order注解表⽰执⾏过滤顺序,值越⼩,越先执⾏,这个Order⼤⼩在我们编程过程中就像处理HTTP请求的⽣命周期⼀样⼤有⽤处。当然,如果没有指定Order,则过滤器的调⽤顺序跟添加的过
滤器顺序相反,过滤器的实现是责任链模式。
最后,在启动类上添加@ServletComponentScan 注解即可正常使⽤⾃定义过滤器了。
(2)、使⽤FilterRegistrationBean对Filter进⾏⾃定义注册
本⽂以第⼆种实现⾃定义Filter注册:
package com.ller.filter;
llect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import t.annotation.Bean;
import t.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.List;
@Configuration
@Component
public class RestFilterConfig {
@Autowired
private AuthTokenFilter filter;
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(filter);
//设置(模糊)匹配的url
List<String> urlPatterns = wArrayList();
urlPatterns.add("/api/v1/goods/*");
urlPatterns.add("/api/v1/userinfo/*");
registrationBean.setUrlPatterns(urlPatterns);
registrationBean.setOrder(1);
registrationBean.setEnabled(true);
return registrationBean;
}
}
RestFilterConfig
请⼤家特别注意urlPatterns,属性urlPatterns指定要过滤的URL模式。对于Filter的作⽤区域,这个参数居功⾄伟。
注册好Filter,当Spring Boot启动时监测到有javax.servlet.Filter的bean时就会⾃动加⼊过滤器调⽤链ApplicationFilterChain。
调⽤⼀个API试试效果:
通常情况下,我们在Spring Boot下都会⾃定义⼀个全局统⼀的异常管理增强 GlobalExceptionHandler (和上⾯这个显⽰会略有不同)。
根据我的实践,过滤器⾥抛出异常,不会被全局唯⼀的异常管理增强捕获到并进⾏处理,这个和Inteceptor以及下⼀篇⽂章介绍的⾃定义AOP拦截不同。
到这⾥,⼀个通过⾃定义Filter实现的简易安全认证服务就搞定了。
三、⾃定义
1、实现
继承接⼝HandlerInterceptor,实现,接⼝⽅法有下⾯三个:
preHandle是请求执⾏前执⾏
postHandle是请求结束执⾏
afterCompletion是视图渲染完成后执⾏
package com.ller.interceptor;
import com.power.demomon.AppConst;
import com.power.demomon.BizResult;
import com.power.act.AuthTokenService;
import com.power.demo.util.PowerLogger;
import com.power.demo.util.SerializeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
* 认证token
* */
@Component
public class AuthTokenInterceptor implements HandlerInterceptor {
@Autowired
private AuthTokenService authTokenService;
/*
* 请求执⾏前执⾏
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean handleResult = false;
String token = Header(AppConst.AUTH_TOKEN);
BizResult<String> bizResult = authTokenService.powerCheck(token);
System.out.println(SerializeUtil.Serialize(bizResult));
handleResult = IsOK();
PowerLogger.info("auth token interceptor拦截结果:" + handleResult);
if (IsOK() == true) {
PowerLogger.info("auth token interceptor passed");
} else {
throw new Message());
}
return handleResult;
}
/*
* 请求结束执⾏
* */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  }
/*
* 视图渲染完成后执⾏
* */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
AuthTokenInterceptor
⽰例中,我们选择在请求执⾏前进⾏token安全认证。
认证服务就是过滤器⾥介绍的AuthTokenService,业务逻辑层实现复⽤。
2、注册
定义⼀个InterceptorConfig类,继承⾃WebMvcConfigurationSupport,WebMvcConfigurerAdapter已经过时。
将AuthTokenInterceptor作为bean注⼊,其他设置拦截的URL和过滤器⾮常相似:
package com.ller.interceptor;
llect.Lists;
import t.annotation.Bean;
import t.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.fig.annotation.DefaultServletHandlerConfigurer;
import org.springframework.fig.annotation.InterceptorRegistry;
import org.springframework.fig.annotation.ResourceHandlerRegistry;
import org.springframework.fig.annotation.WebMvcConfigurationSupport;
import java.util.List;
@Configuration
@Component
public class InterceptorConfig extends WebMvcConfigurationSupport { //WebMvcConfigurerAdapter已经过时
private static final String FAVICON_URL = "/favicon.ico";
/**
* 发现如果继承了WebMvcConfigurationSupport,则在yml中配置的相关内容会失效。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。