【SpringBoot】1、SpringBoot整合JWT实现Token验证

news2025/1/14 1:07:10

这里写目录标题

  • 1.单点登录
    • 1.1 单系统登录
      • 1.1.1 单系统登录流程(使用Session实现单系统登录)
    • 1.2 多系统(单点)登录
      • 1.2.1 单点登录实现方案
        • 1.2.1.1 Session跨域
        • 1.2.1.2 Spring Session共享
    • 1.3 Token机制
      • 1.3.1 传统身份认证
      • 1.3.2 基于Token的身份认证
    • 1.4 JWT机制
      • 1.4.1 JWT数据结构

1.单点登录

单点登录(Single Sign On), 简称为SSO, 是目前比较流行的企业业务整合的解决方案之一.

SSO的定义:在多个应用系统中, 用户只需要登录一次就可以访问所有相互信任的应用系统, 企业间需要相互授信

1.1 单系统登录

众所皆知, HTTP是无状态的协议, 这意味着服务器无法确认用户的信息。 于是乎,W3C就提出了:给每一个用户都发一个通行证,无论谁访问的时候都需要携带通行证,这样服务器就可以从通行证上确认用户的信息。通行证就是Cookie。

如果说Cookie是检查用户身上的”通行证“来确认用户的身份,那么Session就是通过检查服务器上的”客户明细表“来确认用户的身份的。Session相当于在服务器中建立了一份“客户明细表”。
在这里插入图片描述

1.1.1 单系统登录流程(使用Session实现单系统登录)

一、登录
用户登录成功后, 通过request获取Session(本质是根据Cookie中携带的JSESSIONID寻找Session)
在这里插入图片描述

  • 如果没有携带JSESSIONID或者JSESSIONID找不到Session, 说明用户未登录, 此时将用户信息保存到Session对象中(默认会以Cookie方式向客户端返回JSESSIONID, 但此JSESSIONID是会话级别的)
  • 如果Session能找到, 说明当前用户已经登录

二、记住我(关闭掉浏览器后,重新打开浏览器还能保持登录状态)
因为默认返回的JSESSIONID是会话级别的, 我们可以手动为Cookie中添加JSESSIONID信息,设置Cookie的过期时间, 此时不管你的浏览器是否关闭,Cookie中都会携带JSESSION信息

//登录成功后,手动添加cookie,保存JSESSIONID信息
Cookie cookie = new Cookie("JSESSIONID", session.getId());
//300年后过期(永久有效)
cookie.setMaxAge(60 * 60 * 24 * 30 * 12 * 300); //设置cookie 和 session生命周期同步.
response.addCookie(cookie);

三、注销(退出登录):从Session中删除用户的信息

 session.removeAttribute("user");

1.2 多系统(单点)登录

多系统、单一位置登录, 实现多系统同时登录的一种技术

1.2.1 单点登录实现方案

  • Session跨域
  • Spring Session共享
  • Toekn机制(主要方案)

1.2.1.1 Session跨域

所谓的Session跨域就是摒弃了系统提供的Session. 而使用自定义的类似Session的机制来保存客户端数据的一种方案

如:通过设置cookie的domain来实现cookie的跨域传递。在cookie中传递一个自定义的session_id。这个session_id是客户端的唯一标记。将这个标记作为key,将客户端需要保存的数据作为value,在服务端进行保存(数据库保存或NoSQL保存)。这种机制就是Session的跨域解决。

一、什么是跨域
客户端请求的时候, 请求的服务器, 不是同一个ip、端口、域名、主机名的时候, 都称为跨域

二、什么是域
在应用模型中, 一个完整的有独立访问路径的功能集合称为一个域

三、Session跨域可以解决的两个问题

  • 同一个域名下, 不同的服务实例共享Session,
  • 不同的域名共享Session, 如: 百度的若干域名, 搜索引擎(www.baidu.com), 百度贴吧(tie.baidu.com), 百度知道(zhidao.baidu.com), 百度地图(map.baidu.com)等

四、Session跨域的实现流程

  1. 用户登录成功后, 判断是否携带CookieName, 此处的CookieName作用等同于系统提供的JSESSIONID,我们可以将CookieName自定义为任何值,它在所有的Cookie中起唯一标识的作用(标识这个key-value是用来实现SSO的), 然后用过CookieName获取value
  2. 我们可以将这个value理解为sessionId, 之前系统默认根据这个sessionId寻找session, 如果session不存在, 则代表没有登录过, 如果存在则代表已经登录. 我们现在拿这个value去持久化库(mysql或nosql)中匹配
    • 如果通过value找到数据, 则代表已登录, 刷新redis中这条数据的生命, 重新设置Cookie
    • 如果通过value找不到数据, 则代表首次登录
      • 1.首先将value和用户信息以hash的形式存储到redis中, 并设置生命(30分钟)
      • 2.重新设置Cookie(名称和value不变, 主要设置domain这个值, 将domain设置为.baidu.com, 这样就能保证这个cookie会发送到www.baidu.com、tie.baidu.com等等

代码实现:

  • 登录部分
@Controller
@RequestMapping("sso")
public class UserController {

    @Autowired
    private RedisTemplate redisTemplate;

    private static final String COOKIE_NAME = "sso_session_id";

    @GetMapping("login")
    @ResponseBody
    public String login(@RequestParam("un") String userName, @RequestParam("pw") String password,
                        HttpServletRequest request, HttpServletResponse response) {
        String token = getToken(userName, password);

        if (StringUtils.isNotEmpty(token)) {
            // 登录成功, 将token存入Cookie
            setCookie(request, response, COOKIE_NAME, token);
            return "success";
        }
        // 没有token, 代表用户名或密码错误
        return "error";
    }

    // 获取token(类似于JSESSIONID)
    private String getToken(String userName, String password) {
        if (userName.equals("wxf") && password.equals("123456")) {
            // 登录成功

            // 步骤一:生成token(此处的token的作用类似于JSESSIONID)
            String token = UUID.randomUUID().toString();

            // 步骤二:将用户信息保存到redis中
            User user = new User(userName, password);
            redisTemplate.opsForValue().set(token, JSON.toJSONString(user), 1800);
            return token;
        }
        // 登录失败
        return null;

    }
	
	/**
	* 设置cookie
	*/
    private void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue) {
        // 创建cookie
        Cookie cookie = new Cookie(cookieName, cookieValue);

        if (null != request) {
            // 分析解析域名
            String domainName = getDomainName(request);

            // 设置域名的cookie
            cookie.setDomain(domainName);

        }
        // 从根路径下的后面任意路径地址,cookie都有效
        cookie.setPath("/");
        // response响应写入到客户端即可
        response.addCookie(cookie);
    }
	
	/**
	* 根据url来设置domain
	*/
    private static final String getDomainName(HttpServletRequest request) {
        // 定义一个变量domainName
        String domainName = null;

        // 获取完整的请求URL地址。请求url,转换为字符串类型
        String serverName = request.getRequestURL().toString();
        // 判断如果请求url地址为空或者为null
        if (serverName == null || serverName.equals("")) {
            domainName = "";
        } else {
            // 不为空或者不为null,将域名转换为小写。域名不敏感的。大小写一样
            serverName = serverName.toLowerCase();
            // 判断开始如果以http://开头的
            if (serverName.startsWith("http://")) {
                // 截取前七位字符
                serverName = serverName.substring(7);
            } else if (serverName.startsWith("https://")) {
                // 否则如果开始以https://开始的。//截取前八位字符
                serverName = serverName.substring(8);
            }
            // 找到/开始的位置,可以判断end的值是否为-1,如果为-1的话说明没有这个值
            // 如果存在这个值,找到这个值的位置,否则返回值为-1
            final int end = serverName.indexOf("/");
            // .test.com www.test.com.cn/sso.test.com.cn/.test.com.cn spring.io/xxxx/xxx
            // 然后截取到0开始到/的位置
            if (end != -1) {
                // end等于-1。说明没有/。否则end不等于-1说明有这个值
                serverName = serverName.substring(0, end);
                // 这是将\\.是转义为.。然后进行分割操作。
                final String[] domains = serverName.split("\\.");
                // 获取到长度
                int len = domains.length;
                // 注意,如果是两截域名,一般没有二级域名。比如spring.io/xxx/xxx,都是在spring.io后面/拼接的
                // 如果是三截域名,都是以第一截为核心分割的。
                // 如果是两截的就保留。三截以及多截的就
                // 多截进行匹配域名规则,第一个相当于写*,然后加.拼接后面的域名地址
                if (len > 3) {
                    // 如果是大于等于3截的,去掉第一个点之前的。留下剩下的域名
                    domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
                } else if (len <= 3 && len > 1) {
                    // 如果是2截和3截保留
                    domainName = "." + domains[len - 2] + "." + domains[len - 1];
                } else {
                    domainName = serverName;
                }
            }
        }
        // 如果域名不为空并且有这个:
        if (domainName != null && domainName.indexOf(":") > 0) {
            // 将:转义。去掉端口port号,cookie(cookie的domainName)和端口无关。只看域名的。
            String[] ary = domainName.split("\\:");
            domainName = ary[0];
        }
        // 返回域名
        System.out.println("==============================================" + domainName);
        return domainName;
    }

}
  • 校验登录
    我们在拦截器中, 根据请求携带的Cookie校验用户是否登录

