手摸手 Spring Cloud Gateway + JWT 实现登录认证

2023-05-16

你好,我是悟空。

前言

上篇我已经讲解了 Spring Cloud 的原理和实战,这次就要结合 JWT 来实现登录认证的功能了。

本文已收录至《深入剖析 Spring Cloud 底层架构原理》,已更新 18 讲。

目录

通过本文你会掌握以下知识点:

  • 如何用认证服务做登录认证。
  • 如何生成 JWT 令牌(Token)
  • 如何用 Gateway 对 Token 验证。
  • Gateway 如何从 Token 中拿到用户信息并转发给业务服务。
  • 业务服务如何从请求中拿到身份信息处理业务逻辑。
  • 如何刷新令牌。

本篇还是基于我的开源项目 PassJava 作为讲解。

PassJava 开源地址:https://github.com/Jackson0714/PassJava-Platform

前置知识点:

  • 深入理解 Spring Cloud Gateway 的原理
  • 我的师父把 「JWT 令牌」玩到了极致

在讲解之前有必要澄清下什么是认证、授权、凭证,这三个方面是一个系统中最基础的安全设计。

认证、授权、凭证

1.1 认证(Authentication)

认证表示你是谁。系统如何正确分辨出操作用户的真实身份,比如通过输入用户名和密码来辨别身份。

1.2 授权(Authorization)

授权表示你能干什么。系统如何控制一个用户能看到哪些数据和操作哪些功能,也就是具有哪些权限。

1.3 凭证(Credential)

表示你如何证明你的身份。系统如何保证它与用户之间的承诺是双方当时真实意图的体现,是准确、完整和不可抵赖的。

接下来我们看下使用 JWT 作为凭证完成认证的原理。

认证的原理

在如下的认证时序图中,有以下几种角色:

  • 客户端:表示 APP 端或 PC 端的前端页面。
  • 网关:表示 Spring Cloud Gateway 网关服务,这里。
  • 认证服务:用来接收客户的登录请求、登出请求、刷新令牌的操作。
  • 业务服务:和系统业务相关的微服务。

认证和校验身份的流程如下所示:

认证和校验身份流程

用户登录:客户端在登录页面输入用户名和密码,提交表单,调用登录接口。

转发请求:这里会先将登录请求发送到网关服务 passjava-gateway,网关对于登录请求会直接转发到认证服务 passjava-auth。(网关对登录请求不做 token 校验,这个可以配置不校验哪些请求 URL)

认证:认证服务会将请求参数中的用户名+密码和数据库中的用户进行比对,如果完全匹配,则认证通过。

生成令牌:生成两个令牌:access_token 和 refresh_token(刷新令牌),刷新令牌我们后面再说,这里其实也可以只用生成一个令牌 access_token。令牌里面会包含用户的身份信息,如果要做权限管控,还需要在 token 里面包含用户的权限信息,权限这一块不在本篇展开,会放到下一篇中进行讲解。

客户端缓存 token:客户端拿到两个 token 缓存到 cookie 中或者 LocalStorage 中。

携带 token 发起请求:客户端下次想调用业务服务时,将 access_token 放到请求的 header 中。

网关校验 token:请求还是先到到网关服务,然后由它校验 access_token 是否合法。如果 access_token 未过期,且能正确解析出来,就说明是合法的 access_token。

携带用户身份信息转发请求:网关将 access_token 中携带的用户的 user_id 放到请求的 header 中,转发给真正的业务服务。

处理业务逻辑:业务服务从 header 中拿到用户的 user_id,然后处理业务逻辑,处理完后将结果延原理返回给客户端。

接下来我们看下项目的整体架构。

项目整体结构

Github 项目地址:https://github.com/Jackson0714/PassJava-Platform

