SpringBoot整合Jwt 前后端分离认证

jwt基本概念不多说了,到处都是,直接上代码

一、生成Jwt

目录结构👇

Java前后端分离是怎么实现的 前后端分离 jwt_spring

创建一个普通的springboot 项目: springboot-jwt-demo

只需要加一个Spring Web的模块就ok

接下来咱们导入pom依赖

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

这个依赖是java jwt的依赖,里面帮我们封装了一些功能。咱们拿来即用即可

二、编写一个Jwt的工具包

JwtUtils

package com.xiaozhoubg.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.Base64Utils;

import java.util.Collections;
import java.util.Date;

public class JwtUtils {

    //2. 这里调用springboot的base64加密包,加密一个 字符串  ,因为一会我们要解密所以放到外部
    private static final byte[] KEY = Base64Utils.encode("Lanou3g".getBytes());

    //3. 自定定义一个方法,这样咱们现在只需要传用户id,时间自动3600秒(半小时)
    //如果说需要自己定义时间,那就调 1 方法即可。
    public static String newToken(Long id){
        return newToken(id,10);
    }

    //1. 创建一个变量 用户id ,过期时间秒 seconds 3600
    public static String newToken(Long id,long seconds){


        String token = Jwts.builder() //建造者方法
                .setId(String.valueOf(id)) //指定id值
                .setExpiration(new Date(System.currentTimeMillis() + seconds * 1000))//指定过期时间
                .addClaims(Collections.singletonMap("duration",seconds))
                .signWith(SignatureAlgorithm.HS256,KEY)//给签名指定算法 和密钥(base64加密的字符串)
                .compact();

        return token;
    }

//    4. 从token中取出id(用户id)
    //需要注意时,当token解析不出来的时候该方法会抛出异常(比如假token,过期token)
    public static Long getId(String token){
        //Claims是负载,也就是token的第二部分。也就是说咱们获取到他的负载
        Claims claims = (Claims) Jwts.parser()
                .setSigningKey(KEY)
                .parse(token)
                .getBody();

        String id =claims.getId();

        return Long.parseLong(id);
    }


//    用户登录,后台生成一个token,token返回给用户
//    用户每次请求都携带token,交给后台做验证
//    后台验证之后把token放到响应头再返回给用户
//    用户从响应头中取出token,更新本地的缓存

}

具体的步骤和原因,在代码的注释中包含了

三、测试类测试运行

咱们来编写一个测试类,这里教大家一个小窍门

在方法上面Alt+Enter 然后选生成test方法可以快速生成test方法

Java前后端分离是怎么实现的 前后端分离 jwt_java_02

@Test
   public void newToken() throws InterruptedException {
        String token = JwtUtils.newToken(10L, 10);
        System.out.println(token);


//        来测试 上面我们设置了token时长10秒
//        接下来咱们睡眠12秒再取,也就是说token过期了咱再取.发现方法抛出了异常
        Thread.sleep(12000);

        Long id = JwtUtils.getId(token);
        System.out.println(id);
    }

运行结果肯定是会报Jwt过期异常的,接下来咱们把 线程休眠 Thread.sleep(12000); 注释掉,再启动,控制台成功拿到 id 10

或者说咱们要是不喜欢的话,可以把异常给它抛出,让它返回null

public static Long getId(String token) {
        //Claims是负载,也就是token的第二部分。也就是说咱们获取到他的负载
        String id = null;
        try {
            Claims claims = (Claims) Jwts.parser()
                    .setSigningKey(KEY)
                    .parse(token)
                    .getBody();

            id = claims.getId();
        } catch (ExpiredJwtException e) {
            return null;
        } catch (MalformedJwtException e) {
            return null;
        } catch (SignatureException e) {
            return null;
        } catch (IllegalArgumentException e) {
            return null;
        }

        return Long.parseLong(id);
    }

四、编写前端index.html

接下来咱们编写前端,直接在线引用jQuery即可,咱们用它的ajax

resources => static 目录下 创建 index.html (模拟前后端分离)

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>jwt</title>
    <!-- 在线引用jQuery -->
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</head>
<body>
<!-- 写一个表单 -->
<form id="login-form" action="/login" method="post">

    用户名: <input type="text" name="username">
    密码: <input type="text" name="password">

    <input type="submit" >
