SpringCloudAlibaba:4.3云原生网关higress的JWT 认证

概述

简介

JWT是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。

定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息,特别适用于分布式站点的单点登录(SSO)场景

session认证的缺点

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

2.扩展性:对于分布式应用,需要实现 session 数据共享

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

JWT的优点

1.无状态

2.适合移动端应用

3.单点登录友好

流程

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage,此后,客户端每次与服务器通信,都要带上这个JWT。 你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

编写JWT的流程

第一步:头部header

JSON对象,描述 JWT 的元数据 { "alg": "HS256", "typ": "JWT" }

alg属性表示签名的算法,默认是 HMAC SHA256(写成 HS256 )

typ属性表示这个令牌(token)的类型(type),统一写为 JWT

第二步:载荷payload

{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }

内容又可以分为3中标准:标准中注册的声明/公共的声明/私有的声明

1.payload-标准中注册的声明 (建议但不强制使用):

iss: jwt签发者

sub: jwt所面向的用户

aud: 接收jwt的一方

exp: jwt的过期时间,这个过期时间必须要大于签发时间

nbf: 定义在什么时间之前,该jwt都是不可用的.

iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。 2.payload-公共的声明 : 公共的声明可以添加任何的信息。一般这里我们会存放一下用户的基本信息(非敏感信息)

3.payload-私有的声明 : 私有声明是提供者和消费者所共同定义的声明。需要注意的是,不要存放敏感信息!!!

第三步:签证signature

签证的值是对头部和载荷进行base64UrlEncode后使用指定算法签名生成【私钥签名,到时候用公钥解密】

1.放入头部

2.放入载荷

3.使用私【公】钥签署

4.设置签名算法

如:以默认HS256为例,指定一个密钥(secret),就会按照如下公式生成

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret, )

第四步

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户

示例

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>java_sc_alibaba</artifactId>
        <groupId>jkw.life</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>test-higress-jwt-8007</artifactId>

    <dependencies>
        <!-- nacos-discovery -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringMVC-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- JWT-->
        <dependency>
            <groupId>org.bitbucket.b_c</groupId>
            <artifactId>jose4j</artifactId>
            <version>0.7.0</version>
        </dependency>
    </dependencies>

</project>

application.yml

server:
  port: 8007
spring:
  application:
    name: test-higress-jwt-8007
  cloud:
    nacos:
      discovery:
        # 配置 nacos注册中心地址
        server-addr: 192.168.66.103:8848

启动类

package jkw;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@Slf4j
@EnableDiscoveryClient
@SpringBootApplication
public class Main8007 {
    public static void main(String[] args) {
        SpringApplication.run(Main8007.class, args);
        log.info("************** 服务提供者 8001 启动成功 ************");
    }
}

工具类【utils.JWTUtil】

package jkw.utils;

import org.jose4j.json.JsonUtil;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.ErrorCodes;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;

import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * JWT工具类
 */
public class JWTUtil {

