shiro-密码比较的设计 CredentialsMatcher -为什么Java中的密码优先使用 char[] 而不是String?

密码比较
昨天的时候,笔者仔细的追踪了,整个获取信息的主要的设计,通过用户的唯一标识得到 AuthenticationInfo 然后和 AuthenticationToken (用户名 密码),进行比较! 有一个专门的设计类,用来处理密码匹配的比较的。而且很复杂~

AuthenticatingRealm中有一个成员变量
private CredentialsMatcher credentialsMatcher; 凭据的匹配,就是密码的比较辣。这个是一个接口!默认的实现为 SimpleCredentialsMatcher 这个类!都是面向接口编程的~

AuthenticatingRealm 类中的一个函数,通过匹配的实现,进行密码信息的比较工作。

 protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        CredentialsMatcher cm = getCredentialsMatcher();
        if (cm != null) {
             //
            if (!cm.doCredentialsMatch(token, info)) {
                //not successful - throw an exception to indicate this:
                String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
                throw new IncorrectCredentialsException(msg);
            }
        } else {
            throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
                    "credentials during authentication.  If you do not wish for credentials to be examined, you " +
                    "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
        }
    }

看看比较类信息的继承图 可以看出来这里的SimpleCredentialsMatcher 只是简单的实现加密,下面的Hash的单向加密算法MD5,SHA子类的算法 http://www.cnblogs.com/crazylqy/p/4813483.html 对此不是非常的了解,一般的JDK都是要实现的。接下来就是看看这个密码匹配的整个炉子实现的解析!,你发现没有,无论是realm门面还是我们的密码匹配都是设计的扩展性十足的,这样的学习列子,学习看源码好处真的非常棒!
这里写图片描述

CredentialsMatcher 接口,这个接口呢,结合了昨天的,一个是我们登录的时候密码和用户名,AuthenticationToken的实现类 和 通过用户名获得的当前用户的所有的信息,昨天已经很详细的解析了,比如权限,角色信息等等。AuthenticationInfo的实现类 SimpleAuthenticationInfo 。这两个信息的凭证也是密码进行比较,就是实现的意义。

/**
 * Interface implemented by classes that can determine if an AuthenticationToken's provided
 * credentials matches a corresponding account's credentials stored in the system.
 *
 * <p>Simple direct comparisons are handled well by the
 * {@link SimpleCredentialsMatcher SimpleCredentialsMatcher}.  If you
 * hash user's credentials before storing them in a realm (a common practice), look at the
 * {@link HashedCredentialsMatcher HashedCredentialsMatcher} implementations,
 * as they support this scenario.
 *
 * @see SimpleCredentialsMatcher
 * @see AllowAllCredentialsMatcher
 * @see Md5CredentialsMatcher
 * @see Sha1CredentialsMatcher
 * @since 0.1
 */
public interface CredentialsMatcher {

    /**
     * Returns {@code true} if the provided token credentials match the stored account credentials
     */
    boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);

}

CodecSupport这个抽象类,是实现了一个Utis方法的类,充满了String,Char Byte之间的转换。


/**
 * Base abstract class that provides useful encoding and decoding operations, especially for character data.
 *
 * @since 0.9
 */
public abstract class CodecSupport {


    public static final String PREFERRED_ENCODING = "UTF-8";

    /**
     * @param chars the character array to be converted to a byte array.
     * @return the byte array of the UTF-8 encoded character array.
     */
    public static byte[] toBytes(char[] chars) {
        return toBytes(new String(chars), PREFERRED_ENCODING);
    }
    public static byte[] toBytes(char[] chars, String encoding) throws CodecException {
        return toBytes(new String(chars), encoding);
    }
    public static byte[] toBytes(String source) {
        return toBytes(source, PREFERRED_ENCODING);
    }

    /**
     * Converts the specified source to a byte array via the specified encoding, throwing a
     * {@link CodecException CodecException} if the encoding fails.
     */
    public static byte[] toBytes(String source, String encoding) throws CodecException {
        try {
            return source.getBytes(encoding);
        } catch (UnsupportedEncodingException e) {
            String msg = "Unable to convert source [" + source + "] to byte array using " +
                    "encoding '" + encoding + "'";
            throw new CodecException(msg, e);
        }
    }

    /**
     * Converts the specified byte array to a String using 
     * 将bytes转换为String 
     */
    public static String toString(byte[] bytes) {
        return toString(bytes, PREFERRED_ENCODING);
    }

    /**
     * Converts the specified byte array to a String using the specified character encoding.  This implementation
     * does the same thing as <code>new {@link String#String(byte[], String) String(byte[], encoding)}</code>, but will
     * wrap any {@link UnsupportedEncodingException} with a nicer runtime {@link CodecException}, allowing you to
     * decide whether or not you want to catch the exception or let it propagate.
     */
    public static String toString(byte[] bytes, String encoding) throws CodecException {
        try {
            return new String(bytes, encoding);
        } catch (UnsupportedEncodingException e) {
            String msg = "Unable to convert byte array to String with encoding '" + encoding + "'.";
            throw new CodecException(msg, e);
        }
    }

    /**
     * Returns the specified byte array as a character array using the
     * bytes ->String->Char
     * {@link CodecSupport#PREFERRED_ENCODING PREFERRED_ENCODING}.
     */
    public static char[] toChars(byte[] bytes) {
        return toChars(bytes, PREFERRED_ENCODING);
    }

