java - Java 更改序列化的属性类型
问题描述
我想创建一个用于向数据库添加新用户的休息服务。所以基本上,服务会接受这样的请求:
{"login":"testUser","mail":"testUser@gmail.com","password":"123abc"}
现在的问题是:我听说我们不应该将密码存储为字符串,而应存储为 Java 中的字符数组,所以这就是我所做的。
这是我的用户类
package com.mailReminder.restservice.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import javax.persistence.*;
import java.util.Arrays;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String login;
private String mail;
private char[] password;
private byte[] salt;
public User(String login, String mail, char[] password) {
this.login = login;
this.mail = mail;
this.password = password;
}
public User() {
this.id = 0L;
this.login = "";
this.mail = "";
this.password = null;
this.salt = null;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public char[] getPassword() {
return password;
}
public void setPassword(char[] password) {
this.password = password;
}
public byte[] getSalt() {
return salt;
}
public void setSalt(byte[] salt) {
this.salt = salt;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", login='" + login + '\'' +
", mail='" + mail + '\'' +
", password='" + password + '\'' +
'}';
}
}
为了保护密码,我使用了这个加密器:
package com.mailReminder.restservice.security;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
public class PasswordSecurer{
public static byte[] getSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
return salt;
}
public static byte[] hashPassword(char[] password, byte[] salt) {
int iterationCount = 65536;
int keyLength = 640;
KeySpec spec = new PBEKeySpec(password, salt, iterationCount, keyLength);
SecretKeyFactory factory = null;
try {
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return factory.generateSecret(spec).getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
}
所以我需要将 char 数组转换为 byte 数组,这样就可以了。问题是,在对密码进行哈希处理后,char 数组中填充了类似这样的内容
[-108, -71, 126, -39, -6, 65, 50, 42, -51, 55, -88, -121, -103, 55, 109, 22, 12, -21, 33, 72, 122、-127、-31、90、49、75、90、-79、83、-99、-50、-100、-66、29、45、-60、8、41、1、115、-65 , -124, -5, -47, 71, -105, 28, -68, -128, 12, -111, -93, -27, -102, 51, -119, -99, 49, -23, -27、96、-42、-128、124、61、-87、17、-15、-46、-85、-16、-62、106、-1、-79、79、53、108、- 56, -128]
这就是现在被插入到数据库中的 varchar 字段,它被杰克逊像这样解析。
这是控制器
package com.mailReminder.restservice.controller;
import com.mailReminder.restservice.model.User;
import com.mailReminder.restservice.repository.UserRepository;
import com.mailReminder.restservice.security.PasswordSecurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
@RestController
public class UserController {
private final UserRepository userRepository;
@Autowired
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PostMapping("/register")
public ResponseEntity<Object> register(@RequestBody User newUser) {
byte[] salt = PasswordSecurer.getSalt();
byte[] hashedPassword = PasswordSecurer.hashPassword(Arrays.toString(newUser.getPassword()).toCharArray(), salt);
System.out.println(Arrays.toString(hashedPassword));
if (hashedPassword == null)
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("");
if (userRepository.findByLogin(newUser.getLogin()) != null)
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("{\"errorCode\":400,\"errorMessage\":\"User already exists\"}");
System.out.println(Arrays.toString(hashedPassword));
newUser.setPassword(Arrays.toString(hashedPassword).toCharArray());
newUser.setSalt(salt);
userRepository.save(newUser);
return ResponseEntity.ok(newUser);
}
@GetMapping("/register")
public @ResponseBody
Iterable<User> getAllUsers() {
return userRepository.findAll();
}
}
在我决定接受 json 作为请求之前,我使用 byte[] 作为密码,这就是它在数据库中的样子,也是我现在想要的样子。
bAz}+ø¶Eí|¿ºmƇJ¾½7HÏuBƒpl¾¤A4lypyí:Ç-eqݸӨEó4¹ÝÍ6 žÊ©§_~9W¸Ô%œL—`Œ©cX§
但我无法将其更改为 User 类中的 byte[],因为 Jackson 因错误而崩溃:JSON 解析错误:无法byte[]
从 String反序列化类型的值
所以我的问题是:我能做些什么,让我的密码看起来再次散列而不将 char[] 更改为字符串?
解决方案
获取字节数组并将其转换为 Base64:
byte[] bytes = {-108, -71, 126, -39, -6, 65, 50, 42, -51, 55, -88, -121, -103, 55, 109, 22, 12, -21, 33, 72, 122, -127, -31, 90, 49, 75, 90, -79, 83, -99, -50, -100, -66, 29, 45, -60, 8, 41, 1, 115, -65, -124, -5, -47, 71, -105, 28, -68, -128, 12, -111, -93, -27, -102, 51, -119, -99, 49, -23, -27, 96, -42, -128, 124, 61, -87, 17, -15, -46, -85, -16, -62, 106, -1, -79, 79, 53, 108, -56, -128};
String output = Base64.getEncoder().encodeToString(bytes);
System.out.println( "base64 is " + output );
然后你会得到:
base64 is lLl+2fpBMirNN6iHmTdtFgzrIUh6geFaMUtasVOdzpy+HS3ECCkBc7+E+9FHlxy8gAyRo+WaM4mdMenlYNaAfD2pEfHSq/DCav+xTzVsyIA=
将此作为字符串存储在您的数据库中,并且仅与它进行比较。密码应该是一种方式——您现在也需要一种方法来重置密码,以防忘记密码。
请注意,您还需要以相同的方式保存盐,以便稍后再次验证哈希。
推荐阅读
- discord - 如何启用多个命令
- swift - 线程 1:尝试在物理设备上运行我的应用程序时出现 EXC_BAD_ACCESS (code=1, address=0x9)
- jenkins - Jenkins 管道不会复制前一阶段生成的文件
- javascript - TypeScript - 元素隐式具有“任何”类型,因为“字符串”类型的表达式不能用于索引类型
- python - 将 tkinter 设置为始终是当前活动窗口
- webgl - WebGL 渲染抗锯齿
- node.js - 这个通用版本的 mongodb.MongoClient.connect 脚本中的“回调”参数是什么?
- sql - Jupyter Notebook 卡在运行时
- c++ - 如何使用 Flex/Bison 从文件中获取数据并将其存储到其他类的结构数据中?
- c++ - C++类型推导重载函数