关于SpringBoot创建存储令牌的媒介类和过滤器的问题

网友投稿 567 2022-12-07

关于SpringBoot创建存储令牌的媒介类和过滤器的问题

关于SpringBoot创建存储令牌的媒介类和过滤器的问题

之所以需要创建存储令牌的媒介类,是因为后面的filter界面要使用。

一、创建ThreadLocalToken类

创建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();//把绑定的数据删除了

}

}

下图为创建目录的层级关系:

二、创建OAuth2Filter类

创建过滤器的目的:

因为OAuth2Filter类要读写ThreadLocal中的数据,所以OAuth2Filter类必须要设置成多例的,否则ThreadLocal将无法使用。

配置文件中,添加JWT需要的密匙,过期时间和缓存过期时间。

emos:

jwt:

#密钥

secret: abc123456

#令牌过期时间(天)

expire: 5

#令牌缓存时间(天数)

cache-expire: 10

在com.example.emos.wx.config.shiro中创建OAuth2Filter类。

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使用OAuth2Filter类默认是多例。

@Value("${emos.jwt.cache-expire}")

考察的一个知识点,从xml文件中获取属性文件的属性值。

因为要在Redis中操作,所以要声明private RedisTemplate redisTemplate;

申明好这个对象后,就可以对redis中的数据进行读写操作了。

filter类用来区分哪些请求应该被shiro处理,哪些请求不该被shiro处理。

如果请求被shiro处理的话,那么createToken方法就被执行了,

createToken从请求中获取令牌字符串,然后封装成令牌对象OAuth2Token,交给shiro框架去处理。

getRequestToken是一个自定义方法,用来获取令牌字符串,然后传递给字符串Token对象。

@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 {

//获取请求token

String token = getRequestToken((HttpServletRequest) request);

if (StringUtils.isBlank(token)) {

return null;

}

return new OAuth2Token(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(ReqpZazMXuestMethod.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);

}

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:springboot 拦截器执行两次的解决方案
下一篇:MyBatis批量插入的几种方式效率比较
相关文章

 发表评论

暂时没有评论,来抢沙发吧~