Spring Security学习之密码加密

前言

密码安全的重要性:

  1. 防止被黑客“拖库”

    1. 拖库:数据库术语,从数据库中导出数据。现特指网站遭到入侵后,黑客窃取数据库文件

    2. 如果发生拖库:大量用户信息被暴露,面临数据安全和数据隐私双重威胁,尤其如果数据库密码是明文存储

    3. 预防错误:数据库加密

  2. 如果拖库已经发生,那么密码如果有加密的话可以最大限度的防止被破解从而减少安全损失

    1. 提高密码破解难度,让黑客主动放弃高成本破解

    2. 另外通知用户登录,跳转手机号/邮箱验证,验证通过话重置密码后方能正常使用应用,减少黑客破解后的可操作用户数

  3. 最好的方法就是密码加密和限制密码的强制,禁止低强度密码

常见加密算法

  1. MD5
    1. 只能加密,无法解密。匹配方式是先加密加入到数据库中,登录的时候使用同样的加密方式将密码加密,然后和从数据库拿出的密码进行匹配
    2. MD5的摘要值有限,最多只能表示36的16次方个。完全可以通过构建反查表(通过遍历构建字典)去穷举暴力破解,或者巧合会发生“碰撞”
    3. MD5码以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位 散列值
    4. MD5也可用于电子签名,通过验证保证数据传输过程中数据未被篡改(数据传递的时候传递数据对象+MD5加密后的数据对象,接收方拿取到数据后,使用同样的方法加密数据对象,然后和数据中的传递方加密的值进行匹配,如果匹配成功说明数据对象未被篡改)
  2. MD5加盐
    1. MD5加密的时候添加一个自定义的key,匹配加密传来的密码同样方式加密后匹配
    2. 由于黑客无法获取key,所以暴力破解难度增加
    3. 为了实现盐值真正的随机,可以在添加用户的时候使用UUID作为key,然后将加密后的密码和UUID存入数据库,然后用户登录根据传来的密码加上从数据库获取UUID使用MD5加密后匹配数据库中该用户的密码
    4. 当然也可以将密码存储格式设为UUID:加密后的key,这样直接从数据库获取密码切分后重复第三点的操作即可,相当于节省了一个存储字段
    5. 但是随着穷举法的优化和超级计算机的出现,总会穷举成功,特别是使用散列算法反而会助推穷举速度。我们能做的就是使用慢的加密方式,慢到穷举成本非常高
  3. BCrypt
    1. 加密方式很慢,密文的迭代次数为2的12次方(12为成本参数,根据计算能力越来越强,成本参数会相应调整)
    2. 匹配方式同MD5,密码加密后存入数据库;用户登录使用同样加密方式加密传来的密码,然后和根据username从数据库中获取的密码做匹配
    3. Spring Security学习之密码加密_第1张图片
  4. RSA 非对称加密算法,目前遇到的比较多的使用场景是数据传输的时候使用,之前使用支付宝当面付功能的时候,支付宝使用的就是RSA加密

  5. DES,AES 对称加密算法

Spring Security中的加密

使用PasswordEncoder接口内置密码加密机制

public interface PasswordEncoder {
    String encode(CharSequence var1);


    boolean matches(CharSequence var1, String var2);


    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

需要使用加密的话选择BCryptPasswordEncoder实现类,在配置类中创建一个加密bean加入到spring容器中,并将加密方式注入到认证配置方法中

@EnableWebSecurity // 自带@Configuration,此处无需再加@Configutaion注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserDetailsServiceImpl userDetailsServiceImpl;