    public static void main(String[] args) throws JoseException {
        RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
        //生成公钥
        //{"kty":"RSA","n":"ziX1yaqbQWGGto1B4NxvmYifUTSigM2LEN0KubXoxt7t9Nz9NaqES4Y36e_v_DhT_v0mKC74pReTWcDVSXZE49jFWphBNlcsnWOsjMlntVlZ_rOQyLZEMhcqshQVvBU8UPFoc77UYBddAjnnShdrSsP5e9qMeMAJVsRCEJZ3Y1IkwRmUGThhmqXNGn1UEhtMSrXDewkre7AWNVkixky7SV-0WhdA6QrEPtLfXoXBQseO2QgRAA73Gc7rs1hF89lKphcBx_mtngonAltNtGGuDhXriBCnt_zuUx8Bt7S-XlECxjSFtHbWKsgOuWTXxMIOVMHoerinsDP1AKmIqPo5xw","e":"AQAB"}
        final String publicKey = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY);
        System.out.println(publicKey);
        //生成私钥
        //{"kty":"RSA","n":"ziX1yaqbQWGGto1B4NxvmYifUTSigM2LEN0KubXoxt7t9Nz9NaqES4Y36e_v_DhT_v0mKC74pReTWcDVSXZE49jFWphBNlcsnWOsjMlntVlZ_rOQyLZEMhcqshQVvBU8UPFoc77UYBddAjnnShdrSsP5e9qMeMAJVsRCEJZ3Y1IkwRmUGThhmqXNGn1UEhtMSrXDewkre7AWNVkixky7SV-0WhdA6QrEPtLfXoXBQseO2QgRAA73Gc7rs1hF89lKphcBx_mtngonAltNtGGuDhXriBCnt_zuUx8Bt7S-XlECxjSFtHbWKsgOuWTXxMIOVMHoerinsDP1AKmIqPo5xw","e":"AQAB","d":"LclImg4GhbL_lLQzGZpcPyGVIRgrr6f3ZztxEmZQ2TrSZzxeEPlagNvCt3bPOpnYLh5Tx0EHgMOHuruVo8dc7a5Lxx9h_IvIIPzuaiahninGT0fatHmnE-kJVpwXZ7rftqqnpG2SBfWqdsAdmtswvV5hnxyfboJYkKjuc3i385r4s1s0pTZp33C6adHWB7B_dyVouQyjKQfUu_hToD32omJcTmJNcsTIKOR2Lztx-2Dzc4V99-3qDVbwXbBTfle_1tIeHYtSOsaBqVWpqdPOXSw5D4QuFjqUIXEVhIpTw5qNIejlAJ6wSHKUpU7DRm7t7Yl3yH5TqGr1WRB5eA6cYQ","p":"91hbnLKTPYeZrIq77YoSO0mzu8Q-rdJOv9Nece_oTL9zxLyV0JdzBWRkCUEqoaWSb6j1oeXC1qliHOYyls3Jf1e7bjFvJVedPGRtmEBWW_-nRFrIIYKswcco5_qRy4fHtOdNqwObyO1-8hLlu46kU5pxujlHKz0DAclLVOG8Wjs","q":"1VySxagoE5SDnhb2PDmNRF1uXrFHo1bg62KqAfCaws-MtNxaC9dtfBAHKH-s2QEfMYpiLv1i0IkronhvUZug0L-DzLemdwCyV98naaBElMzkTsC2hZpkmqR95HTACFSzpC5KRspl1ZvIxq-U-n5BY9asqpkhoyn7dCpkRy9tWeU","dp":"R5SEfqaXQdk6Odq0ZBvvBsVfhFlYokkYjR8IWATLv1owkKDa4lDR8p-I67y2L62Q4UuOOloZtrGyORbNUSMgyv-CuHMJ7U6brFyL8uG7nEgyCfATts7wW-vdBLVY-APFYa8GpRUYQl-ouzmIzmyLVb5-ZxwoYnT3p86vRFNHhP0","dq":"i9qIYoNc8aixtVh7wvI-hQdxJySxPoHeIKyln2vlJbkCFDMz2vs0ytN-va8iz4OKvOBmh0KUGPkw3uhun2GRwgMnE3N17B9Kx4qAvR3OlnLPXEe53E1dkHguBSf6D_vlXMLy8QAOTDw3GPVSg_dqSVUYDSMfB2KnbnezD24pEXk","qi":"kSGhqTuRjKESkVeJdWipF9kFy5-1p6T2Ym-S74PVVjBChuasvkkJtfLppw-yb0fX504TjoLDJIvYNp2fDY3Gv8CVr_W4cQjViEONrbGspzQTZmPAVEH0TiovFZ7z_KNp3P5Pl6JXMhOwxKsEM3hGj8IL6-4Bkh9-wOHG1mOi4sQ"}
        final String privateKey = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
        System.out.println(privateKey);
    }

    //公钥
    private static final String publicKeyString = "{\"kty\":\"RSA\",\"n\":\"ziX1yaqbQWGGto1B4NxvmYifUTSigM2LEN0KubXoxt7t9Nz9NaqES4Y36e_v_DhT_v0mKC74pReTWcDVSXZE49jFWphBNlcsnWOsjMlntVlZ_rOQyLZEMhcqshQVvBU8UPFoc77UYBddAjnnShdrSsP5e9qMeMAJVsRCEJZ3Y1IkwRmUGThhmqXNGn1UEhtMSrXDewkre7AWNVkixky7SV-0WhdA6QrEPtLfXoXBQseO2QgRAA73Gc7rs1hF89lKphcBx_mtngonAltNtGGuDhXriBCnt_zuUx8Bt7S-XlECxjSFtHbWKsgOuWTXxMIOVMHoerinsDP1AKmIqPo5xw\",\"e\":\"AQAB\"}";
    //私钥
    private static final String privateKeyString = "{\"kty\":\"RSA\",\"n\":\"ziX1yaqbQWGGto1B4NxvmYifUTSigM2LEN0KubXoxt7t9Nz9NaqES4Y36e_v_DhT_v0mKC74pReTWcDVSXZE49jFWphBNlcsnWOsjMlntVlZ_rOQyLZEMhcqshQVvBU8UPFoc77UYBddAjnnShdrSsP5e9qMeMAJVsRCEJZ3Y1IkwRmUGThhmqXNGn1UEhtMSrXDewkre7AWNVkixky7SV-0WhdA6QrEPtLfXoXBQseO2QgRAA73Gc7rs1hF89lKphcBx_mtngonAltNtGGuDhXriBCnt_zuUx8Bt7S-XlECxjSFtHbWKsgOuWTXxMIOVMHoerinsDP1AKmIqPo5xw\",\"e\":\"AQAB\",\"d\":\"LclImg4GhbL_lLQzGZpcPyGVIRgrr6f3ZztxEmZQ2TrSZzxeEPlagNvCt3bPOpnYLh5Tx0EHgMOHuruVo8dc7a5Lxx9h_IvIIPzuaiahninGT0fatHmnE-kJVpwXZ7rftqqnpG2SBfWqdsAdmtswvV5hnxyfboJYkKjuc3i385r4s1s0pTZp33C6adHWB7B_dyVouQyjKQfUu_hToD32omJcTmJNcsTIKOR2Lztx-2Dzc4V99-3qDVbwXbBTfle_1tIeHYtSOsaBqVWpqdPOXSw5D4QuFjqUIXEVhIpTw5qNIejlAJ6wSHKUpU7DRm7t7Yl3yH5TqGr1WRB5eA6cYQ\",\"p\":\"91hbnLKTPYeZrIq77YoSO0mzu8Q-rdJOv9Nece_oTL9zxLyV0JdzBWRkCUEqoaWSb6j1oeXC1qliHOYyls3Jf1e7bjFvJVedPGRtmEBWW_-nRFrIIYKswcco5_qRy4fHtOdNqwObyO1-8hLlu46kU5pxujlHKz0DAclLVOG8Wjs\",\"q\":\"1VySxagoE5SDnhb2PDmNRF1uXrFHo1bg62KqAfCaws-MtNxaC9dtfBAHKH-s2QEfMYpiLv1i0IkronhvUZug0L-DzLemdwCyV98naaBElMzkTsC2hZpkmqR95HTACFSzpC5KRspl1ZvIxq-U-n5BY9asqpkhoyn7dCpkRy9tWeU\",\"dp\":\"R5SEfqaXQdk6Odq0ZBvvBsVfhFlYokkYjR8IWATLv1owkKDa4lDR8p-I67y2L62Q4UuOOloZtrGyORbNUSMgyv-CuHMJ7U6brFyL8uG7nEgyCfATts7wW-vdBLVY-APFYa8GpRUYQl-ouzmIzmyLVb5-ZxwoYnT3p86vRFNHhP0\",\"dq\":\"i9qIYoNc8aixtVh7wvI-hQdxJySxPoHeIKyln2vlJbkCFDMz2vs0ytN-va8iz4OKvOBmh0KUGPkw3uhun2GRwgMnE3N17B9Kx4qAvR3OlnLPXEe53E1dkHguBSf6D_vlXMLy8QAOTDw3GPVSg_dqSVUYDSMfB2KnbnezD24pEXk\",\"qi\":\"kSGhqTuRjKESkVeJdWipF9kFy5-1p6T2Ym-S74PVVjBChuasvkkJtfLppw-yb0fX504TjoLDJIvYNp2fDY3Gv8CVr_W4cQjViEONrbGspzQTZmPAVEH0TiovFZ7z_KNp3P5Pl6JXMhOwxKsEM3hGj8IL6-4Bkh9-wOHG1mOi4sQ\"}";

    /**
     * 生成token
     *
     * @param userId   用户id
     * @param username 用户名
     * @return
     */
    public static String sign(Long userId, String username) throws JoseException {

        // 第一步:载荷payload
        JwtClaims claims = new JwtClaims();
        // 注册的声明 1.jwt签发者
        claims.setIssuer("user");
        // 注册的声明 2.jwt所面向的用户
        claims.setSubject("subject");
        // 注册的声明 3.接收jwt的一方
        claims.setAudience("Audience");
        // 注册的声明 4.jwt的过期时间,这个过期时间必须要大于签发时间【从现在开始10分钟】
        claims.setExpirationTimeMinutesInTheFuture(10000);
        // 注册的声明 5.定义在什么时间之前,该jwt都是不可用的【2分钟前】
        claims.setNotBeforeMinutesInThePast(2);
        // 注册的声明 6.jwt的签发时间
        claims.setIssuedAtToNow();
        // 注册的声明 7.jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
        claims.setGeneratedJwtId();
        // 公共的声明:可可以添加任何的信息,一般这里我们会存放一下用户的基本信息
        claims.setClaim("userId", userId);
        claims.setClaim("username", username);


        // 第二步:签证signature:其值是对头部header和载荷payload进行base64UrlEncode后使用指定算法签名生成
        JsonWebSignature jws = new JsonWebSignature();
        // 1.放入头部
        jws.setKeyIdHeaderValue("keyId");
        // 2.放入载荷
        jws.setPayload(claims.toJson());
        // 3.使用私钥签名
        PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyString)).getPrivateKey();
        jws.setKey(privateKey);
        // 4.设置签名算法
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);

        //第三步:算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户
        String jwt = jws.getCompactSerialization();
        return jwt;
    }

    /**
     * 验证jwt
     *
     * @param jwt
     */
    public static void checkJwt(String jwt) throws MalformedClaimException, JoseException {

        //1.引入公钥,使用公钥对私钥的签名解密
        PublicKey publicKey = new RsaJsonWebKey(JsonUtil.parseJson(publicKeyString)).getRsaPublicKey();
        //2.使用JwtConsumer解密
        JwtConsumer jwtConsumer = new JwtConsumerBuilder().setRequireExpirationTime()
                .setAllowedClockSkewInSeconds(30) // 允许在验证基于时间的令牌时留有一定的余地,以计算时钟偏差,单位/秒
                .setRequireSubject() // 主题声明
                .setExpectedIssuer("user") // 验证 jwt签发者
                .setExpectedAudience("Audience") // 验证 接收jwt的一方
                .setVerificationKey(publicKey) // 用公钥验证签名 ,验证私钥
                .setJwsAlgorithmConstraints( // 使用生成jwt的签名算法解密
                        new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST, // 白名单
                                AlgorithmIdentifiers.RSA_USING_SHA256))
                .build();
        try {
            // 验证JWT并将其处理为jwtClaims
            JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
            //如果JWT失败的处理或验证,将会抛出InvalidJwtException,希望能有一些有意义的解释关于哪里出了问题
            System.out.println("JWT validation succeeded! " + jwtClaims);
        } catch (InvalidJwtException e) {
            System.out.println("Invalid JWT! " + e);
            // 对JWT无效的(某些)特定原因的编程访问也是可能的
            // 在某些情况下,您是否需要不同的错误处理行为。
            // JWT是否已经过期是无效的一个常见原因
            if (e.hasExpired()) {
                System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
            }
            // 或者观众是无效的
            if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID)) {
                System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
            }
        }
    }

}


