package com.nms.swspkmas_standalone.filter;
|
|
import cn.hutool.crypto.digest.MD5;
|
|
import com.alibaba.fastjson.JSONObject;
|
import com.nms.swspkmas_standalone.response.CommonResult;
|
import com.nms.swspkmas_standalone.response.ResultCode;
|
import com.nms.swspkmas_standalone.shiro.token.JwtToken;
|
import com.nms.swspkmas_standalone.utils.JwtTokenUtil;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.shiro.authc.AuthenticationException;
|
import org.apache.shiro.authc.AuthenticationToken;
|
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
|
import org.apache.shiro.web.util.WebUtils;
|
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.PrintWriter;
|
import java.util.HashMap;
|
import java.util.Map;
|
|
/**
|
* @Author ling.quan
|
* @Date 2022/2/17 15:27
|
* @Desciption
|
*/
|
@Slf4j
|
public class JwtFilter extends BasicHttpAuthenticationFilter {
|
|
// 存放token的map key为登录时的token加密,value为验证token
|
public final static Map<String, String> tokenMap = new HashMap<>();
|
|
/**
|
* 前置拦截处理
|
* @param request
|
* @param response
|
* @return
|
* @throws Exception
|
*/
|
@Override
|
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
|
//servlet请求与响应的转换
|
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
|
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
|
|
//跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
|
if(httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())){
|
httpServletResponse.setStatus(HttpStatus.OK.value());
|
return false;
|
}
|
return super.preHandle(request, response);
|
}
|
|
/**
|
* 后置拦截处理
|
* @param request
|
* @param response
|
* @throws Exception
|
*/
|
@Override
|
protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
|
//添加跨域支持
|
this.fillCorsHeader(WebUtils.toHttp(request), WebUtils.toHttp(response));
|
}
|
|
/**
|
* 过滤器拦截请求的入口方法,所有请求都会进入该方法
|
* 返回true则允许访问
|
* @param request
|
* @param response
|
* @param mappedValue
|
* @return
|
*/
|
@Override
|
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
// 原用来判断是否是登录请求,在本例中不会拦截登录请求,用来检测Header是否包含token字段
|
if(this.isLoginRequest(request,response)){//看看源码,调用了isLoginAttempt()方法
|
return false; //返回false后进入onAccessDenied()方法,返回错误信息
|
}
|
|
boolean allowed = false;
|
try{
|
//检测header里的JWT Token内容是否正确,尝试使用token进行登录
|
allowed = this.executeLogin(request,response);
|
} catch (Exception e){ //未找到token
|
log.error(e.getMessage());
|
}
|
return allowed || super.isPermissive(mappedValue);
|
}
|
|
/**
|
* 检测Header中是否包含token字段
|
* @param request
|
* @param response
|
* @return
|
*/
|
@Override
|
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
|
return ((HttpServletRequest) request).getHeader(JwtTokenUtil.USER_LOGIN_TOKEN) == null;
|
}
|
|
/**
|
* 身份验证,检查JWT token是否合法
|
* @param request
|
* @param response
|
* @return
|
* @throws Exception
|
*/
|
@Override
|
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
|
//从请求头里拿到token
|
AuthenticationToken token = createToken(request,response);
|
if(token == null){
|
String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.";
|
throw new IllegalStateException(msg);
|
}
|
try{
|
Subject subject = getSubject(request,response);
|
subject.login(token); //让shiro进行登录验证
|
//没出错则验证成功
|
return onLoginSuccess(token,subject,request,response);
|
} catch (AuthenticationException e){
|
return onLoginFailure(token, e, request, response);
|
}
|
}
|
|
/**
|
* 从Header中提取 JWT token
|
* @param request
|
* @param response
|
* @return
|
*/
|
@Override
|
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
|
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
String authorization = httpServletRequest.getHeader(JwtTokenUtil.USER_LOGIN_TOKEN);
|
return new JwtToken(authorization);
|
}
|
|
/**
|
* isAccessAllowed()方法返回false,会进入该方法,表示拒绝访问
|
* @param request
|
* @param response
|
* @return
|
* @throws Exception
|
*/
|
@Override
|
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
|
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
|
httpServletResponse.setCharacterEncoding("UTF-8");
|
httpServletResponse.setContentType("application/json;charset=UTF-8");
|
httpServletResponse.setStatus(HttpStatus.OK.value());
|
|
PrintWriter writer = httpServletResponse.getWriter();
|
|
writer.write(JSONObject.toJSONString(CommonResult.failed(ResultCode.UNAUTHORIZED)));
|
fillCorsHeader(WebUtils.toHttp(request),httpServletResponse);
|
return false;
|
}
|
|
/**
|
* shiro利用 JWT Token 登录成功后,进入该方法
|
* @param token
|
* @param subject
|
* @param request
|
* @param response
|
* @return
|
* @throws Exception
|
*/
|
@Override
|
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
|
HttpServletResponse httpResponse = WebUtils.toHttp(response);
|
String newToken = null;
|
|
//登录成功后判断是否需要续签token
|
if(token instanceof JwtToken) {
|
JwtToken jwtToken = (JwtToken) token;
|
// boolean needUpdate = JWTUtils.isNeedUpdate(JwtFilter.tokenMap.get(MD5.create().digestHex(jwtToken.getCredentials().toString())), JwtTokenUtil.expiration);
|
// if (needUpdate) {
|
// newToken = JWTUtils.createToken(jwtToken.getPrincipal().toString(), JWTUtils.expireTime);
|
// JwtFilter.tokenMap.put(MD5.create().digestHex(jwtToken.getCredentials().toString()), newToken);
|
// }
|
}
|
// if(newToken != null){
|
// httpResponse.setHeader(JWTUtils.USER_LOGIN_TOKEN,newToken);
|
// }
|
return true;
|
}
|
|
/**
|
* 利用 JWT token 登录失败,会进入该方法
|
* @param token
|
* @param e
|
* @param request
|
* @param response
|
* @return
|
*/
|
@Override
|
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
|
return false;//直接返回false,交给后面的 onAccessDenied()方法处理
|
}
|
//跨域请求的解决方案之一
|
protected void fillCorsHeader(HttpServletRequest request, HttpServletResponse response){
|
response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
|
response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,HEAD");
|
response.setHeader("Access-Control-Allow-Headers",
|
request.getHeader("Access-Control-Request-Headers"));
|
}
|
|
}
|