Springboot整合JWT

1. 应用场景

前后端分离项目保持登录状态。

问题:ajax请求如何跨域,将无法携带jsessionid,这样会导致服务器端的session不可用。如何解决?

后端:

登录接口在验证过用户密码后,将用户的身份信息转换成一个特殊格式的字符串(token),放入响应体(或响应头)。

前端:

浏览器收到登录成功响应后,保存该token在本地,当下次发送请求时,取出该token并携带在请求头(或请求体中)。

后端:

使用filter或者拦截器校验请求中的token是否有效,如果有效就代表用户已登录,放行。

2. 对于token的需求

(1) 可以自定义信息(用户身份、权限信息)

(2) 需要使得服务器端可以进行有效性校验(即token只能由服务器端颁发,不能由其他第三方伪造)
(3) 需要能设置有效期

3. JWT的数据结构

https://jwt.io/ 官网地址

(1)HEADER //头

(2)PAYLOAD //体

(3)SIGNATURE //签名

4. 生成JWT

HMACSHA256(

base64UrlEncode(header) + "." +base64UrlEncode(payload),
your-256-bit-secret
)
(1)计算header的base64编码
(2)计算payload的base64编码
(3)计算签名

5. 校验token是否合法

原理:重新计算token的sign,与token中的sign进行比对,一致则合法。

6. 校验token有效期

原理,在jwt的payload中加入过期日期,然后在校验token时检查是否过期

这是一个测试类需要导入Hutool工具类

java 复制代码
package com.qf.fmall.jwt;

import cn.hutool.jwt.JWT;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;

public class TestJwt {

    public static final String secretkey="qfsyjava2302";

    public static void main(String[] args) throws UnsupportedEncodingException, InterruptedException {

        String token1 = createToken();
        //验证token的合法性
//        String  token="eyJoZWFkZXIwMSI6InRlc3QxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsdWZmeSIsImlhdCI6MTY5MzIwNjc2NiwicGhvbmUiOiIxMzg5OTk5ODg4OCIsInNleCI6MSwiYWdlIjoxOX0.UY3v3twt30GAN6iCOlTTZwA0YpwMBFDg3JyWgxp1_7U";
        Thread.sleep(3000);
        //使用hutool工具类校验Jwt
        boolean validate = JWT.of(token1)//把token字符串传入,用来构建jwt对象
                .setKey(secretkey.getBytes("utf-8"))//传入计算签名要使用的密钥
//                .verify() 只验证token值,不验证时间
                .validate(3l);//校验jwt是否合法,本质上就是重算签名,并且比较签名是否一致。
                //这里的leeway是秒,就是在原本的设定的失效时间,容许容忍几秒
        System.out.println(validate);
    }

public  static  String  createToken() throws UnsupportedEncodingException {
    //利用hutool工具类生成Jwt

    HashMap<String, Object> payloadMap = new HashMap<>();
    payloadMap.put("age",19);
    payloadMap.put("sex",1);
    payloadMap.put("phone","13899998888");

    HashMap<String, Object> headMap = new HashMap<>();
    headMap.put("header01","test123");
    String token = JWT.create()
            .addHeaders(headMap) //放入自定义Jwt  头
            .setSubject("luffy")//在 jwt 的payload中放入 sub(代表用户身份信息)
            .setExpiresAt(new Date(System.currentTimeMillis()+1000*3))// 在jwt的payload中放入 jwt 失效时间
            .setIssuedAt(new Date()) //在 jwt 的payload 中 放入 jwt的签发时间
            .addPayloads(payloadMap)  // 在jwt 的payload 中放入其他自定义内容
            .setKey(secretkey.getBytes("utf-8")) //放入计算jwt 需要的密钥字符串
            .sign();
    System.out.println(token);
    return token;
}


}

7. 开启shiro登录校验

1.用拦截器的方式实现

实现拦截器接口

java 复制代码
package com.qf.fmall.interceptor;

import cn.hutool.jwt.JWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      //0.判断如果是浏览器发送的Option 请求,直接放行
        String method = request.getMethod();
        if (method.equals("OPTIONS")){
            return true;
        }


        //1.获取 请求头中的 token
        String token= request.getHeader("token");

        //校验jwttoken
        if (token==null){
            log.info("没带token,拒绝访问");
            return false;
        }
        boolean validate = JWT.of(token)
                .setKey("qfsyjava2302".getBytes("utf-8"))
                .validate(0);

        return validate;

    }
}

注册拦截器

java 复制代码
package com.qf.fmall.config;

