JWT令牌技术解决spring博客项目的登录问题(三)

5. 实现登陆

        分析

        传统思路:

        1、 登陆⻚⾯把⽤⼾名密码提交给服务器. 

        2、服务器端验证⽤⼾名密码是否正确, 并返回校验结果给后端

        3、如果密码正确, 则在服务器端创建 Session . 通过 Cookie 把 sessionId 返回给浏览器.

        问题:

        集群环境下⽆法直接使⽤Session. 

        原因分析:

        我们开发的项⽬, 在企业中很少会部署在⼀台机器上, 容易发⽣单点故障. (单点故障: ⼀旦这台服务器挂 了, 整个应⽤都没法访问了). 所以通常情况下, ⼀个Web应⽤会部署在多个服务器上, 通过Nginx等进⾏负载均衡,此时, 来⾃⼀个⽤⼾的请求就会被分发到不同的服务器上,请求逻辑如下所示:

        假如我们使⽤Session进⾏会话跟踪, 会出现下面的场景:

         1. ⽤⼾登录 ⽤⼾登录请求, 经过负载均衡, 把请求转给了第⼀台服务器, 第⼀台服务器进⾏账号密码 验证, 验证成功后, 把Session存在了第⼀台服务器上

        2. 查询操作 ⽤⼾登录成功之后, 携带Cookie(⾥⾯有SessionId)继续执⾏查询操作, ⽐如查询博客列 表. 此时请求转发到了第⼆台机器, 第⼆台机器会先进⾏权限验证操作(通过SessionId验证⽤⼾是否 登录), 此时第⼆台机器上没有该⽤⼾的Session, 就会出现问题,来提⽰⽤⼾登录, 这样就会给用户带来不好的体验;该场景的逻辑如下所示:

        接下来我们介绍另一种⽅案: 令牌技术

5.1 令牌技术

        令牌其实就是⼀个⽤⼾⾝份的标识, 其实本质就是⼀个字符串. ⽐如我们出⾏在外, 会带着⾃⼰的⾝份证, 需要验证⾝份时, 就掏出⾝份证⾝份证不能伪造, 可以辨别真假 ;其运行逻辑如下所示:

        服务器具备⽣成令牌和验证令牌的能⼒

        我们使⽤令牌技术, 继续思考上述场景:

        1. ⽤⼾登录 ⽤⼾登录请求, 经过负载均衡, 把请求转给了第⼀台服务器, 第⼀台服务器进⾏账号密码 验证, 验证成功后, ⽣成⼀个令牌, 并返回给客⼾端.

        2. 客⼾端收到令牌之后, 把令牌存储起来. 可以存储在Cookie中, 也可以存储在其他的存储空间(⽐如 localStorage)

        3. 查询操作 ⽤⼾登录成功之后, 携带令牌继续执⾏查询操作, ⽐如查询博客列表. 此时请求转发到了 第⼆台机器, 第⼆台机器会先进⾏权限验证操作. 服务器验证令牌是否有效, 如果有效, 就说明⽤⼾已 经执⾏了登录操作, 如果令牌是⽆效的, 就说明⽤⼾之前未执⾏登录操作 

        令牌的优缺点

        优点:

                 • 解决了集群环境下的认证问题

                 • 减轻服务器的存储压⼒(⽆需在服务器端存储)

        缺点:

        需要⾃⼰实现(包括令牌的⽣成, 令牌的传递, 令牌的校验)

        当前企业开发中, 解决会话跟踪使⽤最多的⽅案就是令牌技术.  

5.2 JWT令牌

        令牌本质就是⼀个字符串, 他的实现⽅式有很多, 我们采⽤⼀个JWT令牌来实现.

5.2.1 介绍

        JWT全称: JSON Web Token

        官⽹:JSON Web Tokens - jwt.io

        JSON Web Token(JWT)是⼀个开放的⾏业标准(RFC 7519), ⽤于客⼾端和服务器之间传递安全可靠的信息. 其本质是⼀个token, 是⼀种紧凑的URL安全⽅法.