状态码枚举类

package jkw.vo;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 状态码枚举类
 */
@Getter
@AllArgsConstructor
public enum CodeEnum {
    SUCCESS(200, "OK"),
    SYSTEM_ERROR(500, "系统异常"),
    ;


    private final Integer code;
    private final String message;
}

返回结果封装类

package jkw.vo;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

/**
 * 返回结果封装类
 */
@Data
@AllArgsConstructor
public class BaseResult<T> implements Serializable {
    private Integer code;//状态码(成功:200,失败:其他)
    private String message;//提示信息
    private T data;//返回数据

    //构建成功结果
    public static <T> BaseResult<T> ok() {
        return new BaseResult(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMessage(), null);
    }

    //构建带有数据的成功结果
    public static <T> BaseResult<T> ok(T data) {
        return new BaseResult(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMessage(), data);
    }

}

用户服务

package jkw.service;


import com.alibaba.nacos.common.utils.StringUtils;
import jkw.utils.JWTUtil;
import jkw.vo.BaseResult;
import org.jose4j.lang.JoseException;
import org.springframework.stereotype.Service;

/**
 * 用户服务
 */
@Service
public class UserService {


    /**
     * 登录
     *
     * @param username
     * @param password
     * @return
     * @throws JoseException
     */
    public BaseResult login(String username, String password) throws JoseException {
        // 1.用户名或者密码校验
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            return new BaseResult(301, "用户名或者密码为空", null);
        }
        // 2.判断用户名和密码是否正确
        if (username.equals("admin") && password.equals("123456")) {
            // 颁发登录token
            String token = JWTUtil.sign(1001L, "admin");
            return BaseResult.ok(token);
        } else {
            return new BaseResult(301, "用户名或者密码不对", null);
        }
    }


}


