配置安全的密码
我们回忆第一章:一个不安全应用的剖析中,审计人员认为密码以明文形式进行存储是最高优先级的安全风险。实际上,在任何安全系统中,密码安全都是保证已经经过认证的安全实体是真实可靠的重要方面。安全系统的设计人员必须保证密码存储时,任何恶意的用户想要进行破解都是非常困难的。
在数据库存储时,需要遵守以下的准则:
l 密码不能以明文的形式进行存储(简单文本);
l 用户提供的密码必须与数据库存储的密码进行比较;
l 密码不能应用户的请求提供(即使用户忘记了密码)。
对于大多数应用来说,为了满足以上要求最适合的方式是对密码进行单向的编码或加密。单向编码提供了安全和唯一的特性,这对于正确的认证用户非常重要,它能够保证密码一旦被加密就不能再被解密了。
在大多数的安全应用设计中,一般没有必要和实际要求根据用户的请求检索用户的密码,因为如果没有附加的安全认证,提供用户的实际密码会有很大的安全风险。作为替代方案,大多数的应用为用户提供了重设密码的功能,要么需要提供额外的认证信息(如社会保险号码、生日、缴税ID或其它个人信息),要么通过一个基于email的系统。
【存储其它类型的敏感信息:以下的准则可以应用于密码以及其它类型的敏感信息,包括社会保险号以及信用卡信息(尽管基于应用的不同,它们中的一些需要解密的能力)。比较常见的是数据库以多种形式来存储这些敏感信息,比如用户16个数字的信用卡数字可以用一种高度加密的形式存储,但是最后的四位数字以明文的形式存储(作为佐证,可以回忆一些商业站点会显示XXXX XXXX XXXX 1234来帮助你分别已存储的信用卡)。】
基于我们(实际上并不太符合现实)使用SQL来访问HSQL数据库中用户的环境,你可能已经在思考如何对密码进行编码。HSQL以及其它大多数的数据库并没有提供加密方法作为数据库的内置功能。
一般来说,bootstrap过程(为系统添加初始的用户和数据)会联合使用一些SQL加载和Java代码。根据应用的复杂性,这个过程也可能会变得很复杂。
对于JBCP Pets应用来说,我们将会继续使用嵌入式数据库声明和对应的SQL,并且会添加一点Java代码在初始化加载后执行来加密数据库中的所有密码。为了使得密码加密能够正常工作,两个过程必须同步的使用密码加密以确保密码能够被一致的处理和校验。
在Spring Security中,密码加密已经进行了封装,通过o.s.s.authentication.encoding.PasswordEncoder接口的实现类来定义。通过使用<authentication-provider>元素里的<password-encoder>声明我们能够很容易地配置密码编码:
<authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="jdbcUserService"> <password-encoder hash="sha"/> </authentication-provider> </authentication-manager>
你可能会很高兴的了解到Spring Security已经提供了一系列PasswordEncoder的实现,它们可以用于不同的需求和安全需要。要使用哪个实现可以通过<password-encoder>元素的hash属性来指定。
以下的列表列出了内置的实现类以及它们的优点。这些实现类都在o.s.s.authentication.
Encoding包下。
实现类 | 描述 | hash值 |
PlaintextPasswordEncoder | 以明文的形式编码。DaoAuthenticationProvider默认的密码编码器。 | plaintext |
Md4PasswordEncoder | PasswordEncoder使用MD4 hash算法。MD4并不是一个安全的算法——不推荐使用这个编码器 | md4 |
Md5PasswordEncoder | PasswordEncoder使用MD5的单向编码算法。 | md5 |
ShaPasswordEncoder | PasswordEncoder使用SHA单向加密算法。这个编码器支持配置密码的强度级别。 | sha sha-256 |
LdapShaPasswordEncoder | 在集成LDAP认证存储时使用,实现了LDAP SHA和LDAP SSHA算法。我们将会在第九章:LDAP目录服务讲述LDAP时,学习更多关于这个算法的知识。 | {sha} {ssha} |
与Spring Security其他领域一样,可以引用一个PasswordEncoder的实现类以提供更精确的配置,并允许PasswordEncoder通过依赖注入织入到其它的bean中。对于JBCP Pets来说,我们需要使用这个bean引用的方法来编码用户的初始数据。
让我们了解一下为JBCP Pet应用配置基本密码编码的过程。
配置密码编码
配置基本的密码编码涉及到两个地方——在我们的SQL脚本执行后,加密载入数据库中数据的密码,并确保DaoAuthenticationProvider被配置成使用PasswordEncoder。
配置PasswordEncoder
首先,作为通常的Spring bean,声明一个PasswordEncoder的实例
<bean class="org.springframework.security.authentication. encoding.ShaPasswordEncoder" id="passwordEncoder"/>
你会发现我们使用了SHA-1的PasswordEncoder实现。这是一个高效的单向加密算法,在密码存储中经常用到。
配置AuthenticationProvider
我们需要配置DaoAuthenticationProvider来持有一个对PasswordEncoder的引用,这样它就可以在用户登录时,编码并比较用户提供的密码。添加<password-encoder>声明并指向我们在前面定义的bean的ID:
<authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="jdbcUserService"> <password-encoder ref="passwordEncoder"/> </authentication-provider> </authentication-manager>
如果在此时重启应用,并尝试登录,你会发现前面合法的登录凭证现在都会被拒绝。这是因为数据库中存储的密码(在启动时通过test-users-groups-data.sql脚本加载的)并没有以加密的形式存储。我们需要用一些java代码对启动数据进行后置的处理。
编写数据库启动的密码编码器
我们对SQL加载的数据进行编码的方式是使用了Spring bean的初始化方法,它将在embedded-database bean实例化完成后执行。这个bean, com.packtpub.springsecurity.security.DatabasePasswordSecurerBean很简单:
public class DatabasePasswordSecurerBean extends JdbcDaoSupport {
@Autowired
private PasswordEncoder passwordEncoder;
public void secureDatabase() {
getJdbcTemplate().query("select username, password from users",
new RowCallbackHandler(){
@Override
public void processRow(ResultSet rs) throws SQLException {
String username = rs.getString(1);
String password = rs.getString(2);
String encodedPassword =
passwordEncoder.encodePassword(password, null);
getJdbcTemplate().update("update users set password = ?
where username = ?", encodedPassword,username);
logger.debug("Updating password for username:
"+username+" to: "+encodedPassword);
}
});
}
}
代码使用了JdbcTemplate功能来遍历所有的数据库中所有的用户并使用注入的PasswordEncoder引用对密码进行编码。每一个密码都进行了更新。
配置启动的密码编码
我们需要配置这个Spring bean的声明以使其在web应用启动时及<embedded-database>初始化后再进行该类的初始化。Spring bean的依赖跟踪机制保证DatabasePasswordSecurerBean能够在合适的时机执行:
<bean class="com.packtpub.springsecurity.security. DatabasePasswordSecurerBean" init-method="secureDatabase" depends-on="dataSource"> <property name="dataSource" ref="dataSource"/> </bean>
如果你此时重启JBCP Pets应用,你会发现数据库中的密码已经进行了编码,登录功能可以正常使用了。