JWT入门指南

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>
    <!-- hutool -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.7.17</version>
    </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
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 常用的加密算法


在这里插入图片描述


6.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加密)(盐不需要保存表中)

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-crypto</artifactId>
    <version>5.4.2</version>
</dependency>

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("密码错误!");
        }
    }
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白豆五

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值