用户控制器

package jkw.controller;

import jkw.service.UserService;
import jkw.vo.BaseResult;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.lang.JoseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 用户控制器
 */
@RequestMapping("/user")
@RestController
public class UserCon {


    @Autowired
    private UserService userService;


    @PostMapping("/login")
    public BaseResult login(String username, String password) throws MalformedClaimException, JoseException {
        return userService.login(username, password);
    }


}

higress配置

文档

https://higress.io/zh-cn/docs/plugins/jwt-auth/

1.路由配置【8007-higress-jwt 精确匹配 | /user/login test-higress-jwt-8007.DEFAULT-GROUP.public.nacos】

2.插件配置【全局,配置】:策略:JWT Auth 

consumers:
- issuer: "user"
  jwks: |
    {
     "keys": [
     {
      "kty":"RSA",
      "n":"ziX1yaqbQWGGto1B4NxvmYifUTSigM2LEN0KubXoxt7t9Nz9NaqES4Y36e_v_DhT_v0mKC74pReTWcDVSXZE49jFWphBNlcsnWOsjMlntVlZ_rOQyLZEMhcqshQVvBU8UPFoc77UYBddAjnnShdrSsP5e9qMeMAJVsRCEJZ3Y1IkwRmUGThhmqXNGn1UEhtMSrXDewkre7AWNVkixky7SV-0WhdA6QrEPtLfXoXBQseO2QgRAA73Gc7rs1hF89lKphcBx_mtngonAltNtGGuDhXriBCnt_zuUx8Bt7S-XlECxjSFtHbWKsgOuWTXxMIOVMHoerinsDP1AKmIqPo5xw",
      "e":"AQAB",
      "kid": "keyId",
      }
     ]
    }
  name: "consumer1"
