乐趣区

关于spring-security:记录一次将数据库中密码升级为密文过程

前言

改一个老我的项目,老我的项目原来存储明码用的明文,要升级成密文。大略分子工作有两个,一是存储明码时加密,一是将原来数据库中的明码加密。

应用 BCryptPasswordEncoder 加密

BCryptPasswordEncoder 是 springboot 我的项目中最罕用的明码加密形式,由 spring security 向咱们提供。
咱们首先在配置文件下引入 spring security

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

然而这也会导致一个问题,如果引入这个包的话就会主动启用 spring security,再申请的时候就须要用户认证,未认证的用户申请会报 403。然而老我的项目并没有应用 spring security,而是本人写的登录,手动验证用户名明码。所以我须要敞开 spring security 作用。
在网上查了一下,解决办法是通过在启动类上增加正文去疏忽 security 无关类。

@SpringBootApplication(exclude = { SecurityAutoConfiguration.class})

这个解决形式还有来的及验证,首先在单元测试有问题了。
单元测试在测试登录 c 层时,报错 403,这必定是因为 security 认证没有通过,解决办法是通过在
@AutoConfigureMockMvc 上退出参数

@AutoConfigureMockMvc(addFilters = false)

@AuthConfigureMockMvc 负责依赖注入 mockMvc,而咱们应用 mockMvc 进行模仿申请。spring security 在校验时通过一条过滤器链,咱们减少 addFilters = false 使得过滤器不执行,从而不进行认证。这个问题解决了,他的子测试类又报问题了。

    @Test
    public void getById() throws Exception {logger.info("新增一个学院");
        College college = collegeService.getOneSavedCollege();
        logger.info("获取新增学院,断言胜利");
        Long id = college.getId();
        mockMvc.perform(get(baseUrl + "/" + id)
                .cookie(this.cookie))
                .andExpect(status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(id));
    }

他在第 7 行的 mockMvc.perform()报了 java.lang.NullPointerException 异样,

打断点发现是 cookie 为 null, 这就是父类通过登录获取 cookie, 子类通过 cookie 进行身份认证。
然而当初通过测试发现咱们禁用过滤器获取不到 cookie 了。咱们禁用过滤器是不想让 security 起作用,然而也造成了获取不到 cookie 了。我尝试应用其余办法解决禁用 security 的问题。通过网上给的办法,如退出 @WithMockUser,都没有起成果, 申请还是报错 403。

我只想要 BCryptPasswordEncoder, 却因为引入他所在的包产生了负影响。询问老师后,老师给出倡议能不能只引入 BCryptPasswordEncoder 包,在网上找了一番并没有这个包。老师说把 BCryptPasswordEncoder 类代码复制下来应用,BCryptPasswordEncoder 类并没有依赖其余类。引入后,能够失常应用,问题解决。

将生产环境的用户明码明文变密文

因为我的项目在生产环境曾经应用一段时间了,认为明码都变成密文了,判断明码形式也变了,咱们也要讲原来用的的明码变为密文,能力不影响用户登录。思路是将所有用户取出来而后明码加密一边而后存回去。要害是如何实现只执行一次。如果明码加密两次必定就不对了。
参考老我的项目是通过一个数据库存储后盾版本,而后在启动时判断是否是此版本,如果不是,存储此版本并将明码加密。

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        String version = "1.0.0";
        if (!this.schemaHistoryRepository.findById(version).isPresent()) {this.schemaHistoryRepository.save(new SchemaHistory(version));
            List<User> users = this.userRepository.findAll();
            for (User user: users) {
                // 将未加密的数据库中的明码加密
                user.setPassword(user.getPassword());
            }
            this.userRepository.saveAll(users);
        }
    }

BCryptPasswordEncoder

在改写 setPassword()办法的过程中遇到个问题,因为用户在存储明码时生成一个密文,而后用户登录时前台传给后盾 json 类型的 username 和 password。后盾通过 setPassword()为 user 赋值,这时前台传来的明码又加密了一编,而 BCryptPasswordEncoder 雷同数据两次加密后果不一样。导致不能通过判断加密后的明码验证明码谬误与否。
这不得不重写一个 setPasswordWithEncoder()办法去加密明码,setPassword()办法保留为了接管前台传来的 password。
这种解决办法使得改起来变得特地凌乱,改单元测试时不晓得用什么办法才对。老师倡议从新建设一个类用于接管前台传来的用户对象

/**
 * 用于对登录的 user 实体接管
 */
public class LoginUser {
  private String username;

  private String password;

  public LoginUser(String username, String password) {
    this.username = username;
    this.password = password;
  }

  public LoginUser() {}

  public String getUsername() {return username;}

  public void setUsername(String username) {this.username = username;}

  public String getPassword() {return password;}

  public void setPassword(String password) {this.password = password;}
}

用 user 实体类的 setPassword()办法进行明码加密。接管的时候应用 LoginUser,password 还是前台传来的,这样就能正确判断明码正确与否了。
方才咱们说 BCryptPasswordEncoder 对雷同数据两次加密后果不雷同,这是因为 BCryptPasswordEncoder 在加密时退出了一个随机生成的盐值,两次生成的盐值不一样,导致加密后的后果不一样。那么他是如何认证明码是否正确的呢?
每次生成盐值时会将明文与对应盐值存储起来,在验证时获取到对应的盐值再进行加密,比拟两次加密后的后果是否雷同。

这个盐值也写到了加密后的明码中
想要对加密算法理解更多,请参考[明码安全性策略] (https://segmentfault.com/a/11…

总结

感激潘老师在解决问题中给予的领导。

参考

https://www.zhihu.com/questio…
https://segmentfault.com/a/11…

退出移动版