还记得上次我写过几篇在实际项目中如何使用jwt《公众号授权 + jwt》、《小程序授权+ jwt》、《微信支付》
紧接着,就有个小伙伴,问了我一个这样的问题:授权使用=jwt签发token后,登录、注册等,用户是不需要token的,此时,应该怎么排除这些请求的url呢?
得嘞,今天咱们就掰扯掰扯这件事,如何使用“过滤器”或“拦截器”实现登录、注册的过滤
在写此文时,我曾想到小编曾经的SAO操作,回看自己的代码,我滑稽的笑了。
因为小编的Controller层代码都是类似这样写的:
@RestController
public class LoginController {
@Resource
private HttpServletRequest request;
@Resource
private JwtUtil jwtUtil;
@ApiOperation(value = "根据订单id删除订单接口")
@PostMapping(value = "/deleteById")
public RespResult deleteById(String id) {
String header = request.getHeader("token");//获取头信息,头中key为token
if (StringUtils.isEmpty(header)) {
return new RespResult(400, "请求头不能为空"); //头为空返回权限不足
}
if (!header.startsWith("Bearer ")) { // 和前端约定,头的值是Bearer + 空格 + jwt签发的token
return new RespResult(400, "token错误");//头不符合约定,返回权限不足
}
String token = header.substring(7); //提取头中的token
Claims claims = jwtUtil.parseJWT(token); //使用jwt工具解密token
if (claims == null) {
return new RespResult(400, "token验证失败");
}
//调用service层删除订单
// ...... 此处省略
return new RespResult(200, "删除成功");
}
}
卧CAO!小编这代码可真“丧心病狂”,现在只有一个接口,当接口有几十个时,写几十遍吗?,代码不冗余吗?何况一天就8小时,7小时在复制粘贴,怕是非常酸爽按。。。
接下来,我们就好好唠一唠:如何用过滤器或拦截器干掉上面冗余的代码!
首先,我们要使用过滤器,肯定的先了解过滤器的原理;
过滤器 (Filter) 是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器 (也就是过滤链) 对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改。也可以对响应进行过滤,拦截或修改响应。
如图,浏览器发出的请求先递交给第一个filter进行过滤,符合规则则放行,递交给filter链中的下一个过滤器进行过滤。过滤器在链中的顺序与它在yml或config中配置顺序有关。
大家都知道,filter中最重要的一个方法就是:doFilter()方法
在doFilter()方法中,chain.doFilter()前的一般是对request执行的过滤操作,chain.doFilter后面的代码一般是对response执行的操作。
来来来,我们看一下过滤链代码的执行顺序是怎么样的:
这图 so easy 吧:
在chain.doFilter()前,对请求资源进行过滤(eg: 登录权限验证、资源访问权限控制、敏感词汇过滤、字符编码转换等等操作)
在chain.doFilter()后,给前端一些响应,例:{“code”: “6000”,“message”: “TOKEN 验证失败”}
首先,在yml配置文件中,设置不需要过滤的URI
filter:
config:
excludeUrls: /user/login,/user/register #登录和注册不需过滤,直接放行
接下来,写一个过滤器:MyFilter
@Slf4j
@Order(1) //数字越小,越先执行此过滤器
@Component
@WebFilter(filterName = "MyFilter", urlPatterns = {"/**"}) // 过滤规则——所有
public class MyFilter implements Filter {
@Resource
private JwtUtils jwtUtils; // 自己jwt工具类
@Value("${filter.config.excludeUrls}")
private String excludeUrls; // 获取配置文件中不需要过滤的uri
private List<String> excludes;
// token验证失败时的响应消息
private static final String VALID_ERROR = "{\"code\": \"6000\",\"message\": \"TOKEN 验证失败\"}";
@Override
public void init(FilterConfig filterConfig) {
// 初始化
// 移除配置文件中不过滤url,字符串的前空白和尾部空白
excludes = Splitter.on(",").trimResults().splitToList(this.excludeUrls);
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String uri = request.getRequestURI(); //获取请求uri
String token = request.getHeader("token"); // 获取头中token
try {
if (this.isExcludesUrl(uri)) { // 判断请求uri是否需要过滤(方法在下面)
chain.doFilter(req, resp); // 不需要,放行
} else {
if (!validateParams(token)) { // 验证头中的token(方法在下面)
response.getWriter().write(VALID_ERROR); // 验证失败,返回验证失败消息
return;
}
chain.doFilter(request, resp); // 验证成功,放行
}
} catch (Exception ex) {
log.error("Exception error", ex);
response.getWriter().write(VALID_ERROR); // 抛出异常,返回验证失败消息
} finally {
response.flushBuffer(); // 将缓冲信息输出到页面
}
}
private boolean validateParams(String token) {
// 验证是否为空,格式是否满足预定要求
if (!StringUtils.isEmpty(token) && token.startsWith("bearer ")) {
String token = token.substring(7);
Map<String, Object> map = jwtUtils.extractJwt(token); // 掉用jwt解密方法解密
if (map != null) {
return true; // 解密成功,返回true
}
}
return false; // 解密失败,返回false
}
private boolean isExcludesUrl(String path) {
for (String v : this.excludes) {
if (path.startsWith(v)) {// 判断请求uri 是否满足配置文件uri要求
return true; // 满足、也就是请求uri 为 登录、注册,返回true
}
}
return false; // 不满足、也就是请求uri 不是登录、注册,返回false
}
}
最后,我们以空的请求头使用Postman访问下“根据订单id删除订单接口”接口:localhost:8080/deleteById;结果为:
{
"code": 6000,
"message": "TOKEN 验证失败"
}
这样,一个简单的filter,就取代了上面小编“辣眼睛”的操作,同时也排除了不需要验证的接口(登录、注册等),这才是真的SAO。。。
来,我们先看看拦截器的原理;
拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
他有三个方法:
分别实现预处理、后处理(调用了Service并返回ModelAndView,但未进行页面渲 染)、返回处理(已经渲染了页面)
首先,编写拦截器配置文件类:
/**
* Create By CodeCow on 2020/7/26.
*/
public class MyInterceptorConfig extends WebMvcConfigurationSupport {
@Resource
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器,要声明拦截器对象和要拦截的请求
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**") //所有路径都被拦截
.excludePathPatterns("/user/login") // 排除用户登录请求
.excludePathPatterns("/user/register"); // 排除用户注册请求
}
}
接下来,编写拦截器实现类
@Component
public class MyInterceptor implements HandlerInterceptor {
@Resource
private JwtUtils jwtUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String headToken = request.getHeader("token"); // 获取头中token
// 验证是否为空,格式是否满足预定要求
if (!StringUtils.isEmpty(headToken) && headToken.startsWith("bearer ")) {
String token = headToken.substring(7);
Map<String, Object> map = jwtUtils.extractJwt(token); // 掉用jwt解密方法解密
if (map != null) {
request.setAttribute("claims_map", map); // 把jwt解密后的map放入request域中
// 其他操作略。。。。。
}
// 其他操作略。。。。。
}
return true;
}
}
最后,当我们需要用到token时,==直接从request域中获取“claims_map”==即可;
这样,一个简单的Interceptor,同样取代了上面小编的SAO操作,真香!
好啦,今就先聊到这里吧,本文仅仅是抛砖引玉而已,浅聊了过滤器和拦截器配合jwt的简单实用,实际项目中如何选择,还得看个人爱好和编程风格
其次,大道万千,殊途同归,不管是过滤器还是拦截器,其实原理都大同小异,懂其原理,小小filter和Interceptor,还不信手拈来。。。
答应我,别再if/else校验请求参数了可以吗
微信小程序支付 + 公众号支付 (含源码)
微信公众号授权+获取用户信息 + jwt登录 (含源码)
微信小程序授权+获取用户手机号 + jwt登录 (含源码)
中国传统文化,先仔细看,若有用,再收藏, 给自己一点思考的时间,微信搜索:CodeCow,关注这个非常SAO的程序员
也可以加小编微信:CodeCow-6666 私信小编