import com.qf.fmall.interceptor.JwtInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class IntercepterConfig implements WebMvcConfigurer {
    @Autowired
    JwtInterceptor jwtInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/index/indeximg");
    }
}

2.整合shiro过滤器

1.前端登录方法

java 复制代码
 @GetMapping("/login")
    @CrossOrigin
    public ResultVo login(@RequestParam("username") String username,@RequestParam("password") String password) throws JsonProcessingException {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token =new UsernamePasswordToken(username,password);

        try {
            subject.login(token);
            Users principal = (Users) subject.getPrincipal();

            //当用户登录成功之后,创建jwt
            //把principal---> json
            ObjectMapper mapper = new ObjectMapper();//获取springboot内置的json转换对象
            String userjson = mapper.writeValueAsString(principal);

            String jwt = JWT.create()
                    .setSubject(userjson)
                    .setExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 30))
                    .setIssuedAt(new Date())
                    .setKey("qfsyjava2302".getBytes())
                    .sign();


            return ResultVo.vo(10000,jwt,principal);
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return ResultVo.vo(112300,"failed",null);
        }
    }

2.后端验证过滤器

java 复制代码
package com.qf.fmall.filter;

import cn.hutool.jwt.JWT;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.springframework.stereotype.Component;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

@Component("jwt")
@Slf4j
public class JwtFilter extends AccessControlFilter {

    /**
     * 用于验证访问受保护的方法
     * @param servletRequest
     * @param servletResponse
     * @param o
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //0.判断如果是浏览器发送的Option 请求,直接放行
        String method = request.getMethod();
        if (method.equals("OPTIONS")){
            return true;
        }


        //1.获取 请求头中的 token
        String token= request.getHeader("token");

        //校验jwttoken
        if (token==null){
            log.info("没带token,拒绝访问");
            return false;
        }
        boolean validate = JWT.of(token)
                .setKey("qfsyjava2302".getBytes("utf-8"))
                .validate(0);

        return validate;

    }

    /**
     * onAccessDenied 方法是Shiro框架中的一个回调方法,当主体(用户)被拒绝访问特定资源时会被调用。该方法不返回布尔值。
     * 当主体尝试访问受保护的资源但权限不足或未认证时,Shiro会触发 onAccessDenied 方法。通常,它用于处理被拒绝访问的情况,例如将用户重定向到错误页面或返回错误消息。
     * 因此,说 onAccessDenied 方法返回 true 是没有意义的。该方法的目的是处理访问被拒绝的情况,而不是返回布尔值。
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        return false;
    }
}

3.让请求走shiro过滤器

在ShiroConfig类下写

java 复制代码
    //配置Shiro过滤器链
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        chainDefinition.addPathDefinition("/user/login**","anon");
        chainDefinition.addPathDefinition("/user/regist**","anon");
        chainDefinition.addPathDefinition("/error","anon");

      //让/index/indeximg 由自定义的shiro filter 进行处理, "jwt" 这个值是IOC容器中filter的名字
        chainDefinition.addPathDefinition("/index/indeximg","jwt");
//        chainDefinition.addPathDefinition("/**","authc");
        return chainDefinition;

    }

但是shiro不仅会在shiro过滤器链下注册此过滤器,还会在springmvc过滤器链上注册

所以我们,还要让springmvc上的过滤器链上的此过滤器失效

4.让全局过滤器链上的JwtFilter失效

java 复制代码
package com.qf.fmall.config;

import com.qf.fmall.filter.JwtFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 注意,为了不让 自定义filter也被中注册到全局FitlterChain中,
 * 需要添加如下配置类
 */
@Configuration
public class FilterConfig {

    @Autowired
    JwtFilter jwtFilter;

    @Bean
    public FilterRegistrationBean<JwtFilter> jwtFilterFilterRegistrationBean(){
        FilterRegistrationBean<JwtFilter> registrationBean = new FilterRegistrationBean<>();

        //要注册的filter的对象
        registrationBean.setFilter(jwtFilter);

        //让当前filter不用注册到,全局过滤器链上
        registrationBean.setEnabled(false);
   
        return registrationBean;

    }


}
相关推荐
2402_857589367 分钟前
SpringBoot框架:作业管理技术新解
java·spring boot·后端
HBryce2410 分钟前
缓存-基础概念
java·缓存
一只爱打拳的程序猿25 分钟前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
杨荧26 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
minDuck28 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
白子寰33 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
王俊山IT1 小时前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。1 小时前
c++多线程
java·开发语言
小政爱学习!1 小时前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript