4.5 创建OAuth2Filter类
注意事项:
因为在 OAuth2Filter 类中要读写 ThreadLocal 中的数据,所以 OAuth2Filter 类必 须要设置成多例的,否则 ThreadLocal 将无法使用。
```package com.example.emos.wx.config.shiro;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import org.apache.http.HttpStatus;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@Component
@Scope("prototype")
public class OAuth2Filter extends AuthenticatingFilter {
@Autowired
private ThreadLocalToken threadLocalToken;
@Value("${emos.jwt.cache-expire}")
private int cacheExpire;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private RedisTemplate redisTemplate;
/**
* 拦截请求之后,用于把令牌字符串封装成令牌对象
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest req= (HttpServletRequest) request;
String token=getRequestToken(req);
if(StrUtil.isBlank(token)){
return null;
}
return new OAuth2Token(token);
}
/**
* 拦截请求,判断请求是否需要被Shiro处理
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// Ajax提交application/json数据的时候,会先发出Options请求
HttpServletRequest req= (HttpServletRequest) request;
if(req.getMethod().equals(RequestMethod.OPTIONS.name())){
return true;
}
return false;
}
/**
* 该方法用于处理所有应该被Shiro处理的请求
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest req= (HttpServletRequest) request;
HttpServletResponse resp= (HttpServletResponse) response;
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
threadLocalToken.clear();
//获取请求token,如果token不存在,直接返回401
String token=getRequestToken(req);
if(StrUtil.isBlank(token)){
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
resp.getWriter().print("无效的令牌");
return false;
}
try{
jwtUtil.verifierToken(token);
}catch (TokenExpiredException e){
//客户端令牌过期,查询Redis中是否存在令牌,如果存在令牌就重新生成一个令牌给客户端
if(redisTemplate.hasKey(token)){
redisTemplate.delete(token);
int userId=jwtUtil.getUserId(token);
token=jwtUtil.createToken(userId);
redisTemplate.opsForValue().set(token,userId+"",cacheExpire, TimeUnit.DAYS);
//把新令牌绑定到线程
threadLocalToken.setToken(token);
}
else{
//如果Redis不存在令牌,让用户重新登录
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
resp.getWriter().print("令牌已过期");
return false;
}
}catch (Exception e){
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
resp.getWriter().print("无效的令牌");
return false;
}
boolean bool=executeLogin(request,response);
return bool;
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletRequest req= (HttpServletRequest) request;
HttpServletResponse resp= (HttpServletResponse) response;
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
try{
resp.getWriter().print(e.getMessage());
}catch (Exception exception){
}
return false;
}
@Override
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req= (HttpServletRequest) request;
HttpServletResponse resp= (HttpServletResponse) response;
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
super.doFilterInternal(request, response, chain);
}
/**
* 获取请求头里面的token
*/
private String getRequestToken(HttpServletRequest request){
String token=request.getHeader("token");
if(StrUtil.isBlank(token)){
token=request.getParameter("token");
}
return token;
}
}
```
4.6 创建ShiroConfig类
我们要创建的 ShiroConfig 类,是用来把 OAuth2Filter 和 OAuth2Realm 配置到Shiro框架,这 样我们辛苦搭建的Shiro+JWT才算生效。
4.7 利用AOP,把更新的令牌返回给客户端
我们在写 OAuth2Filter 的时候,把更新后的令牌写到 ThreadLocalToken 里面的 ThreadLocal 。那么这个小节,我们要创建 AOP切面类 ,拦截所有Web方法的返回值,在返回 的 R对象 中添加更新后的令牌。
————————————————