在Spring Boot中使用Spring Security对密码加盐的最佳实践是什么? [英] What is the best practice to salt a password with spring security in spring boot?

查看:175
本文介绍了在Spring Boot中使用Spring Security对密码加盐的最佳实践是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Spring Boot在Java中为在线商店创建REST API,我想将用户密码安全地存储在数据库中, 为此,我使用的是Spring Security附带的BCrypt,我使用MySQL和JPA-Hibernate来实现持久性.

I'm creating a REST API in java for an online store with Spring Boot, I want to securely store user passwords in the database, for this I am using BCrypt that comes included with spring security, I use MySQL and JPA-Hibernate for persistence.

我正在实现它,如下所示:

And I am implementing it as follows:

这是用户实体:

@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端点的控制器:

This is the controller that exposes the API endpoints:

@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);
    }
}

这是我的问题所在,我的方法有效,但是我觉得这不是最好的方法,我不喜欢更改密码设置程序,我希望保留设置程序的标准格式,例如在用户服务中我认为有机会以不同的方式处理用户和密码更新,因此请尝试在实体中使用@DynamicUpdate批注,但是由于更新中未提供字段而不是保存它们,因此它无法正常工作像空值一样.

This is where my question comes, my method is working but I feel it is not the best way, I do not feel comfortable changing the password setter I would prefer to keep the standard form of a setter, as in the user service I think there is Opportunity to handle the user and password update differently, so try to use the @DynamicUpdate annotation in the entity but it simply does not work properly since the fields not provided in the update instead of leaving them as they were are saved Like nulls.

我正在寻找的是一种使用Spring Boot处理密码安全性的更好方法.

What I'm looking for is a better way to handle the security of passwords using Spring Boot.

推荐答案

首先,您希望为在线商店中的每个用户(例如fe别名或电子邮件)提供一个唯一字段,以将其用作标识符,而不向最终用户公开ID值. 另外,据我了解,您想使用Spring Security来保护Web应用程序. Spring Security使用ROLE指示用户权限(例如ROLE_USER,ROLE_ADMIN).因此,最好有一个字段(一个列表,一个单独的UserRole实体)来跟踪用户角色.

First of all you would like to have a unique field for each user in your online store (f.e. alias, or email), to use it as an identifier, without exposing id value to the end users. Also, as I understand, you want to use Spring Security to secure your web application. Spring security uses ROLEs to indicate user authorities (f.e. ROLE_USER, ROLE_ADMIN). So it would be nice to have a field (a list, a separate UserRole entity) to keep track of user roles.

让我们假设您向用户字段别名(private String alias;)添加了唯一约束,并添加了简单的private String role;字段.现在,您想设置Spring Security以使所有人都可以使用"/shop"和所有子资源(例如"/shop/search"),这是不安全的,资源"/折扣"仅适用于注册用户和资源"/admin"仅适用于管理员.

Let's assume, that you added unique constraint to User field alias (private String alias;) and added simple private String role; field. Now you want to set up Spring Security to keep '/shop' and all sub-resources (f.e. '/shop/search') open to everyone, unsecured, resource '/discounts' available only for registered users and resource '/admin' available for administrator only.

要实现它,您需要定义几个类.让我们从UserDetailsS​​ervice的实现开始(Spring Security需要获取用户信息):

To implement it, you need to define several classes. Let's start with implementation of UserDetailsService (needed by Spring Security to get user information):

@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 Security的主要类是对WebSecurityConfigurerAdapter进行扩展的类(该示例来自具有基于表单的身份验证的应用程序,但是您可以根据需要进行调整):

Then, the main class for configuring Spring Security is one, that extends WebSecurityConfigurerAdapter (the example was taken from the application with a form based authentication, but you can adjust it for your needs):

@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中,您可以使用类似以下内容的东西:

Then, in your UserService you can use something like:

...
@Autowired
private PasswordEncoder passwordEncoder;

public User addEntity(User user) {
...
    user.setPassword(passwordEncoder.encode(user.getPassword()))
...
}

所有其他检查(例如登录尝试或访问资源)都会根据配置自动执行.还有很多事情需要设置和考虑,但是我希望我能够解释整个想法.

All other checks (f.e. for login attempt or for accessing resource) Spring Security will do automatically, according to the configuration. There are many more things to setup and consider, but I hope I was able to explain the overall idea.

编辑

在任何spring Component或Configuration中定义bean如下

Define bean as follows within any spring Component or Configuration

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

然后将其自动连接到您的UserService类中

Then autowire it in your 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;

    }

    ...
}

之后,您可以在User类中保留简单的setter.

After that you can keep simple setter in User class.

这篇关于在Spring Boot中使用Spring Security对密码加盐的最佳实践是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