springboot整合JWT

本文介绍了JWT(JSONWebToken)在JavaWeb中的应用,对比了session的不足,阐述了JWT的简洁性、自包含性和安全性优势。详细讲解了JWT的结构组成,包括标头、有效负载和签名,并提供了整合JWT的关键步骤和示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述        

JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于各方之间安全地将信息作为JSON对象传输,在数据传输的过程中还可以完成数据加密、签名等相关处理。

 二、优势所在

在JavaWeb阶段,经常使用session来存储,以方便用来判断用户是否操作等等。但这又恰巧暴露了一个问题,用session存储是需要占用服务器内存的。当用户只有一两个的时候没什么问题,但是当用户成千上万的话,服务器就很难招架得住了。并且session是基于cookie来实现的,因此数据很容易被截获,遭遇CSRF跨域伪造攻击,因此不太安全。这时JWT的优势就突显出来的:

  • 简洁(Compact):可以通过URL、POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
  • 因为Token是以JSON加密的形式保存在客户端的,所有JWT是跨语言的,原则上任何web形式都支持。
  • 不需要在服务端保存会话信息,特别适用于分布式微服务

三、结构组成

1.标头(Header)

标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256(默认)或RSA。它会使用Base64编码组成WT结构的第一部分。值得注意的是,Base64是一种编码,也就是说,它是可以被翻译回原来的样子的,它并不是一种加密过程。

JSON
{
    "alg":"HS256",
    "typ":"JWT"
}

2.有效负载(Payload)

有效负荷是包含声明,通常将需要传递的数据存放在Payload中,同样的是使用Base64编码,但这同时说明这一块是可以被反编译的,这是什么意思呢?就是太敏感的信息存放在这里的话存在被捕获的可能,因此官方推荐在Payload中不要存放敏感的信息,例如住址、密码、手机号等。

3.签名(Signature)

签名是由编码后的标头和有效负荷以及我们提供的一个密钥,然后使用标头规定的签名算法进行签名。因此,签名的主要作用是保证JWT没有被篡改过,保证token的合法性。

这里特别注意的是,密钥secret 是验证 token 是否合法的重要凭证,如果用于验证的密钥被外界所获取到的话,我们所建立的验证防线将如同虚设!

 四、开始整合JWT

1.添加核心依赖

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.2</version>
        </dependency>

2.编写 JwtUtils,提高代码重用率

package com.example.springboot_jwt.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JWTUtils {
    //首先顶一个token失效时间 2分钟
    private static final Long EXPIRATION=60 * 1000l* 2;
    //设置一个密钥,一般长度较长,比较复杂
    private static final String SECRET="3idi83rie9";

    /**
     * 生成token的方法
     * @param claimMap
     * @return
     */
    public static String creatToken(Map<String,String> claimMap){
        //设置正式的失效时间=当前时间+失效时间戳
        Date expiration=new Date(new Date().getTime()+EXPIRATION);
        //设置JWT的头部
        Map<String, Object> map=new HashMap<>();
        map.put("alg","hs256");
        map.put("typ","JWT");
        //   创建token
        JWTCreator.Builder builder = JWT.create();
        // 使用lambda创建playload:有效负载,存放需要传递的数据,通过base64编码,也可以被反编码。
        claimMap.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        return builder.withHeader(map)//添加头部,可省略保持默认
                .withExpiresAt(expiration)//设置过期时间
                .sign(Algorithm.HMAC256(SECRET));//设置签名解密算法

    }

    /**
     * 对token进行验证
     * @param token
     * @return
     */
    public static DecodedJWT verifyToken(String token){
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
        System.out.println("验证token的函数得到的token"+verify.getToken());
        System.out.println("验证token的函数得到的header"+verify.getHeader());
        System.out.println(verify.getPayload());
        return verify;
    }
}

 3.编写过滤器

为了不用在每一个controller控制器访问之前都写一遍获取token,将token放在请求头中,简化开发。

package com.example.springboot_jwt.interceptor;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.example.springboot_jwt.util.JWTUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;


