- 作者简介:一名后端开发人员,每天分享后端开发以及人工智能相关技术,行业前沿信息,面试宝典。
- 座右铭:未来是不可确定的,慢慢来是最快的。
- 个人主页:极客李华-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');
这个表包含了以下四个字段:
-
id:表示用户的 ID,是一个自增主键,用于唯一标识每个用户。
-
username:表示用户名,是一个最大长度为 50 的字符串,不能为空,用于登录和显示用户信息。
-
password:表示密码哈希值,是一个最大长度为 100 的字符串,不能为空,用于验证用户身份。
-
salt:表示盐值,是一个长度为 16 的二进制数据,可以为 NULL,用于增强密码的安全性。
需要注意的是,在这个表中,我们为 username 列添加了一个名为 idx_username 的唯一索引。该索引用于确保用户名的唯一性,避免同一用户名被重复注册。同时,我们将 id 列设置为自增主键,以便自动生成用户的 ID 值,并将其作为唯一标识符。
在实现用户注册、登录等功能时,我们可以通过 SQL 语句对该表进行查询、插入、更新、删除等操作,以实现用户信息的管理和维护。
代码演示
原理演示
在后端代码中,给用户密码加密的具体实现方式会依赖于你选择的加密算法以及使用的工具库。以下是一种可能的实现方式:
-
首先,在用户注册时,将明文密码转换为一个字节数组。
-
选取一个合适的加密算法进行密码加密。例如,可以使用 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);
-
将加密后的密码存储到数据库中。在保存密码时,不要直接将明文密码存储到数据库中,而应该存储加密后的密码。
-
在用户登录时,比对用户输入的明文密码和数据库中存储的加密后的密码是否一致。如果一致,则认证通过;否则认证失败。
需要注意的是,加密算法的选择和加密次数的设置需要根据实际需求进行调整。另外,盐值的使用可以增加密码的破解难度,建议在加密时设置一个随机的盐值。
项目代码
创建项目
项目结构
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)
- 新增用户:选择POST请求,URL为http://localhost:8081/user,Body中选择raw格式,类型选择JSON,然后输入以下请求体数据:
{
"username": "test",
"password": "123456"
}
测试结果
- 根据 ID 删除用户:选择DELETE请求,URL为http://localhost:8081/user/1(假设要删除ID为1的用户),点击Send按钮发送请求,如果成功删除该用户,则应该收到以下响应:
{
"code": 200,
"message": "删除用户成功"
}
测试结果
- 更新用户:选择PUT请求,URL为http://localhost:8081/user,Body中选择raw格式,类型选择JSON,然后输入以下请求体数据:
{
"id": 2,
"username": "test2",
"password": "654321"
}
测试结果
-
根据 ID 获取用户信息:选择GET请求,URL为http://localhost:8081/user/2(假设要获取ID为2的用户)
测试结果
-
获取所有用户信息:选择GET请求,URL为http://localhost:8081/user
测试结果
-
用户登录:选择POST请求,URL为http://localhost:8081/user/login,Body中选择raw格式,类型选择JSON,然后输入以下请求体数据:
{
"username": "test",
"password": "123456"
}
如果大家觉得有用的话,可以关注我下面的微信公众号,极客李华,我会在里面更新更多行业资讯,企业面试内容,编程资源,如何写出可以让大厂面试官眼前一亮的简历等内容,让大家更好学习编程,我的抖音,B站也叫极客李华。大家喜欢也可以关注一下