JWT入门指南

news2024/11/27 17:45:14

1、Token认证


随着 Restful API、微服务的兴起,基于 Token 的认证现在已经越来越普遍。基于token的用户认证是一种服务端无状态的认证方式,所谓服务端无状态指的token本身包含登录用户所有的相关数据,而客户端在认证后的每次请求都会携带token,因此服务器端无需存放token数据。

当用户认证后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。

分布式单点登录三种常见方式:(SSO)

  • 第一种,session广播机制实现。(把session复制到另一台服务器中)

    • 缺点:模块较多时,拷贝session比较浪费资源;比如 中间会存在多份一样的数据 ;session默认过期时间30分钟,过期需要重新登录
  • 第二种,使用cookie+redis实现。

    • cookie客户端技术:存在浏览器中,每次发送请求,带着cookie值进行发送

    • redis,读取速度快,基于key-value存储(keys *)

    • 用户登录后,把数据分别放到两个地方cookie、redis

      • ① redis,在key里生成唯一随机值(ip、用户id、uuid) ,在value里放用户数据
      • ② cookie,把redis里面生成key值放到cookie里面。
    • 访问项目其他模块时,发送请求带着cookie进行发送,然后其他模块去获取cookie值,也就是拿着cookie去redis中查询,如果能查到数据表示这个用户已登录。

  • 第三种,token认证(按照一定规则生成字符串,字符串可以包含用户信息) ,jwt


2、JWT 概述


JWT(全称:JSON Web Token),通过数字签名的方式,以JSON对象作为载体,在不同的服务终端之间安全的传输信息。JWT 是实现Token无状态会话认证技术的一种标准。

JWT作用:通常用于web应用程序的 身份验证鉴权 。JWT会在用户登录后生成一个令牌,并在后续每个请求中将该令牌作为身份凭证发送给服务器,系统在处理用户请求之前,都要先进行JWT的安全校验,通过之后再进行相应的业务处理。


3、JWT的组成


JWT令牌由Header、Payload、Signature三部分组成,每部分字符串中间用. 拼接。JWT令牌的最终格式是这样的: xxx.yyy.zzz

# Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjpbeyJ1cmwiOiJodHRwczovL3Rvb2x0dC5jb20ifV0sImlhdCI6MTY0NjExMDgwNSwiZXhwIjoyNTU2MTE1MTk5LCJhdWQiOiIiLCJpc3MiOiJ0b29sdHQuY29tIiwic3ViIjoiIn0.NhUwqiPfYey9pKHSfrG-ptqEOamIQFK3-K7IrTeBFYU

3.1 Header(标头)


Header(标头),通常由两部分组成:令牌的类型 和 所用的加密算法,然后将该JSON对象数据进行Base64 URL编码,得到的字符串就是JWT令牌的第一部分。

{
    "type":"JWT", # 表示要生成JWT类型的token
    "alg":"HS256" # 加密算法
}

3.2 Payload(载荷)


Payload(载荷),有效数据存储区,主要定义自定义字段和内置字段数据。通常会把用户信息和令牌过期时间放在这里,同样也是一个JSON对象,里面的key和value可随意设置,然后经过Base64 URL编码后得到JWT令牌的第二部分,由于这个部分是没有加密的(因为Base64是编码,可以直接解码),建议只存放一些非敏感信息。

{
    "sub": "1234567890",
    "name": "aopmin",
    "admin": true
}

Payload的内置字段:

  • iss(Issuer):令牌的签发者
  • sub(Subject):所面向的用户或实体
  • aud(Audience):令牌的接收者
  • exp(Expiration Time):令牌的过期时间(以UNIX时间戳表示)
  • nbf(Not Before):令牌的生效时间(以UNIX时间戳表示)
  • iat(Issued At):令牌的签发时间(以UNIX时间戳表示)
  • jti(JWT ID):令牌的唯一标识符

3.3 Signature(签名)


Signature(签名), 使用头部Header定义的加密算法,对前两部分Base64编码拼接的结果进行加密,加密时的秘钥服务私密保存,加密后的结果在通过Base64Url编码得到JWT令牌的第三部分。

