`
kong0itey
  • 浏览: 306048 次
社区版块
存档分类
最新评论

《Spring Security3》第四章第三部分翻译下(密码加salt)(转载)

阅读更多

你是否愿意在密码上添加点salt

 

如果安全审计人员检查数据库中编码过的密码,在网站安全方面,他可能还会找到一些令其感到担心的地方。让我们查看一下存储的 admin guest 用户的用户名和密码值:

        

用户名

明文密码

加密密码

admin

admin

7b2e9f54cdff413fcde01f330af6896c3cd7e6cd

guest

guest

2ac15cab107096305d0274cd4eb86c74bb35a4b4

         这看起来很安全——加密后的密码与初始的密码看不出有任何相似性。但是如果我们添加一个新的用户,而他碰巧和 admin 用户拥有同样的密码时,又会怎样呢?

 

用户名

明文密码

加密密码

fakeadmin

admin

7b2e9f54cdff413fcde01f330af6896c3cd7e6cd

         现在,注意 fakeadmin 用户加密过后密码与 admin 用户完全一致。所以一个黑客如果能够读取到数据库中加密的密码,就能够对已知的密码加密结果和 admin 账号未知的密码进行对比,并发现它们是一样的。如果黑客能够使用自动化的工具来进行分析,他能够在几个小时内破坏管理员的账号。

【鉴于作者本人使用了一个数据库,它里面的密码使用了完全一致的加密方式,我和工程师团队决定进行一个小的实验并查看明文 password SHA-1 加密值。当我们得到 password 的加密形式并进行数据库查询来查看有多少人使用这个相当不安全的密码。让我们感到非常吃惊的是,这样的人有很多甚至包括组织的一个副总。每个用户都收到了一封邮件提示他们选择难以猜到的密码有什么好处,另外开发人员迅速的使用了一种更安全的密码加密机制。】

         请 回忆一下我们在第三章中提到的彩虹表技术,恶意的用户如果能够访问到数据库就能使用这个技术来确定用户的密码。这些(以及其它的)黑客技术都是使用了哈希 算法的结果都是确定的这一特点——即相同的输入必然会产生相同的输出,所以攻击者如果尝试足够的输入,他们可能会基于已知的输入匹配到未知的输出。

        

         一种通用且高效的方法来添加安全层加密密码就是包含 salt (这个单词就是盐的意思,但为了防止直译过来反而不好理解,这里直接使用这个单词——译者注)。 Salt 是第二个明文组件,它将与前面提到的明文密码一起进行加密以保证使用两个因素来生成(以及进行比较)加密的密码值。选择适当的 salt 能够保证两个密码不会有相同的编码值,因此可以打消安全审计人员的顾虑,同时能够避免很多常见类型的密码暴力破解技术。

         比较好的使用 salt 的实践不外乎以下的两种类型:

l  使用与用户相关的数据按算法来生成——如,用户创建的时间;

l  随机生成的,并且与用户的密码一起按照某种形式进行存储(明文或者双向加密)。(所谓的双向加密 two-way encrypte ,指的是加密后还可以进行解密的方式——译者注)

如下图就展现了一个简单的例子,在例子中 salt 与用户的登录名一致:



 【需要记住的是 salt 被添加到明文的密码上,所以 salt 不能进行单向的加密,因为应用要查找用户对应的 salt 值以完成对用户的认证。】

         Spring Security 为我们提供了一个接口 o.s.s.authentication.dao.SaltSource ,它定义了一个方法根据 UserDetails 来返回 salt 值,并提供了两个内置的实现:

l  SystemWideSaltSource 为所有的密码定义了一个静态的 salt 值。这与不使用 salt 的密码相比并没有提高多少安全性;

l  ReflectionSaltSource 使用 UserDetails 对象的一个 bean 属性得到用户密码的 salt 值。

鉴于 salt 值应该能够根据用户数据得到或者与用户数据一起存储, ReflectionSaltSource 作为内置的实现被广泛使用。

配置salted 密码

         与前面配置简单密码加密的练习类似,添加支持 salted 密码的功能也需要修改启动代码和 DaoAuthenticationProvider 。我们可以通过查看以下的图来了解 salted 密码的流程是如何改变启动和认证的,本书的前面章节中我们见过与之类似的图:



 让我们通过配置 ReflectionSaltSource 实现 salt 密码,增加密码安全的等级。

声明 SaltSource Spring bean

         dogstore-base.xml 文件中,增加我们使用的 SaltSource 实现的 bean 声明:

 

Xml代码  收藏代码
  1. < bean   class = "org.springframework.security.authentication.dao.ReflectionSaltSource"   id = "saltSource" >   
  2.   < property   name = "userPropertyToUse"   value = "username" />   
  3. </ bean >   

 我们配置 salt source 使用了 username 属性,这只是一个暂时的实现,在后面的练习中将会进行修正。你能否想到这为什么不是一个好的 salt 值吗?

SaltSource 织入到 PasswordEncoder

         我们需要将 SaltSource 织入到 PasswordEncoder 中,以使得用户在登录时提供的凭证信息能够在与存储值进行比较前,被适当的 salted 。这通过在 dogstore-security.xml 文件中添加一个新的声明来完成:

 

Xml代码  收藏代码
  1. < authentication-manager   alias = "authenticationManager" >   
  2.   < authentication-provider   user-service-ref = "jdbcUserService" >   
  3.     < password-encoder   ref = "passwordEncoder" >   
  4.       < salt-source   ref = "saltSource" />   
  5.     </ password-encoder >   
  6.   </ authentication-provider >   
  7. </ authentication-manager >   

 你如果在此时重启应用,你不能登录成功。正如在前面练习中的那样,数据库启动时的密码编码器需要进行修改以包含 SaltSource

增强 DatabasePasswordSecurerBean

         UserDetailsService 引用类似,我们需要为 DatabasePasswordSecurerBean 添加对另一个 bean 的引用(即 SaltSource ——译者注),这样我们就能够为用户得到合适的密码 salt

 

Java代码  收藏代码
  1. public   class  DatabasePasswordSecurerBean  extends  JdbcDaoSupport {  
  2.   @Autowired   
  3.   private  PasswordEncoder passwordEncoder;  
  4.   @Autowired   
  5.   private  SaltSource saltSource;  
  6.   @Autowired   
  7.   private  UserDetailsService userDetailsService;  
  8.     
  9.   public   void  secureDatabase() {  
  10.     getJdbcTemplate().query("select username, password from users" ,   
  11.                              new  RowCallbackHandler(){  
  12.       @Override   
  13.       public   void  processRow(ResultSet rs)  throws  SQLException {  
  14.         String username = rs.getString(1 );  
  15.         String password = rs.getString(2 );  
  16.         UserDetails user =   
  17.           userDetailsService.loadUserByUsername(username);  
  18.         String encodedPassword =   
  19.           passwordEncoder.encodePassword(password,   
  20.           saltSource.getSalt(user));  
  21.         getJdbcTemplate().update("update users set password = ?   
  22.           where username = ?",   
  23.         encodedPassword,   
  24.           username);  
  25.         logger.debug("Updating password for  username:   
  26.                      "+username+"  to: "+encodedPassword);  
  27.       }  
  28. });  
  29. }  
  30. }  

 回忆一下, SaltSource 是要依赖 UserDetails 对象来生成 salt 值的。在这里,我们没有数据库行对应 UserDetails 对象,所以需要请求 UserDetailsService (我们的 CustomJdbcDaoImpl )的 SQL 查询以根据用户名查找 UserDetails

         到这里,我们能够启动应用并正常登录系统了。如果你添加了一个新用户并使用相同的密码(如 admin )到启动的数据库脚本中,你会发现为这个用户生成的密码是不一样的,因为我们使用用户名对密码进行了 salt 。即使恶意用户能够从数据库中访问密码,这也使得密码更加安全了。但是,你可能会想为什么使用用户名不是最安全的可选 salt ——我们将会在稍后的一个练习中进行介绍。

增强修改密码功能

         我们要完成的另外一个很重要的变化是将修改密码功能也使用密码编码器。这与为 CustomJdbcDaoImpl 添加 bean 引用一样简单,并需要 changePassword 做一些代码修改:

 

Java代码  收藏代码
  1. public   class  CustomJdbcDaoImpl  extends  JdbcDaoImpl {  
  2.   @Autowired   
  3.   private  PasswordEncoder passwordEncoder;  
  4.   @Autowired   
  5.   private  SaltSource saltSource;  
  6.   public   void  changePassword(String username, String password) {  
  7.     UserDetails user = loadUserByUsername(username);  
  8.     String encodedPassword = passwordEncoder.encodePassword   
  9.       (password, saltSource.getSalt(user));  
  10.     getJdbcTemplate().update(  
  11.       "UPDATE USERS SET PASSWORD = ? WHERE USERNAME = ?" ,  
  12.       encodedPassword, username);  
  13.   }  

 这里对 PasswordEncoder SaltSource 的使用保证了用户的密码在修改时,被适当的 salt 。比较奇怪的是, JdbcUserDetailsManager 并不支持对 PasswordEncoder SaltSource 的使用,所以如果你使用 JdbcUserDetailsManager 作为基础进行个性化,你需要重写一些代码。

 

配置自定义的 salt source

         我们在第一次配置密码 salt 的时候就提到作为密码 salt username 是可行的但并不是一个特别合适的选择。原因在于 username 作为 salt 完全在用户的控制下。如果用户能够改变他们的用户名,这就使得恶意的用户可以不断的修改自己的用户名——这样就会重新 salt 他们的密码——从而可能确定如何构建一个伪造的加密密码。

         更安全做法是使用 UserDetails 的一个属性,这个属性是系统确定的,用户不可见也不可以修改。我们会为 UserDetails 对象添加一个属性,这个属性在用户创立时被随机设置。这个属性将会作为用户的 salt

扩展数据库 scheama

         我们需要 salt 要与用户记录一起保存在数据库中,所以要在默认的 Spring Security 数据库 schema 文件 security-schema.sql 中添加一列:

 

Sql代码  收藏代码
  1. create   table  users(  
  2.   username varchar_ignorecase(50) not   null   primary   key ,  
  3.   password  varchar_ignorecase(50)  not   null ,  
  4.   enabled boolean not   null ,  
  5.   salt varchar_ignorecase(25) not   null   
  6.   );  

 接下来,添加启动的 salt 值到 test-users-groups-data.sql 脚本中:

 

Sql代码  收藏代码
  1. insert   into  users(username,  password , enabled, salt)  values  ( 'admin' ,'  
  2. admin',true , CAST (RAND()*1000000000  AS   varchar ));  
  3. insert   into  users(username,  password , enabled, salt)  values  ( 'guest' ,'  
  4. guest',true , CAST (RAND()*1000000000  AS   varchar ));  

 要注意的是,需要用这些新的语句替换原有的 insert 语句。我们选择的 salt 值基于随机数生成——你选择任何随机 salt 都是可以的。

修改 CustomJdbcDaoImpl UserDetails service 配置

         与本章前面讲到的自定义数据库模式中的步骤类似,我们需要修改从数据库中查询用户的配置以保证能够获得添加的“ salt ”列的数据。我们需要修改 dogstore-security.xml 文件中 CustomJdbcDaoImpl 的配置:

 

Xml代码  收藏代码
  1. < beans:bean   id = "jdbcUserService"    
  2.   class = "com.packtpub.springsecurity.security.CustomJdbcDaoImpl" >   
  3.   < beans:property   name = "dataSource"   ref = "dataSource" />   
  4.   < beans:property   name = "enableGroups"   value = "true" />   
  5.   < beans:property   name = "enableAuthorities"   value = "false" />   
  6.   < beans:property   name = "usersByUsernameQuery" >   
  7.     < beans:value > select username,password,enabled,   
  8.                  salt from users where username  = ?  
  9.     </ beans:value >   
  10.   </ beans:property >   
  11. </ beans:bean >   
 

重写基础的 UserDetails 实现

         我们需要一个 UserDetails 的实现,它包含与用户记录一起存储在数据库中的 salt 值。对于我们的要求来说,简单重写 Spring 的标准 User 类就足够了。要记住的是为 salt 添加 getter setter 方法,这样 ReflectionSaltSource 密码 salter 就能够找到正确的属性了。

 

 

Java代码  收藏代码
  1. package  com.packtpub.springsecurity.security;  
  2. // imports   
  3. public   class  SaltedUser  extends  User {  
  4.   private  String salt;  
  5.   public  SaltedUser(String username, String password,   
  6.                     boolean  enabled,  
  7.     boolean  accountNonExpired,  boolean  credentialsNonExpired,  
  8.     boolean  accountNonLocked, List<GrantedAuthority>   
  9.             authorities, String salt) {  
  10.     super (username, password, enabled,   
  11.           accountNonExpired, credentialsNonExpired,  
  12.     accountNonLocked, authorities);  
  13.     this .salt = salt;  
  14.   }  
  15.   public  String getSalt() {  
  16.     return  salt;  
  17.   }  
  18.   public   void  setSalt(String salt) {  
  19.     this .salt = salt;  
  20.   }  
  21. }  

 我们扩展了 UserDetails 使其包含一个 salt 域,如果希望在后台存储用户的额外信息其流程是一样的。扩展 UserDetails 对象与实现自定义的 AuthenticationProvider 时经常联合使用。我们将在第六章:高级配置和扩展 讲解一个这样的例子。

 

扩展 CustomJdbcDaoImpl 功能

         我们需要重写 JdbcDaoImpl 的一些方法,这些方法负责实例化 UserDetails 对象、设置 User 的默认值。这发生在从数据库中加载 User 并复制 User UserDetailsService 返回的实例中:

 

Java代码  收藏代码
  1. public   class  CustomJdbcDaoImpl  extends  JdbcDaoImpl {  
  2.   public   void  changePassword(String username, String password) {  
  3. getJdbcTemplate().update(  
  4.     "UPDATE USERS SET PASSWORD = ? WHERE USERNAME = ?"    
  5.      password, username);  
  6. }  
  7. @Override   
  8. protected  UserDetails createUserDetails(String username,  
  9.           UserDetails userFromUserQuery,  
  10.           List<GrantedAuthority> combinedAuthorities) {  
  11.           String returnUsername = userFromUserQuery.getUsername();  
  12.           if  (!isUsernameBasedPrimaryKey()) {  
  13.               returnUsername = username;  
  14.       }  
  15.   return   new  SaltedUser(returnUsername,   
  16.     userFromUserQuery.getPassword(),userFromUserQuery.isEnabled(),  
  17.     true true true , combinedAuthorities,   
  18.     ((SaltedUser) userFromUserQuery).getSalt());  
  19. }  
  20. @Override   
  21. protected  List<UserDetails> loadUsersByUsername(String username) {  
  22.       return  getJdbcTemplate().   
  23.       query(getUsersByUsernameQuery(),   
  24.       new  String[] {username},   
  25.       new  RowMapper<UserDetails>() {  
  26.           public  UserDetails mapRow(ResultSet rs,  int  rowNum)   
  27.                              throws  SQLException {  
  28.              String username = rs.getString(1 );  
  29.              String password = rs.getString(2 );  
  30.              boolean  enabled = rs.getBoolean( 3 );  
  31.              String salt = rs.getString(4 );  
  32.              return   new  SaltedUser(username, password,    
  33.                enabled, true true true ,   
  34.                AuthorityUtils.NO_AUTHORITIES, salt);  
  35.             }  
  36.         });  
  37.   }  
  38. }  

 createUserDetails loadUsersByUsername 重写了父类的方法——与父类不同的地方在代码列表中已经着重强调出来了。添加了这些变化,你可以重启应用并拥有了更安全、随机的 salt 密码。你可能会愿意加一些日志和实验,以查看应用运行期间和启动时用户数据加载时的加密数据变化。

         要记住的是,尽管在这个例子中说明的是为 UserDetails 添加一个简单域的实现,这种方式可以作为基础来实现高度个性化的 UserDetails 对象以满足应用的业务需要。对于 JBCP Pets 来说,审计人员会对数据库中的安全密码感到很满意——一项任务被完美完成。

分享到:
评论

相关推荐

    Spring Security3

    ### Spring Security3 相关知识点概述 #### 第一章:一个不安全应用的剖析 **安全审计** - **目的**:识别系统中的安全隐患,并评估安全措施的有效性。 - **过程**:通过模拟攻击来测试系统的安全性,分析日志记录...

    spring security 安全权限管理手册

    ##### 第3章:自定义数据库表结构 **3.1 自定义表结构** 在某些情况下,可能希望使用自定义的表结构来存储用户和权限信息。例如,可能需要添加额外的字段来支持更多的业务需求。 **3.2 初始化数据** 同样,需要...

    Spring Security3 中文版 张卫滨 推荐

    根据给定的信息,我们可以从《Spring Security3 中文版》一书中提炼出多个重要的知识点,主要涉及Spring Security的基础概念、具体实现以及高级配置等方面。下面将详细解释每一部分的关键内容。 ### 第一章:一个不...

    spring security3中文文档

    ### Spring Security3中文文档知识点概览 #### 第一章:一个不安全应用的剖析 - **安全审计**:介绍如何通过安全审计来发现系统中的安全漏洞,并解释审计的重要性。 - **关于样例应用**:本章节以JBCPPets应用为例...

    spring security 参考手册中文版

    第四部分 Web应用程序安全 112 13.安全过滤器链 112 13.1 DelegatingFilterProxy 112 13.2 FilterChainProxy 113 13.2.1绕过滤网链 115 13.3过滤器排序 115 13.4请求匹配和HttpFirewall 116 13.5与其他基于过滤器的...

    SpringSecurity 中文

    #### 第三章:增强用户体验 **自定义登录页** - **实现**:通过配置文件指定自定义的登录页面路径。 - **登录逻辑**:定义表单提交地址,并配置相应的处理器。 **理解退出功能** - **实现**:添加Logout按钮,...

    Java 开发基于Java的两个通用安全模块的设计与实现.rar

    - **OAuth2和OpenID Connect**:如果应用需要支持第三方登录,可以使用OAuth2进行授权,OpenID Connect则是在OAuth2基础上添加了身份验证层。 3. **加密解密模块**: - **对称加密**:使用同一密钥进行加密和解密...

    关于java MD5加密

    1. 引入相关库:Java的标准库已经包含了MD5的实现,无需额外引入第三方库。首先需要导入`java.security.MessageDigest`和`java.security.NoSuchAlgorithmException`这两个类。 ```java import java.security....

    基于JAVA的安全电子商务-project

    3. **数据加密**:在存储敏感信息如用户密码时,应使用安全的哈希算法如SHA-256,结合盐值(salt)进行加密。对于交易信息,可能需要使用AES(高级加密标准)等对称加密算法,以保护隐私。 4. **身份验证与授权**:...

    leetcode

    8. **OAuth2**:一种授权协议,允许第三方应用在用户授权的情况下获取资源。Java可以用`spring-security-oauth2`库实现OAuth2服务器端和客户端的开发。 9. **安全编码**:防止SQL注入、XSS攻击等,Java开发者应该...

    java登录程序

    - **CSRF(跨站请求伪造)**:防止恶意第三方冒用用户身份进行操作,可以使用CSRF token来验证请求的合法性。 - **XSS(跨站脚本攻击)**:对用户输入进行过滤和转义,避免注入恶意脚本。 6. **会话管理**: - *...

    登录名

    - 实现密码哈希和加盐(salt)以增加安全性,避免明文存储密码。 - 设置会话超时和强制登出,以防止会话固定攻击。 - 对敏感数据进行加密,如用户密码和JWT本身。 - 使用CSRF(跨站请求伪造)防护,防止恶意第三方...

    java问题解决途径

    例如,你可以使用C3P0或HikariCP等第三方库来实现连接池功能。在应用程序启动时初始化连接池,并配置最大连接数、超时时间等参数,从而优化数据库访问性能。 #### 6. Java中的异常处理 - **知识点概述**:异常处理...

    UserLogin

    在Java中,可以集成Google的Authenticator库或使用第三方服务实现。 9. 用户注册和账户管理:除了登录功能,通常还需要用户注册和账户管理功能,包括密码找回、权限修改等。这些都需要与数据库交互,并遵循良好的...

    “我爱我家”地方农产品商城开题报告【答辩高分内容】.doc

    - **安全性设计**:采用Spring Security框架下的BCryptPasswordEncoder加密技术,保障用户信息安全。 ##### 2.2 功能模块 - **用户管理模块**:实现用户注册、登录、个人信息修改等功能。 - **商品展示与搜索**:...

    用户注册

    在Java中,可以使用第三方库如Google的reCAPTCHA或者自己实现简单的数学问题验证。 8. **邮箱验证**:为了确认用户邮箱的有效性,通常会发送一封验证邮件,其中包含一个链接让用户点击完成验证。JavaMail API可以...

Global site tag (gtag.js) - Google Analytics