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

上一篇《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接口,就形成了完整的闭环。

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

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

相关文章

接口测试简介以及接口测试用例设计思路

接口测试简介 1.什么是接口 接口就是内部模块对模块&#xff0c;外部系统对其他服务提供的一种可调用或者连接的能力的标准&#xff0c;就好比usb接口&#xff0c;他是系统向外接提供的一种用于物理数据传输的一个接口&#xff0c;当然仅仅是一个接口是不能进行传输的&#x…

多层感知机学习XOR实例

多层感知机学习XOR实例 多层感知机介绍使用多层感知机学习XOR实例传统统计数学方法&#xff08;传统机器学习&#xff09;使用多层感知机学习XOR 总结 多层感知机介绍 多层感知机&#xff08;Multilayer Perceptron&#xff0c;MLP&#xff09;&#xff0c;又称为深度前馈网络…

JAVA并发编程之锁应用

Java并发包是Java中提供的一个用于支持多线程编程的工具包。Java并发包提供了多种机制来控制线程的执行&#xff0c;保证线程的安全性和可靠性。下面我们将介绍Java并发包的使用方法&#xff0c;并给出示例。 synchronized public class SynchronizedDemo { ​private int v;…

自学网络安全,一般人我劝你还是算了吧

学前感言: 我为什么会这样说&#xff0c;要一般人自学网络安全就算了&#xff0c;因为我不是一般人 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发 .3.有时多 google,baidu,我们往往都…

termux-x11教程

小粉丝已经求稿两个星期了&#xff0c;不写是不行了。 termux-x11 是Termux的一个图形化项目&#xff0c;官方是这么介绍的。 A Termux add-on app providing Android frontend for Xwayland.安装工具 我们需要在Termux和安卓系统上安装工具以成功的运行程序。 x11-repo&am…

使用canvas给图片添加水印

上接文章“图片处理” canvas元素其实就是一个画布&#xff0c;我们可以很方便地绘制一些文字、线条、图形等&#xff0c;它也可以将一个img标签里渲染的图片画在画布上。 我们在上传文件到后端的时候&#xff0c;使用input标签读取用户本地文件后得到的其实是一个Blob对象&a…

HNU-电路与电子学-小班3

第三次讨论 1 、直接用晶体管而不是逻辑门实现异或门&#xff0c;并解释这个电路是如何工作的。 &#xff08;6个 MOS 管构成&#xff09; 2 、通信双方约定采用 7 位海明码进行数据传输。请为发送方设计海明码校验位 生成电路&#xff0c;采用功能块和逻辑门为接收方设计海…

ISO_IEC_7816-3

介绍 ISO/IEC 7816 是一系列标准&#xff0c;规定了集成电路卡和此类卡的使用 互换。 这些卡是用于在外部世界和卡中的集成电路之间协商的信息交换的识别卡。 作为信息交换的结果&#xff0c;卡传递信息&#xff08;计算结果、存储的数据&#xff09;和/或修改其内容&#xff0…

路径规划算法:基于果蝇优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于果蝇优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于果蝇优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法果蝇…

2023全国酒店数据

数据内容字段结构 hotel_id int(11) NOT NULL, name varchar(100) DEFAULT NULL, name_en varchar(100) DEFAULT NULL, short_name varchar(100) DEFAULT NULL, province varchar(20) DEFAULT NULL, city_id int(11) DEFAULT NULL, city varchar(20…

Android12之源码手动生成aidl对应java/cpp/ndk/rust服务(一百五十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

博客系统(ssm版本)

在前面的文章中给大家介绍过博客系统的servlet版本&#xff0c;但是servlet的技术非常的老旧&#xff0c;我们在企业中用的都是springboot相关的框架&#xff0c;本章内容就是讲述如何一步一步的利用ssm的技术来实现博客系统。 目录 前期配置 创建数据库 配置文件 公共文件…

【JavaSE】Java基础语法(五):数组详解

文章目录 &#x1f378;1.1 数组介绍&#x1f378;1.2 数组的动态初始化1.2.1 什么是动态初始化1.2.2 动态初始化格式&#x1f378;1.3 数组元素访问1.3.1 什么是索引1.3.2 访问数组元素格式1.3.3 示例代码 &#x1f378;1.4 内存分配1.4.1 内存概述1.4.2 java中的内存分配 &am…

【Java-10】深入浅出线程安全、死锁、状态、通讯、线程池

主要内容 线程安全线程死锁线程的状态线程间通讯线程池 1 线程安全 1.1 线程安全产生的原因 多个线程在对共享数据进行读改写的时候&#xff0c;可能导致的数据错乱就是线程的安全问题了 问题出现的原因 : 多个线程在对共享数据进行读改写的时候&#xff0c;可能导致的数据…

第五十天学习记录:C语言进阶:位段

位段 什么是位段 位段的声明和结构是类似的&#xff0c;有两个不同&#xff1a; 1、位段的成员可以是int,unsigned int或signed int。 2、位段的成员名后边有一个冒号和一个数字。 #define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>//位段-二进制位 struct A {int …

GoWeb -- gin框架的入门和使用(2)

前言 书接上回&#xff0c;在gin的框架使用中&#xff0c;还有着许多方法以及它们的作用&#xff0c;本篇博客将会接着上次的内容继续记录本人在学习gin框架时的思路和笔记。 如果还没有看过上篇博客的可以点此跳转。 map参数 请求url&#xff1a; http://localhost:8080/us…

什么是IPAM?如何使用IPAM来管理IP地址和DHCP?

在计算机网络中&#xff0c;IPAM&#xff08;IP Address Management&#xff09;是一种用于管理IP地址和DHCP&#xff08;Dynamic Host Configuration Protocol&#xff09;的工具或系统。IPAM旨在简化和集中管理IP地址分配、子网划分和DHCP配置等任务。本文将详细介绍IPAM的概…

奇偶分频电路

目录 偶数分频 寄存器级联法 计数器法 奇数分频 不满足50%占空比 50%占空比 偶数分频 寄存器级联法 寄存器级联法能实现2^N的偶数分频&#xff0c;具体做法是采用寄存器结构的电路&#xff0c;每当时钟上升沿到来的时候对输出结果进行翻转&#xff0c;以此来实现偶数分…

chatgpt赋能python:Python中日期转换:从字符串到日期对象

Python中日期转换&#xff1a;从字符串到日期对象 作为一个经验丰富的Python工程师&#xff0c;日期转换在我的日常编码工作中经常遇到。Python提供了一些内置函数和模块&#xff0c;可以将字符串转换为日期对象或将日期对象格式化为特定的字符串。本篇文章将带您深入了解Pyth…

【JavaSE】Java基础语法(二十二):包装类

文章目录 1. 基本类型包装类2. Integer类3. 自动拆箱和自动装箱4. int和String类型的相互转换 1. 基本类型包装类 基本类型包装类的作用 将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据常用的操作之一&#xff1a;用于基本数据类型与字符串之间的…