签名的作用:防止JWT令牌被篡改。

//将头部和载荷base64编码后的数据进行拼接
var encodeStr = base64UrlEncode(header) + "." + base64UrlEncode(payload);
//使用头部定义的算法,对拼接后的数据进行加密 //secret 盐值、秘钥
var signature = HMACSHA256(encodeStr,secret); 

4、JWT的使用


1、创建springboot项目,项目名:springboot-jwt

2、向pom.xml中,导入如下依赖:

<dependencies>
    <!-- springmvc -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- junit -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!-- jwt -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

注:如果使用JDK8以后的版本,jwt需要引入额外的4个依赖 jaxb-api、jaxb-impl、jaxb-core、activation。


3、使用JWT生成Token:

package com.baidou.test;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;

import java.util.Date;
import java.util.UUID;

/**
 * 使用JWT生成token和验证token
 *
 * @author 白豆五
 * @version 2023/06/19
 * @since JDK8
 */
public class JwtTest {


    /**
     * 测试使用JWT生成令牌
     * 应用场景:用户登录生成token
     */
    @Test
    public void testCreatJwt() {
        String secretKey = "aopmin"; //秘钥
        // 使用Jwts工具类构建一个令牌
        String token = Jwts.builder()
                // 1.设置JWT头部信息(类型和加密算法)
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                // 2.设置JWT载荷数据
                .setId(UUID.randomUUID().toString()) //内置字段jti:表示唯一ID
                .setSubject("all") //内置字段sub:面向所有用户
                .setIssuedAt(new Date()) //内置字段ita:token创建时间
                .setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) //内置字段exp:token过期时间,30分钟
                .claim("username", "aopmin") //自定义字段,kv格式
                .claim("userId", "1001") //自定义字段
                // 3.设置JWT签名信息(加密算法,秘钥)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact(); //最后调用compact()方法生成最终的token
        
        System.out.println("token = " + token);
        //由于使用UUID生成唯一标识,所以每次生成的token都不一样
    }
}

输出结果:

token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5MmJkNTQxOC1lNjBkLTRiMjYtYmVkNS01NDlkZmYyOTliZmEiLCJzdWIiOiJhbGwiLCJpYXQiOjE2ODcxOTI4MTQsImV4cCI6MTY4NzE5NDYxNCwidXNlcm5hbWUiOiJhb3BtaW4iLCJ1c2VySWQiOiIxMDAxIn0.H6aI4ozESqiamOfY9NxunnEs0y3AhOTHcXsFFYmPut4

在线token解密:https://tooltt.com/jwt-decode/

在这里插入图片描述

在这里插入图片描述


4、使用JWT验证Token:

/**
 * 测试使用JWT验证令牌
 * 应用场景:用户登录后请求系统携带token令牌,系统对token进行验证,判断是否合法
 * 令牌解析失败的情况:
 *    1.令牌过期
 *    2.令牌被篡改
 * 结论:一个合法的Tokn令牌一定是未过期、未被篡改的令牌
 */
@Test
public void testcheckToken() {
    // 秘钥
    String secretKey = "aopmin";
    // 待验证的token
    String tokenStr = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5MmJkNTQxOC1lNjBkLTRiMjYtYmVkNS01NDlkZmYyOTliZmEiLCJzdWIiOiJhbGwiLCJpYXQiOjE2ODcxOTI4MTQsImV4cCI6MTY4NzE5NDYxNCwidXNlcm5hbWUiOiJhb3BtaW4iLCJ1c2VySWQiOiIxMDAxIn0.H6aI4ozESqiamOfY9NxunnEs0y3AhOTHcXsFFYmPut4";
    // 通过密钥验证签名是否被篡改
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(tokenStr);
    
    // 获取头
    JwsHeader header = claimsJws.getHeader();
    // 获取载荷
    Claims body = claimsJws.getBody();
    // 获取签名
    String signature = claimsJws.getSignature();

    System.out.println("头信息:" + header);
    System.out.println("载荷信息:" + body);
    System.out.println("签名信息:" + signature);
}

