在 spring 引导中使用 spring 安全性加盐的最佳做法是什么?
What is the best practice to salt a password with spring security in spring boot?
我正在 java 中为带有 Spring 引导的在线商店创建 REST API,我想将用户密码安全地存储在数据库中,
为此,我使用 spring 安全性附带的 BCrypt,我使用 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.
}
这是用户的仓库 class:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
这是用户class的服务:
@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 我更愿意保留 setter,因为在用户服务中,我认为有机会以不同方式处理用户和密码更新,所以尝试在实体中使用 @DynamicUpdate
注释,但由于未提供字段,它根本无法正常工作在更新中而不是像空值一样保存它们。
我正在寻找的是使用 Spring Boot.
处理密码安全性的更好方法
首先,您希望在线商店中的每个用户都有一个唯一的字段(f.e。别名或电子邮件),将其用作标识符,而不会将 id 值暴露到最后用户。
另外,据我了解,您想使用 Spring 安全性来保护您的 Web 应用程序。 Spring 安全性使用 ROLE 来指示用户权限 (f.e。ROLE_USER,ROLE_ADMIN)。所以最好有一个字段(一个列表,一个单独的 UserRole 实体)来跟踪用户角色。
假设您向用户字段别名 (private String alias;
) 添加了唯一约束并添加了简单的 private String role;
字段。现在您要设置 Spring 安全性以保持 '/shop' 和所有子资源 (f.e. '/shop/search') 对所有人开放,不安全,资源 '/discounts' 可用仅供注册用户使用,资源“/admin”仅供管理员使用。
要实现它,您需要定义几个class。让我们从 UserDetailsService 的实现开始(Spring 安全需要获取用户信息):
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository repository;
@Autowired
public UserDetailsServiceImpl(UserRepository repository) {
this.repository = repository;
}
@Override
public UserDetails loadUserByUsername(String alias) {
User user = repository.findByAlias(alias);
if (user == null) {
//Do something about it :) AFAIK this method must not return null in any case, so an un-/ checked exception might be a good option
throw new RuntimeException(String.format("User, identified by '%s', not found", alias));
}
return new org.springframework.security.core.userdetails.User(
user.getAlias(), user.getPassword(),
AuthorityUtils.createAuthorityList(user.getRole()));
}
}
然后,用于配置 Spring 安全性的主要 class 是一个扩展 WebSecurityConfigurerAdapter
(该示例取自具有基于表单的身份验证的应用程序,但您可以调整它满足您的需求):
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/shop/**").permitAll()
.antMatchers("/discounts/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.usernameParameter("alias")
.passwordParameter("password")
.loginPage("/login").failureUrl("/login?error").defaultSuccessUrl("/")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID", "remember-me")
.logoutSuccessUrl("/")
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
然后,在您的 UserService
中,您可以使用如下内容:
...
@Autowired
private PasswordEncoder passwordEncoder;
public User addEntity(User user) {
...
user.setPassword(passwordEncoder.encode(user.getPassword()))
...
}
所有其他检查(f.e。用于登录尝试或访问资源)Spring 安全将根据配置自动执行。还有很多事情需要设置和考虑,但我希望我能够解释总体思路。
编辑
在任何 spring 组件或配置中按如下方式定义 bean
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
然后在您的 UserService 中自动装配它 class
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public User addEntity(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword());
return userRepository.save(user);
}
...
public boolean isPassword(Object password, Long id) {
User user = userRepository.findOne(id);
String stringPassword = (String)((Map)password).get("password");
return passwordEncoder.matches(stringPassword, user.getPassword());
}
public boolean updatePassword(Object passwords, Long id) {
User user = userRepository.findOne(id);
String oldPassword = (String)((Map)passwords).get("oldPassword");
String newPassword = (String)((Map)passwords).get("newPassword");
if (!passwordEncoder.matches(oldPassword, newPassword)) {
return false;
}
user.setPassword(passwordEncoder.encode(newPassword));
updateEntity(user);
return true;
}
...
}
之后您可以在用户 class 中保持简单 setter。
我正在 java 中为带有 Spring 引导的在线商店创建 REST API,我想将用户密码安全地存储在数据库中, 为此,我使用 spring 安全性附带的 BCrypt,我使用 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.
}
这是用户的仓库 class:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
这是用户class的服务:
@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 我更愿意保留 setter,因为在用户服务中,我认为有机会以不同方式处理用户和密码更新,所以尝试在实体中使用 @DynamicUpdate
注释,但由于未提供字段,它根本无法正常工作在更新中而不是像空值一样保存它们。
我正在寻找的是使用 Spring Boot.
处理密码安全性的更好方法首先,您希望在线商店中的每个用户都有一个唯一的字段(f.e。别名或电子邮件),将其用作标识符,而不会将 id 值暴露到最后用户。 另外,据我了解,您想使用 Spring 安全性来保护您的 Web 应用程序。 Spring 安全性使用 ROLE 来指示用户权限 (f.e。ROLE_USER,ROLE_ADMIN)。所以最好有一个字段(一个列表,一个单独的 UserRole 实体)来跟踪用户角色。
假设您向用户字段别名 (private String alias;
) 添加了唯一约束并添加了简单的 private String role;
字段。现在您要设置 Spring 安全性以保持 '/shop' 和所有子资源 (f.e. '/shop/search') 对所有人开放,不安全,资源 '/discounts' 可用仅供注册用户使用,资源“/admin”仅供管理员使用。
要实现它,您需要定义几个class。让我们从 UserDetailsService 的实现开始(Spring 安全需要获取用户信息):
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository repository;
@Autowired
public UserDetailsServiceImpl(UserRepository repository) {
this.repository = repository;
}
@Override
public UserDetails loadUserByUsername(String alias) {
User user = repository.findByAlias(alias);
if (user == null) {
//Do something about it :) AFAIK this method must not return null in any case, so an un-/ checked exception might be a good option
throw new RuntimeException(String.format("User, identified by '%s', not found", alias));
}
return new org.springframework.security.core.userdetails.User(
user.getAlias(), user.getPassword(),
AuthorityUtils.createAuthorityList(user.getRole()));
}
}
然后,用于配置 Spring 安全性的主要 class 是一个扩展 WebSecurityConfigurerAdapter
(该示例取自具有基于表单的身份验证的应用程序,但您可以调整它满足您的需求):
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/shop/**").permitAll()
.antMatchers("/discounts/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.usernameParameter("alias")
.passwordParameter("password")
.loginPage("/login").failureUrl("/login?error").defaultSuccessUrl("/")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID", "remember-me")
.logoutSuccessUrl("/")
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
然后,在您的 UserService
中,您可以使用如下内容:
...
@Autowired
private PasswordEncoder passwordEncoder;
public User addEntity(User user) {
...
user.setPassword(passwordEncoder.encode(user.getPassword()))
...
}
所有其他检查(f.e。用于登录尝试或访问资源)Spring 安全将根据配置自动执行。还有很多事情需要设置和考虑,但我希望我能够解释总体思路。
编辑
在任何 spring 组件或配置中按如下方式定义 bean
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
然后在您的 UserService 中自动装配它 class
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public User addEntity(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword());
return userRepository.save(user);
}
...
public boolean isPassword(Object password, Long id) {
User user = userRepository.findOne(id);
String stringPassword = (String)((Map)password).get("password");
return passwordEncoder.matches(stringPassword, user.getPassword());
}
public boolean updatePassword(Object passwords, Long id) {
User user = userRepository.findOne(id);
String oldPassword = (String)((Map)passwords).get("oldPassword");
String newPassword = (String)((Map)passwords).get("newPassword");
if (!passwordEncoder.matches(oldPassword, newPassword)) {
return false;
}
user.setPassword(passwordEncoder.encode(newPassword));
updateEntity(user);
return true;
}
...
}
之后您可以在用户 class 中保持简单 setter。