global_auth: false

3.路由配置【路由级别,使用】:策略:JWT Auth

allow:
- "consumer1"

4.测试步骤:开启服务test-higress8006和test-higress-jwt-8007

首先访问http://www.jkw.com/user/login【admin/123456】获取jwt,

然后在访问配置jwt Auth的http://www.jkw.com/test/index

【请求头中添加Authorization,值为Bearer jwt的值,前面一定要加Bearer 】

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

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

相关文章

liunx命令行 带颜色

for i in {1..49}; do echo -e "\033[;${i}m 这是${i}的效果 oldboy\E[0M"; done

如何彻底将CAD或者Cadence卸载干净

最近因为升级软件需要先彻底删除这两个软件&#xff0c;发现无论如何都不能卸载干净&#xff0c;于是乎找到这样一个软件帮助卸载或查找剩余的软件残留&#xff1a; 官网&#xff1a;https://geekuninstaller.com 支持软件和 UWP 应用的卸载&#xff0c;查看软件注册表和安装目…

防爆地下水位自动监测设备

TH-DSW1随着科技的不断进步&#xff0c;地下水资源监测技术也在日新月异。防爆地下水位自动监测设备作为一种先进的水文监测工具&#xff0c;其应用不仅提高了水资源管理的效率&#xff0c;还为保障水资源安全提供了有力支撑。 一、防爆地下水位自动监测设备的优势 防爆地下水…

