JWT由繁入简 带你三分钟明白JWT的使用原则(直接上手COPY有手就行)

先上代码 再看解释😊

后端:

第一步:

引入pom依赖(我这里是jdk1.8引入的jjwt

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

第二步

这是全部的创建token和解析token

 

package com.example.hyzn.demos.utils;

import cn.hutool.core.lang.UUID;
import io.jsonwebtoken.*;

import java.util.Date;

public class JwtUtil {
    private static long time= 1000*60*60*24;//设置的过期时间是一天
    private static String signature="admin";
    
    //创建token的代码
    public static String createToken(){
        JwtBuilder jwtBuilder = Jwts.builder();
        String stringJwtBuilder = jwtBuilder
                //header
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                //patload
                .claim("userName", "Shen")
                .claim("role", "admin")
                .setSubject("admin-test")
                .setExpiration(new Date(System.currentTimeMillis() + time))
                .setId(UUID.randomUUID().toString())
                //signature
                .signWith(SignatureAlgorithm.HS256, signature)
                .compact();
       return stringJwtBuilder;
    }
   //解析token的代码
    public static boolean checkToken(String token){
        if(token == null){
            return false;
        }
        try {
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(signature).parseClaimsJws(token);
        } catch (Exception e) {
            return false;
        }
        return true;
    }
}

第三步

测试entity(必须要有这个Serializable序列化)

package com.example.hyzn.demos.domain.Request;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * 学生表
 * @TableName user
 */
@TableName(value ="user")
@Data
public class UserQuest implements Serializable {
    /**
     * 账号
     */
    @TableId
    private String userName;

    /**
     * 密码
     */
    private String password;

    /**
     * token
     */
    private String token;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

第四步

后端的controller层这里面设置的是死的账号密码

/**
 * @author 32551
 */
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    private final String USERNAME="admin";

    private final String PASSWORD="123456";

    @PostMapping("/register")
    public BaseResponse<Long> userRegister(@RequestBody UserVo userVo) {
        if (userVo == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        return ResultUtils.success(userService.userRegister(userVo), "成功");
    }


    @PostMapping("/login")
    public BaseResponse<UserQuest> userLogin(@RequestBody UserQuest userQuest) {
       if(USERNAME.equals(userQuest.getSno())&&PASSWORD.equals(userQuest.getPassword())){
           userQuest.setToken(JwtUtil.createToken());
           return ResultUtils.success(userQuest,"成功");
       }
       return null;
    }
    @GetMapping("/checkToken")
    public Boolean checkToken(HttpServletRequest request){
        String token = request.getHeader("token");
        return JwtUtil.checkToken(token);
    }
}

第五步

解决跨域问题 (这里面的   .allowedOrigins("http://localhost:9898/#/userLogin","http://localhost:9898/#/chat"是用来拦截前端页面的按自己的需要来)


/**
 * 全局跨域处理
 */
@Configuration
public class CoresConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(@NotNull CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOriginPatterns("*")
                        .allowedOrigins("http://localhost:9898/#/userLogin", "http://localhost:9898/#/chat")
                        .allowCredentials(true)
                        .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                        .maxAge(3600);
            }
        };
    }
}


这里的后端就大功告成了

前端:

在router配置文件中设置文件跳转问题

这里面配置跳转页面拦截

router.beforeEach((to, from, next) => {
    // 允许未登录访问的路径
    const openPaths = ['/userLogin', '/chat'];

    if (openPaths.includes(to.path)) {
        // 如果目标路径是开放路径,直接放行
        next();
    } else {
        // 获取存储在本地的admin对象
        let admin = JSON.parse(window.localStorage.getItem('access-admin'));

        if (!admin) {
            // 如果没有admin对象,跳转到登录页面
            next({ path: '/userLogin' });
        } else {
            // 验证token的合法性
            axios({
                url: 'http://localhost:7877/user/checkToken',
                method: 'get',
                headers: {
                    token: admin.token
                }
            }).then((response) => {
                if (!response.data) {
                    // 如果token验证失败,跳转到登录页面
                    console.log('校验失败');
                    next({ path: '/userLogin' });
                } else {
                    // 如果token验证成功,继续导航
                    next();
                }
            }).catch(() => {
                // 处理验证请求失败的情况
                next({ path: '/userLogin' });
            });
        }
    }
});

