这篇文章主要介绍SpringBoot如何创建存储令牌的媒介类和过滤器,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
创建ThreadLocalToken类的目的:
在com.example.emos.wx.config.shiro
中创建ThreadLocalToken
类。
写入如下代码:
package com.example.emos.wx.config.shiro; import org.springframework.stereotype.Component; @Component public class ThreadLocalToken { private ThreadLocal local=new ThreadLocal(); //因为要在ThreadLocal中保存令牌,所以需要setToken。 public void setToken(String token){ local.set(token); } public String getToken(){ return (String) local.get(); } public void clear(){ local.remove();//把绑定的数据删除了 } }
下图为创建目录的层级关系:
创建过滤器的目的:
因为OAuth3Filter
类要读写ThreadLocal
中的数据,所以OAuth3Filter
类必须要设置成多例的,否则ThreadLocal
将无法使用。
在配置文件中,添加JWT需要的密匙,过期时间和缓存过期时间。
emos: jwt: #密钥 secret: abc123456 #令牌过期时间(天) expire: 5 #令牌缓存时间(天数) cache-expire: 10
在com.example.emos.wx.config.shiro
中创建OAuth3Filter
类。
package com.example.emos.wx.config.shiro; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.TokenExpiredException; import org.apache.commons.lang3.StringUtils; 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;
在写好@Scope("prototype")
后,就表明以后Spring使用OAuth3Filter
类默认是多例。
@Value("${emos.jwt.cache-expire}")
考察的一个知识点,从xml文件中获取属性文件的属性值。
因为要在Redis中操作,所以要声明private RedisTemplate redisTemplate;
申明好这个对象后,就可以对redis中的数据进行读写操作了。
filter类用来区分哪些请求应该被shiro处理,哪些请求不该被shiro处理。
如果请求被shiro处理的话,那么createToken方法就被执行了,
createToken从请求中获取令牌字符串,然后封装成令牌对象OAuth3Token,交给shiro框架去处理。
getRequestToken是一个自定义方法,用来获取令牌字符串,然后传递给字符串Token对象。
@Component @Scope("prototype") public class OAuth3Filter 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 { //获取请求token String token = getRequestToken((HttpServletRequest) request); if (StringUtils.isBlank(token)) { return null; } return new OAuth3Token(token); }
filter过滤这一块细讲一下: isAccessAllowed
是判断哪些请求可以被shiro处理,哪些不可以被shiro处理。
由于isAccessAllowed
方法中request
是ServletRequest
,所以需要进行转换HttpServletRequest
,
然后判断这次request
请求是不是options请求。如果不是,就需要被shiro处理。
/** * 拦截请求,判断请求是否需要被Shiro处理 */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { HttpServletRequest req = (HttpServletRequest) request; // Ajax提交application/json数据的时候,会先发出Options请求 // 这里要放行Options请求,不需要Shiro处理 if (req.getMethod().equals(RequestMethod.OPTIONS.name())) { return true; } // 除了Options请求之外,所有请求都要被Shiro处理 return false; }
那么,shiro是怎么处理的呢?
onAccessDenied 方法
设置响应的字符集,和响应的请求头。setHeader
方法用来设置跨域请求。
/** * 该方法用于处理所有应该被Shiro处理的请求 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; resp.setHeader("Content-Type", "text/html;charset=UTF-8"); //允许跨域请求 resp.setHeader("Access-Control-Allow-Credentials", "true"); resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin")); //clear方法用来清理threadLocal类中的方法, threadLocalToken.clear(); //获取请求token,如果token不存在,直接返回401 String token = getRequestToken((HttpServletRequest) request); if (StringUtils.isBlank(token)) { resp.setStatus(HttpStatus.SC_UNAUTHORIZED); resp.getWriter().print("无效的令牌"); return false; }
然后验证令牌是否过期。
如果验证出现问题,就会抛出异常。
通过捕获异常,就知道是令牌有问题,还是令牌过期了。JWTDecodeException
是内容异常。
通过redisTemplate
的hasKey
查询Redis
是否存在令牌。
如果存在令牌,就删除老令牌,重新生成一个令牌,给客户端。executeLogin
方法,让shiro
执行realm
类。
try { jwtUtil.verifierToken(token); //检查令牌是否过期 } catch (TokenExpiredException e) { //客户端令牌过期,查询Redis中是否存在令牌,如果存在令牌就重新生成一个令牌给客户端 if (redisTemplate.hasKey(token)) { redisTemplate.delete(token);//删除老令牌 int userId = jwtUtil.getUserId(token); token = jwtUtil.createToken(userId); //生成新的令牌 //把新的令牌保存到Redis中 redisTemplate.opsForValue().set(token, userId + "", cacheExpire, TimeUnit.DAYS); //把新令牌绑定到线程 threadLocalToken.setToken(token); } else { //如果Redis不存在令牌,让用户重新登录 resp.setStatus(HttpStatus.SC_UNAUTHORIZED); resp.getWriter().print("令牌已经过期"); return false; } } catch (JWTDecodeException 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.setStatus(HttpStatus.SC_UNAUTHORIZED); resp.setContentType("application/json;charset=utf-8"); resp.setHeader("Access-Control-Allow-Credentials", "true"); resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin")); try { resp.getWriter().print(e.getMessage());//捕获认证失败的消息 } catch (IOException exception) { } return false; }
获取请求头里面的token
/** * 获取请求头里面的token */ private String getRequestToken(HttpServletRequest httpRequest) { //从header中获取token String token = httpRequest.getHeader("token"); //如果header中不存在token,则从参数中获取token if (StringUtils.isBlank(token)) { token = httpRequest.getParameter("token"); } return token; }
doFilterInternal
方法从父类doFilterInternal中继承,掌管拦截请求和响应的。这里不覆写。
@Override public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { super.doFilterInternal(request, response, chain); } }
以上是“SpringBoot如何创建存储令牌的媒介类和过滤器”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注亿速云行业资讯频道!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。