package com.zong.config.shiro.jwt;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.zong .exception.CustomException;
import com.zong .model.common.Constant;
import com.zong .model.common.ResponseBean;
import com.zong .util.JedisUtil;
import com.zong .util.JwtUtil;
import com.zong .util.common.JsonConvertUtil;
import com.zong .util.common.PropertiesUtil;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class JwtFilter extends BasicHttpAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (this.isLoginAttempt(request, response)) {
try {
this.executeLogin(request, response);
} catch (Exception e) {
String msg = e.getMessage();
Throwable throwable = e.getCause();
if (throwable instanceof SignatureVerificationException) {
msg = "Token或者密钥不正确(" + throwable.getMessage() + ")";
} else if (throwable instanceof TokenExpiredException) {
if (this.refreshToken(request, response)) {
return true;
} else {
msg = "Token已过期(" + throwable.getMessage() + ")";
}
} else {
if (throwable != null) {
msg = throwable.getMessage();
}
}
this.response401(response, msg);
return false;
}
} else {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
String httpMethod = httpServletRequest.getMethod();
String requestURI = httpServletRequest.getRequestURI();
logger.info("当前请求 {} Authorization属性(Token)为空 请求类型 {}", requestURI, httpMethod);
// mustLoginFlag = true 开启任何请求必须登录才可访问
final Boolean mustLoginFlag = false;
if (mustLoginFlag) {
this.response401(response, "请先登录");
return false;
}
}
return true;
}
/**
* 这里我们详细说明下为什么重写
* 可以对比父类方法,只是将executeLogin方法调用去除了
* 如果没有去除将会循环调用doGetAuthenticationInfo方法
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
this.sendChallenge(request, response);
return false;
}
/**
* 检测Header里面是否包含Authorization字段,有就进行Token登录认证授权
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
// 拿到当前Header中Authorization的AccessToken(Shiro中getAuthzHeader方法已经实现)
String token = this.getAuthzHeader(request);
return token != null;
}
/**
* 进行AccessToken登录认证授权
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
// 拿到当前Header中Authorization的AccessToken(Shiro中getAuthzHeader方法已经实现)
JwtToken token = new JwtToken(this.getAuthzHeader(request));
// 提交给UserRealm进行认证,如果错误他会抛出异常并被捕获
this.getSubject(request, response).login(token);
// 如果没有抛出异常则代表登入成功,返回true
return true;
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
// 跨域已经在OriginFilter处全局配置
/*HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}*/
return super.preHandle(request, response);
}
/**
* 此处为AccessToken刷新,进行判断RefreshToken是否过期,未过期就返回新的AccessToken且继续正常访问
*/
private boolean refreshToken(ServletRequest request, ServletResponse response) {
// 拿到当前Header中Authorization的AccessToken(Shiro中getAuthzHeader方法已经实现)
String token = this.getAuthzHeader(request);
// 获取当前Token的帐号信息
String account = JwtUtil.getClaim(token, Constant.ACCOUNT);
// 判断Redis中RefreshToken是否存在
if (JedisUtil.exists(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account)) {
// Redis中RefreshToken还存在,获取RefreshToken的时间戳
String currentTimeMillisRedis = JedisUtil.getObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account).toString();
// 获取当前AccessToken中的时间戳,与RefreshToken的时间戳对比,如果当前时间戳一致,进行AccessToken刷新
if (JwtUtil.getClaim(token, Constant.CURRENT_TIME_MILLIS).equals(currentTimeMillisRedis)) {
// 获取当前最新时间戳
String currentTimeMillis = String.valueOf(System.currentTimeMillis());
// 读取配置文件,获取refreshTokenExpireTime属性
PropertiesUtil.readProperties("config.properties");
String refreshTokenExpireTime = PropertiesUtil.getProperty("refreshTokenExpireTime");
// 设置RefreshToken中的时间戳为当前最新时间戳,且刷新过期时间重新为30分钟过期(配置文件可配置refreshTokenExpireTime属性)
JedisUtil.setObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account, currentTimeMillis, Integer.parseInt(refreshTokenExpireTime));
// 刷新AccessToken,设置时间戳为当前最新时间戳
token = JwtUtil.sign(account, currentTimeMillis);
// 将新刷新的AccessToken再次进行Shiro的登录
JwtToken jwtToken = new JwtToken(token);
// 提交给UserRealm进行认证,如果错误他会抛出异常并被捕获,如果没有抛出异常则代表登入成功,返回true
this.getSubject(request, response).login(jwtToken);
// 最后将刷新的AccessToken存放在Response的Header中的Authorization字段返回
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setHeader("Authorization", token);
httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization");
return true;
}
}
return false;
}
/**
* 无需转发,直接返回Response信息
*/
private void response401(ServletResponse response, String msg) {
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
try (PrintWriter out = httpServletResponse.getWriter()) {
String data = JsonConvertUtil.objectToJson(new ResponseBean(HttpStatus.UNAUTHORIZED.value(), "无权访问(Unauthorized):" + msg, null));
out.append(data);
} catch (IOException e) {
logger.error("直接返回Response信息出现IOException异常:{}", e.getMessage());
throw new CustomException("直接返回Response信息出现IOException异常:" + e.getMessage());
}
}
}