我正在使用Spring Boot在线创建一个Java的REST API,我想安全地将用户密码存储在数据库中, 为此,我使用的BCrypt包含在Spring安全中,我使用MySQL和JPA- Hibernate的持久性。在春季开机时用春季安全密码密码的最佳做法是什么?
而且我如下实现它:
这是用户实体:
@Entity
@SelectBeforeUpdate
@DynamicUpdate
@Table (name = "USER")
public class User {
@Id
@GeneratedValue
@Column(name = "USER_ID")
private Long userId;
@Column(name = "ALIAS")
private String alias;
@Column(name = "NAME")
private String name;
@Column(name = "LAST_NAME")
private String lastName;
@Column(name = "TYPE")
private String type;
@Column(name = "PASSWORD")
private String password;
public String getPassword() {
return password;
}
/**
* When adding the password to the user class the setter asks if it is necessary or not to add the salt,
* if this is necessary the method uses the method BCrypt.hashpw (password, salt),
* if it is not necessary to add the salt the string That arrives is added intact
*/
public void setPassword(String password, boolean salt) {
if (salt) {
this.password = BCrypt.hashpw(password, BCrypt.gensalt());
} else {
this.password = password;
}
}
//Setters and Getters and etc.
}
这是用户级的库:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
这是服务用户等级:
@Service
public class UserService{
private UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User addEntity(User user) {
//Here we tell the password setter to generate the salt
user.setPassword(user.getPassword(), true);
return userRepository.save(user);
}
public User updateEntity(User user) {
User oldUser = userRepository.findOne(user.getUserId());
/*
*This step is necessary to maintain the same password since if we do not do this
*in the database a null is generated in the password field,
*this happens since the JSON that arrives from the client application does not
*contain the password field, This is because to carry out the modification of
*the password a different procedure has to be performed
*/
user.setPassword(oldUser.getPassword(), false);
return userRepository.save(user);
}
/**
* By means of this method I verify if the password provided by the client application
* is the same as the password that is stored in the database which is already saved with the salt,
* returning a true or false boolean depending on the case
*/
public boolean isPassword(Object password, Long id) {
User user = userRepository.findOne(id);
//To not create an entity that only has a field that says password, I perform this mapping operation
String stringPassword = (String)((Map)password).get("password");
//This method generates boolean
return BCrypt.checkpw(stringPassword, user.getPassword());
}
/**
*This method is used to update the password in the database
*/
public boolean updatePassword(Object passwords, Long id) {
User user = userRepository.findOne(id);
//Here it receive a JSON with two parameters old password and new password, which are transformed into strings
String oldPassword = (String)((Map)passwords).get("oldPassword");
String newPassword = (String)((Map)passwords).get("newPassword");
if (BCrypt.checkpw(oldPassword, user.getPassword())){
//If the old password is the same as the one currently stored in the database then the new password is updated
//in the database for this a new salt is generated
user.setPassword(newPassword, true);
//We use the update method, passing the selected user
updateEntity(user);
//We return a true boolean
return true;
}else {
//If the old password check fails then we return a false boolean
return false;
}
}
//CRUD basic methods omitted because it has no case for the question
}
这是一个公开的API端点控制器:
@RestController
@CrossOrigin
@RequestMapping("/api/users")
public class UserController implements{
UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@RequestMapping(value = "", method = RequestMethod.POST)
public User addEntity(@RequestBody User user) {
return userService.addEntity(user);
}
@RequestMapping(value = "", method = RequestMethod.PUT)
public User updateEntity(@RequestBody User user) {
return userService.updateEntity(user);
}
@RequestMapping(value = "/{id}/checkPassword", method = RequestMethod.POST)
public boolean isPassword(@PathVariable(value="id") Long id, @RequestBody Object password) {
return userService.isPassword(password, id);
}
@RequestMapping(value = "/{id}/updatePassword", method = RequestMethod.POST)
public boolean updatePassword(@PathVariable(value="id") Long id, @RequestBody Object password) {
return userService.updatePassword(password, id);
}
}
这是我的问题来了,我的方法是工作,但我觉得这是不是最好的方式,我觉得不舒服更改密码二传手我宁愿保留setter的标准形式,因为在用户服务中我认为有机会处理用户和密码更新的不同,所以尝试在实体中使用@DynamicUpdate
注释,但它不能正常工作,因为字段没有在更新中提供,而不是像原先一样保留它们。
我在寻找的是使用Spring Boot处理密码安全性的更好方法。
你的回答让有很大的意义,从这个想法来的MVC模型被跟随,并且我从产生类似thymeleaf的意见,这种情况是访问控制是由客户端处理,为的是变量'type'被使用,该API只需要适当地存储在数据库中的密码和回答的问题,这是相同的密码?并更新正确的,我的问题是有关的最佳方式中的数据来实现'PasswordEncoder'或一些其他的解决方案 – CorrOrtiz
在这种情况下,我会推荐给define'BCryptPasswordEncoder'豆和自动装配'PasswordEncoder'接口(更容易使未来很可能切换到其他编码器)以及它的方法'encode(..)'和'matches(...)'。一般来说,做同样的事情为你的代码,但你不必在你的代码上'BCrypt'严格依赖。我会将PasswordEncoder相关代码放在一个具有自描述名称的服务中,而不是放在setter中。此外,建议使用'字符串[]'或'的char []'与原始密码操作(防止它们存储在字符串常量池)。 – dimuha
如果你有时间把你解释什么一个小例子,这样我可以表明它的答案,更新了回答这个问题 – CorrOrtiz