跨协议通讯无缝对接:Modbus-BACnet楼宇智能转换器深度解析

在现代化的建筑群里&#xff0c;智能楼宇管理系统如同神经系统&#xff0c;协调着各设备的运行。某大型商业综合体&#xff0c;集购物中心、办公区、酒店于一体&#xff0c;面对着来自不同供应商的设备&#xff0c;如何实现统一管理和高效通讯成了首要挑战。特别是其内部既有采…

UE5 FARFilter筛选器使用方法

UE5 查找资源时可以用FARFilter进行筛选&#xff0c;之前可以用ClassNames进行筛选&#xff0c;但是5.1之后就弃用这个属性改成ClassPaths属性 构造一个FTopLevelAssetPath对象需要两个FName参数&#xff0c;但是没找到应该传什么 查找官方文档&#xff0c;明显是错误的&#x…

基于SSM的“小型企业人事管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“小型企业人事管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 登录界面 个人信息页面 用户打卡页面 扣…

Docker 入门篇(七)-- Docker 安装 nginx

引言 Docker 系列文章 Docker 入门篇&#xff08;一&#xff09;-- 简介与安装教程&#xff08;Windows和Linux&#xff09; Docker官方镜像 https://hub.docker.com/ 一、安装 nginx 1.安装环境 Linux 环境&#xff1a;centos 7docker 版本&#xff1a;26.1.0nginx版本&…

CTF-密码学基础

概述 密码学(Cryptolopy)&#xff1a;是研究信息系统安全保密的科学 密码学研究的两个方向&#xff1a; 密码编码学(Cryptography)&#xff1a;主要研究对信息进行编码&#xff0c;实现对信息的隐蔽密码分析学(Cryptanalytics)&#xff1a;主要研究加密信息的破译或消息的伪造…

Baidu Comate——让软件研发更高效、更智能

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;给自己一个梦想&#xff0c;给世界一个惊喜。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章目录 一、Baidu Co…

Spring 事务及事务传播机制(1)

目录 事务 回顾: 什么是事务 为什么需要事务 事务的操作 Spring事务的实现 Spring编程式事务(简单了解即可, 问就是基本不用) 观察事务提交 观察事务回滚 Spring声明式事务 Transactional Transactional作用 事务 回顾: 什么是事务 定义: 事务是指逻辑上的一组操作, 构…

最大数字——蓝桥杯十三届2022国赛大学B组真题