Gitee 项目地址:https://toscode.gitee.com/jayh2018/PassJava-Platform

  • 认证服务:passjava-auth
  • 网关服务:passjava-gateway
  • JWT 公共项目:passjava-jwt,认证服务和网关服务都会引用这个公共项目。
  • 业务服务:passjava-member,会员服务作为本次案例的业务服务。
  • Nacos 注册配置中心

PassJava-Platform 框架

认证服务:passjava-auth

passjava-auth 服务

核心类就是 JwtAuthController 类,里面有登录接口和刷新令牌的接口。

网关服务:passjava-gateway

passjava-gateway 服务

核心类就是 JwtAuthCheckFilter 全局过滤器。

如果不需要在服务端保存刷新令牌,可以不需要 redis 配置。

JWT 公共项目

passjava-jwt 服务

核心类就是 PassJavaJWTTokenUtil 工具类。认证服务用引入 JWT 项目后用来生成 token,网关服务引入 JWT 项目后用来校验 token 合法性。

业务服务

这里我选择了会员微服务作为本次演示的业务微服务。

它从网关转发的请求 Header 中拿到 userId, 根据 userId 查询 member 信息。

passjava-member 服务

核心文件是 MemberController 类、MemberEntity实体类、MemberService服务类、MemberDao 类和 mapper 文件。

启动的服务

Nacos 注册配置中心

首先启动 Nacos 服务。和 PassJava 项目配套使用的 Nacos 工具已经上传到网盘,下载后直接运行启动脚本就可以将 Nacos 在本地启动。

启动教程:

www.passjava.cn/#/01.项目简介/7.本地部署项目Mac版

网关、会员、认证服务

启动以下三个微服务,分别为网关、会员、认证服务。

检查下 nacos 注册中心上是否注册了这三个服务:可以看到确实有上面的三个微服务。

如何做登录认证

登录认证就是校验下用户提交的账户名和密码与本地数据库中的是否完全匹配,如果匹配,就认证通过。就是下方这个流程的 1、2、3 步。

第一步:提交用户名和密码

这里用 Postman 工具模拟前端发起登录请求,请求的 URL 如下:

http://localhost:8060/api/auth/login

image-20220814161920022

请求是向网关服务 passjava-gateway 发起的,所以可以看到上面的 URL 中 localhost 和 8060 是网关的 host 和 port。

然后 API 地址为 /api/auth/login,这个地址经过网关的路由匹配后会转发到 passjava-auth 服务的登录 API。

http://localhost:10001/auth/login

关于网关转发的原理可以参考这篇:深入理解 Spring Cloud Gateway 的原理

请求参数如下:

{
    "userId": "wukong",
    "password": "123456"
}

账号和密码都是密文的,转发到认证服务后,会根据 userId 查询出系统用户,然后将 password 参数加密后对比系统用户的密码。

所以为了让用户登录成功,还需要在数据库插入一条系统用户,用户 id 为 wukong,密码是对 123456 加密后的密码。

在线加密工具地址:

https://www.bejson.com/encrypt/bcrpyt_encode/

第二步:转发登录请求

转发登录请求是网关服务做的,所以我们来看下做了那些事情。

在 Gateway 项目的 application-routers.yml 中配置路由规则:

spring:
  cloud:
    gateway:
      routes:
        - id: route_auth # 认证微服务路由规则
          uri: lb://passjava-auth # 负载均衡,将请求转发到注册中心注册的 passjava-auth 服务
          predicates: # 断言
            - Path=/api/auth/** # 如果前端请求路径包含 api/auth,则应用这条路由规则
          filters: #过滤器
            - RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的api替换成空

在 application.properties 引入 application-routers.yml

spring:
  profiles:
    include: routers, jwt

第三步:验证用户账号和密码

这一步是认证服务的登录 API 里面做的。在 AuthController 中定义 login 接口,核心步骤就是查找系统用户和比对密码。

登录 API

用户名和密码匹配成功后,就会生成 JWT 令牌。

如何生成令牌

生成令牌就是通过工具类 PassJavaJwtTokenUtil 生成 JWT Token,也就是流程图中的第四步。

流程图-生成 JWT 令牌

生成令牌的核心代码如下:

生成 JWT 的核心代码

使用这个工具类的前提是我们需要先引入 jjwt 依赖。这个在 passjava-jwt 项目的 pom 文件中引入。

引入 jjwt 依赖

用 Postman 工具调用后,可以看到生成的令牌如下:

生成令牌

用 base64 解码后,可以看到 token 中的 PAYLOAD 里面包含了用户 id 和用户名。

生成 JWT 的加密密钥一般都是写到配置文件中。这里我是配置在 passjava-jwt 项目的 application-jwt.yml 配置文件中的。

JWT 配置项

然后认证服务就会将 JWT 令牌返回给客户端了。当客户端想要查询这个 userId 对应的会员信息时,就可以在请求的 Header 中带上 JWT 令牌。

如何携带 JWT 发送请求

客户端(浏览器或 APP)拿到 JWT 后,可以将 JWT 存放在浏览器的 Cookie 或 LocalStorage(本地存储) 或者内存中。

发送请求时在请求 Header 的 Authorization 字段中设置 JWT,这个字段其实可以自定义,但是我建议用 Authorization,因为这是一种业界标准。

另外告诉大家一个小技巧,在 Postman 工具中有个地方专门配置 Authorization,然后自动加到 Header 中,不用自己手动加 Header。

还有一个点需要注意,这里配置的 Authorization 的认证类型为 Bearer Token。它表示令牌可以是任意字符串格式的令牌。然后会在 Authorization 字段中加上一个前缀 Bearer。所以我们在网关服务解析 Header 中的 Authorization 时,需要去掉这个前缀 Bearer,代码如下所示:

去掉 Bearer 前缀

网关如何验证 JWT 和转发请求

网关验证 Token和转发请求

网关接收到前端发起的业务请求后,会先验证请求的 Header 中是否携带 Authorization 字段,以及里面的 Token 是否合法。然后解析 Token 中的 userId 和 username,放到 header 中再进行转发,也就是流程图中的第七步和第八步。

网关是通过多个过滤器 Filter对请求进行串行拦截处理的,所以我们可以自定义一个全局过滤器,对所有请求进行校验,当然对于一些特殊请求比如登录请求就不需要校验了,因为调用登录请求的时候还没有生成 Token。

网关的全局过滤器 JwtAuthCheckFilter 的核心代码如下所示:

网关的全局过滤器 JwtAuthCheckFilter

会员服务处理业务逻辑

会员服务接收到网关转发的请求后,就从 Header 中拿到用户身份信息,然后通过 userId 获取会员信息。

注意:有的时候业务逻辑并不需要身份信息,更多的时候是需要检验用户的操作权限是否足够。其实 Token 里面也是可以携带权限信息的,不过这是下一篇讲解授权的部分。

获取 userId 的方式其实可以通过加一个拦截器,由拦截器将 Header 中的 userId 和 username 放到线程中,后续的 controller,service,dao 类都可以从线程里面拿到 userId 和 username,不用通过传参的方式。

获取 userId 的方式:

  • 方式一:从 request 的 Header 中拿到 userId。代码简单,但是如果其他地方也要用到 userId,则需要通过方法传参的方式传递 userId。

  • 方式二:从线程变量里面拿到 userId。代码复杂,使用简单。好处是所有地方统一从一个地方获取。

Request 中获取 userId 方式

代码示例如下:

下面介绍如何使用拦截器方式将 userId 存入线程变量的方式。

拦截器方式

在 passjava-common 模块中新增一个拦截器,获取请求头中的身份信息,加入到线程变量中。文件名为 HeaderInterceptor。

将拦截器注册到 WebMvcConfigurer。文件名为 WebMvcConfig.java。

配置文件中需要定义一个配置项:

文件名;org.springframework.boot.autoconfigure.AutoConfiguration.imports
配置项:com.jackson0714.passjava.common.config.WebMvcConfig

然后 passjava-member 服务引入这个拦截器配置。

@Import({WebMvcConfig.class})

通过上面两种方式中的任意一种拿到 userId 后,通过 userId 查询会员的详情。这里需要注意的是这个 user 既是系统用户也是系统中的会员。关于查询会员的数据库操作就不在此展开了。

执行结果如下图所示:

如何刷新令牌

还有一个内容是关于如何刷新令牌的。当认证服务返回给客户端的 JWT 也就是 access_token 过期后,客户端是通过发送登录请求重新拿到 access_token 吗?

这种重新登录的操作如果很频繁(因 JWT 过期时间较短),对于用户来说体验就很差了。客户端需要跳转到登录页面,让用户重新提交用户名和密码,即使客户端有记住用户名和密码,但是这种跳转的到登录页的操作会大幅度降低用户的体验,甚至导致用户不想再用第二次。

有没有一种比较优雅的方式让客户端重新拿到 access_token 或者说延长 access_token 有效期呢?

我们知道 JWT 生成后是不能篡改里面的内容,即使是 JWT 的有效期也不行。所以延长 access_token 有效期的做法并不适合,而且如果长期保持一个 access_token 有效,也是不安全的。

那就只能重新生成 access_token 了。方案其实挺简单,客户端拿之前生成的 JWT 调用后端一个接口,然后后端校验这个 JWT 是否合法,如果是合法的就重新生成一个新的返回给客户端。客户端自行替换掉之前本地保存的 access_token 就可以了。

生成 access_token 和 refresh_token

这里有一个巧妙的设计,就是生成 JWT 时,返回了两个 JWT token,一个 access_token,一个 refresh_token,这两个 token 其实都可以用来刷新 token,但是我们把 refresh_token 设置的过期时间稍微长一点,比如两倍于 access_token,当 access_token 过期后,refresh_token 如果还没有过期,就可以利用两者的过期时间差进行重新生成令牌的操作,也就是刷新令牌,这里的刷新指的是客户端重置本地保存的令牌,以后都用新的令牌。

饥饿模式和懒模式

当然,在 access_token 过期之前,客户端提前刷新令牌也是可以的,我称这种提前刷新的模式为饥饿模式(单例模式中也有这种叫法),而过期后再刷新令牌的模式我称之为懒模式。两种模式都可以用,前者需要客户端定期检查过期时间,增加了复杂性;后者则会出现短暂的请求失败的情况,得拿到新的令牌后才会成功。

刷新令牌的操作完全是通过客户端自己控制的,而且客户端也不仅限于浏览器,还有可能是第三方服务。

一次性

通常情况下,我们会将刷新令牌 refresh_token 设置为只能用一次,来保证刷新令牌的安全性。而这种就需要服务端来缓存刷新令牌了,当用过一次后,就从缓存里面主动剔除掉。但这样就违背了 JWT 无状态的特性,这个完全看业务需求来决定是否使用这种缓存方式。

如下图所示,生成令牌时我将刷新令牌缓存到了 Redis 里面。当我用 refresh_token 调用刷新 API 时,会主动剔除掉这个 key,下次再用相同的 refresh_token 刷新令牌时,因 Redis 中不存在这个 key,就会提示刷新刷新失败了。

缓存令牌

留两个小问题:

  • 有没有办法让 access_token 主动失效?
  • 场景题:如何保证同一个用户只能登录一台设备?

总结

虽然本篇是讲实战内容的,但是里面有涉及了很多原理性内容,比如网关、JWT 的原理。

结合实战讲解,相信大家对如何使用 Spring Cloud Gateway + JWT 实现登录认证有了充分的理解。

本篇只讲解了认证和凭证,授权部分还没有触及,所以这也是下篇要讲解的内容,来追更吧~

最后再说一句,别白嫖,点赞转发下哦~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手摸手 Spring Cloud Gateway + JWT 实现登录认证 的相关文章

  • openmv探索_4_AprilTag标记追踪

    原理及代码 AprilTag标记追踪 空间坐标系的建立 以镜头中心为坐标系原点 xff0c 建立空间坐标系 图2 1 空间坐标系 旋转角度 xff08 参考系是上图中的坐标系 xff09 1 初始状态 图3 1 物体摆放的初始位置 上图的
  • Xsens MTi传感器 ROS下配置

    Xsens MTi传感器 ROS 1 概述2 MTI设置 MTmanager3 ROS下信息发布 MTSDK4 找不到设备 xff15 参考链接 xff16 延伸阅读传感器配置节点程序分析经典SLAM 1 概述 主要介绍Ubuntu下对Xs
  • 雷达点云 PointCloud2 格式转换

    雷达点云 sensor msgs PointCloud2 pcl PointCloud 数据格式转换参考代码 官方对点云格式的介绍 xff0c 主要有四种 xff0c sensor msgs PointCloud已经弃用 参考 sensor
  • Gmapping 之安装与配置

    Gmapping 之安装与配置 1 概述2 安装2 1 github仓库 3 运行3 1 运行截图3 2 重采样时的提示信息3 3 tf树 96 rosrun rqt tf tree rqt tf tree 96 3 4 所有的坐标变换信息
  • Gmapping Dropped 100.00% of messages so far 解决办法

    Dropped 100 00 of messages so far解决办法 概述显示tf树正常情况报错情况参考链接Gmapping 概述 运行Gmappping时出现错误提示 xff0c 其他参数 xff1a 话题等都设置正确了 xff0c
  • LOAM SLAM安装与配置

    LOAM SLAM安装与配置 1 概述2 安装3 运行4 结果5 其他6 参考链接 1 概述 简单介绍LOAM SLAM的安装与配置 xff0c 希望能帮助大家 xff0c 同时供自己以后参考 2 安装 运行LOAM需要PCL等库支持 xf
  • 什么是MSB、LSB,什么是大端、小端,区别是什么?

    MSB是Most Significant Bit的缩写 xff0c 最高有效位 在二进制数中 xff0c MSB是最高加权位 与十进制数字中最左边的一位类似 通常 xff0c MSB位于二进制数的最左侧 xff0c LSB位于二进制数的最右
  • ROS报错 cant locate node 解决办法

    ROS报错 can 39 t locate node 解决办法 1 概述2 原因3 解决办法4 其他 1 概述 ROS突然出现不能运行节点的问题 xff0c 这里简单介绍一下自己的解决办法 这个方法还是有缺陷 xff0c 更新方法在ROS编
  • ubuntu 开机后 按键 鼠标不能用

    ubuntu 开机后 按键 鼠标不能用 1 现象2 解决办法3 参考链接 1 现象 版本ubuntu 1804安装bumblebee后 xff0c 重启机器出现不能使用鼠标 键盘的现象 xff0c 具体点就是 xff0c 只有开机后的桌面显
  • 相机标定之使用ROS节点程序

    相机标定之使用ROS节点程序 1 概述2 准备3 步骤4 相关链接 1 概述 简单介绍如何使用ROS节点程序进行针孔模型相机标定的步骤 xff0c 供自己以后参考 xff0c 同时希望给大家带来帮助 2 准备 提前准备好棋盘格并打印出来 x
  • 相机标定之使用Matlab工具箱

    相机标定之使用Matlab工具箱 1 概述2 准备3 步骤4 相关链接 1 概述 简单介绍如何使用Matlab进行针孔模型相机标定的步骤 xff0c 供自己以后参考 xff0c 同时希望给大家带来帮助 2 准备 提前准备好棋盘格并打印出来
  • QT安装教程

    QT安装教程 1 概述2 下载安装2 1 下载2 2 安装2 3 安装配置其他环境2 4 安装成功2 5 问题及解决 3 参考链接 1 概述 简单介绍如何在Ubuntu下安装QT xff0c 希望能够帮助大家 xff0c 同时给自己一个参考
  • C++面向过程

    参考文档 xff1a C 43 43 教程 C 43 43 简介 概述 C 43 43 是一种静态类型的 编译式的 通用的 大小写敏感的 不规则的编程语言 xff0c 支持过程化编程 面向对象编程和泛型编程 C 43 43 是 C 的一个超
  • vector 指针 的指针

    vector 不能用指针 xff0c 用指针 xff0c push back会报错 xff0c size 也不对 vector本身用对象 xff0c 内容 简化为A 可以用指针 xff0c 当指针对象中还有指针B时 xff0c 就要用new
  • cmake find_package opencv 找不到

    目录 cmakelist设置方法ok 环境变量设置方法ok linux写法 cmakelists txt完整示例 find opencv lib find package OpenCV REQUIRED NO MODULE should b
  • liveplayer

    npm i 64 liveqing liveplayer S lt template gt lt div class 61 34 live player 34 gt lt div style 61 34 width 640px height
  • DLT algorithm needs at least 6 points for pose estimation from 3D-2D point correspondences. (expecte

    DLT algorithm needs at least 6 points for pose estimation from 3D 2D point correspondences expected 39 count gt 61 6 39
  • 关于OSD

    OSD的主要实现方法和类型 目前有两种主要的OSD实现方法 xff1a 外部OSD发生器与视频处理器间的叠加合成 xff1b 视频处理器内部 支持OSD xff0c 直接在视频缓存内部叠加OSD信息 外部OSD发生器与视频处理器间的叠加合成
  • RTK使用笔记-千寻CORS模式

    一 千寻CORS模式 与基站 43 接收机1对1相比 xff0c 优点为携带方便 xff0c 也不用考虑10公里移动基站问题 xff1b 缺点为第一千寻CORS模式有自己基站涵盖范围 xff0c 所以需要提前确定好范围 xff08 下文有介
  • c/c++语言实现登陆界面

    C C 43 43 语言实现登陆界面 整体功能介绍 实现一个登陆界面 1 输出一个登陆界面 2 用户名能够实现邮箱验证 xff0c regex库 xff0c 密码要不可见 3 进度条的模拟实现 4 音乐播放 分步实现 1 输出一个登陆界面

随机推荐

  • _getch()函数的介绍

    getch 函数的介绍及实例演示 我们一般所使用的 getchar 函数在读入一个字符时必须按一下 Enter 键 xff0c 该代码才会继续运行 但是 getch 函数读入一个字符时 不用 enter 代码会继续跑 xff1b 最简单使用
  • 嵌入式常见面试题

    嵌入式LINUX常见面试问题总结 声明 xff1a 本文是将常见的面试问题进行汇总 xff0c 但大多数问题也是开发中较为常见的技术盲区 xff01 在此进行了汇总 xff0c 以便后续进行参考 xff01 所有的答案部分是自己编写 xff
  • LINUX基础试题大全(1)

    说明 xff1a 此文章由于题数庞大 xff0c 为方便阅读本人将其分为四篇文章为大家分享 xff01 答案会今后不断进行更新 xff01 LINUX基础试题大全 xff08 1 xff09 填空题题 LINUX基础试题大全 xff08 2
  • C语言 | 深入学习数组

    说明 xff1a 本文主要讨论一维数组 xff0c 适宜程度 xff1a 对C语言初步认识及想深入学习者 1 从编译器角度理解数组 从编译器角度理解来讲 xff0c 数组也是一个变量 xff0c 和普通的变量没有本质的区别 变量的本质指的是
  • C语言 结构体 联合体 | 嵌套使用

    一 简单的实例分析 题目 xff1a 获取0x12345678各个字节 答案 xff1a span class token comment 方法一 span span class token macro property span clas
  • Linux 网络编程笔记3 | 内存 系统调用

    七 内存 1 虚拟内存 物理内存 半导体内存和换页文件 虚拟内存 xff1a 地址空间 xff0c 虚拟的存储区域 xff0c 应用程序所访问的都是虚拟内存 物理内存 xff1a 存储空间 xff0c 实际的存储区域 xff0c 只有系统内
  • 树莓派替代品

    51单片机 转载于 https www cnblogs com wanghuaqiang p 11481958 html
  • C++ 笔记10 | 多态(polymorphic)

    span class token variable eg 实现图形库 xff0c 用于显示各种图形 span span class token variable 图形基类 span span class token punctuation
  • QT 笔记3 | Qt设计师使用 Qt创造器使用

    六 Qt设计师使用 designer 案例1 xff1a 使用设计师重构加法计算器 1 创建工程目录 mkdir Calculator2 2 进入工程目录 xff0c 执行 designer 启动设计师 1 xff09 在新建窗体界面 xf
  • QT 笔记5 |Qt多线程(QThread)

    一 Qt多线程 QThread 1 创建线程方法1 xff1a QObject moveToThread class Myclass public QObject Q OBJECT public slots void func void 耗
  • QT 笔记6 | Qt网络编程

    回顾 xff1a 1 Qt多线程 QThread 1 xff09 创建线程 方法1 xff1a moveToThread 方法2 xff1a 继承QThread xff0c 重写run函数 2 xff09 线程同步 互斥锁 xff1a QM
  • QT 笔记7 | UDP编程

    回顾 xff1a 1 xff09 控件类 QT 43 61 widgets QApplication Qt的gui应用程序 QWidget 控件基类 QLabel 标签 QPushButton 按钮 QDialog 对话框 QMainWin
  • 学习c语言

    今天学习了if语句和 xff45 xff4c xff53 xff45 运用 xff43 语言更加顺手 xff0c 之前一些都能实施 xff0c 真是太开心了 include lt stdio h gt int main 初始化 int pr
  • 求符合给定条件的整数集(做题)

    题目如上 xff1b 首先我们先想思路 xff1a 先来一个输入 xff0c 读入这个数 xff0c 然后我们需要三个变量来储存这三个数 xff1b 然后我们遍历所有的组合 xff0c 这个依靠循环 接下来是代码 xff1a include
  • 水仙花数(做题)

    代码如下 xff1a include lt stdio h gt int main int a scanf 34 d 34 amp a float t t 61 0 1 while a gt 0 t 61 t 10 a 判断几位数 int
  • 一分钟了解动态内存分配

    谈到这 xff0c 必然离不开malloc函数 在上面可以看出此函数需要一个头文件 include lt stdilb h gt 而且返回类型是void 传进去的是空间大小 xff0c 此函数申请的空间是字节为单位的 这其中的就分配了100
  • 动态内存分配深究

    接下来我们将探究以下三个问题 xff1a 1 相邻两次malloc得到的空间是否是连续的呢 xff1f 2 你得到的空间的实际大小是否就是你要求的大小呢 xff1f 3 如果你malloc零长度会得到什么结果呢 xff1f 第一个问题 xf
  • 同一个页面不打开两次

    lt script language 61 34 javascript 34 gt function popwin3 path window open path 34 cart 34 34 height 61 520 width 61 52
  • 超易懂!二分查找 详析

    二分算法的 本质 是 xff1a 假如我们可以找到事物的 某种性质 xff0c 这种性质 可以将区间一分为二 xff0c 一半满足 xff0c 一半不满足 我们就可以二分 另外 xff0c 有 连续性 必可以 二分 二分模板一共有两个 xf
  • 手摸手 Spring Cloud Gateway + JWT 实现登录认证

    你好 xff0c 我是悟空 前言 上篇我已经讲解了 Spring Cloud 的原理和实战 xff0c 这次就要结合 JWT 来实现登录认证的功能了 本文已收录至 深入剖析 Spring Cloud 底层架构原理 xff0c 已更新 18