美文网首页
Spring cloud oauth2 研究--密码加密方式

Spring cloud oauth2 研究--密码加密方式

作者: 输入昵称就行 | 来源:发表于2019-05-11 23:48 被阅读0次

    背景

    抱着什么都不懂的情况下去学习使用spring boot 2.x集成oauth2功能,所有的参数后台直接明文配置,结果发现报错了,怎么都无法返回正确的token,Full authentication is required to access this resource
    通过后台日志报错内容是: There is no PasswordEncoder mapped for the id null

    寻根

    通过日志,找到了报错的这个类 UnmappedIdPasswordEncoder#matches(CharSequence rawPassword,String prefixEncodedPassword)

    public class DelegatingPasswordEncoder implements PasswordEncoder {
        
        private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
        // 省略代码
    
        private class UnmappedIdPasswordEncoder implements PasswordEncoder {
    
            @Override
            public String encode(CharSequence rawPassword) {
                throw new UnsupportedOperationException("encode is not supported");
            }
    
            @Override
            public boolean matches(CharSequence rawPassword,
                String prefixEncodedPassword) {
                String id = extractId(prefixEncodedPassword);
                throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");
            }
        }
    }
    
    

    这个类是DelegatingPasswordEncoder的一个内部类,通过查看代码发现有个属性叫做defaultPasswordEncoderForMatches说明这个内部类是一个默认的密码加密方式,但是从matches这个方法可以看出目标是获取{}一对大括号之间的字符串,如果找不到就会报错,这说明所有的密码的格式必然是要{xxx}xxxxx这种方式。

    // 获取 {} 之间的字符串
    private String extractId(String prefixEncodedPassword) {
        if (prefixEncodedPassword == null) {
            return null;
        }
        int start = prefixEncodedPassword.indexOf(PREFIX);
        if (start != 0) {
            return null;
        }
        int end = prefixEncodedPassword.indexOf(SUFFIX, start);
        if (end < 0) {
            return null;
        }
        return prefixEncodedPassword.substring(start + 1, end);
    }
    

    继续从这个内部类找,可以看到调用内部类的方法位于

    
        // 这个matches方式就是进行密码校验的
        @Override
        public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
            // 从字段名字很容易理解
            // rawPassword 原始密码:也就是用户请求的密码
            // prefixEncodedPassword 带有前缀的加密算法加密之后的密码
            if (rawPassword == null && prefixEncodedPassword == null) {
                return true;
            }
            // 获取密码前缀加密算法
            String id = extractId(prefixEncodedPassword);
            // 从一个加密算法的map种获取加密具体的加密算法对象
            PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
            if (delegate == null) {
                // 如果获取不到就会使用默认的密码加密算法,实际是一定会抛出错误的
                // 这个内部类里面再次调用extraId的目的只是为了获取一下错误的前缀而已
                return this.defaultPasswordEncoderForMatches
                    .matches(rawPassword, prefixEncodedPassword);
            }
            String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
            return delegate.matches(rawPassword, encodedPassword);
        }
    

    通过以上代码可以看到具体的加密算法位于this.idToPasswordEncoder,这是一个map,通过DelegatingPasswordEncoder的构造方法进行初始化的,查看创建该构造函数追踪到PasswordEncoderFactories,只有一个方法

        // 这个初始化就显示了所有oauth2的加密方式,比如常用的MD5,bcrypt等,
        // ==noop==代表不加密
        public static PasswordEncoder createDelegatingPasswordEncoder() {
            String encodingId = "bcrypt";
            Map<String, PasswordEncoder> encoders = new HashMap<>();
            encoders.put(encodingId, new BCryptPasswordEncoder());
            encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
            encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
            encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
            encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
            encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
            encoders.put("scrypt", new SCryptPasswordEncoder());
            encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
            encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
            encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
    
            return new DelegatingPasswordEncoder(encodingId, encoders);
        }
    

    至此oauth对密码的处理,以后就能够很熟悉了

    存储的密码格式就是 {xxx}yyyyyyyy 的方式了
    比如:

    123456 -> 存储为: {MD5}e10adc3949ba59abbe56e057f20f883e
    123456 -> 存储为:{bcrypt}2a10$cDzOYM.AnjxRKyAwQ8LYR.4tJ3WlQKrC4oeus0NfqQsQfjG0jBiRG

    在调试跟进源码的过程中,发现oauth2的clientSecret 以及密码模式的password都是走的同一个逻辑校验,这也让我更容易理解客户端/自然人都可以作为资源所有者这个概念。

    相关文章

      网友评论

          本文标题:Spring cloud oauth2 研究--密码加密方式

          本文链接:https://www.haomeiwen.com/subject/bkhqaqtx.html