    public static char[] toChars(byte[] bytes, String encoding) throws CodecException {
        return toString(bytes, encoding).toCharArray();
    }

    protected boolean isByteSource(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String ||
                o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }

    protected byte[] toBytes(Object o) {
        if (o == null) {
            String msg = "Argument for byte conversion cannot be null.";
            throw new IllegalArgumentException(msg);
        }
        if (o instanceof byte[]) {
            return (byte[]) o;
        } else if (o instanceof ByteSource) {
            return ((ByteSource) o).getBytes();
        } else if (o instanceof char[]) {
            return toBytes((char[]) o);
        } else if (o instanceof String) {
            return toBytes((String) o);
        } else if (o instanceof File) {
            return toBytes((File) o);
        } else if (o instanceof InputStream) {
            return toBytes((InputStream) o);
        } else {
            return objectToBytes(o);
        }
    }


    protected String toString(Object o) {
        if (o == null) {
            String msg = "Argument for String conversion cannot be null.";
            throw new IllegalArgumentException(msg);
        }
        if (o instanceof byte[]) {
            return toString((byte[]) o);
        } else if (o instanceof char[]) {
            return new String((char[]) o);
        } else if (o instanceof String) {
            return (String) o;
        } else {
            return objectToString(o);
        }
    }

    protected byte[] toBytes(File file) {
        if (file == null) {
            throw new IllegalArgumentException("File argument cannot be null.");
        }
        try {
            return toBytes(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            String msg = "Unable to acquire InputStream for file [" + file + "]";
            throw new CodecException(msg, e);
        }
    }
    protected byte[] toBytes(InputStream in) {
        if (in == null) {
            throw new IllegalArgumentException("InputStream argument cannot be null.");
        }
        final int BUFFER_SIZE = 512;
        ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead;
        try {
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            return out.toByteArray();
        } catch (IOException ioe) {
            throw new CodecException(ioe);
        } finally {
            try {
                in.close();
            } catch (IOException ignored) {
            }
            try {
                out.close();
            } catch (IOException ignored) {
            }
        }
    }
    protected byte[] objectToBytes(Object o) {
        String msg = "The " + getClass().getName();
        throw new CodecException(msg);
    }
    protected String objectToString(Object o) {
        return o.toString();
    }
}

SimpleCredentialsMatcher 只是简单的进行了扩展,没有使用到加密MD5子类的信息~


public class SimpleCredentialsMatcher extends CodecSupport implements CredentialsMatcher {


    protected Object getCredentials(AuthenticationToken token) {
        return token.getCredentials();
    }

    protected Object getCredentials(AuthenticationInfo info) {
        return info.getCredentials();
    }
    protected boolean equals(Object tokenCredentials, Object accountCredentials) {
        if (log.isDebugEnabled()) {
            log.debug("Performing credentials equality check for tokenCredentials of type [" +
                    tokenCredentials.getClass().getName() + " and accountCredentials of type [" +
                    accountCredentials.getClass().getName() + "]");
        }
        if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) {
            if (log.isDebugEnabled()) {
                log.debug("Both credentials arguments can be easily converted to byte arrays.  Performing " +
                        "array equals comparison");
            }
            byte[] tokenBytes = toBytes(tokenCredentials);
            byte[] accountBytes = toBytes(accountCredentials);
            return Arrays.equals(tokenBytes, accountBytes);
        } else {
            return accountCredentials.equals(tokenCredentials);
        }
    }
    /**
    * 判断一下是否相等,得到凭证!这里使用Object的意思也是为了各种可能的扩展吧
    **/
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        Object tokenCredentials = getCredentials(token);
        Object accountCredentials = getCredentials(info);
        return equals(tokenCredentials, accountCredentials);
    }

}

下面的Hash的处理,不太想去看了,各种版本,有很多废弃啦,写的很烦~~~,不过知道意思就好了。

为什么Java中的密码优先使用 char[] 而不是String?
https://www.zhihu.com/question/36734157 知乎答的说的不错!但是最好还是加密
String在Java中是不可变对象,如果作为普通文本存储密码,那么它会一直存在内存中直至被垃圾收集器回收。这就意味着一旦创建了一个字符串,如果另一个进程把尝试内存的数据导出(dump),在GC进行垃圾回收之前该字符串会一直保留在内存中,那么该进程就可以轻易的读取到该字符串。

而对于数组,可以在使用该数组之后显示地擦掉数组中的内容,你可以使用其他不相关的内容把数组内容覆盖掉,例如,在使用完密码后,我们将char[]的值均赋为0,如果有人能以某种方式看到内存映像,他只能看到一串0;而如果我们使用的是字符串,他们便能以纯文本方式看到密码。因此,使用char[]是相对安全的。

推荐使用char[],这是从安全角度来选择的。但是,我们应当注意到,即使是用char[]处理密码也只是降低被攻击的概率而已,还是会有其他方法攻破数组处理的密码。

另一方面,使用String的时候,你可能会不经意间将密码打印出来(如log文件),此时,使用char[]就显得更加的安全了,如:

public static void main(String[] args) {
Object pw = “Password”;
System.out.println(“String: ” + pw);

pw = "Password".toCharArray();
System.out.println("Array: " + pw);

}

此时的输出结果将会是

String: Password

Array: [C@5829428e

实际上,即使使用了char[]保存密码也仍然不够安全,内存中还是可能会有这串数据的零碎副本,因此,建议使用加密的密码来代替普通的文本字符串密码,并且在使用完后记得立即清除。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值