- 前言
- 直接上代码
- 总结
前言
之前写过一篇“Oauth2.0实现token刷新功能”,发现大家阅读的还是特别多,那个是基于Spring Cloud实现的刷新功能,现在将它改为基于普通过滤器实现token刷新。
直接上代码
基本思路还是跟之前一样,过滤器拦截请求验证token,token过期后请求单点登录服务器换取新的access_token和refresh_token,将两个token放到过滤器返回体的头部。从而不影响本次请求,同时还能在本次请求的过程中做到无痕刷新token。
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//请求的全路径
String url = request.getRequestURI();
log.info("拦截到的请求路径:" + url);
AtomicBoolean matchFlag = new AtomicBoolean(true);
//配置文件配置的不需要过滤器拦截的请求路径
List<String> urlList = interceptorPathBean.getInclude();
for (String excludeUrl : urlList) {
if (url.startsWith(excludeUrl)) {
matchFlag.set(false);
break;
}
}
//不过滤oprions请求
if(((HttpServletRequest) req).getMethod().equals(RequestMethod.OPTIONS.name())){
matchFlag.set(false);
}
if(matchFlag.get()){ //需要过滤
//过滤前端的请求,校验token(前端每次请求需要携带token)
String accessToken = request.getHeader("Authorization");
//前端请求的refreshToken
String refreshToken = request.getHeader("refreshToken");
MyUser myUser;
try {
//这儿就直接解析前端传过来的token,没有去单点登录服务器验证
//如果解析成功就证明token有效
//验证用的包应该是com.auth0.java-jwt,自己查一下
myUser = JwtUtil.parseJWT(accessToken);
} catch (TokenExpiredException e) {
log.info("accessToken过期,尝试刷新token");
//这个http请求的方法下边给一下,必须是basic验证的方式请求
String result = HttpClientForOauth.sendHttpPost(oauthValue.getRefreshTokeUriPrefix() + refreshToken,
oauthValue.getClientId(),
oauthValue.getClientSecret());
Map<String, Object> resultMap = JSONArray.parseObject(result);
//获取到token
String accessToken2 = String.valueOf(resultMap.get("access_token"));
//获取到refresh_token信息
String refreshToken2 = String.valueOf(resultMap.get("refresh_token"));
//获取到error信息(接口报错,通常是因为refresh_token也过期了)
String error = String.valueOf(resultMap.get("error"));
//说明刷新成功,更新token
if (accessToken2 != null && refreshToken2 != null && "null".equals(error)) {
myUser = JwtUtil.parseJWT(accessToken2);
request.setAttribute("user", myUser);
response.setHeader("Access-Control-Expose-Headers", "accessToken,refreshToken"); //必须加,否则vue无法获取头中的信息
//response.setHeader("Access-Control-Expose-Headers", "refreshToken");//必须加,否则vue无法获取头中的信息
response.setHeader("accessToken", accessToken2);
response.setHeader("refreshToken", refreshToken2);
} else {
log.error("Token已过期,refreshToken也过期");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return;
}
} catch (UnsupportedJwtException e) {
log.error("Token格式错误");
//response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
return;
} catch (MalformedJwtException e) {
log.error("Token没有被正确构造");
response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
return;
} catch (SignatureException e) {
log.error("签名失败");
response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
return;
} catch (IllegalArgumentException e) {
log.error("非法参数异常{}",e);
response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
return;
} catch (Exception e) {
log.error("其它所有异常",e);
response.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
return;
}
//将token解析出来的用户信息放入请求头,以便服务确定当前登录用户
request.setAttribute("user", myUser);
chain.doFilter(request, response);
}else{ //不需要过滤
chain.doFilter(request, response);
}
}
上边换取新的token用到的http请求,其实也是用code获取token的请求
public static String sendHttpPost(String httpUrl, String clientId, String clientSecret) {
HttpPost httpPost = new HttpPost(httpUrl);
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
HttpEntity entity;
String responseContent = null;
try {
// 创建默认的httpClient实例.
httpClient = HttpClients.createDefault();
//单点登录验证code时需要的客户端用户名密码
String encoding = DatatypeConverter.printBase64Binary((clientId+":"+clientSecret).getBytes("UTF-8"));
httpPost.setHeader("Authorization", "Basic " +encoding);
// 执行请求
response = httpClient.execute(httpPost);
entity = response.getEntity();
responseContent = EntityUtils.toString(entity, "UTF-8");
} catch (Exception e) {
log.error("身份验证接口错误",e);
} finally {
try {
// 关闭连接,释放资源
if (response != null) {
response.close();
}
if (httpClient != null) {
httpClient.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return responseContent;
}
再贴一个解决跨域的过滤器,可能会用到。如果需要,这个过滤器应该在刷新token的过滤器之前执行(设置@Order的值)
@Component
@Order(value=-1)
public class CrossFilter implements Filter {
/*跨域请求配置*/
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest reqs = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin",reqs.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "5000");
response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,Authorization,X-Token,accessToken,refreshToken");
//response.setHeader("Access-Control-Expose-Headers","accessToken,refreshToken");
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(reqs, res);
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
总结
这种方式前端需要在拦截请求的时候每次查看返回体的头部有没有新的token,如果有就将前端保存的token替换成新的,从而实现token的刷新
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)