后端
/**
* JWT核心过滤器配置
* 所有的请求都会先经过Filter,所以我们继承官方的BasicHttpAuthenticationFilter,并且重写鉴权的方法。
* 执行流程 preHandle->isAccessAllowed->isLoginAttempt->executeLogin
*/
public class JwtFilter extends BasicHttpAuthenticationFilter {
/**
* logger
*/
private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);
/**
* 判断用户是否想要进行 需要验证的操作
* 检测header里面是否包含Authorization字段即可
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
String auth = getAuthzHeader(request);
return auth != null && !auth.equals("");
}
/**
* 此方法调用登陆,验证逻辑
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginAttempt(request, response)) {
try {
// 进行Shiro的登录jwtRealm
JwtToken token = new JwtToken(getAuthzHeader(request));
getSubject(request, response).login(token);
} catch (AuthenticationException e) {
try {
if (this.refreshToken(request, response)) {
return true;
}
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
}
}else {
// 没有携带Token
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
// 获取当前请求类型
String httpMethod = httpServletRequest.getMethod();
// 获取当前请求URI
String requestURI = httpServletRequest.getRequestURI();
logger.info("当前请求 {} Authorization属性(Token)为空 请求类型 {}", requestURI, httpMethod);
// mustLoginFlag = true 开启任何请求必须登录才可访问/
Boolean mustLoginFlag = true;
if (mustLoginFlag) {
this.response401(response, "请先登录");
return false;
}
}
return true;
}
/**
* 无需转发,直接返回Response信息
*/
private void response401(ServletResponse response, String msg) {
try {
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
httpServletResponse.sendRedirect("/static/baodian/index.html");
} catch (Exception e) {
logger.error("直接返回Response信息出现IOException异常:" + e.getMessage());
throw new CustomException("直接返回Response信息出现IOException异常:" + e.getMessage());
}
// 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 RestResponse(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());
// }
}
/**
* 提供跨域支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) 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"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
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) throws UnsupportedEncodingException {
// 拿到当前Header中Authorization的AccessToken(Shiro中getAuthzHeader方法已经实现)
String token = getAuthzHeader(request);
JwtConfig jwtConfig = SpringUtils.getBean("jwtConfig");
// 当前Token的账号(手机号)
String phone = jwtConfig.getPhoneByToken(token);
// final String refreshToken = jwtConfig.createTokenByWxAccount(phone);
// 判断Redis中RefreshToken是否存在
if (JedisUtil.exists(RedisConstant.PREFIX_SHIRO_REFRESH_TOKEN_WX + phone)) {
// Redis中RefreshToken还存在,获取RefreshToken的时间戳
String JwtId = JedisUtil.getObject(RedisConstant.PREFIX_SHIRO_REFRESH_TOKEN_WX + phone).toString();
// 获取当前AccessToken中的时间戳,与RefreshToken的时间戳对比,如果当前时间戳一致,进行AccessToken刷新
if (jwtConfig.getJwtIdByToken(token).equals(JwtId)) {
// 刷新AccessToken,设置时间戳为当前最新时间戳
token = jwtConfig.createTokenByWxAccount(phone);
// 获取当前最新jwtId
String newJwtId = jwtConfig.getJwtIdByToken(token);
// 读取配置文件,获取refreshTokenExpireTime属性
PropertiesUtil.readProperties("config.properties");
String accessTokenExpireTime = PropertiesUtil.getProperty("accessTokenExpireTime");
// 设置AccessToken中的时间戳为当前最新时间戳,且刷新过期时间重新为30分钟过期(配置文件可配置refreshTokenExpireTime属性)
JedisUtil.setObject(RedisConstant.PREFIX_SHIRO_ACCESS_TOKEN_WX + phone, newJwtId, Integer.parseInt(accessTokenExpireTime));
// 设置RefreshToken中的时间戳为当前最新时间戳,且刷新过期时间重新为30分钟过期(配置文件可配置refreshTokenExpireTime属性)
JedisUtil.setObject(RedisConstant.PREFIX_SHIRO_REFRESH_TOKEN_WX + phone, newJwtId);
// 将新刷新的AccessToken再次进行Shiro的登录
JwtToken jwtToken = new JwtToken(token);
// 提交给UserRealm进行认证,如果错误他会抛出异常并被捕获,如果没有抛出异常则代表登入成功,返回true
this.getSubject(request, response).login(jwtToken);
// 最后将刷新的AccessToken存放在Response的Header中的Authorization字段返回
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
//此处会重新请求你的url 并且返回给前端
httpServletResponse.setHeader("Authorization", token);
httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization");
return true;
}else {
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 RestResponse(HttpStatus.UNAUTHORIZED.value(), "无权访问", null));
out.append(data);
return true;
} catch (IOException ez) {
logger.error("直接返回Response信息出现IOException异常:" + ez.getMessage());
throw new CustomException("直接返回Response信息出现IOException异常:" + ez.getMessage());
}
}
}else {
String msg = "Token已过期";
//Token认证失败直接返回Response信息
this.response401(response, msg);
}
return false;
}
}
小程序端
var app = getApp();
const regeneratorRuntime = require("../regenerator/runtime");
function wxPromisify(fn) {
return function (obj = {}) {
return new Promise((resolve, reject) => {
obj.success = function (res) {
console.log(res)
if (res.header.Authorization!=null){
wx.setStorageSync("token", res.header.Authorization)
}
if (res.data.resultCode==401){
wx.removeStorageSync("userInfo")
wx.showModal({
title: '加载失败',
content: '请从新登录',
showCancel: false,
success: function (res) {
if (res.cancel) {
//点击取消,默认隐藏弹框
} else {
//点击确定
wx.navigateTo({
url: '../phone/phone',
})
}
}
})
}
//成功
resolve(res)
}
obj.fail = function (res) {
wx.hideLoading()
//失败
console.log(res)
reject(res)
}
fn(obj)
})
}
}
//无论promise对象最后状态如何都会执行
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
/**
* 微信请求get方法
* url
* data 以对象的格式传入
*/
function getRequest(url, data) {
var getRequest = wxPromisify(wx.request)
return getRequest({
url: app.globalData.host + url,
method: 'GET',
data: data,
header: {
'Content-type': 'application/x-www-form-urlencoded',// 默认值,
'Authorization': wx.getStorageSync("token")
},
})
}
function uploadFile(url, filePath, formData,name){
var uploadFile = wxPromisify(wx.uploadFile)
return uploadFile({
// 模拟https
url: app.globalData.host + url, //需要用HTTPS,同时在微信公众平台后台添加服务器地址
filePath: filePath, //上传的文件本地地址
name: name,
formData: formData,
header: {
'Content-type': 'multipart/form-data',
'Authorization': wx.getStorageSync("token")
}
})
}
/**
* 微信请求post方法封装
* url
* data 以对象的格式传入
*/
function postRequest(url, data) {
var postRequest = wxPromisify(wx.request)
return postRequest({
url: app.globalData.host + url,
method: 'POST',
data: data,
header: {
'Content-type': 'application/x-www-form-urlencoded',// 默认值,
'Authorization': wx.getStorageSync("token")
},
})
}
/**
* 微信请求post方法封装
* url
* data 以对象的格式传入
*/
function postJsonRequest(url, data) {
var postRequest = wxPromisify(wx.request)
return postRequest({
url: app.globalData.host + url,
method: 'POST',
data: data,
header: {
'Content-type': 'application/json',// 默认值,
'Authorization': wx.getStorageSync("token")
},
})
}
/**
* 微信请求delete方法
* url
* data 以对象的格式传入
*/
function deleteRequest(url, data) {
var getRequest = wxPromisify(wx.request)
return getRequest({
url: app.globalData.host + url,
method: 'delete',
header: {
'Content-type': 'application/x-www-form-urlencoded',// 默认值,
'Authorization': wx.getStorageSync("token")
},
})
}
/**
* 微信请求put方法
* url
* data 以对象的格式传入
*/
function putRequest(url, data) {
var getRequest = wxPromisify(wx.request)
return getRequest({
url: app.globalData.host + url,
method: 'PUT',
data: data,
header: {
'Content-type': 'application/x-www-form-urlencoded',// 默认值,
'Authorization': wx.getStorageSync("token")
},
})
}
/**
* 微信请求putJson方法
* url
* data 以对象的格式传入
*/
function putJsonRequest(url, data) {
var getRequest = wxPromisify(wx.request)
return getRequest({
url: app.globalData.host + url,
method: 'PUT',
data: data,
header: {
'Content-type': 'application/json',// 默认值,
'Authorization': wx.getStorageSync("token")
},
})
}
module.exports = {
postRequest,
getRequest,
deleteRequest,
putRequest,
postJsonRequest,
putJsonRequest,
uploadFile
}