5.2.2 JWT组成

        JWT由三部分组成, 每部分中间使⽤点 (.) 分隔,⽐如:aaaaa.bbbbb.cccc

         • Header(头部) 头部包括令牌的类型(即JWT)及使⽤的哈希算法(如HMAC SHA256或RSA)

         • Payload(负载) 负载部分是存放有效信息的地⽅, ⾥⾯是⼀些⾃定义内容. ⽐如: {"userId":"123","userName":"shenemngyao"} , 也可以存在jwt提供的现场字段, ⽐如 exp(过期时间戳)等. 此部分不建议存放敏感信息, 因为此部分可以解码还原原始内容.

        • Signature(签名) 此部分⽤于防⽌jwt内容被篡改, 确保安全性. 防⽌被篡改, ⽽不是防⽌被解析. JWT之所以安全, 就是因为最后的签名. jwt当中任何⼀个字符被篡改, 整个令牌都会校验失败. 就好⽐我们的⾝份证, 之所以能标识⼀个⼈的⾝份, 是因为他不能被篡改, ⽽不是因为内容加密.(任 何⼈都可以看到⾝份证的信息, jwt 也是) 

        对上⾯部分的信息, 使⽤Base64Url 进⾏编码, 合并在⼀起就是jwt令牌 Base64是编码⽅式,⽽不是加密⽅式

5.3 JWT令牌⽣成和校验

5.3.1. 引⼊JWT令牌的依赖

 <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <!-- or jjwt-gson if Gson is preferred -->
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <!-- or jjwt-gson if Gson is preferred -->
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>

5.3.2. JWT令牌的⽣成和校验

        使⽤Jar包中提供的API来完成JWT令牌的⽣成和校验,代码如下:

package com.example.spring_blog_24_9_8;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import org.junit.jupiter.api.Test;

import javax.crypto.SecretKey;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JWTUtisTest {
    //过期时间: 1小时的毫秒数
    private final static long EXPIRATION_DATE = 60 * 60 * 1000;
    //秘钥
    private final static String secretString = "MDDOvlf8sQ675YuOsOxu45EXYK1dl/PoAyJq9C8vta0=";
    //生成安全秘钥
    private final static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));

    //生成令牌
    @Test
    public void genToken(){
        //⾃定义信息
        Map<String, Object> claim = new HashMap<>();
        claim.put("userId", 1);
        claim.put("userName", "shenmengyao");

        String token = Jwts.builder()
                .setClaims(claim) //⾃定义内容(负载
                .setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_DATE))//设置过期时间
                .signWith(key)
                .compact();
        System.out.println(token);
    }

    //生成key
    @Test
    public void genKey(){
        //生成key
        SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String encode = Encoders.BASE64.encode(secretKey.getEncoded());
        System.out.println(encode);
    }
    //校验令牌
    @Test
    public void parseToken(){
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6InNoZW5tZW5neWFvIiwidXNlcklkIjoxLCJleHAiOjE3MjU5NTMzMTl9.Aa7kKOH6vJFo09eEYCLtGCqRh6lBiXucRh-ze-F-IY8";
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        Claims body = null;
        try {
            body = build.parseClaimsJws(token).getBody();
        } catch (Exception e) {
            System.out.println("令牌校验失败");
        }
        System.out.println(body);
    }
}

生成密钥: 

生成令牌:

我们把⽣成的令牌通过官⽹进⾏解析, 就可以看到我们存储的信息了:

令牌校验:

         令牌解析后, 我们可以看到⾥⾯存储的信息,如果在解析的过程当中没有报错,就说明解析成功了. 令牌解析时, 也会进⾏时间有效性的校验, 如果令牌过期了, 解析也会失败. 修改令牌中的任何⼀个字符, 都会校验失败, 所以令牌⽆法篡改

        学习令牌的使⽤之后, 接下来我们通过令牌来完成⽤⼾的登录

        1. 登陆⻚⾯把⽤⼾名密码提交给服务器.

        2. 服务器端验证⽤⼾名密码是否正确, 如果正确, 服务器⽣成令牌, 下发给客⼾端.

        3. 客⼾端把令牌存储起来(⽐如Cookie, local storage等), 后续请求时, 把token发给服务器

        4. 服务器对令牌进⾏校验, 如果令牌正确, 进⾏下⼀步操作

 5.4 约定前后端交互接⼝

[请求]
/user/login
username=shenmengyao&password=111111
[响应] 
{
 "code": 200,
 "msg": "",
 "data": 
"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsImlhdCI6MTY5ODM5N
zg2MCwiZXhwIjoxNjk4Mzk5NjYwfQ.oxup5LfpuPixJrE3uLB9u3q0rHxxTC8_AhX1QlYV--E"
}
//验证成功, 返回token, 验证失败返回 ""

5.5 实现服务器代码

        创建JWT⼯具类 

package com.example.spring_blog_24_9_8.utils;


import com.example.spring_blog_24_9_8.constants.Constant;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;

import java.security.Key;
import java.util.Date;
import java.util.Map;

@Slf4j
public class JwtUtils {
    //过期时间: 1小时的毫秒数
    private final static long EXPIRATION_DATE = 600000 * 60 * 1000;
    private final static String secretString = "2LT0G2Og7zG9aOPJR+Y4cLMGicyUoRVPS4J0xRlpZzg=";
    private final static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));

    //生成令牌
    public static String genToken(Map<String, Object> claim){
       return Jwts.builder()
                .setClaims(claim)
                .setExpiration(new Date(System.currentTimeMillis()+EXPIRATION_DATE))
                .signWith(key)
                .compact();
    }
    /**
     * 解析令牌
     * @param token
     * @return
     */
    public static Claims parseToken(String token){
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        Claims body = null;
        try {
            body = build.parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            log.error("token过期, 校验失败, token:",token);

        } catch (Exception e) {
            log.error("token校验失败, token:",token);
        }
        return body;
    }
    //校验令牌
    public static boolean checkToken(String token){
        Claims body = parseToken(token);
        if (body==null){
            System.out.println("555");
            return false;
        }
        System.out.println("444");
        return true;
    }
    public static Integer getUserIdFromToken(String token){
        Claims body = parseToken(token);
        if (body!=null){
            System.out.println("222");
            return (Integer) body.get("id");

        }
        System.out.println("111");
        return null;
    }
}

创建 UserController

package com.example.spring_blog_24_9_8.controller;

import com.example.spring_blog_24_9_8.model.Result;
import com.example.spring_blog_24_9_8.model.User;
import com.example.spring_blog_24_9_8.service.UserService;
import com.example.spring_blog_24_9_8.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public Result login(HttpServletRequest request, HttpServletResponse
            response, String username, String password){
        if (!StringUtils.hasLength(username) ||
                !StringUtils.hasLength(password)){
            log.error("username:"+username+",password:"+password);
            return Result.fail(-1, "⽤⼾名或密码为空");
        }
        //判断账号密码是否正确
        User user = userService.getUserInfo(username);
        if (user==null || !user.getPassword().equals(password)){
            return Result.fail(-1, "⽤⼾名或密码错误");
        }
        //登录成功, 返回token
        Map<String , Object> claims = new HashMap<>();
        claims.put("id", user.getId());
        claims.put("username", user.getUserName());
        String token = JwtUtils.genToken(claims);
        System.out.println("⽣成token:"+ token);
        return Result.success(token);
    }
}

userservice:

@Service
public class UserService {
 @Autowired
 private UserMapper userMapper;
 public User getUserInfo(String username){
      return userMapper.selectByName(username);
 }
}

5.6 实现客⼾端代码

        修改 login.html, 完善登录⽅法 前端收到token之后, 保存在localstorage中

function login() {
            //发送ajax请求, 获得token
            $.ajax({
                type:"post",
                url: "/user/login",
                data:{
                    "username": $("#username").val(),
                    "password": $("#password").val()
                },
                success:function(result){
                    if(result.code==200 && result.data != ""){
                        //存储token
                        localStorage.setItem("user_token", result.data);
                        location.href = "blog_list.html";
                    }else{  // 自行补充完整
                        alert("用户名或密码错误")
                    }
                }
            });
        }

local storage相关操作

        存储数据

localStorage.setItem("user_token","value");

        读取数据

 localStorage.getItem("user_token");

        删除数据

localStorage.removeItem("user_token");

        运行服务器,访问登录页面,输入正确的账号和密码:

        输入错误的账号和密码,查看返回的结果:

本文的内容到这里就结束了,谢谢观看!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/874396.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【spring】IDEA 新建一个spring boot 项目

参考新建项目-sprintboot 选择版本、依赖,我选了一堆 maven会重新下载一次么?

Git 的使用以及vscode 下git 的使用(一)

1、git 和svn Git 和 SVN 都是版本控制系统&#xff0c;它们都用于管理代码的版本&#xff0c;但它们之间有一些显著的区别&#xff1a; 分布式 vs 集中式&#xff1a;Git 是一个分布式版本控制系统&#xff0c;这意味着每个开发者都拥有整个代码库的完整副本&#xff0c;并且…

计算机网络 --- 【2】计算机网络的组成、功能

目录 一、计算机网络的组成 1.1 从组成部分看 1.2 从工作方式看 1.3 从逻辑功能看 1.4 总结 二、计算机网络的功能 2.1 数据通信 2.2 资源共享​编辑 2.3 分布式处理 2.4 提高可靠性 2.5 负载均衡 一、计算机网络的组成 1.1 从组成部分看 我们举例分析计算机网络从…

python求两条曲线的边界线

问题描述&#xff1a; 已知两个平面曲线点集&#xff0c;一个取自yx**2-2&#xff0c;x的区间为[-4,4],此点集存在列表X1,Y1中&#xff0c;另外一个取自y-2*x,x的区间为[-5,5]&#xff0c;此点集存在列表X2,Y2中&#xff0c;两条曲线将平面分成几个部分&#xff0c;现在要求两…

若依框架登录鉴权详解(动态路由)

若依框架登录鉴权&#xff1a;1.获取token&#xff08;过期在响应拦截器中实现&#xff09;,2.基于RBAC模型获取用户、角色和权限信息&#xff08;在路由前置守卫&#xff09;&#xff0c;3.根据用户权限动态生成&#xff08;从字符串->组件&#xff0c;根据permission添加动…

NUUO网络视频录像机 css_parser.php 任意文件读取漏洞复现

0x01 产品简介 NUUO网络视频录像机(Network Video Recorder,简称NVR)是NUUO Inc.生产的一种专业视频监控设备,它广泛应用于零售、交通、教育、政府和银行等多个领域。能够同时管理多个IP摄像头,实现视频录制、存储、回放及远程监控等功能。它采用先进的视频处理技术,提供…

【笔记】408刷题笔记

文章目录 三对角三叉树求最小带权路径UDP报文首部和TCP报文首部IP报文首部TCP报文首部UDP报文首部 刷新和再生的区别地址译码 为了区分队空队满&#xff0c;可以使用三种处理方式 1&#xff09;牺牲一个单元 队头指针在队尾指针的下一位置作为队满的标志 队满条件&#xff1a;(…

会声会影2024发布了没有? 会声会影2024更新哪些内容?

嘿&#xff0c;亲爱的的朋友们&#xff0c;今天我要跟大家安利一款让我彻底沉迷、不能自拔的神器 —— 会声会影2024&#xff01;如果你还在为视频编辑头疼&#xff0c;那么准备好迎接你的救星吧&#xff01; 会声会影2024是一款功能全面的视频编辑软件&#xff0c;它不仅能帮你…

ThreadLocal常见面试题

1.请介绍一下ThreadLocal底层是怎么实现的&#xff1f; 一个线程开始运行的时候&#xff0c;通过set方法会把值放入threadLocals这个变成中&#xff0c;他的类型是ThreadLocalMap对象&#xff0c;里面是Entry数组&#xff0c;每一个Entry是键值对形式&#xff0c;key就是Thread…

Vue 3 + Element Plus 封装单列控制编辑的可编辑表格组件

在Web应用开发中&#xff0c;经常需要提供表格数据的编辑功能。本文将介绍如何使用Vue 3结合Element Plus库来实现一个支持单列控制编辑功能的表格&#xff0c;并通过封装组件的形式提高代码的复用性。通过本教程&#xff0c;你将学会如何构建一个具备单列控制编辑功能的表格组…

NISP 一级 | 3.3 网络安全防护与实践

关注这个证书的其他相关笔记&#xff1a;NISP 一级 —— 考证笔记合集-CSDN博客 0x01&#xff1a;虚拟专用网络 VPN 概述 虚拟专用网络&#xff08;Virtual Private Network&#xff0c;VPN&#xff09;是在公用网络上建立专用网络的技术。整个 VPN 网络的任意两个节点之间的连…

基于Java+SpringBoot+Vue+MySQL的美容美发管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于SpringBootVue的美容美发管理系统【附源码文档】、前后…

HAProxy--高性能反向代理

文章目录 Web架构负载均衡介绍为什么使用负载均衡负载均衡类型 HAProxy简介应用场景HAProxy是什么HAProxy功能 脚本安装HAProxy基础配置global多进程和线程HAProxy日志配置项 Proxies配置-listen-frontend-backendserver配置 frontendbackend配置实例子配置文件 HAProxy调度算法…

OpenCV findTours函数及其用法

OpenCV的findTours函数的原型如下&#xff1a; 函数参数&#xff1a; Image 输入图像&#xff0c;需8位单通道图像。非零像素被视为1。零像素保持为0&#xff0c;因 此图 像被视为二进制。您可以使用compare、inRange、threshold、adaptiveThreshold、Canny等从灰度或彩色图像…

注册网站怎么注册

网站注册成为我们日常生活中不可或缺的一部分。无论是社交媒体、电子商务平台还是各种在线服务&#xff0c;注册都是参与这些平台的第一步。下面将为您详细介绍一般网站注册的步骤&#xff0c;帮助您轻松完成注册过程。 1. 选择合适的网站 在注册之前&#xff0c;首先要确定您…

MeterSphere的一次越权审计

1 MeterSphere简介 MeterSphere是一个一站式开源持续测试平台&#xff0c;它提供了测试跟踪、接口测试、UI测试和性能测试等功能。它全面兼容JMeter、Selenium等主流开源标准&#xff0c;助力开发和测试团队实现自动化测试&#xff0c;加速软件的高质量交付。MeterSphere 的特点…

python爬虫基础:了解html

编辑器vscode <!DOCTYPE html> <html><head><title>第一个网页</title></head><body><h1>字体</h1><h2>字体</h2><h3>字体</h3><p>Lorem, ipsum dolor sit amet consectetur adipisicing…

网络学习-eNSP配置NAT

NAT实现内网和外网互通 #给路由器接口设置IP地址模拟实验环境 <Huawei>system-view Enter system view, return user view with CtrlZ. [Huawei]undo info-center enable Info: Information center is disabled. [Huawei]interface gigabitethernet 0/0/0 [Huawei-Gigabi…

ETL数据集成丨MySQL到MySQL的数据迁移实践

前言 MySQL数据迁移至另一MySQL数据库的过程&#xff0c;不仅是数据复制或移动的操作那么简单&#xff0c;它还涉及到一系列策略性考量和技术优化&#xff0c;旨在实现数据的高效、安全传输&#xff0c;以及确保目标系统的高性能运行。其深远意义在于为企业的数字化转型提供强…

vscode 中使用 yarn 出错

问题 vscode 中使用 yarn 爆红&#xff0c;类似下图的错误&#xff1a; 原因 由于vscode中的集成终端使用的是powershell&#xff0c;所以需要设置下该权限才能正常使用yarn 解决 找到 powershell&#xff0c;以管理身份运行 输入&#xff1a;set-ExecutionPolicy Remot…