    /**
     * 建立身份认证方式 AuthenticationManagerBuilder创建一个AuthenticationManager用于进行身份认证
     * 包括:内存验证、LDAP验证、基于JDBC的验证、添加UserDetailsService、添加AuthenticationProvider
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        // 使用明文
//        auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(NoOpPasswordEncoder.getInstance());
        // 使用密文,加密方式使用自定义的passwordEncoder类
        auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(myPasswordEncoder());
    }

    /**
     * 开启认证 配置需要认证的url
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       // 自定义登录配置
        // authorizeRequests()方法返回一个URL拦截注册器,可调用其提供的anyRequest(),antMatchers()等方法匹配系统给的URL并为其制定安全策略
        http.authorizeRequests()
                .antMatchers("/app/**").permitAll()
                // 限制既有ADMIN又有USER的用户访问
                .antMatchers("/admin/**").access("hasRole('ADMIN') and hasRole('USER')")
                .antMatchers("/user/**").hasRole("USER")
                // 任意一个http请求都会进行认证
                .anyRequest().authenticated()
                .and()
                // 设置表单验证登录
                .formLogin()
                .permitAll()
                .and()
                // 禁止csrf攻击
                .csrf().disable();
    }


    /**
     * 创建一个BCryptPasswordEncoder对象,并使用@Bean将其加入到spring容器中统一管理
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder(){
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder;
    }
}

配置加密后,重启项目,可以发现之前的密码登录失败。主要是SS框架会将这个密码加密,然后和数据库中的密码进行匹配,目前一个是加密后的一个是明文自然匹配不上,所以登录失败

Spring Security学习之密码加密_第2张图片

拓展

如果一个项目中已经有一些老用户使用的是明文或者其他加密方式加密,解决方式有两种:

  1. 重写PasswordEncoder并继承BCryptPasswordEncoder,在其中的matchers方法中,对从数据库获取的密码进行判断,如果不是BCryptPassword(根据其BCRYPT_PATTENR进行正则表达式匹配),则进行自定义处理

    1. 如果是明文,可Bcrypt加密后存入数据库,下一次登录的时候走SS框架bcrypt验证逻辑

    2. 如果是密文,写自己的原来那一套的登录验证逻辑即可

  2. 如果之前的密码是通过MD5加密过的

    1. 已有用户的旧密码可以“拖库”后,在MD5之上使用bcrypt加密后重新存入到数据库中,下次登录的时候会将前端传来的密码首先用MD5加密然后在用bcrypt加密,最后和从数据库拿取的密码进行匹配

    2. 新添加用户直接MD5加密 bcrypt加密后将密码存入数据库

    3. 其实相当于就是对bcrypt的encode和matcher都添加一步,就是在bcrypt加密之前使用MD5先加密

private static Pattern BCRYPT_PATTERN =Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");
// 自定义的密码加密方式
public class MyPasswordEncoder extends BCryptPasswordEncoder {
    /**
     * bcrypt的加密正则表达式
     */
    private static Pattern BCRYPT_PATTERN =Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");


    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        // 不是bcrypt加密
        if(!BCRYPT_PATTERN.matcher(encodedPassword).matches()){
            // 系统现在是明文,只要前端传来的密码和数据库中的密码相同即可
            if(Objects.equals(rawPassword,encodedPassword)){
                return true;
            }
        }
        // 是bcrypt加密,使用框架认证方式
        return super.matches(rawPassword, encodedPassword);
    }
}

将这个新的加密方式注入spring容器,并绑定到auth认证配置中

//将这个新的加密方式注入spring容器,并绑定到auth认证配置中
@EnableWebSecurity // 自带@Configuration,此处无需再加@Configutaion注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    UserDetailsServiceImpl userDetailsServiceImpl;


    /**
     * 建立身份认证方式 AuthenticationManagerBuilder创建一个AuthenticationManager用于进行身份认证
     * 包括:内存验证、LDAP验证、基于JDBC的验证、添加UserDetailsService、添加AuthenticationProvider
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 使用密文,加密方式使用自定义的passwordEncoder类
        auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(myPasswordEncoder());
    }

    /**
     * 开启认证 配置需要认证的url
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       // 自定义登录配置
        // authorizeRequests()方法返回一个URL拦截注册器,可调用其提供的anyRequest(),antMatchers()等方法匹配系统给的URL并为其制定安全策略
        http.authorizeRequests()
                .antMatchers("/app/**").permitAll()
                // 限制既有ADMIN又有USER的用户访问
                .antMatchers("/admin/**").access("hasRole('ADMIN') and hasRole('USER')")
                .antMatchers("/user/**").hasRole("USER")
                // 任意一个http请求都会进行认证
                .anyRequest().authenticated()
                .and()
                // 设置表单验证登录
                .formLogin()
                // 设置登录页不设置访问限制,即登录页不用进行拦截
                .permitAll()
                .and()
                // 禁止csrf攻击
                .csrf().disable();
    }


    /**
     * 创建一个BCryptPasswordEncoder对象,并使用@Bean将其加入到spring容器中统一管理
     * @return
     */
    @Bean
    PasswordEncoder myPasswordEncoder(){
//        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        // 使用自定义的加密方法
        PasswordEncoder passwordEncoder = new MyPasswordEncoder();
        return passwordEncoder;
    }

创建用户成功后可以在数据表user中看到新加用户的密码已经加密后存储

 

总结

密码加密其实就是给一个encode方法和matcher方法,其中encode方法主要是认证的时候框架自己/自定义认证中将浏览器传来的密码encode加密,然后利用matcher方法匹配新加密后的值和从数据库获取的值(自定义的话需要自己手动调用encode方法加密,两个方法看自己需求是否重写,如果不重写,即复用bcrypt的方法)

密码必须使用encode加密后存入数据库,加密的方式是导入加密类,如果是使用系统自带brcypt,那就直接new一个对象或者在配置类中写一个方法创建bean对象到spring容器中,然后其他地方使用的时候直接@Autowired注入依赖即可;如果是自定义的加密方式,可以在类前使用@Component加入容器,或者在配置类中创建bean对象。然后其他地方使用注入依赖

    /**
     * 创建一个BCryptPasswordEncoder对象,并使用@Bean将其加入到spring容器中统一管理
     * @return
     */
    @Bean
    PasswordEncoder myPasswordEncoder(){
//        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        // 使用自定义的加密方法
        PasswordEncoder passwordEncoder = new MyPasswordEncoder();
        return passwordEncoder;
    }

 

你可能感兴趣的:(Spring,Security学习,Java学习,后端开发,Spring,Security,java)