输出结果:

头信息:{typ=JWT, alg=HS256}
载荷信息:{jti=92bd5418-e60d-4b26-bed5-549dff299bfa, sub=all, iat=1687192814, exp=1687194614, username=aopmin, userId=1001}
签名信息:H6aI4ozESqiamOfY9NxunnEs0y3AhOTHcXsFFYmPut4

5、SpringBoot+JWT快速整合


5.1 用户登录生成Token


1、创建实体类

package com.baidou.pojo;

import lombok.Data;

import java.io.Serializable;

/**
 * 用户实体
 *
 * @author 白豆五
 * @version 2023/06/20
 * @since JDK8
 */
@Data
public class User implements Serializable {
    private String id;
    private String userName;
    private String passWord;
    /**
     * token字符串
     */
    private String token;
}

2、创建JWT工具类

package com.baidou.utils;

import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.UUID;

/**
 * JWT工具类
 *
 * @author 白豆五
 * @version 2023/06/20
 * @since JDK8
 */
public class JwtUtil {

    //过期时间:1个小时
    public static final long EXPIRE = 1000 * 60 * 60 * 1;
    //秘钥
    public static final String APP_SECRET = "aopmin";


    /**
     * 创建Token
     *
     * @param id       用户ID
     * @param userName 用户名称
     * @return jwtToken
     */
    public static String createToken(String id, String userName) {
        // 使用Jwts工具类构建一个令牌
        String jwtToken = Jwts.builder()
                // 1.设置JWT头部信息(类型和加密算法)
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                // 2.设置JWT载荷数据
                .setId(UUID.randomUUID().toString()) //内置字段jti:表示唯一ID
                .setSubject("all") //内置字段sub:面向所有用户
                .setIssuedAt(new Date()) //内置字段ita:token创建时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) //内置字段exp:token过期时间,token过期时间30分钟
                .claim("username", userName) //自定义字段,kv格式
                .claim("userId", id) //自定义字段
                // 3.设置JWT签名(加密算法,秘钥)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact(); //最后调用compact()方法生成最终的token
        return jwtToken;
    }