问题分析 这道题属于贪心加回溯。所有操作如果能使得高位的数字变大必定优先用在高位&#xff0c;因为对高位的影响永远大于对低位的影响。然后我们再来分析一下&#xff0c;如何使用这两种操作&#xff1f;对于加操作&#xff0c;如果能使这一位的数字加到9则变成9&#xff0…

^_^填坑备忘^_^C#自动化编程实现STK+Exata对卫星互联网星座进行网络仿真

C#实际选择 STK11版本 or STK12版本的问题备注。 【C#自动化客户端调用STK时&#xff0c;实际选择 STK11版本 or STK12版本 的调试运行备注】 以下代码“更新并重新打包备份为”〔testSTKQualNetInterface备份08.1_★避坑★【种子卫星&#xff1a;天线直接安装在卫星上&#…

电机控制系列模块解析(19)—— 反电势观测器

随着现代工业自动化技术的飞速发展&#xff0c;交流电机作为关键的动力装置&#xff0c;其控制精度与效率日益受到重视。其中&#xff0c;无位置传感器控制技术由于其成本低、可靠性高、系统简洁等优点&#xff0c;逐渐成为研究热点。本文将对交流电机反电势观测器这一关键技术…

三维空间刚体运动

三维空间刚体运动是指刚体在三维空间中的运动&#xff0c;这种运动由平移和旋转构成。平移是指物体在空间中沿某一方向移动一定的距离&#xff0c;而旋转则是指物体绕某一轴旋转一定的角度。这两种运动都不会改变物体的形状和大小&#xff0c;因此被称为刚体运动。 在描述三维…

Qt跨平台开发demo(适用萌新)

最近需要参与一款Qt跨平台的软件开发&#xff0c;在此之前&#xff0c;特把基础信息做学习和梳理&#xff0c;仅供参考。 所使用的技术和版本情况如下&#xff1a; 虚拟机&#xff1a;VMware 16.2.5操作系统&#xff1a;ubuntu-20.04.6-desktop-amd64&#xff1a;Mysql数据库…

大模型入坑记:搭建本地大模型微调环境

为了让大模型发挥更大用途&#xff0c;决定在本地搭建大模型微调环境&#xff0c;在原有的PC上加装Tesla V100&#xff0c;前前后后耗时一个多月&#xff0c;遇到若干技术问题&#xff0c;好在目前已基本得到解决&#xff0c;也打破了很多网上店家包括身边专家对GPU搭建上的一些…

正版软件 | Total Uninstall - Windows 全功能卸载程序 新手入门教程

『软件简介』 Total Uninstall 是一款先进的系统监控与卸载工具&#xff0c;它通过创建安装前后的系统快照&#xff0c;为用户提供了一种全新的程序管理方式。这款软件具备两个主要功能&#xff1a;一是能够独立于系统自带的卸载程序&#xff0c;彻底移除已安装的应用程序&…

FPGA -手写异步FIFO

一&#xff0c;FIFO原理 FIFO&#xff08;First In First Out&#xff09;是一种先进先出的数据缓存器&#xff0c;没有外部读写地址线&#xff0c;使用起来非常简单&#xff0c;只能顺序写入数据&#xff0c;顺序的读出数据&#xff0c;其数据地址由内部读写指针自动加1完成&a…

开源数据可视化大屏对接表单数据实践!

如果你需要一个表单系统&#xff0c;进行数据收集&#xff1b;可以使用tduck填鸭进行私有化部署&#xff0c;进行表单制作&#xff0c;完成数据收集。 在实际业务中&#xff0c;往往需要将收集的数据进行展示或分析&#xff1b;此时就可以使用表单数据推送到TReport中&#xf…

AMBA总线介绍

AMBA&#xff08;Advanced Microcontroller Bus Architecture&#xff09;是由ARM&#xff08;Advanced RISC Machines&#xff09;公司设计的一种高性能、高带宽的总线架构。AMBA总线广泛应用于各种嵌入式系统中&#xff0c;包括数字信号处理器、图形处理器、嵌入式处理器以及…
最新文章