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

简介: 使用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站也叫极客李华。大家喜欢也可以关注一下

相关文章
|
11月前
|
存储 Oracle 关系型数据库
服务器数据恢复—光纤存储上oracle数据库数据恢复案例
一台光纤服务器存储上有16块FC硬盘,上层部署了Oracle数据库。服务器存储前面板2个硬盘指示灯显示异常,存储映射到linux操作系统上的卷挂载不上,业务中断。 通过storage manager查看存储状态,发现逻辑卷状态失败。再查看物理磁盘状态,发现其中一块盘报告“警告”,硬盘指示灯显示异常的2块盘报告“失败”。 将当前存储的完整日志状态备份下来,解析备份出来的存储日志并获得了关于逻辑卷结构的部分信息。
|
SQL 数据库 数据安全/隐私保护
数据库数据恢复——sql server数据库被加密的数据恢复案例
SQL server数据库数据故障: SQL server数据库被加密,无法使用。 数据库MDF、LDF、log日志文件名字被篡改。 数据库备份被加密,文件名字被篡改。
|
9月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
1666 5
|
10月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
1542 10
|
9月前
|
存储 弹性计算 安全
现有数据库系统中应用加密技术的不同之处
本文介绍了数据库加密技术的种类及其在不同应用场景下的安全防护能力,包括云盘加密、透明数据加密(TDE)和选择列加密。分析了数据库面临的安全威胁,如管理员攻击、网络监听、绕过数据库访问等,并通过能力矩阵对比了各类加密技术的安全防护范围、加密粒度、业务影响及性能损耗。帮助用户根据安全需求、业务改造成本和性能要求,选择合适的加密方案,保障数据存储与传输安全。
|
安全 Java 数据库
Jasypt加密数据库配置信息
本文介绍了使用 Jasypt 对配置文件中的公网数据库认证信息进行加密的方法,以提升系统安全性。主要内容包括:1. 背景介绍;2. 前期准备,如依赖导入及版本选择;3. 生成密钥并实现加解密测试;4. 在配置文件中应用加密后的密码,并通过测试接口验证解密结果。确保密码安全的同时,保障系统的正常运行。
851 3
Jasypt加密数据库配置信息
|
11月前
|
人工智能 安全 Java
Spring Boot yml 配置敏感信息加密
本文介绍了如何在 Spring Boot 项目中使用 Jasypt 实现配置文件加密,包含添加依赖、配置密钥、生成加密值、在配置中使用加密值及验证步骤,并提供了注意事项,确保敏感信息的安全管理。
1652 1
|
存储 Java 数据安全/隐私保护
Java技术栈揭秘:Base64加密和解密文件的实战案例
以上就是我们今天关于Java实现Base64编码和解码的实战案例介绍。希望能对你有所帮助。还有更多知识等待你去探索和学习,让我们一同努力,继续前行!
701 5
|
存储 关系型数据库 数据库
高性能云盘:一文解析RDS数据库存储架构升级
性能、成本、弹性,是客户实际使用数据库过程中关注的三个重要方面。RDS业界率先推出的高性能云盘(原通用云盘),是PaaS层和IaaS层的深度融合的技术最佳实践,通过使用不同的存储介质,为客户提供同时满足低成本、低延迟、高持久性的体验。
|
SQL 存储 分布式数据库
分布式存储数据恢复—hbase和hive数据库数据恢复案例
分布式存储数据恢复环境: 16台某品牌R730xd服务器节点,每台服务器节点上有数台虚拟机。 虚拟机上部署Hbase和Hive数据库。 分布式存储故障: 数据库底层文件被误删除,数据库不能使用。要求恢复hbase和hive数据库。
545 12

热门文章

最新文章