再设置login页面

主要看js代码就好了 

前端你们得自己配置跳转路由 我这里安装了router

<template>
  <div class="login-page">
    <van-nav-bar title="登录" left-arrow />
    <div class="login-container">
      <h2 class="login-title">欢迎登录移动服务平台</h2>
      <van-field class="van-border" v-model="sno" label="" placeholder="请输入身份证号" />
      <van-field class="van-border" v-model="password" label="" type="password" placeholder="请输入密码" />
      <van-button type="danger" class="login-button" @click="onLogin">登录</van-button>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import {showFailToast, showSuccessToast, Toast} from 'vant';
import myaxios from "../Axios/myaxios";

const router=useRouter();
const sno = ref('');
const password = ref('');

const onLogin = () => {
    myaxios.post('/user/login', {
      sno: sno.value,
      password: password.value
    }).then(response=>{
      console.log(response.data)
      if(response.data!=null){
        localStorage.setItem('access-admin',JSON.stringify(response.data))//把信息存储到本地
        router.push('/')
      }
    })
};


</script>

<style scoped>
.login-page {
  display: flex;
  flex-direction: column;
}

.login-container {
  flex: 1;
  padding: 20px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.login-title {
  text-align: center;
  margin-bottom: 30px;
}

.login-button {
  margin-top: 20px;
  border-radius: 10px;
}

.van-border {
  border: 1px solid #969896;
  margin-top: 10px;
  border-radius: 10px;
}
</style>

这时候就都完事了

看运行代码结果

那就再来解释解释什么是JWT

jwt的全名称

JSON Web Token(JSON Web令牌)

JWT作用:

  • 身份验证

    JWT常用于身份验证。在用户登录时,服务器生成一个JWT并返回给客户端。客户端每次请求时都带上这个JWT,服务器通过验证JWT来确认用户的身份。
  • 信息交换

    由于JWT可以携带声明(claims),它们可以用来在不同的服务之间传递信息。这些声明可以包含用户的权限、角色等信息。
  • 安全性

    JWT使用签名来确保内容的完整性和真实性。常见的签名算法有HMAC和RSA。通过签名,接收方可以验证消息是否被篡改过。
  • 无状态性

    JWT是无状态的,所有的信息都包含在token本身中,不需要在服务器上存储会话信息。这使得JWT在分布式系统中尤其有用。

JWT的结构

JWT由三个部分组成,每一部分使用.分隔:

  1. Header(头部):

    • 通常包含两部分:令牌类型(JWT)和使用的签名算法(如HMAC SHA256或RSA)。
{
  "alg": "HS256",
  "typ": "JWT"
}
  1. Payload(负载):

    • 包含声明(claims)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册声明(如iss, exp, sub, aud)、公开声明和私有声明。
    • {
        "sub": "1234567890",
        "name": "John Doe",
        "admin": true
      }
      
  2. Signature(签名):

    • 用于验证消息在传输过程中是否被篡改。签名部分是使用header中指定的算法对header和payload进行签名得到的。HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

工作流程

  1. 用户通过认证(如登录)后,服务器生成JWT并返回给客户端。
  2. 客户端将JWT存储在本地(如localStorage或cookie)。
  3. 客户端每次请求时都将JWT包含在HTTP头部中(如Authorization: Bearer <token>)。
  4. 服务器接收到请求后,验证JWT的有效性和真实性。
  5. 服务器处理请求并返回响应。

  • 15
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值