public class JwtInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HashMap<String, Object> map = new HashMap<>();
        //获取请求头中令牌 获取请求头中的token,每次前端发送请求都要给我们传递token
        String token = request.getHeader("token");
        System.out.println("拦截器中的token是:" + token);
        try {
            //验证令牌
            JWTUtils.verifyToken(token);
            //验证成功,请求放行
            return true;
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            map.put("msg","无效签证");
        }catch (TokenExpiredException e){
            e.printStackTrace();
            map.put("msg","token过期");
        }catch (AlgorithmMismatchException e){
            e.printStackTrace();
            map.put("msg","token算法不一致!");
        }catch (Exception e){
            e.printStackTrace();
            map.put("msg","token无效!!");
        }
        //设置状态
        map.put("state",false);
        //将map转换为json
        String json= new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

4.配置过滤器

package com.example.springboot_jwt.config;

import com.example.springboot_jwt.interceptor.JwtInterceptor;
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 MyConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JwtInterceptor())
                .addPathPatterns("/**")//添加拦截路径
                .excludePathPatterns("/login/user","/login/other");//添加放行路径
    }
}

** 注意:添加的放行路径会影响到后面测试接口。

              声明该类是一个配置类,注入到容器中。

5.编写yaml配置文件

spring:
  datasource: #数据源配置
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///mybatis
    username: root
    password: root
mybatis: #mapper访问路径
  mapper-locations: classpath:mapper/*.xml
  configuration: #sql日志输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

6.创建实体类

package com.example.springboot_jwt.entity;

import java.io.Serializable;

public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;

    public User() {
    }

    public User(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

7.编写mapper

package com.example.springboot_jwt.mapper;

import com.example.springboot_jwt.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
    User login(User user);
}

 8.编写mapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springboot_jwt.mapper.UserMapper">


    <select id="login" resultType="com.example.springboot_jwt.entity.User">
        select * from user where username=#{username} and password=#{password}
    </select>

</mapper>

9.编写service

package com.example.springboot_jwt.service;

import com.example.springboot_jwt.entity.User;

public interface UserService {
    User longin(User user);
}

10.编写service实现

package com.example.springboot_jwt.service.impl;

import com.example.springboot_jwt.entity.User;
import com.example.springboot_jwt.mapper.UserMapper;
import com.example.springboot_jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    UserMapper userMapper;
    @Override
    public User longin(User user) {
        return userMapper.login(user);
    }
}

  11.编写控制器

package com.example.springboot_jwt.handler;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.springboot_jwt.entity.User;
import com.example.springboot_jwt.service.UserService;
import com.example.springboot_jwt.util.JWTUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/login")
public class UserHandler {
    @Resource
    UserService userService;
    @GetMapping("/user")
    public Map<String,Object> login(User user){
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());
        HashMap<String, Object> map = new HashMap<>();
        try {
            HashMap<String, String> payload = new HashMap<>();
            User login=userService.longin(user);
            if (null!=login){
                payload.put("id",user.getId()+"");
                payload.put("username",user.getUsername());
                payload.put("password",user.getPassword());
                // 生成JWT的令牌
                String s = JWTUtils.creatToken(payload);
                map.put("state",true);
                map.put("msg","认证成功");
                //相应token
                map.put("token",s);
            }
        }catch (Exception e){
            map.put("state",false);
            map.put("msg",e.getMessage());
        }
        return map;

    }
    @PostMapping("/other")
    public Map<String,Object> other(HttpServletRequest request){
        Map<String,Object> map=new HashMap<>();
        String token = request.getHeader("token");
        DecodedJWT decodedJWT = JWTUtils.verifyToken(token);
        System.out.println(decodedJWT.getClaim("username").asString());
        System.out.println(decodedJWT.getClaim("password").asString());
        map.put("state",true);
        map.put("msg","请求成功");
        return map;
    }
}

12.数据库表设计

表名为 user

列名数据类型是否为空主键备注
idint主键id
usernamevarchar(50)用户名
passwordvarchar(50)密码

五、测试

1.登录测试

 左边是参数传递,右边是响应结果,如下所示:

{
	"msg": "认证成功",
	"state": true,
	"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6ImFkbWluIiwiaWQiOiJudWxsIiwiZXhwIjoxNjkyMzU3NTQxLCJ1c2VybmFtZSI6ImFkbWluIn0.PPC1tClbLGDuc61kaSmXr11fQ-s2DvNOGxc0KBxPCEc"
}

2.其它接口验证(token验证)

 将第一接口传递回来的token作为参数,传递至token验证的请求头中,通过调用接口来验证token的是否正确

在本次案例中,本人设置token的有效时长为两分钟,两分钟之后的测试结果如下:

控制台输出结果如下:

 token已经于某某某时间失效...

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值