</form>


存好后第二部需要取了,从浏览器缓存取到token
<button id="btn-user-info">获取用户信息</button>

<script>
    $('#login-form').submit(function (form) {
        //这里要阻止表单默认提交事件
        form.preventDefault();
        //通过ajax发送登录请求
        var username =$('input[name=username]').val();//拿到账户密码然后存到data
        var password =$('input[name=password]').val();

        var data ={
            username:username,
            password:password,
        }//存到data,下面通过post发送到controller

        $.post('/login',data,function (resp) {
            console.log(resp)
            //登录之后把token存起来,这段代码的意思是将后端传过来的值resp.token传到 浏览器请求头缓存中,命名为token
            window.localStorage.setItem("token",resp.token);

        });
        })

    
    //这里是取token的方法
    $('#btn-user-info').click(function () {
        // 1.从缓存中取出token,
        var token = window.localStorage.getItem('token');
        // 2. 发送请求同时,把token放到请求头里
        console.log("wote??"+token)
        $.ajax({
            url:'/info',
            type:'get',
            headers:{
                token:token
            },
            success:function (resp) {

                console.log(resp)


            }
        })

    })
</script>

</body>
</html>

五、编写一个UserController

UserController

这里咱们模拟从数据库中查找出来的数据即可,并不影响什么,大家可以自己去加上数据库

package com.xiaozhoubg.controller;

import com.xiaozhoubg.utils.JwtUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.Map;

@RestController
public class UserController {

    //注意,这里咱们不用链接数据库自己定义一个数据做模拟即可,实际开发中再替换成数据库,相信
    //大家一定都知道如何替换成数据库。

    //登录
    @PostMapping("/login")
    public Map login(String username,String password){
        //接收前端传值并且判断 账户密码
        if ("admin".equals(username)&&"admin".equals(password)){
            //赋值id=1001
            Long id =1001L;
            //根据这个id 去 咱们之前的JwtUtils中newToken方法 生成 token 
            String token = JwtUtils.newToken(id);
            //创建好token后return回前端
            return Collections.singletonMap("token",token);
        }else {
            //账户密码错误返回异常
            return Collections.singletonMap("erro","用户名或者密码错误");
        }
    }

    //获取token
    //@RequestHeader 注解可以去拿请求头
    @GetMapping("/info")
    public Map userInfo(@RequestHeader String token){
		// 根据JwtUtils.getId方法去通过token拿到id,判断id是否正确
        if (1001L == JwtUtils.getId(token)){
            return Collections.singletonMap("user","admin");

        }else {
             return Collections.singletonMap("user","token失效");

        }

    }
}

六、运行测试

ok接下来咱们运行测试一下哈

Java前后端分离是怎么实现的 前后端分离 jwt_spring_03

填写 用户名 密码 然后 点击提交,可以在Application里面看到咱们的请求头中有了token

Java前后端分离是怎么实现的 前后端分离 jwt_spring_04


接下来咱们点击 获取用户信息 在Network可以看到这个请求的请求头携带了token

Java前后端分离是怎么实现的 前后端分离 jwt_ajax_05

控制台也打印出来了后台的传值,因为token正确,那如果token错误呢,方法会报个异常,这个异常咱们怎么处理呢?我相信大家有很多方法去处理了,这里就不写了。

Java前后端分离是怎么实现的 前后端分离 jwt_java_06

成了!

七、总结

本节案例地址

咱们的jwt是有过期时间的,那么怎么来弥补这一点呢??这里给大家一个思路,咱们可以编写一个拦截器,并且在jwt工具类中封装一个方法,这个方法是去判断jwt,去判断jwt剩余的时间,假设剩余时间不足30%了,咱们可以让它重新生成一个新的jwt,咱们每一次请求或特定的请求,都走一遍拦截器,让拦截器去调咱们封装好的 刷新jwt的方法 把这个方法 return 的值加到请求头中。这是一个办法。

这就是jwt啦,大家可以去加上mysql,我这里没加mysql。

当然了,除了Jwt,咱们也可以使用UUID+Redis来实现,把随机生成的字符串保存到Redis中。