    /**
     * 判断Token是否、有效
     *
     * @param jwtToken token
     * @return true:token有效 、false:token失效
     */
    public static boolean checkToken(String jwtToken) {
        // 非空判断
        if (StrUtil.isBlank(jwtToken)) {
            return false;
        }

        try {
            // 通过密钥验证签名是否被篡改
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }


    /**
     * 判断Token是否存在、有效
     *
     * @param request 从请求头中拿token
     * @return true:token有效 、false:token失效
     */
    public static boolean checkToken(HttpServletRequest request) {

        try {

            String jwtToken = request.getHeader("token");
            if (StrUtil.isBlank(jwtToken)) {
                return false;
            }
            // 通过密钥验证签名是否被篡改
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    /**
     * 根据Token获取用户ID
     *
     * @param request 从请求头中拿token
     * @return userID
     */
    public static String getUserIdByJwtToken(HttpServletRequest request) {
        // 从请求头中拿token
        String jwtToken = request.getHeader("token");
        // 非空判断
        if (StrUtil.isBlank(jwtToken)) {
            return "";
        }
        // 通过密钥验证签名是否被篡改
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        // 获取载荷信息
        Claims claims = claimsJws.getBody();
        // 用户ID
        return (String) claims.get("userId");
    }

}

3、创建控制器类:UserController

package com.baidou.controller;

import cn.hutool.core.util.StrUtil;
import com.baidou.pojo.User;
import com.baidou.utils.JwtUtil;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

/**
 * 用户接口
 *
 * @author 白豆五
 * @version 2023/06/20
 * @since JDK8
 */
@RestController //前后端分离使用json格式
@RequestMapping("user")
public class UserController {

    // region 登录相关

    /**
     * 用户登录接口
     *
     * @param user
     * @return
     */
    @PostMapping("/login")
    public User login(@RequestBody User user) {
        // 把账号密码直接写死(不让他走数据库)
        String userName = "aopmin";
        String passWord = "123456";

        // 非空校验
        if (StrUtil.isAllBlank(user.getUserName(), user.getPassWord())) {
            return null;
        }

        // 如果用户名和密码都正确,创建token
        if (userName.equals(user.getUserName()) && passWord.equals(user.getPassWord())) {
            // 创建Token:token保存到user对象中
            user.setToken(JwtUtil.createToken(user.getId(), user.getUserName()));
            return user;
        }

        // 用户名和密码不正确,返回null
        return null;
    }

    /**
     * 验证Token是否过期
     *
     * @param token 用户token
     * @return true未过期、false已过期
     */
    @GetMapping("/check_token")
    public boolean checkToken(String token) {
        return JwtUtil.checkToken(token);
    }


    /**
     * 验证Token是否过期 --- 前端把token放到请求头中
     *
     * @param request 从请求头中拿token
     * @return true未过期、false已过期
     */
    @GetMapping("/check_token2")
    public boolean checkToken(HttpServletRequest request) {
        return JwtUtil.checkToken(request);
    }


    // endregion
}

4、启动项目,使用Apifox测试接口

测试登录接口:http://localhost:8080/user/login

在这里插入图片描述

token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4MzcyNDY3Zi0xOGY4LTQ0YjEtYTIzMi0zNjcwZTk3ODZjZDYiLCJzdWIiOiJhbGwiLCJpYXQiOjE2ODcxOTgxNzIsImV4cCI6MTY4NzIwMTc3MiwidXNlcm5hbWUiOiJhb3BtaW4iLCJ1c2VySWQiOiIxMDAxIn0.8JZMuIeqf1VXuz6-SSDDD48hGRGmjDUNI9xjJd0RjL8

测试验证token接口:http://localhost:8080/user/check_token

在这里插入图片描述


测试验证token接口(前端把token放到请求头中):http://localhost:8080/user/check_token2

在这里插入图片描述


5.2 跨域配置


前后端会存在跨域问题。

在发送请求时,如果出现以下任意一种情况,那么它就是跨域请求:

  • 协议不同,如 http 、https;

  • 域名不同,如 www.taobao.com、www.jd.com、www.baidu.com

  • 端口不同,如 http:localhost:8080、http:localhost:8081

后端解决方案:

package com.baidou.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 跨域配置
 *
 * @author 白豆五
 * @version 2023/06/20
 * @since JDK8
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    /**
     * 添加跨域配置
     * @param registry 注册器
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 覆盖所有请求
        registry.addMapping("/**") // 配置可以跨域的路径,/**表示匹配所有请求路径
                .allowedOrigins("*") // 允许所有的请求,也可以指定具体的请求,例如 allowedOrigins("http://example.com")
                .allowedHeaders("*") // 允许所有请求头访问,也可以指定具体的请求头访问,例如 allowedHeaders("Content-Type", "Authorization")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD") // 允许的HTTP方法,根据需要添加或删除特定的HTTP方法
                .maxAge(3600); // 预检请求的缓存时间,单位为秒
    }
}

5.3 使用拦截器验证Token


1、创建验证token的拦截器

package com.baidou.interceptor;

import com.baidou.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Token拦截器 ———— 验证Token
 *
 * @author 白豆五
 * @version 2023/06/20
 * @since JDK8
 */
@Slf4j
@Configuration
public class TokenInterceptor implements HandlerInterceptor {

    // 在控制器请求处理方法被调用之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       
        log.info("验证token的拦截器执行了,token:{}",request.getHeader("token"));
       
        // 要求前端必须把token放到请求头中
        if (!JwtUtil.checkToken(request)) {
            return false; //验证失败
        }
        return true; //放行
    }
}

2、创建WebConfig配置类,注册拦截器

package com.baidou.config;

import com.baidou.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * SpringMVC配置类
 *
 * @author 白豆五
 * @version 2023/06/20
 * @since JDK8
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private TokenInterceptor tokenInterceptor;

    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor)
                .addPathPatterns("/**"); // 添加拦截器,并指定要拦截的路径

    }
}

3、编写测试接口:

/**
 * 从token中获取用户名
 *
 * @param request
 * @return
 */
