单点登录二:登录过程使用摘要算法和加盐的意义以及demo练习

news2025/1/12 2:43:09

上一篇《springboot项目使用redis、springSecurity、jwt实现单点登录》写了关于单点登录的架子,但是没有实现密码验证的细节。这里使用盐和摘要算法来实现一个密码验证的完整过程demo。

1、依赖没变,还是上一篇内容那些

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <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>
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

2、POJO类,新增了PO用于注册用户的持久化

public class UserDO {
    private String name;
    private String password;

    public UserDO() {
    }

    public UserDO(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

public class UserPO {

    private String username;
    private String password;
    private String salt;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}

3、service层

package com.loong.nba.player.service;

import com.loong.nba.player.pojo.UserDO;
import com.loong.nba.player.pojo.UserPO;

public interface UserService {

    /**
     * 添加用户
     * @return 用户id
     */
     UserPO addUser(UserDO userDO);

     boolean comparePassword(UserDO userDO);

}
package com.loong.nba.player.service;

import com.loong.nba.player.pojo.UserDO;
import com.loong.nba.player.pojo.UserPO;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * @author 
 * @date 2023/5/19
 */
@Service
public class UserServiceImpl implements UserService {

    private final RedisTemplate<String, UserPO> redisTemplate;

    public UserServiceImpl(RedisTemplate<String, UserPO> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public UserPO addUser(UserDO userDO) {

        // 定义盐的长度(字节数)
        int saltLength = 6;

        // 创建一个安全的随机数生成器
        SecureRandom secureRandom = new SecureRandom();

        // 生成盐
        byte[] salt = new byte[saltLength];
        secureRandom.nextBytes(salt);

        // 将盐转换为字符串或字节数组
        String saltString = encodeSalt(salt);

        // 计算密码摘要
        String password = encryptPassword(userDO.getPassword(), saltString);

        UserPO userPO = new UserPO();
        userPO.setUsername(userDO.getName());
        userPO.setPassword(password);
        userPO.setSalt(saltString);

        redisTemplate.opsForValue().set(userPO.getUsername()+"账号", userPO);

//        byte[] saltBytes = decodeSaltString(saltString);

        System.out.println("Salt (Base64 string): " + saltString);
//        System.out.println("Salt (byte array): " + Base64.getEncoder().encodeToString(saltBytes));
        return userPO;
    }

    String encodeSalt(byte[] salt) {
        return Base64.getEncoder().encodeToString(salt);
    }

    byte[] decodeSaltString(String saltString) {
        return Base64.getDecoder().decode(saltString);
    }

    String encryptPassword(String password, String salt) {

        String plaintext = password + salt;

        try {
            // 创建SHA-256算法的MessageDigest实例
            MessageDigest digest = MessageDigest.getInstance("SHA-256");

            // 计算中文字符串的摘要
            byte[] hash = digest.digest(plaintext.getBytes(StandardCharsets.UTF_8));

            // 将摘要字节数组转换为十六进制字符串表示
            String digestHex = bytesToHex(hash);
            System.out.println("SHA-256摘要:" + digestHex);
            return digestHex;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 将摘要字节数组转换为十六进制字符串表示
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexStringBuilder = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexStringBuilder.append('0');
            }
            hexStringBuilder.append(hex);
        }
        return hexStringBuilder.toString();
    }


    @Override
    public boolean comparePassword(UserDO userDO) {

        UserPO user = redisTemplate.opsForValue().get(userDO.getName()+"账号");
        String salt = user.getSalt();
        String password = encryptPassword(userDO.getPassword(), salt);
        if (user.getPassword().equals(password)) {
            return true;
        }
        return false;
    }

}

4、配置类

redis配置我用的spring原生的写法

package com.loong.nba.player.config;

import com.loong.nba.player.pojo.UserPO;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author 
 * @date 2023/5/22
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, UserPO> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, UserPO> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        //设置key序列化
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        //设置value的序列化
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());

        template.afterPropertiesSet();
        return template;
    }
}

这个security配置中区别是将新增的注册接口“/user”加入到允许放行的白名单中

 5、controller

package com.loong.nba.player.controller;

import com.loong.nba.player.pojo.UserDO;
import com.loong.nba.player.pojo.UserPO;
import com.loong.nba.player.service.UserServiceImpl;
import com.loong.nba.player.util.JwtUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;

/**
 * @author 
 * @date 2023/5/15
 */
@RestController
public class LoginController {

    private final JwtUtil jwtUtil;

    private final UserServiceImpl userService;

    public LoginController(JwtUtil jwtUtil, UserServiceImpl userService) {
        this.jwtUtil = jwtUtil;
        this.userService = userService;
    }


