JWT 详解

前言:

本博客为转载整合博客(主打一个:我们只做博客的搬运工),参考博客主要有:
https://blog.csdn.net/weixin_45070175/article/details/118559272?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170459940116800215060893%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=170459940116800215060893&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogtop_positive~default-1-118559272-null-null.nonecase&utm_term=jwt&spm=1018.2226.3001.4450

以及讲义:
链接:https://pan.baidu.com/s/1-m74G-15V8a36y9D7tqaMA?pwd=d95d
提取码:d95d
–来自百度网盘超级会员V5的分享

以及B站视频:https://www.bilibili.com/video/BV1i54y1m7cP

如有雷同,肯定是我搬运的!如有侵权,还请联系笔者。

1.什么是JWT

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

​ 官网地址: https://jwt.io/introduction/

翻译: jsonwebtoken(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名

通俗解释

JWT简称JSON Web Token,也就是通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

2.JWT能做什么

1.授权

  • 这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。

2.信息交换

  • JSON Web Token是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。

3.为什么是JWT

基于传统的Session认证

1.认证方式

-我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

2.认证流程

在这里插入图片描述

3.暴露问题

  • 1.每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大

  • 2.用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

  • 3.因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

  • 4.在前后端分离系统中就更加痛苦:如下图所示
    也就是说前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session 每次携带sessionid 到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是CSRF(跨站伪造请求攻 击)攻击,session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。还有就是 sessionid就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。 不方便集群应用。

基于JWT认证

在这里插入图片描述

1.认证流程

  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。

  • 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同lll.zzz.xxx的字符串。 token head.payload.singurater

  • 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。

  • 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题) HEADER

  • 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。

  • 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

2.jwt优势

  • 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快

  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。

  • 不需要在服务端保存会话信息,特别适用于分布式微服务。

4.JWT的结构是什么?

1.令牌组成

  • 1.标头(Header)
  • 2.有效载荷(Payload)
  • 3.签名(Signature)

因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz
Header.Payload.Signature

2.Header

  • 标头通常由两部分组成:令牌的类型(type)即JWT和所使用的签名算法(algorithm ),例如HMAC SHA256或RSA。它会使用 Base64 编码组成 JWT 结构的第一部分。

  • 注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。