@GetMapping("/getName")
public String getUserName(HttpServletRequest request) {
    // 从请求头中拿token
    String token = request.getHeader("token");
    // 非空判断
    if (StrUtil.isBlank(token)) {
        return "";
    }
    // 通过密钥验证签名是否被篡改
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey("aopmin").parseClaimsJws(token);
    // 获取载荷信息
    Claims claims = claimsJws.getBody();
    // 用户ID
    return (String) claims.get("username");
}

4、测试:http://localhost:8080/user/getName

在这里插入图片描述
在这里插入图片描述


6、加密算法(扩展)


6.1 常用的加密算法


在这里插入图片描述


5.2 密码加密技术选型


在这里插入图片描述

6.2.1 MD5加密方式


MD5一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。这套算法的程序在 RFC 1321 标准中被加以规范。1996年后该算法被证实存在弱点,可以被加以破解,对于需要高度安全性的数据,专家一般建议改用其他算法,如SHA-2。2004年,证实MD5算法无法防止碰撞(collision),因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途。

示例:

package com.baidou.test;

import org.springframework.util.DigestUtils;

/**
 * 测试MD5加密算法
 *
 * @author 白豆五
 * @version 2023/06/20
 * @since JDK8
 */
public class MD5Test {
    public static void main(String[] args) {
        // 使用spring框架提供的DegestUtils工具类实现MD5加密
        String s1 = DigestUtils.md5DigestAsHex("hello".getBytes());
        String s2 = DigestUtils.md5DigestAsHex("hello".getBytes());
        System.out.println(s1); // 5d41402abc4b2a76b9719d911017c592
        System.out.println(s2); // 5d41402abc4b2a76b9719d911017c592
        System.out.println(s1.equals(s2)); // true
    }
}

注意:md5对相同的内容加密,每次加密后的密文是相同的,所以不太安全。


6.2.2 MD5+盐


基于md5+随机字符串进行手动加密,增加破解md5的复杂度。(这种方式盐需要保存到表中)

在md5的基础上进行手动加盐(salt)处理:

package com.baidou.test;

import org.springframework.util.DigestUtils;

/**
 * 测试:MD5+盐方式
 *
 * @author 白豆五
 * @version 2023/06/20
 * @since JDK8
 */
public class Md5SaltTest {
    public static void main(String[] args) {
        // 盐值
        String salt = "2023-04-29"; 
        // 明文密码
        String pwd = "admin";
        // MD5加密的密码
        String md5Pwd = DigestUtils.md5DigestAsHex(pwd.getBytes());
        // MD5+盐加密的密码
        String md5Pwd2 = DigestUtils.md5DigestAsHex((pwd + salt).getBytes());
        System.out.println(md5Pwd); // 21232f297a57a5a743894a0e4a801fc3
        System.out.println(md5Pwd2); // 1676be8379ca0a334d035cbd32cb24de
    }
}

注意:这种方式,同样的密码,如果盐的值是随机字符串,那么加密多次的密码是不相同的;


6.2.3 Bcrypt加密方式


在用户模块中,对于用户密码的保护,我们通常对密码进行加密,然后存放在数据库中,在用户进行登录的时候,将其输入的密码进行加密然后与数据库中存放的密文进行比较,以验证用户密码是否正确。 目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全。

BCrypt官网:http://www.mindrot.org/projects/jBCrypt/

1、从官网下载源码,将源码类BCrypt拷贝到工程中;(当然Hutool工具类中也提供了BCrypt加密)(盐不需要保存表中)

2、新建测试类,main方法中编写代码,实现对密码的加密;

3、BCrypt不支持反运算,只支持密码校验。

BCrypt常用工具方法:

  • gensalt():生成盐;(随机字符串)
  • hashpw(明文密码,盐):加密方法;
  • checkpw(明文密码, 密文密码):验证方法;

示例:

package com.baidou.test;

/**
 * 测试Bcrypt加密方式
 *
 * @author 白豆五
 * @version 2023/06/20
 * @since JDK8
 */
public class BcryptTest {

