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

  • 作者简介:一名后端开发人员,每天分享后端开发以及人工智能相关技术,行业前沿信息,面试宝典。
  • 座右铭:未来是不可确定的,慢慢来是最快的。
  • 个人主页极客李华-CSDN博客
  • 合作方式:私聊+
  • 这个专栏内容:BAT等大厂常见后端java开发面试题详细讲解,更新数目100道常见大厂java后端开发面试题。
  • 我的CSDN社区:https://bbs.csdn.net/forums/99eb3042821a4432868bb5bfc4d513a8
  • 微信公众号,抖音,b站等平台统一叫做:极客李华,加入微信公众号领取各种编程资料,加入抖音,b站学习面试技巧,职业规划

使用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
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Shiro是一个强大的Java安全框架,可以帮助开发者轻松地实现身份认证、授权管理和会话管理等安全功能。而Spring Boot是一个用于简化Spring应用开发的框架,它提供了一些开箱即用的特性,使得开发者可以快速搭建一个基于Spring的应用。 将ShiroSpring Boot结合使用,可以让我们更方便地使用Shiro的各种安全特性。Shiro提供了一些与Spring集成的组件,例如我们可以通过使用Shiro的注解来实现权限控制,同时也可以利用Spring的依赖注入特性来处理Shiro的一些配置。 在Spring Boot使用Shiro主要有以下步骤:首先,我们需要在Spring Boot的配置文件配置Shiro的相关信息,例如Realm(用于认证和授权的数据源)、filter(用于拦截和处理请求)等。接着,我们需要创建一个自定义的Realm来实现具体的认证和授权逻辑。然后,我们可以使用Shiro提供的注解在Controller层进行权限控制,例如@RequiresAuthentication用于要求用户进行身份认证,@RequiresRoles用于要求用户具有某个角色等等。最后,在前端页面或者API,我们可以通过Shiro提供的标签或者API来展示或者处理与用户权限相关的信息。 总结起来,ShiroSpring Boot的结合为我们提供了一种便捷的方式来实现应用的安全功能。我们可以通过Shiro来管理用户的身份认证和权限控制,通过Spring Boot来简化应用的开发过程。这样,我们可以更加专注于业务逻辑的实现,同时也提高了应用的安全性和可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客李华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值