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

相关文章
|
5天前
|
Java Spring
Java Spring Boot监听事件和处理事件
通过上述步骤,我们可以在Java Spring Boot应用中实现事件的发布和监听。事件驱动模型可以帮助我们实现组件间的松耦合,提升系统的可维护性和可扩展性。无论是处理业务逻辑还是系统事件,Spring Boot的事件机制都提供了强大的支持和灵活性。希望本文能为您的开发工作提供实用的指导和帮助。
43 15
|
1天前
|
Java 开发者 Spring
java springboot监听事件和处理事件
通过上述步骤,开发者可以在Spring Boot项目中轻松实现事件的发布和监听。事件机制不仅解耦了业务逻辑,还提高了系统的可维护性和扩展性。掌握这一技术,可以显著提升开发效率和代码质量。
18 6
|
7天前
|
Java 开发者 Spring
Java Springboot监听事件和处理事件
通过这些内容的详细介绍和实例解析,希望能帮助您深入理解Spring Boot中的事件机制,并在实际开发中灵活应用,提高系统的可维护性和扩展性。
33 7
|
1月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
67 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
27天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
111 13
|
21天前
|
存储 druid 分布式数据库
列式存储数据库与超市的关系?
列式存储数据库是一种高效的数据管理方式,类似于超市将相似商品集中摆放。它将相同类型的数据(如年龄、价格)归类存储,便于快速查询和压缩,广泛应用于市场分析、财务报告和健康数据分析等领域。知名产品包括HBase、ClickHouse、Druid和Apache Cassandra等,适合处理大规模数据和实时分析任务。
33 4
|
3月前
|
存储 算法 安全
SpringBoot 接口加密解密实现
【10月更文挑战第18天】
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
2月前
|
存储 数据库
快速搭建南大通用GBase 8s数据库SSC共享存储集群
本文介绍如何GBase8s 数据库 在单机环境中快速部署SSC共享存储集群,涵盖准备工作、安装数据库、创建环境变量文件、准备数据存储目录、修改sqlhost、设置onconfig、搭建sds集群及集群检查等步骤,助你轻松完成集群功能验证。
|
1月前
|
存储 Oracle 关系型数据库
服务器数据恢复—华为S5300存储Oracle数据库恢复案例
服务器存储数据恢复环境: 华为S5300存储中有12块FC硬盘,其中11块硬盘作为数据盘组建了一组RAID5阵列,剩下的1块硬盘作为热备盘使用。基于RAID的LUN分配给linux操作系统使用,存放的数据主要是Oracle数据库。 服务器存储故障: RAID5阵列中1块硬盘出现故障离线,热备盘自动激活开始同步数据,在同步数据的过程中又一块硬盘离线,RAID5阵列瘫痪,上层LUN无法使用。