    private static String pwdEncrypt = null; //模拟数据库表中的密码

    public static void main(String[] args) {
        // 模拟用户注册
        register("123456");
        // 模拟用户登录
        checkPwd("123456");
    }

    /**
     * 用户注册方法
     *
     * @param pwd 明文密码
     * @return 盐
     */
    public static void register(String pwd) {
        // 生成盐值
        String salt = BCrypt.gensalt();
        // 加密
        pwdEncrypt = BCrypt.hashpw(pwd, salt);
        System.out.println("盐: " + salt + ",加密后密码: " + pwdEncrypt);
    }

    /**
     * 模拟用户登录
     * @param pwd 用户输入的密码
     */
    public static void checkPwd(String pwd) {
        // 解密
        boolean isMatch = BCrypt.checkpw(pwd, pwdEncrypt);
        if (isMatch) {
            System.out.println("密码正确!");
        } else {
            System.out.println("密码错误!");
        }
    }
}

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

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

相关文章

利用SQL注入漏洞登录后台

所谓SQL注入&#xff0c;就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串&#xff0c;最终达到欺骗服务器执行恶意的SQL命令&#xff0c;比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的&#xff0c;这类表单特别容易受到SQ…

A7+linux4.14内核SPI 总线通讯异常问题分析

I.问题现象、 2023年1月18日&#xff0c;A7核心板 升级内核版本时&#xff0c;发现SPI总线无法跟wk2168通讯&#xff0c;打印信息如下&#xff1a;nts_io_init in gpmi-nand 1806000.gpmi-nand: mode:4 ,failed in set feature. [bus:0~select:0]wk2xxx_probe() GENA 0xFF reg…

【动态规划】简单多状态dp问题(2)买卖股票问题

买卖股票问题 文章目录 【动态规划】简单多状态dp问题&#xff08;2&#xff09;买卖股票问题1. 最佳买卖股票时机含冷冻期&#xff08;买卖股票Ⅰ&#xff09;1.1 题目解析1.2 算法原理1.2.1 状态表示1.2.2 状态机1.2.3 状态转移方程1.2.4 初始化1.2.5 填表顺序1.2.6 返回值 1…

26.利用概率神经网络分类 预测基于PNN的变压器故障诊断(附matlab程序)

1.简述 学习目标&#xff1a; 概率神经网络分类预测 基于PNN的变压器故障诊断 概率神经网络是由Specht博士在1989年首先提出&#xff0c; 是一种与统计信号处理的许多概念有着紧密联系的并行算法。它实质上是一个分类器&#xff0c;根据概率密度函数的无参估计进行贝叶斯决策…

