使用shiro对数据库中的密码进行加密存储(java+springboot+shiro)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 使用shiro对数据库中的密码进行加密存储(java+springboot+shiro)

使用shiro对数据库中的密码进行加密存储(java+springboot+shiro)

简介:本文讲解如何对数据库中的密码进行加密存储,

如果大家觉得有用的话,可以关注我下面的微信公众号,极客李华,我会在里面更新更多行业资讯,企业面试内容,编程资源,如何写出可以让大厂面试官眼前一亮的简历等内容,让大家更好学习编程,我的抖音,B站也叫极客李华。大家喜欢也可以关注一下

构建数据库

Shiro 是一个 Java 安全框架,提供了身份认证、授权、加密等安全相关的功能。

在 Shiro 中对用户密码进行加密可以通过实现 org.apache.shiro.authc.credential.CredentialsMatcher 接口来完成。一般情况下,可以使用已有的加密算法比如 MD5、SHA 等来进行密码加密,也可以自定义加密方式。

以下是一个简单的用户表示例:

CREATE TABLE users (
  id bigint(20) NOT NULL AUTO_INCREMENT,
  username varchar(50) NOT NULL,
  password varchar(100) NOT NULL,
  salt binary(16) DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY idx_username (username)
);
INSERT INTO users (username, password, salt) VALUES ('user1', 'password1', '1234567890123456');
INSERT INTO users (username, password, salt) VALUES ('user2', 'password2', '1234567890123456');

这个表包含了以下四个字段:

  1. id:表示用户的 ID,是一个自增主键,用于唯一标识每个用户。
  2. username:表示用户名,是一个最大长度为 50 的字符串,不能为空,用于登录和显示用户信息。
  3. password:表示密码哈希值,是一个最大长度为 100 的字符串,不能为空,用于验证用户身份。
  4. salt:表示盐值,是一个长度为 16 的二进制数据,可以为 NULL,用于增强密码的安全性。

需要注意的是,在这个表中,我们为 username 列添加了一个名为 idx_username 的唯一索引。该索引用于确保用户名的唯一性,避免同一用户名被重复注册。同时,我们将 id 列设置为自增主键,以便自动生成用户的 ID 值,并将其作为唯一标识符。

在实现用户注册、登录等功能时,我们可以通过 SQL 语句对该表进行查询、插入、更新、删除等操作,以实现用户信息的管理和维护。

代码演示

原理演示

在后端代码中,给用户密码加密的具体实现方式会依赖于你选择的加密算法以及使用的工具库。以下是一种可能的实现方式:

  1. 首先,在用户注册时,将明文密码转换为一个字节数组。
  2. 选取一个合适的加密算法进行密码加密。例如,可以使用 Apache Shiro 框架提供的 SimpleHash 类来生成加密后的密码。示例代码如下:

algorithmName:指定使用的加密算法的名称。在这里我们选择了 MD5 算法。

hashIterations:指定加密次数。加密次数越多,密码越难破解,但是也会增加计算时间。在这里我们只加密一次。

salt:盐值,可以选择自定义或者使用默认值。盐值是一个随机数,用于增强密码的安全性。如果不指定盐值,则使用默认值。

plaintextPassword:明文密码。

hashedPassword:加密后的密码。使用 SimpleHash 对象的 toString() 方法可以将其转换为字符串形式。

String algorithmName = "MD5"; // 选择 MD5 算法
int hashIterations = 1; // 加密次数
Object salt = null; // 盐值,可以选择自定义或者使用默认值
Object hashedPassword = new SimpleHash(algorithmName, plaintextPassword, salt, hashIterations);
  1. 将加密后的密码存储到数据库中。在保存密码时,不要直接将明文密码存储到数据库中,而应该存储加密后的密码。
  2. 在用户登录时,比对用户输入的明文密码和数据库中存储的加密后的密码是否一致。如果一致,则认证通过;否则认证失败。

需要注意的是,加密算法的选择和加密次数的设置需要根据实际需求进行调整。另外,盐值的使用可以增加密码的破解难度,建议在加密时设置一个随机的盐值。

项目代码

创建项目

项目结构

pom.xml

shiro的依赖

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-crypto-hash</artifactId>
  <version>1.11.0</version>
</dependency>

完整的pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.10</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>shiroDemo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>shiroDemo</name>
  <description>shiroDemo</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.7.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-jpa</artifactId>
      <version>2.5.6</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>5.6.3.Final</version>
    </dependency>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>javax.persistence-api</artifactId>
      <version>2.2</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.4.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.8.0</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <excludes>
            <exclude>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

pojo

  • User
@Data // 使用 Lombok 简化实体类代码
@AllArgsConstructor
@NoArgsConstructor
public class Users {
    @TableId(type = IdType.AUTO) // 指定 ID 字段为自增主键
    private Long id;
    @TableField("username") // 指定该字段映射到数据库表中的 username 列
    private String username;
    @TableField("password") // 指定该字段映射到数据库表中的 password 列
    private String password;
    @TableField(value = "salt", exist = false) // 指定该字段不与数据库表中的任何列进行映射
    private byte[] salt;
    public Users(String username, String password, byte[] salt) {
        this.username = username;
        this.password = password;
        this.salt = salt;
    }
    public Users(String username, String password) {
        this.username = username;
        setPassword(password);
    }
    public void setPassword(String plaintextPassword) {
        String algorithmName = "MD5"; // 选择 MD5 算法
        int hashIterations = 1; // 加密次数
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);  // 随机生成盐值
        Object hashedPassword = new SimpleHash(algorithmName, plaintextPassword, new SimpleByteSource(salt), hashIterations);
        this.password = hashedPassword.toString();
        this.salt = salt; // 将盐值保存到对象中
    }
}
  • Result
@Data
public class Result<T> {
    private Integer code; // 状态码
    private String message; // 提示信息
    private T data; // 响应数据
    public Result() {}
    public Result(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }
}
  • LoginRequest
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginRequest {
    private String username;
    private String password;
}

UserController

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    // 新增用户
    @PostMapping("")
    public Result<Users> addUser(@RequestBody Users user) {
        boolean success = userService.register(user.getUsername(), user.getPassword());
        if (success) {
            return new Result<>(200, "新增用户成功", user);
        } else {
            return new Result<>(500, "新增用户失败");
        }
    }
    // 根据 ID 删除用户
    @DeleteMapping("/{id}")
    public Result<Void> deleteUserById(@PathVariable Long id) {
        boolean success = userService.removeById(id);
        if (success) {
            return new Result<>(200, "删除用户成功");
        } else {
            return new Result<>(500, "删除用户失败");
        }
    }
    // 更新用户
    @PutMapping("")
    public Result<Users> updateUser(@RequestBody Users user) {
        boolean success = userService.updateById(user);
        if (success) {
            return new Result<>(200, "更新用户信息成功", user);
        } else {
            return new Result<>(500, "更新用户信息失败");
        }
    }
    // 根据 ID 获取用户信息
    @GetMapping("/{id}")
    public Result<Users> getUserById(@PathVariable Long id) {
        Users user = userService.getById(id);
        if (user != null) {
            return new Result<>(200, "获取用户信息成功", user);
        } else {
            return new Result<>(404, "用户不存在");
        }
    }
    // 获取所有用户信息
    @GetMapping("")
    public Result<List<Users>> getAllUsers() {
        List<Users> userList = userService.list();
        return new Result<>(200, "获取用户信息成功", userList);
    }
    // 用户登录
    @PostMapping("/login")
    public Result<Void> login(@RequestBody LoginRequest request) {
        boolean success = userService.login(request.getUsername(), request.getPassword());
        if (success) {
            return new Result<>(200, "登录成功");
        } else {
            return new Result<>(401, "用户名或密码错误");
        }
    }
}

mapper

  • UserMapper
@Mapper
public interface UserMapper extends BaseMapper<Users> {
    Users selectByUsername(String username);
}

service

  • UserService
public interface UserService extends IService<Users> {
    boolean register(String username, String password);
    boolean login(String username, String password);
}
  • UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, Users> implements UserService {
    @Override
    public boolean register(String username, String password) {
        // 生成盐值
        byte[] salt = new byte[16];
        new SecureRandom().nextBytes(salt);
        // 对密码进行加密处理
        String hashedPassword = hash(password, salt);
        // 将用户名、盐值和哈希后的密码保存到数据库中
        Users user = new Users(username, hashedPassword, salt);
        boolean success = save(user);
        return success;
    }
    @Override
    public boolean login(String username, String password) {
        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        Users user = getOne(wrapper);
        if (user == null) {
            // 如果用户不存在,则认为登录失败
            return false;
        }
        String hashedPassword = user.getPassword();
        byte[] salt = user.getSalt();
        // 对用户输入的密码进行加密处理,并将结果与数据库中的哈希值比较
        String hashedInputPassword = hash(password, salt);
        return hashedPassword.equals(hashedInputPassword);
    }
    @Override
    public boolean saveBatch(Collection<Users> entityList, int batchSize) {
        return super.saveBatch(entityList, batchSize);
    }
    private String hash(String password, byte[] salt) {
        int iterations = 10000;
        int keyLength = 256;
        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLength);
        SecretKeyFactory skf;
        try {
            skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            byte[] hash = skf.generateSecret(spec).getEncoded();
            return Base64.getEncoder().encodeToString(hash);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }
}

这是一个基于MyBatis-Plus框架实现的UserService接口的具体实现类UserServiceImpl。这个类提供了用户注册、登录等方法,并使用了安全的密码加密和验证机制。

register()方法:用户注册方法,实现逻辑如下:

a. 生成盐值:首先,该方法会生成一个16字节的随机数作为盐值。

b. 对密码进行加密处理:接着,该方法会调用hash()方法对用户输入的密码进行加密处理,得到哈希后的密码。

c. 将用户名、盐值和哈希后的密码保存到数据库中:最后,该方法会将用户名、盐值和哈希后的密码保存到数据库中。

login()方法:用户登录方法,实现逻辑如下:

a. 根据用户名从数据库中查询用户信息:该方法会根据用户名从数据库中查询对应的用户信息。

b. 如果用户不存在,则认为登录失败:如果查询结果为空,则说明用户不存在,返回false。

c. 对用户输入的密码进行加密处理,并将结果与数据库中的哈希值比较:否则,该方法会对用户输入的密码进行加密处理,得到哈希后的密码,再将其与数据库中的哈希值进行比较,如果相等则说明密码正确,返回true,否则说明密码错误,返回false。

saveBatch()方法:批量保存用户信息方法,直接调用父类的saveBatch()方法实现。

hash()方法:对密码进行加密处理的方法,实现逻辑如下:

a. 设置加密参数:该方法会设置加密算法、加密次数和密钥长度等参数。

b. 生成加密密钥:根据设置的参数以及盐值和密码,生成一个加密密钥。

c. 对加密密钥进行哈希处理:将生成的密钥进行哈希处理,得到哈希后的结果。

d. 将哈希结果进行Base64编码:最后,将哈希结果进行Base64编码,得到一个字符串表示的哈希值。

通过使用MyBatis-Plus框架,可以避免手动编写大量的SQL语句,使代码更加简洁易读。同时,通过使用安全的密码加密和验证机制,可以保证用户信息的安全性。

项目测试(Postman)

  1. 新增用户:选择POST请求,URL为http://localhost:8081/user,Body中选择raw格式,类型选择JSON,然后输入以下请求体数据:
{
    "username": "test",
    "password": "123456"
}

测试结果

  1. 根据 ID 删除用户:选择DELETE请求,URL为http://localhost:8081/user/1(假设要删除ID为1的用户),点击Send按钮发送请求,如果成功删除该用户,则应该收到以下响应:
{
    "code": 200,
    "message": "删除用户成功"
}

测试结果

  1. 更新用户:选择PUT请求,URL为http://localhost:8081/user,Body中选择raw格式,类型选择JSON,然后输入以下请求体数据:
{
    "id": 2,
    "username": "test2",
    "password": "654321"
}

测试结果

  1. 根据 ID 获取用户信息:选择GET请求,URL为http://localhost:8081/user/2(假设要获取ID为2的用户)
    测试结果

  2. 获取所有用户信息:选择GET请求,URL为http://localhost:8081/user
    测试结果

  3. 用户登录:选择POST请求,URL为http://localhost:8081/user/login,Body中选择raw格式,类型选择JSON,然后输入以下请求体数据:
{
    "username": "test",
    "password": "123456"
}

如果大家觉得有用的话,可以关注我下面的微信公众号,极客李华,我会在里面更新更多行业资讯,企业面试内容,编程资源,如何写出可以让大厂面试官眼前一亮的简历等内容,让大家更好学习编程,我的抖音,B站也叫极客李华。大家喜欢也可以关注一下

相关文章
|
1月前
|
存储 Java 数据库
密码专辑:对密码加盐加密,对密码进行md5加密,封装成密码工具类
这篇文章介绍了如何在Java中通过加盐和加密算法(如MD5和SHA)安全地存储密码,并提供了一个密码工具类PasswordUtils和密码编码类PasswordEncoder的实现示例。
30 10
密码专辑:对密码加盐加密,对密码进行md5加密,封装成密码工具类
|
1月前
|
存储 关系型数据库 MySQL
PACS系统 中 dicom 文件在mysql 8.0 数据库中的 存储和读取(pydicom 库使用)
PACS系统 中 dicom 文件在mysql 8.0 数据库中的 存储和读取(pydicom 库使用)
27 2
|
1月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
26 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
1月前
|
安全 算法 Java
数据库信息/密码加盐加密 —— Java代码手写+集成两种方式,手把手教学!保证能用!
本文提供了在数据库中对密码等敏感信息进行加盐加密的详细教程,包括手写MD5加密算法和使用Spring Security的BCryptPasswordEncoder进行加密,并强调了使用BCryptPasswordEncoder时需要注意的Spring Security配置问题。
126 0
数据库信息/密码加盐加密 —— Java代码手写+集成两种方式,手把手教学!保证能用!
|
3天前
|
SQL 安全 算法
揭秘网络安全:漏洞、加密与安全意识的三重奏
【10月更文挑战第39天】在数字时代的交响乐中,网络安全扮演着不可或缺的角色。本文旨在通过浅显易懂的语言,揭示网络安全的三大核心要素:网络漏洞、加密技术以及安全意识。我们将探索这些元素如何相互交织,共同维护我们的数字安全。从初学者到资深专家,每个人都能从中获得宝贵的知识和启示。
|
3天前
|
存储 SQL 安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
【10月更文挑战第39天】在数字化时代,网络安全和信息安全成为了我们生活中不可或缺的一部分。本文将介绍网络安全漏洞、加密技术和安全意识等方面的内容,帮助读者更好地了解网络安全的重要性,并提供一些实用的技巧和方法来保护自己的信息安全。
14 2
|
5天前
|
安全 算法 网络安全
网络安全的盾牌与利剑:漏洞防御与加密技术的双刃舞
【10月更文挑战第37天】在数字世界的海洋里,网络安全是航船的锚,保护我们的数据不受风暴侵袭。本文将深入浅出地探讨网络安全的两大支柱——漏洞防御和加密技术。我们将从网络安全的基本概念出发,逐步深入到漏洞的类型、检测方法以及防御策略。同时,我们也将探索加密技术的原理和应用,如何通过这一技术保护信息的完整性和私密性。最后,我们将讨论提升个人及组织安全意识的重要性,以及如何构建一个安全的网络环境。这不仅是技术人员的战斗,每个人都是自己信息安全的第一道防线。让我们一起扬帆起航,探索网络安全的世界,学习如何成为自己数据的守护者。
|
5天前
|
SQL 安全 网络安全
网络安全的护城河:漏洞防御与加密技术的深度解析
【10月更文挑战第37天】在数字时代的浪潮中,网络安全成为守护个人隐私与企业资产的坚固堡垒。本文将深入探讨网络安全的两大核心要素——安全漏洞和加密技术,以及如何通过提升安全意识来强化这道防线。文章旨在揭示网络攻防战的复杂性,并引导读者构建更为稳固的安全体系。
16 1
|
4天前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
【10月更文挑战第38天】本文将探讨网络安全与信息安全的重要性,包括网络安全漏洞、加密技术和安全意识等方面。我们将通过代码示例和实际操作来展示如何保护网络和信息安全。无论你是个人用户还是企业,都需要了解这些知识以保护自己的网络安全和信息安全。
|
3天前
|
存储 安全 网络安全
网络安全与信息安全:漏洞、加密技术与安全意识的交织
【10月更文挑战第39天】在数字化时代,网络安全与信息安全成为保护个人隐私和组织资产的重要屏障。本文将探讨网络安全中的常见漏洞、加密技术的应用以及提升安全意识的重要性。通过具体案例分析,我们将深入了解网络攻击的手段和防御策略,同时提供实用建议,以增强读者对网络安全的认识和防护能力。