一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
最近在做一个小项目,后台服务只有一个,同时又需要用户的登录鉴权功能。通常来说我们都是写在网关当中,现在我需要在这个后台服务当中自己实现一个过滤器。其实过程与网关当中的没什么不同,只是在gateway当中目前是基于netty响应式的。
本文对于方法做一个简单记录,过程如下:
实现Filter接口
定义自己的过滤器,并且实现Filter接口:
public class AuthFilter implements Filter
重写doFilter方法
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
try {
// 此处省略
filterChain.doFilter(request, response);
} catch (Exception e) {
log.info("[AuthFilter.doFilter] exception: {}", e);
}
}
如上代码省略处就是我们需要做业务逻辑处理的地方,我们既然要做鉴权接口,那么通常做法是获取请求头部的内容,如token,然后对token进行验证等等。
鉴权业务
首先是获取header的关键参数,我此处主要有两个,分别是 token 和 username,分别通过header当中他们的key去获取就可以。
注意我对username进行了解码操作,原因是username有可能是中文,在前端vue的过滤器不允许直接发送UTF-8的中文,所以需要进行编码处理。
如果不能保证username可能为空,需要对其判断后再进行decode,否则会抛出异常。
String token = request.getHeader("AUTHORIZATION");
String username = URLDecoder.decode(request.getHeader("_username"),"UTF-8");
拿到token和username我主要做了下面的判断:
- token不能为空,失败返回未授权错误码。
//token为空
if (StringUtils.isEmpty(token) || "null".equals(token)) {
response.sendError(CommonReturnEnum.UNAUTHORIZED.getCode(), CommonReturnEnum.UNAUTHORIZED.getName());
return ;
}
- jwt解析token,首先是根据用户名从redis当中获取,token不存在存在,就进行JWT解析验证,失败就直接返回未授权错误码。成功则继续。
//验证token
String tokenCache = redisUtil.getString(JwtUtil.REDIS_TOKEN_PREFIX + username);
if (StringUtils.isEmpty(tokenCache)) {
Map<String, String> stringStringMap = JwtUtil.validateToken(token);
if (ObjectUtil.isEmpty(stringStringMap)) {
response.sendError(CommonReturnEnum.UNAUTHORIZED.getCode(), CommonReturnEnum.UNAUTHORIZED.getName());
return;
}
}
- redis存在token,与header的对比,不同就去jwt解析,通过则继续,未通过就返回未授权。
//没过期,对比当前token和缓存的token是否一致
if (!token.equals(tokenCache)) {
boolean tokenAndUser = JwtUtil.validateTokenAndUser(token, username);
if (!tokenAndUser) {
response.sendError(CommonReturnEnum.UNAUTHORIZED.getCode(), CommonReturnEnum.UNAUTHORIZED.getName());
return;
}
}
- token续期。
在前的步骤,即使是redis不存在,或者与header的token不相同,我仍然会通过jwt去解析验证,防止redis数据异常导致问题。
续期的意思就是当用户在token的超时时间内进行了刷新,则我们会重新赋予他一个新的token,这样用户就不需要频繁登录,又不会是账号一直在线。
//token验证通过,token续期
String refreshToken = JwtUtil.refreshToken(token, username);
redisUtil.setObjectExpire(JwtUtil.REDIS_TOKEN_PREFIX + username, refreshToken,
JwtUtil.REDIS_TOKEN_EXPIRE_MINUTE, TimeUnit.MINUTES);
白名单
针对某些特定的路径,是没有携带token等信息的,所以需要过滤器放行。
我这里将需要放行的白名单配置在yml文件当中:
# 请求白名单
filter:
white-list: /user/login,/user/register,/websocket/server/,/integral/pageList
在过滤器使用 @value 获取:
@Value("#{'${filter.white-list}'.split(',')}")
private List<String> whiteList;
在doFilter当中去遍历就好了:
//白名单,放行
for (String url : whiteList) {
if (path.contains(url)) {
filterChain.doFilter(request, response);
return;
}
}
以上就是简单的鉴权过滤器的实现,简单介绍到这。