```json
{
  "alg": "HS256",
  "typ": "JWT"
}

3.Payload

-令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

这里面存的实际可以理解为后端传输给前端的用户信息,所以这里的信息一般是非敏感信息,比如说用户名,用户性别,年龄等。
注意:这里一般不会传输用户的密码等信息,不然存在信息泄露的风险。

4.Signature

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥secret(也可以说是盐值、签名),然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

  • 如:
    HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret)

这里有几个问题需要再强调一下:

签名目的

所谓的“签名的作用是保证 JWT 没有被篡改过。”实际是这样操作的。

在首次登录后,后端会构建这样一个字符串
在这里插入图片描述

然后将其传给前端。
前端浏览器也好,安卓客户端也好进行存储,下一次相同用户访问时,前端会将这个字符串放入header(一般来说,也可能是cookie)里,然后后端在请求中将这个字符串重新取出,后端会根据“·”对这个字符串进行拆分,拿到header、payload、Signature这三个部分,然后使用同样的加密算法比如前面说的HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret)。看得到的结果与前端传过来的Signature是否一致,这样就可以验证JWT 有没有被篡改过。

放在一起

  • 输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比,它更紧凑。
  • 简洁(Compact)
    可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快
  • 自包含(Self-contained)
    负载中包含了所有用户所需要的信息,避免了多次查询数据库

5.使用JWT

1.引入依赖

<!--引入jwt-->
<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>3.4.0</version>
</dependency>

2.生成token

Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 90);
//生成令牌
String token = JWT.create()
  .withClaim("username", "张三")//设置自定义用户名
  .withExpiresAt(instance.getTime())//设置过期时间
  .sign(Algorithm.HMAC256("token!Q2W#E$RW"));//设置签名 保密 复杂
//输出令牌
System.out.println(token);

生成结果
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsicGhvbmUiLCIxNDMyMzIzNDEzNCJdLCJleHAiOjE1OTU3Mzk0NDIsInVzZXJuYW1lIjoi5byg5LiJIn0.aHmE3RNqvAjFr_dvyn_sD2VJ46P7EGiS5OBMO_TI5jg

这里说明一下:

withClaim这个属性可以翻译为伴随属性,实际就是token里面的payload这个字符串里面的内容。默认是Map形式进行存储。有几个需要存储的数据就几个withClaim。

.sign(Algorithm.HMAC256("token!Q2W#E$RW"));

这是签名:"token!Q2W#E$RW"就是盐值或者说签名,这个字符串是一定只能在服务端自留的,不能泄露,一般项目中是在配置文件中配置好。

Algorithm.HMAC256是一种常用的加盐加密算法,这也是默认的加密算法。

在上述设置中我们是没有设置header的,实际生成器会自动按照默认值设置,也就是前面说的默认值。

3.根据令牌和签名解析数据

//jwt验证
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build();
//将token解析为能够获取信息的格式
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("用户名: " + decodedJWT.getClaim("username").asString());
System.out.println("过期时间: "+decodedJWT.getExpiresAt());

注意:
1.取数据需要注意,如果前面我们payload里面存储了两个值,(比如添加一行代码 .withClaim("sex", "男")//设置自定义用户名)但是我们并不能使用类似

String name  = decodedJWT.getClaim("username");
String sex = decodeJWT.getClaim("sex");

直接取值,getClaim本质上是一个Claim类型,在本质一点是一个Map类型,所以需要先get一下再去使用。

String name  = decodedJWT.get().getClaim("username");
String sex = decodeJWT.get().getClaim("sex");

4.常见异常信息

  • SignatureVerificationException: 签名不一致异常
  • TokenExpiredException: 令牌过期异常
  • AlgorithmMismatchException: 算法不匹配异常
  • InvalidClaimException: 失效的payload异常

6.封装工具类

public class JWTUtils {


    private static final String  SING = "!Q@W3e4r%T^Y";

    /**
     * 生成token  header.payload.sing
     */
    public static String getToken(Map<String,String> map, Date expire){
        
        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();

        //payload
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        String token = builder.withExpiresAt(expire)//指定令牌过期时间
                .sign(Algorithm.HMAC256(SING));//sign
        return token;
    }

    /**
     * 验证token 合法性
     *
     */
    public static DecodedJWT verify(String token){
        return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
    }
}

7.JWT的种类

其实JWT(JSON Web Token)指的是一种规范,这种规范允许我们使用JWT在两个组织之间传递安全可靠的信息,JWT的具体实现可以分为以下几种:

nonsecure JWT:未经过签名,不安全的JWT
JWS:经过签名的JWT
JWE:payload部分经过加密的JWT

1.nonsecure JWT

未经过签名,不安全的JWT。其header部分没有指定签名算法

{
  "alg": "none",
  "typ": "JWT"
}

并且也没有Signature部分

2.JWS

JWS ,也就是JWT Signature,其结构就是在之前nonsecure JWT的基础上,在头部声明签名算法,并在最后添加上签名。创建签名,是保证jwt不能被他人随意篡改。我们通常使用的JWT一般都是JWS

为了完成签名,除了用到header信息和payload信息外,还需要算法的密钥,也就是secretKey。加密的算法一般有2类:

  • 对称加密:secretKey指加密密钥,可以生成签名与验签
  • 非对称加密:secretKey指私钥,只用来生成签名,不能用来验签(验签用的是公钥)

JWT的密钥或者密钥对,一般统一称为JSON Web Key,也就是JWK

到目前为止,jwt的签名算法有三种:

  • HMAC【哈希消息验证码(对称)】:HS256/HS384/HS512
  • RSASSA【RSA签名算法(非对称)】(RS256/RS384/RS512)
  • ECDSA【椭圆曲线数据签名算法(非对称)】(ES256/ES384/ES512)

8.Java中使用JWT

官网推荐了6个.ava使用JWT的开源库,其中比较推荐使用的是java-jwtjjwt-root
在这里插入图片描述

1.java-jwt

1.1对称加密

这个在上面的案例中已经展示了,这里就不再做说明。

1.2非对称加密

private static final String RSA_PRIVATE_KEY = "...";
private static final String RSA_PUBLIC_KEY = "...";

/**
     * 生成token
     * @param payload token携带的信息
     * @return token字符串
     */
public static String getTokenRsa(Map<String,String> payload){
    // 指定token过期时间为7天
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DATE, 7);

    JWTCreator.Builder builder = JWT.create();
    // 构建payload
    payload.forEach((k,v) -> builder.withClaim(k,v));

    // 利用hutool创建RSA
    RSA rsa = new RSA(RSA_PRIVATE_KEY, null);
    // 获取私钥
    RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
    // 签名时传入私钥
    String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.RSA256(null, privateKey));
    return token;
}

/**
     * 解析token
     * @param token token字符串
     * @return 解析后的token
     */
public static DecodedJWT decodeRsa(String token){
    // 利用hutool创建RSA
    RSA rsa = new RSA(null, RSA_PUBLIC_KEY);
    // 获取RSA公钥
    RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
    // 验签时传入公钥
    JWTVerifier jwtVerifier = JWT.require(Algorithm.RSA256(publicKey, null)).build();
    DecodedJWT decodedJWT = jwtVerifier.verify(token);
    return decodedJWT;
}

2.jjwt-root

2.1 对称签名

引入依赖

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

使用方法类似,可参考下列代码

public class JwtUtils {
    // token时效:24小时
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    // 签名所需的密钥,对于不同的加密算法来说含义不同
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    /**
     * 根据用户id和昵称生成token
     * @param id  用户id
     * @param nickname 用户昵称
     * @return JWT规则生成的token
     */
    public static String getJwtToken(String id, String nickname){
        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("nickname", nickname)
            	// HS256算法实际上就是MD5加盐值,此时APP_SECRET就代表盐值
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request Http请求对象
     * @return 如果token有效返回true,否则返回false
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            // 从http请求头中获取token字符串
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) return false;
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     * @param request Http请求对象
     * @return 解析token后获得的用户id
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }
}

注意:

jjwt在0.10版本以后发生了较大变化,pom依赖要引入多个

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>

标准规范中对各种加密算法的secretKey的长度有如下要求:

HS256:要求至少 256 bits (32 bytes) HS384:要求至少384 bits (48 bytes)
HS512:要求至少512 bits (64 bytes) RS256 and PS256:至少2048 bits RS384 and
PS384:至少3072 bits RS512 and PS512:至少4096 bits ES256:至少256 bits (32
bytes) ES384:至少384 bits (48 bytes) ES512:至少512 bits (64 bytes)

在jjwt0.10版本之前,没有强制要求,secretKey长度不满足要求时也可以签名成功。但是0.10版本后强制要求secretKey满足规范中的长度要求,否则生成jws时会抛出异常

之前的签名和验签方法都是传入密钥的字符串,已经过时。最新的方法需要传入Key对象。

public class JwtUtils {
    // token时效:24小时
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    // 签名哈希的密钥,对于不同的加密算法来说含义不同
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHOsdadasdasfdssfeweee";

    /**
     * 根据用户id和昵称生成token
     * @param id  用户id
     * @param nickname 用户昵称
     * @return JWT规则生成的token
     */
    public static String getJwtToken(String id, String nickname){
        String JwtToken = Jwts.builder()
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .claim("id", id)
                .claim("nickname", nickname)
                // 传入Key对象
                .signWith(Keys.hmacShaKeyFor(APP_SECRET.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256)
                .compact();
        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
    public static Jws<Claims> decode(String jwtToken) {
        // 传入Key对象
        Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(APP_SECRET.getBytes(StandardCharsets.UTF_8))).build().parseClaimsJws(jwtToken);
        return claimsJws;
    }
}

2.2 非对称签名

生成jwt串的时候需要指定私钥,解析jwt串的时候需要指定公钥

private static final String RSA_PRIVATE_KEY = "...";
private static final String RSA_PUBLIC_KEY = "...";

/**
     * 根据用户id和昵称生成token
     * @param id  用户id
     * @param nickname 用户昵称
     * @return JWT规则生成的token
     */
public static String getJwtTokenRsa(String id, String nickname){
    // 利用hutool创建RSA
    RSA rsa = new RSA(RSA_PRIVATE_KEY, null);
    RSAPrivateKey privateKey = (RSAPrivateKey) rsa.getPrivateKey();
    String JwtToken = Jwts.builder()
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
        .claim("id", id)
        .claim("nickname", nickname)
        // 签名指定私钥
        .signWith(privateKey, SignatureAlgorithm.RS256)
        .compact();
    return JwtToken;
}

/**
     * 判断token是否存在与有效
     * @param jwtToken token字符串
     * @return 如果token有效返回true,否则返回false
     */
public static Jws<Claims> decodeRsa(String jwtToken) {
    RSA rsa = new RSA(null, RSA_PUBLIC_KEY);
    RSAPublicKey publicKey = (RSAPublicKey) rsa.getPublicKey();
    // 验签指定公钥
    Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(jwtToken);
    return claimsJws;
}

9.实际开发中的应用

在实际的SpringBoot项目中,一般我们可以用如下流程做登录:

  • 在登录验证通过后,给用户生成一个对应的随机token(注意这个token不是指jwt,可以用uuid等算法生成),然后将这个token作为key的一部分,用户信息作为value存入Redis,并设置过期时间,这个过期时间就是登录失效的时间
  • 将第1步中生成的随机token作为JWT的payload生成JWT字符串返回给前端
  • 前端之后每次请求都在请求头中的Authorization字段中携带JWT字符串
  • 后端定义一个拦截器,每次收到前端请求时,都先从请求头中的Authorization字段中取出JWT字符串并进行验证,验证通过后解析出payload中的随机token,然后再用这个随机token得到key,从Redis中获取用户信息,如果能获取到就说明用户已经登录
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String JWT = request.getHeader("Authorization");
        try {
            // 1.校验JWT字符串
            DecodedJWT decodedJWT = JWTUtils.decode(JWT);
            // 2.取出JWT字符串载荷中的随机token,从Redis中获取用户信息
            ...
            return true;
        }catch (SignatureVerificationException e){
            System.out.println("无效签名");
            e.printStackTrace();
        }catch (TokenExpiredException e){
            System.out.println("token已经过期");
            e.printStackTrace();
        }catch (AlgorithmMismatchException e){
            System.out.println("算法不一致");
            e.printStackTrace();
        }catch (Exception e){
            System.out.println("token无效");
            e.printStackTrace();
        }
        return false;
    }
}

在实际开发中需要用下列手段来增加JWT的安全性:

  • 因为JWT是在请求头中传递的,所以为了避免网络劫持,推荐使用HTTPS来传输,更加安全
  • JWT的哈希签名的密钥是存放在服务端的,所以只要服务器不被攻破,理论上JWT是安全的。因此要保证服务器的安全
  • JWT可以使用暴力穷举来破解,所以为了应对这种破解方式,可以定期更换服务端的哈希签名密钥(相当于盐值)。这样可以保证等破解结果出来了,你的密钥也已经换了

以上。

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

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

相关文章

【LeetCode每日一题】383. 赎金信(计数模拟)

2024-1-7 文章目录 [383. 赎金信](https://leetcode.cn/problems/ransom-note/)思路&#xff1a;计数模拟 383. 赎金信 思路&#xff1a;计数模拟 1.通过数组对字母进行计数 2.magazine 中的每个字符只能在 ransomNote 中使用一次。 3.判断减一后&#xff0c;是否小于等于0。…

前端ui库搜集

涟漪动画效果 - MDUI 开发文档, Material Design 前端框架添加涟漪动画效果后&#xff0c;会在点击元素时&#xff0c;产生向外扩散的水波纹效果。https://www.mdui.org/docs/ripple#ripple https://semantic-ui.com/ https://getuikit.com/ https://www.purecss.cn/grids.htm…

Linux进程间通讯 -- 管道

Linux进程间通讯 – 管道 文章目录 Linux进程间通讯 -- 管道1. 原理2. 进程间通讯2.1 管道2.1.1 匿名管道 pipe2.2.2 有名管道 FIFO 2.2 信号2.3 共享内存2.4 本地套接字 1. 原理 Linux 进程间通讯&#xff0c;也称为IPC(InterProcess Communication) 在 Linux 中每个进程都具…

Doris初识(01)

Doris初识 初识 Apache Doris 是一个基于 MPP 架构的高性能、实时的分析型数据库&#xff0c;以极速易用的特点被人们所熟知&#xff0c;仅需亚秒级响应时间即可返回海量数据下的查询结果&#xff0c;不仅可以支持高并发的点查询场景&#xff0c;也能支持高吞吐的复杂分析场景…

嵌入式培训机构四个月实训课程笔记(完整版)-Linux系统编程第三天-Linux进程(物联技术666)

更多配套资料CSDN地址:点赞+关注,功德无量。更多配套资料,欢迎私信。 物联技术666_嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记-CSDN博客物联技术666擅长嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记,等方面的知识,物联技术666关注机器学习,arm开发,物联网,嵌入式硬件,单片机…

H266/VVC网络适配层概述

视频编码标准的分层结构 视频数据分层的必要性&#xff1a;网络类型的多样性、不同的应用场景对视频有不同的需求。 编码标准的分层结构&#xff1a;为了适应不同网络和应用需求&#xff0c;视频编码数据根据其内容特性被分成若干NAL单元&#xff08;NAL Unit&#xff0c;NALU…

WEB 3D技术 three.js 顶点旋转

我们来说说几何体顶点的旋转 官网搜索 BufferGeometry 这里 我们有 x y z 三个轴的旋转 例如 我们这样的代码 import ./style.css import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; i…

第二十七周:文献阅读笔记

第二十七周&#xff1a;文献阅读笔记 摘要AbstractDenseNet 网络1. 文献摘要2. 引言3. ResNets4. Dense Block5. Pooling layers6. Implementation Details7. Experiments8. Feature Reuse9. 代码实现 总结 摘要 DenseNet&#xff08;密集连接网络&#xff09;是一种深度学习神…

AI 工具探索(二)

我参加了 奇想星球 与 Datawhale 举办的 【AI办公 X 财务】第一期&#xff0c;现在这是第二次打卡&#xff0c;也即自由探索&#xff0c;我选择 Modelscope 的 Agent 探索&#xff0c;并用gpts创作助理对比&#xff01; 最近想学学小红书的运营方法&#xff0c;选择了 小红书I…

图像分割-Grabcut法

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 本文的C#版本请访问&#xff1a;图像分割-Grabcut法(C#)-CSDN博客 GrabCut是一种基于图像分割的技术&#xff0c;它可以用于将图像…

CnosDB容灾方案概述

本文主要介绍了跟容灾相关的关键技术以及技术整合后形成的几种具体方案&#xff0c;每种方案都在RTO、RPO、部署成本和维护成本等方面有自己的特点和区别&#xff0c;可以根据具体场景选择最合适的方案。 基本概念 RTO&#xff08;Recovery Time Objective&#xff09;&#x…

Qt基本认识

1. 基本认识 1.1 学习方法&#xff1a; &#xff08;1&#xff09;英语阅读能力要好一点 QT将一些类和方法进行了封装&#xff0c;一般是采用英语&#xff08;方法名、属性、子类、父类等等&#xff09;进行介绍 &#xff08;2&#xff09;学习QT reator 1&#xff09;多查帮助…

数据交互系列:认识 cookie

cookie的原理 http本身是一个无状态的请求&#xff0c;cookie最初的原始目的是为了维持状态而产生的。在首次访问网站时&#xff0c;浏览发送请求中并未携带cookie&#xff0c;即发送无状态请求服务器接受请求之后会在请求上的respond header上加入cookie相关信息并返回给浏览…

数字孪生在虚拟现实(VR)中的应用

数字孪生在虚拟现实&#xff08;VR&#xff09;中的应用为用户提供了更深入、沉浸式的体验&#xff0c;同时通过数字孪生技术模拟真实世界的物理实体。以下是数字孪生在VR中的一些应用&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发…

13年测试老鸟,性能测试-全链路压测总结,一文打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、什么是全链路压…

二维和三维联合进行圆孔空间定位

0.任务描述 对空间圆孔进行三维空间的定位&#xff0c;方便后续的抓取或装配流程&#xff1a;使用二维图与opencv霍夫圆检测进行二维上的定位&#xff0c;再从深度图上查询深度信息&#xff0c;结合相机内参计算出相机坐标系下圆孔的三维坐标信息&#xff0c;并在点云上进行标…

自定义View之重写onMeasure

一、重写onMeasure()来修改已有的View的尺寸 步骤&#xff1a; 重写 onMeasure()&#xff0c;并调用 super.onMeasure() 触发原先的测量用 getMeasuredWidth() 和 getMeasuredHeight() 取到之前测得的尺寸&#xff0c;利用这两个尺寸来计算出最终尺寸使用 setMeasuredDimensio…

[通俗易懂]c语言中指针变量和数值之间的关系

一、指针变量的定义 在C语言中&#xff0c;指针变量是一种特殊类型的变量&#xff0c;它存储的是另一个变量的内存地址。指针变量可以用来间接访问和操作内存中的其他变量。指针变量的定义如下&#xff1a; 数据类型 *指针变量名&#xff1b;其中&#xff0c;数据类型可以是任…

SQL优化:执行计划

前面我们讲述了使用索引或分区表来进行存储层次的优化,也讲述了通过条件提升进行结果集的优化。这边文章我们来学习一下其中的细节,即查看数据库是怎么一步一步把数据拿给我们的。也就是执行计划。 语法 explain sql语句 练习 首先,我们来玩下简单的 explain select * …

HUAWEI WATCH 系列 eSIM 全新开通指南来了

HUAWEI WATCH 系列手表提供了eSIM硬件能力&#xff0c;致力为用户提供更便捷、高效的通信体验。但eSIM 业务是由运营商管理并提供服务的&#xff0c;当前运营商eSIM业务集中全面恢复&#xff0c;电信已经全面恢复&#xff0c;移动大部分省份已经全面放开和多号App开通方式&…