    @PostMapping("/login")
    public ResponseEntity<UserDO> postLogin(@RequestBody UserDO userDO, HttpServletResponse response) {

        if (!userService.comparePassword(userDO)) {
           return ResponseEntity.ok(new UserDO());
        }
        String token = jwtUtil.generateToken(userDO.getName());
        response.addHeader("Authorization", "Bearer " + token);
        return ResponseEntity.ok(userDO);
    }

    @PostMapping("/user")
    public UserPO addUser(@RequestBody UserDO userDO) {
        return userService.addUser(userDO);
    }

    @GetMapping("/test")
    public String test() {
        return "hello test";
    }
}

6、演示步骤

可以用任何接口测试工具,我用的是postman

第一步通过/user接口新增一个用户;

第二步通过/login接口登录用户,获取返回的Headers中的token;

第三步用这个token,加到请求头中,请求test接口,就形成了完整的闭环。

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

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

相关文章

职称认定和职称评审有什么区别?甘建二告诉你

职称认定和评审有什么区别呢&#xff1f;通常大家都在说职称认定和评审不知道中间是不是有什么区别&#xff1f;今天甘建二给大家捋一捋&#xff1a; 一、职称认定 职称认定要求学历条件比较严苛的&#xff1a; 1.毕业专业与评审专业一致&#xff0c;不能跨专业认定&#xff0…

基于FPGA的自动曝光算法实现

1 概述 在机器视觉中&#xff0c;自动曝光&#xff08;Auto Exposure&#xff09;是很多成像设备的必备功能。所谓自动曝光&#xff0c;就是根据环境或拍摄物体照明强度自动调节图像传感器的曝光时间&#xff0c;使输出图像的平均灰度&#xff08;亮度&#xff09;保持在一个合…

redis安装和数据类型

关系型数据库和非关系型数据库的区别&#xff1a; ①存储结构不同&#xff0c;关系型数据库是二维表格的方式&#xff0c;非关系型数据库是键值对的形式&#xff08;文档、图文等&#xff09;&#xff1b; ②扩展方式不同&#xff0c;关系型数据库是纵向提升硬件性能&#xf…

嵌入式硬件中Printf函数的原理

作为嵌入式单片机领域小白的我&#xff0c;在查阅STM32、MSP432等串口通信的开发例程时&#xff0c; 总是能看到用 printf&#xff08;&#xff09;这个函数来进行串口的发送功能。 目录 有关printf&#xff08;&#xff09;函数需要解决的疑问&#xff1a; 一、printf&am…

34种ArcGIS常用操作技巧大汇总

概述 ArcGIS产品线为用户提供一个可伸缩的&#xff0c;全面的GIS平台。ArcObjects包含了许多的可编程组件&#xff0c;从细粒度的对象&#xff08;例如单个的几何对象&#xff09;到粗粒度的对象&#xff08;例如与现有ArcMap文档交互的地图对象&#xff09;涉及面极广&#x…

iperf3常用

iperf使用方法详解 iperf3是一款带宽测试工具&#xff0c;它支持调节各种参数&#xff0c;比如通信协议&#xff0c;数据包个数&#xff0c;发送持续时间&#xff0c;测试完会报告网络带宽&#xff0c;丢包率和其他参数。 安装 sudo apt-get install iperf3iPerf3常用的参数&am…

六、数据仓库详细介绍(ETL)方法篇

0x00 前言 上文我们把数据仓库类比我们人类自身&#xff0c;数据仓库“吃”进去的是原材料&#xff08;原始数据&#xff09;&#xff0c;经过 ETL 集成进入数据仓库&#xff0c;然后从 ODS 开始逐层流转最终供给到数据应用&#xff0c;整个数据流动过程中&#xff0c;在一些关…

【JAVAEE】Java中的文件基础

目录 1.文件 1.1什么是文件 1.2文件路径 2.Java中操作文件 File类中常见的属性 File类中常见的构造方法 File类中常见的方法 3.文件内容的读写---数据流 按字节进行数据读InputStream FileInputStream 按字节进行数据写OutputStream 按字符进行数据读FileReader 按…

【JavaSE】Java基础语法(一)

文章目录 1. ⛄常量2. ⛄数据类型2.1 &#x1f320;&#x1f320;计算机存储单元2.2 &#x1f320;&#x1f320;Java 中的数据类型 3. ⛄变量的注意事项4. ⛄键盘录入5. ⛄标识符 1. ⛄常量 常量&#xff1a;在程序运行过程中&#xff0c;其值不可以发生改变的量。 Java中的常…

