shiro集成jwt后会对前端传过来的token进行校验,如果token过期,按照以前的逻辑是后端进行了重定向,开发环境是没有问题的,但是部署在生产环境使用了nginx路由后,发生了请求不到后端未登录接口异常。
由于后端重定向,nginx路由后端的前缀"/datastatistics"没有带上导致了请求不到相应接口的问题,增加重写加上路由后解决了问题。
nginx配置:
仔细思考后觉得,前后端分离后本身由前端来做路由的主导,后端不应该插手怎么跳转的逻辑。虽然这个方案能解决后端跳转后引起的问题,但是我们本是只把后端服务看做是数据的提供接口,所以再做进一步改善。增加过滤器,在过滤器中检查token,如果过期则不再跳转,返回token过期的信息给前端,前端做相应跳转。
import com.alibaba.fastjson.JSONObject;
import com.yntravelsky.datafusion.util.Constant;
import com.yntravelsky.datafusion.util.ServiceResult;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
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.OutputStream;
/**
* jwt过滤器
*
* @Author yangfeng
* @Description preHandle->isAccessAllowed->isLoginAttempt->executeLogin
* @Date 2019-09-18
* @Time 12:36
*/
public class JWTFilter extends BasicHttpAuthenticationFilter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 如果带有 token,则对 token 进行检查,否则直接通过
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException {
//判断请求的请求头是否带上 "Token"
if (isLoginAttempt(request, response)) {
//如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
try {
executeLogin(request, response);
return true;
} catch (Exception e) {
//token 错误
responseError(response, e.getMessage());
}
}
//如果请求头不存在 Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
return true;
}
/**
* 判断用户是否想要登入。
* 检测 header 里面是否包含 Token
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("Authorization");
return token != null;
}
/**
* 执行登陆操作
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("Authorization");
JWTToken jwtToken = new JWTToken(token);
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
getSubject(request, response).login(jwtToken);
// 如果没有抛出异常则代表登入成功,返回true
return true;
}
/**
* 对跨域提供支持
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "*");
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
/**
* 请求异常则需要重新登录
*/
private void responseError(ServletResponse response, String message) {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
ServiceResult<String> ret = new ServiceResult<String>();
// public static int NO_AUTHENTICATION = 401;//没有认证
ret.setCode(Constant.NO_AUTHENTICATION);
ret.setMsg(message);
ret.setSucceed(false);
String jsonString = JSONObject.toJSONString(ret);
OutputStream out = null;
try {
out = response.getOutputStream();
out.write(jsonString.getBytes());
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
接口判断token后返回包装信息。
前端增加对返回状态的判断即可。
if (respone.data.code == 401) {
localStorage.clear();
router.replace({name: 'Login'});
} else {
Notification.error({
title: '提示',
message: respone.data.msg
});
}
reject(respone.data);
如果返回code为401,前端跳转登录。