千家信息网

SpringBoot如何创建存储令牌的媒介类和过滤器

发表于:2024-10-19 作者:千家信息网编辑
千家信息网最后更新 2024年10月19日,这篇文章主要介绍SpringBoot如何创建存储令牌的媒介类和过滤器,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、创建ThreadLocalToken类创建ThreadLo
千家信息网最后更新 2024年10月19日SpringBoot如何创建存储令牌的媒介类和过滤器

这篇文章主要介绍SpringBoot如何创建存储令牌的媒介类和过滤器,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

一、创建ThreadLocalToken类

创建ThreadLocalToken类的目的:

com.example.emos.wx.config.shiro中创建ThreadLocalToken类。
写入如下代码:

package com.example.emos.wx.config.shiro;import org.springframework.stereotype.Component;@Componentpublic 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类

创建过滤器的目的:

因为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方法中requestServletRequest ,所以需要进行转换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 是内容异常。

通过redisTemplatehasKey查询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如何创建存储令牌的媒介类和过滤器"这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注行业资讯频道!

0