1.2.1.2 Spring Session共享

Spring-Session技术是Spring提供的用于处理集群会话共享的解决方案. Spring-Session技术是将用户Session数据保存到第三方存储容器中, 例如Mysql、redis等

Spring-Session技术是解决同域名下多服务器集群session共享问题的, 不能解决跨域名Session共享问题

利用spring session解决共享Session问题
SpringBoot+SpringSession


1.3 Token机制

1.3.1 传统身份认证

HTTP是一种没有状态的协议,也就是它并不知道是谁在访问应用,这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求时,还得再校验一次。这明显是不合适的,如果客户每次访问应用都需要登录验证、登录验证…客户体验会非常差

解决方式:当客户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,然后把这条记录的ID编号发给客户端,客户端收到以后将ID编号存储在Cookie中,下次这个用户再向服务端发送请求的时候,可以携带这个Cookie,这样服务端就会验证这个Cookie里的信息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。

上面说的就是HTTP Session,我们需要在服务端存储为登录的用户生成的Session,这些Session可能会存储在内存、磁盘、或者数据库里。我们可能需要在服务端定期的去清理过期的Session

这种认证出现的问题是:

  • 内存开销问题:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。
  • 可扩展性:在服务器的内存中使用Session存储用户信息,伴随而来的是可扩展性问题。

1.3.2 基于Token的身份认证

使用基于Token的身份认证方式, 在服务端不需要存储用户的登录记录, 大概的流程是这样的:

  1. 客户端使用用户名、密码请求登录
  2. 服务端收到请求, 验证用户名、密码
  3. 验证成功后, 服务端会签发一个Token, 再把这个Token发送给客户端
  4. 客户端收到Token以后可以把它存储起来, 比如放在Cookie里或者Local Storage里
  5. 客户端每次向服务端请求资源时携带服务端签发的Token
  6. 服务器收到请求, 然后去验证客户端请求里面带着的Token, 如果验证成功, 就向客户端返回请求的数据

使用Token的优势:

  • 无状态、可扩展
    在客户端存储的Tokens是无状态的, 并且能够被扩展. 基于这种无状态和不存储Session信息, 负载均衡器能够将用户信息从一个服务器传到其他服务器上
  • 安全性
    请求中发送Token而不再是发送Cookie能够防止CSRF(跨站请求伪造). 即使在客户端使用Cokkie存储Token, Cookie也仅仅是一个存储机制而不是用于认证, 不将信息存储在Session中, 让我们少了对Session的操作

1.4 JWT机制

JWT(JSON Web Token)

JWT是一种紧凑自包含的, 用于在多方传递JSON对象的技术. 传递的数据可以使用数字签名增加其安全性. 可以使用HMAC加密算法或RSA公钥/私钥加密方式

  • 紧凑:数据小, 可以通过URL, POST参数, 请求头发送. 且数据小代表传输速度快
  • 自包含:使用payload数据块记录用户必要且不隐私的数据, 可以有效的减少数据库访问次数, 提高代码能力

JWT是一般用于处理用户身份验证数据信息交换

  • 用户身份验证:一旦用户登录, 每个后续请求都将包含JWT, 允许用户访问该令牌允许的路由、服务和资源. 单点登录是当今广泛使用JWT的一项功能, 因为它的开销很小, 能够轻松的跨域使用
  • 数据信息交换:JWT是一种非常方便的多方传递数据的载体, 因为其可以使用数据签名来保证数据的有效性和安全性

1.4.1 JWT数据结构

JWT的数据结构是:A.B.C
由字符点"."来分隔三部分数据

  • A:header 头信息
  • B:payload 有效荷载
  • C:Signature 签名

一、header
数据结构:{“alg”:“加密算法名称”, “typ”: “JWT”}
alg是加密算法定义内容, 例如HMAC、SHA256、RSA
typ是token类型, 这里固定为JWT

二、payload
在payload数据块中一般用于记录实体(通常为用户信息)或其他数据的, 主要分为三个部分. 分别是已注册信息(registered claims).、公开数据(public claims)、私有数据()

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis集群部署详解

文章目录 集群环境集群搭建测试集群故障转移集群扩容集群缩容 集群环境 集群介绍 1.什么是集群 所谓的集群&#xff0c;就是通过增加服务器的数量&#xff0c;提供相同的服务&#xff0c;从而让服务器达到一个稳定、高效的状态。 2.使用redis集群的必要性 单个redis存在不稳定…

9.6 数组的指针和指向数组的指针变量 - 3

9.6 数组的指针和指向数组的指针变量 - 3 一.回顾二维数组和多维数组的概念二.指向多维数组的指针和指针变量探究1.a:二维数组名&#xff0c;也是整个二维数组的首地址。我们可以认为是第0行的首地址是10002.a1 , a2 分别代表第一行首地址和第二行首地址。3.这表示a[0],a[1],a[…

Mapbox-gl.js v2.13.0 扩展支持4326,4490坐标系

mapbox-gl.js新版本中&#xff0c;支持多种projection 显示效果也不错&#xff0c;根据tiles grid可以看到&#xff0c;还是web_mercator的格网&#xff0c;基于图片做了一定的拉伸形变&#xff0c;想要加载4326的切片格网&#xff0c;依然无法实现。 后来在网上搜索加载4326切…

【JavaWeb】后端(Maven+SpringBoot+HTTP+Tomcat)

目录 一、Maven1.什么是Maven?2.Maven的作用?3.介绍4.安装5.IDEA集成Maven6.IDEA创建Maven项目7.IDEA导入Maven项目8.依赖配置9.依赖传递10.依赖范围11.生命周期 二、SpringBoot1.Spring2.SpringBoot3.SpringBootWeb快速入门 二、HTTP1.HTTP-概述2.HTTP-请求协议3.HTTP-响应协…

【转行互联网】转行互联网必看答疑

课程 追忆寻梦-转行互联网必备知识 https://edu.csdn.net/course/detail/31180 2023年&#xff0c;迟来的编程私教服务 https://bbs.csdn.net/topics/613231237 优先 必读文章 初学者&#xff0c;打算改行学编程&#xff0c;怎么学习java&#xff1f;求指教。https://bb…

如何抓住IT行业最后的红利?网络安全为什么是风口行业?

前言 “没有网络安全就没有国家安全”。当前&#xff0c;网络安全已被提升到国家战略的高度&#xff0c;成为影响国家安全、社会稳定至关重要的因素之一。 网络安全行业特点 1、就业薪资非常高&#xff0c;涨薪快 2021年猎聘网发布网络安全行业就业薪资行业最高人均33.77万…

C语言指针及数组的运行原理

C语言指针及数组的运行原理 文章目录 C语言指针及数组的运行原理一. 指针&#xff08;汇编角度&#xff09;二. 数组&#xff08;汇编角度&#xff09;2.1 数组的定义2.2 指针与数组结合 三. 指令解释参考3.1 nop3.2 leave3.3 ret 这里涉及汇编&#xff0c;虚拟机这边采用的是6…

如何在 Google Cloud 上部署 EMQX 企业版

Google Cloud 的 IoT Core 产品将于 2023 年 8 月 16 日停止服务&#xff0c;随着这一日期的临近&#xff0c;许多用户正在为他们现有的物联网业务寻找新的解决方案&#xff0c;而 EMQX 企业版是实现这一目标的理想选择。 EMQX 企业版是一款大规模分布式 MQTT 消息服务平台&am…

【设计模式】深入浅出--外观模式

文章目录 前言一、外观模式介绍二、案例场景三、外观模式优缺点四、外观模式应用场景总结 前言 不知道大家有没有比较过自己泡茶和去茶馆喝茶的区别&#xff0c;如果是自己泡茶需要自行准备茶叶、茶具和开水&#xff0c;而去茶馆喝茶&#xff0c;最简单的方式就是跟茶馆服务员…

高效的配置文件读取工具支持properties和yaml

JefConfig 前言 日常工作中不知道到大家有没有遇到以下几种情况&#xff1a; 1、在程序启动时需要加载配置文件&#xff0c;但是发现程序只能从固定位置读取配置文件。 2、程序在集成了spring框架后&#xff0c;想从配置文件中获取某个配置&#xff0c;但是发现当前程序并未交…

并查集原理及代码实现

并查集 首先要明确的是并查集是森林。由多棵树组成。 并查集 &#xff08;英文&#xff1a;Disjoint-set data structure&#xff0c;直译为不交集数据结构&#xff09;&#xff0c;用于处理一些 不交集 &#xff08;Disjoint sets&#xff0c;一系列没有重复元素的集合&…

android framework-SystemServer进程

SystemServer进程信息 一、SystemServer整体时序图 涉及源码路径&#xff1a; android-10.0.0_r41\frameworks\base\core\java\com\android\internal\os\ZygoteInit.java android-10.0.0_r41\frameworks\base\core\java\com\android\internal\os\Zygote.java android-10.0.0_r4…

JAVAWeb07-WEB 开发通信协议-HTTP 协议-关联篇

1. 概述 1.1 官方文档及示例说明 1.1.1 请求头 1.1.2 响应头 1.1.3 HTTP 响应状态码 HTTP状态码 当浏览者访问一个网页时&#xff0c;浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前&#xff0c;此网页所在的服务器会返回一个包含HTTP状态码的信息头&a…

leetcode 1372. Longest ZigZag Path in a Binary Tree(二叉树中最长的之字形路径)

找出最长的之字型路径长度。 可以选择从二叉树的任意一个节点出发。 路径长度为路径中的节点数-1. 思路&#xff1a; 符合DFS的特征。 方向是左右交替的&#xff0c;可以定义0&#xff0c;1两个方向。 如果当前方向是左&#xff0c;下一方向就是右&#xff0c;反之亦然。每次…

FinClip|小程序云开发的那点事儿

在开发一个小程序时&#xff0c;除了考虑界面功能逻辑外&#xff0c;还需要后端的数据支持&#xff0c;开发者需要提前考虑服务器、存储和数据库等相关需求的支持能力&#xff0c;此外还可能需要花费时间精力在部署应用、和依赖服务的建设上。 因此&#xff0c;微信小程序为了…

dolphinscheduler3.1.3版本代码编译运行方法

说明 该文档适用于dolphinscheduler 3.1.3-release版本。 一 环境准备 需要使用的环境包括JDK1.8&#xff0c;以及Maven 3.6以上的版本&#xff0c;这里使用低于3.6版本的Maven也可以调试运行&#xff0c;不过在打包的时候会有报错&#xff0c;最好使用高版本的maven。 二 …

数字IC笔试面试常考问题及答案汇总(内含各岗位大厂题目)

经历了无数的笔试面试之后&#xff0c;不知道大家有没有发现数字IC的笔试面试还是有很多共通之处和规律可循的。所以一定要掌握笔试面试常考的问题。 数字IC笔试面试常考问题及答案汇总&#xff08;文末可领全部哦~&#xff09; 验证方向&#xff08;部分题目&#xff09; Q1…

android studio APP工程的项目结构说明及创建

目录 1.APP工程的项目结构图 2.功能说明 2.1app 2.2Gradle Scripts 3.创建新的APP页面 补充&#xff1a; 1.APP工程的项目结构图 2.功能说明 该项目下两个分类&#xff1a;一个是app&#xff08;代表app模块&#xff09;;另一个是Gradle Scripts。 2.1app app下面有3个…

虚拟机安装Centos7,ping不通百度

虚拟机安装Centos7&#xff0c;ping不通百度 一、虚拟机网络配置 网络适配器选择桥接模式&#xff0c;不勾选复制物理网络连接状态。 同时虚拟机使用默认配置都是桥接。 二、配置静态IP 1、首先&#xff0c;查看宿主机的IP和网关 2、配置静态ip的文件地址及修改命令如下&…

mybatis分页插件的基本理解和使用

mybatis分页插件的基本理解和使用 为什么要使用mybatis分页插件&#xff1f; 分页是一种将所有数据分段展示给用户的技术。用户每次看到的不是全部数据&#xff0c;而是其中一部分&#xff0c;如果在其中没有找到自己想要的内容&#xff0c;用户可以通过制定页码或者是翻页的…