VanillaNet实战:使用VanillaNet实现图像分类(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整算法设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试热力图可视化展示…

【掌握Spring事务管理】深入理解事务传播机制的秘密

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 1.Spring 中事务的实现方式 1.1 Spring 编程式…

第八十天学习记录:计算机硬件技术基础:80486微处理器的指令系统

80486微处理器的寻址方式 要使微处理器能够完成指令规定的操作&#xff0c;则指令中须包含2种信息&#xff0c;一是执行什么操作&#xff1b;二是该操作所涉及的数据在哪里&#xff1b;三是结果存于何处&#xff0c;故指令通常操作由操作码字段和操作数字组成&#xff0c;其书…

chatgpt赋能python:Python的抹零功能介绍及使用方法

Python的抹零功能介绍及使用方法 Python是一种广泛使用的编程语言&#xff0c;而其抹零功能是在进行浮点数操作时非常有用的。在本文中&#xff0c;我们将介绍python中抹零的概念、使用方法以及注意事项&#xff0c;以帮助大家更好地使用python中的抹零功能。 什么是抹零&…

【MarkDown】CSDN Markdown之时间轴图timeline详解

文章目录 时间轴图一个关于时间轴图的例子语法分组长时间段或事件文本换行时间段和事件文本样式自定义颜色方案主题基础主题森林主题黑色主题默认主题中性主题 与库或网站集成 时间轴图 时间轴图&#xff1a;现在这是一个实验性的图表。语法和属性可能会在未来版本中更改。除了…

渣土车未苫盖识别系统 yolov8

渣土车未苫盖识别系统通过yolov8python&#xff0c;渣土车未苫盖识别系统对经过的渣土车进行实时监测&#xff0c;当检测到有渣土车未能及时苫盖时&#xff0c;将自动发出警报提示现场管理人员及时采取措施。Yolo模型采用预定义预测区域的方法来完成目标检测&#xff0c;具体而…

chatgpt赋能python:Python抽人代码:如何优化你的抽奖过程?

Python抽人代码&#xff1a;如何优化你的抽奖过程&#xff1f; 简介 抽奖是在网站上进行的一项非常常见的活动。随着技术的发展&#xff0c;抽奖活动的方式也越来越多样化。在这些活动中&#xff0c;人们喜欢使用抽人软件或代码来提高效率并确保随机性。这在Python中是相当简…

chatgpt赋能python:Python查找第二大的数——从入门到实战

Python查找第二大的数——从入门到实战 Python是一门非常强大的编程语言&#xff0c;不仅支持基本的编程技巧&#xff0c;也支持各种复杂的算法和数据结构。本篇文章将介绍如何通过Python编写一个程序&#xff0c;来实现查找数组中第二大的数。 环境准备 想要运行这个程序&a…

SQL 函数:concat函数、concat_ws()函数、group_concat()

SQL 函数&#xff1a;concat函数、concat_ws()函数、group_concat()函数(转载) concat()函数 功能&#xff1a;将多个字符串连接成一个字符串。 语法&#xff1a;concat(str1, str2,…) 返回结果为连接参数产生的字符串&#xff0c;如果有任何一个参数为null&#xff0c;则返…

java - 报错解决集合

ssm-java学习笔记 java.lang.NoSuchMethodException: org.cjh.bean.Dept.<init>()Invalid bound statement (not found)错误解决方法动态sql if java.lang.IndexOutOfBoundsException: Index: 5, Size: 5Failed to determine a suitable driver classjava.sql.SQLExcepti…

第十二章 sys模块

1. sys模块介绍 什么是Python 解释器 当编写Python 代码时&#xff0c;通常都会得到一个包含Python 代码的以.py 为扩展名的文件。要运行编写的代码&#xff0c;就需要使用Python 解释器去执行.py 文件。因此&#xff0c;Python 解释器就是用来执行Python 代码的一种工具。常…

Windows下Nacos的配置与使用

一、什么是 Nacos 以下引用来自 nacos.io Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用…

19使用MATLAB中的BP神经网络来做字母识别

1.简述 学习目标&#xff1a; 学习BP神经网络字母识别 字符识别应用非常广泛&#xff0c;比如车辆牌照自动识别和手写识别等。我们采用BP网络对26个英文字母进行识别&#xff0c;首先将26个字母中每一个字母都通过75的方格进行数字化处理&#xff0c;并用一个向量表示&#x…

中间件 -zookeeper

三连支持 一起鼓励 一起进步 zookeeper 文章目录 一、概述1.Leader 角色2.Follower 角色3.数据同步4. 2PC提交5. Observer 角色6. leader 选举7. 集群组成6. 惊群效应 二、Curator三、应用场景总结 一、概述 首先&#xff0c;在分布式架构中&#xff0c;任何的节点都不能以单点…

支持记录和审计上传/下载的文件内容,支持控制用户连接资产的方式,JumpServer堡垒机v3.4.0发布

2023年6月19日&#xff0c;JumpServer开源堡垒机正式发布v3.4.0版本。在这一版本中&#xff0c;JumpServer新增支持多种资源选择策略&#xff0c;包括用户登录、命令过滤、资产登录和连接方式&#xff1b;支持记录和审计上传/下载的文件内容&#xff0c;进一步提升系统的安全性…

linux(线程同步和互斥)

目录&#xff1a; 1.为什么需要同步和互斥 2.明确一些概念 3.实现一个抢票程序 4.理解加锁和解锁是原子的呢&#xff1f;&#xff1f; ------------------------------------------------------------------------------------------------------------------------- 1.为什么…