2023年我要在深圳考CPDA数据分析师认证,含金量如何?

CPDA数据分析师认证是大数据方面的认证&#xff0c;助力数据分析人员打下扎实的数据分析基础知识功底&#xff0c;为入门数据分析保驾护航。 帮助数据分析人员掌握系统化的数据分析思维和方法论&#xff0c;提升工作效率和决策能力&#xff0c;遇到问题能够举一反三&#xff0c…

opencv图像灰度化

图像灰度化就是将图像的亮度值&#xff08;R,G,B&#xff09;按照一定的方式映射到0-255之间的灰度值上&#xff0c;为了使图像看起来不那么单调&#xff0c;需要将图像的亮度值进行变换。下面简单介绍下 opencv中的灰度化函数&#xff1a; 1、先将图像的像素值转换为R,G,B三个…

手机也可以搭建博客?安卓Termux+Hexo搭建属于你自己的博客网站 - 公网远程访问

文章目录 1. 安装 Hexo2. 安装cpolar内网穿透3. 公网远程访问4. 固定公网地址 Hexo 是一个用 Nodejs 编写的快速、简洁且高效的博客框架。Hexo 使用 Markdown 解析文章&#xff0c;在几秒内&#xff0c;即可利用靓丽的主题生成静态网页。 下面介绍在Termux中安装个人hexo博客并…

【Linux】信号集及相关函数(sigemptyset、sigfillset、sigprocmask)

目录 1、信号集2、自定义信号集相关函数3、sigprocmask函数函数解析代码举例 橙色 1、信号集 多个信号组成的一个集合称为信号集&#xff0c;其系统数据类型为 sigset_t 。 在 PCB 中有两个非常重要的信号集&#xff0c;一个称为“阻塞信号集”&#xff0c;另一个是“未决信号…

Charles 抓包工具下载安装及基础使用

在Charles抓包工具之前讲过了Fiddler抓包工具&#xff0c;在讲之前先来解决读者的该怎么读这两个单词&#xff08; Charles 读&#xff1a;雀奥斯 和 Fiddler 读&#xff1a;非的了 &#xff09;&#xff0c;下面进入正题&#xff0c;有使用过抓包工具的或者看过之前关于Fiddle…

leecode530—二叉搜索树的最小绝对差

leecode530 二叉搜索树的最小绝对差 &#x1f50e;首先要知道二叉搜索树是有序的&#xff0c;补充一下二叉搜索树的相关概念。 &#x1f7e0; 对于 BST 的每一个节点 node&#xff0c;左子树节点的值都比 node 的值要小&#xff0c;右子树节点的值都比 node 的值大。 &#x1f…

数据分析笔记:基本概念,常用图表,报告大纲

1.数据分析 1.1定义 对数据进行分析。数据分析是为了提取有用信息和形成结论而对数据加以详细研究和概括总结的过程。在实际工作中&#xff0c;帮助管理者判断和决策。 1.2步骤 数据分析的基本步骤包括明确思路&#xff0c;制定计划、数据收集、数据处理、数据分析、数据显…

chatgpt赋能Python-python_kanren

Python Kanren&#xff1a;一种强大的逻辑编程工具 Python Kanren是一种基于Python的逻辑编程工具&#xff0c;它可以帮助开发人员轻松地构建复杂的逻辑应用程序。如果您正在寻找一种可以帮助您更快地开发和测试逻辑代码的工具&#xff0c;那么Python Kanren绝对是一个不错的选…

MySQL 用户管理

目录 用户管理 用户 用户信息 创建用户 删除用户 修改用户密码 数据库的权限 给用户 注意&#xff1a;如果发现赋权限后&#xff0c;没有生效&#xff0c;执行如下指令&#xff1a; 回收权限 用户管理 如果我们只能使用 root 用户&#xff0c;这样存在安全隐患。这时…

有没有高清录制视频软件?如何录制清晰的视频?

案例&#xff1a;录屏画质模糊影响观看怎么办&#xff1f; 【我把我在电脑上的操作录制了下来&#xff0c;录屏虽然可以看清楚操作的步骤&#xff0c;但是画质比较模糊&#xff0c;看起来很不舒服。有没有什么方法可以录制清晰画质的视频&#xff1f;】 当今数字化时代&#…

Ubutun安装Anconda3

一、下载Anconda 方法一&#xff1a;官网下载 https://www.anaconda.com/download&#xff08;比较费时&#xff09; 可以点击右键复制地址 使用Wget下载 wget https://repo.anaconda.com/archive/Anaconda3-2023.03-1-Linux-x86_64.sh方法 2&#xff1a;清华源 在清华大…