关于对《Spring Security3》翻译 (第四章 - 第xx章)

10 篇文章 0 订阅


4 第四章

4.1 第四章第一部分

4.1.1 第四章第一部分翻译(上)(数据库管理信息)

这次上传的部分内容是入门级的,比较简单,但是本章整体的功能还是非常重要的。

 

凭证安全存储

到现在为止,我们已经将JBCP Pets站点做了一些用户友好性方面的升级,包括一个自定义的登录页、修改密码以及remember me功能。

 

在本章中,我们将会把到目前为止都在使用的内存存储转移到数据库作为后台的认证存储。我们将会介绍默认的Spring Security数据库schema,并介绍自定义扩展JDBC实现的方式。

 

在本章的课程中,我们将会:

l  理解如何配置Spring Security才能使用JDBC访问数据库服务以实现用户的存储和认证;

l  学习如何使用基于内存数据库HSQLDB的JDBC配置,我们使用这个数据库主要是为了开发测试的目的;

l  使得Spring Security的JDBC能够支持已经存在的遗留数据库schema;

l  掌握两种管理用户名和密码的功能,两者都会涵盖内置的和自定义的方式;

l  掌握配置密码编码的不同方法;

l  理解密码salting技术以提供更安全的方式存储密码;

l  持久化用户的remember me token,使得在服务器重启后token仍能有效;

l  通过配置SSL/TLS加密和端口映射,在传输层上保护应用的安全。

 

使用数据库后台的Spring Security认证

我们进行安全控制的JBCP Pets应用有一个明显问题是基于内存的用户名和密码存在时间比较短,对用户很不友好。一旦应用重启,任何的用户注册,密码修改或者其他的活动都会丢失。这是不可接受的,所以对于JBCP Pets应用的下一个逻辑实现功能就是重新设置Spring Security以使用关系型数据库来进行用户存储和授权。使用JDBC访问数据库能够使得用户的信息能够持久化,及时应用重启依旧有效,另外更能代表现实世界中Spring Security的使用。

配置位于数据库上的认证存储

这个练习的第一部分是建立一个基于Java的关系数据库HyperSQL DB(或简写为HSQL)示例,并装入Spring Security默认的schema。我们将会通过使用Spring Security的嵌入式数据库配置功能来设置HSQL在内存中运行,比起手动的安装数据库,这是一个很简单的配置方法。

 

请记住在这个例子中(包括本书的其余部分),我们将使用HSQL,主要是因为它很容易安装。在使用这些例子的过程中,我们鼓励修改这个配置以使用你喜欢的数据库。鉴于我们不想让本书的这一部分过多关注于复杂数据库的安装,对练习来说我们选择了更简便而不是更接近现实。

创建Spring Security默认的schema(配置位于数据库上的认证存储的下级目录)

我们提供了一个SQL文件(security-schema.sql),它将用来创建Spring Security使用HSQL所依赖的所有表。如果你使用自己的数据库实例,你可能会需要调整schema的定义语法来适应特定的数据库。我们会将SQL文件置于classpath中,在WEB-INF/classes目录下。

配置HSQL嵌入式数据库(配置位于数据库上的认证存储的下级目录

要配置HSQL嵌入式的数据库,我们需要修改dogstore-security.xml文件,以实现启动数据库并运行SQL来创建Spring Security表结构。首先,我们将会在文件的顶部添加对jdbc XML模式的应用:

 

Xml代码   收藏代码
  1. <beans:beans xmlns="http://www.springframework.org/schema/security"  
  2.   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3.   xmlns:beans="http://www.springframework.org/schema/beans"  
  4.   xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
  5.   xsi:schemaLocation="  
  6.     http://www.springframework.org/schema/beans   
  7.     http://www.springframework.org/schema/beans/spring-beans.xsd  
  8.     http://www.springframework.org/schema/jdbc  
  9.     http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd  
  10.     http://www.springframework.org/schema/security   
  11.     http://www.springframework.org/schema/security/   
  12.           spring-security3.0.xsd"  
  13. >  

 接下来,我们声明<embedded-database>元素,以及对SQL脚本的引用:

 

Xml代码   收藏代码
  1. <jdbc:embedded-database id="dataSource" type="HSQL">  
  2.   <jdbc:script location="classpath:security-schema.sql"/>  
  3. </jdbc:embedded-database>  

 如果此时重启服务,你可以在日志上看到初始化HSQL数据库。需要记住的是<embedded-database>只会在内存中创建数据库,所以你在内存中看不到任何东西,也不能使用标准的工具进行查询。

配置JdbcDaoImpl凭证存储(配置位于数据库上的认证存储的下级目录

我们需要修改dogstore-security.xml文件来声明正在使用JDBC的UserDetailsService实现,替换我们在第二章:Spring Security起步第三章:增强用户体验中配置的Spring Security内存UserDetailsService实现。这通过一个对<authentication-manager>声明的一个简单改变来实现:

 

Xml代码   收藏代码
  1. <authentication-manager alias="authenticationManager">  
  2.   <authentication-provider>  
  3.     <jdbc-user-service data-source-ref="dataSource"/>  
  4.   </authentication-provider>  
  5. </authentication-manager>  

 【data-source-ref引用了我们在上一步声明<embedded-database>时定义的bean。】

添加用户定义到schema中(配置位于数据库上的认证存储的下级目录

最后,我们要创建另外一个SQL文件,它将会在内存数据库创建时执行。这个SQL文件将会包含默认用户(admin和guest)的信息,以及GrantedAuthority设置(这一点我们在前一章中已经用过了)。我们将这个文件命名为test-data.sql,并将其与security-schema.sql一起放在WEB-INF/classes下:

 

Sql代码   收藏代码
  1. insert into users(username, password, enabled) values   
  2.   ('admin','admin',true);  
  3. insert into authorities(username,authority) values   
  4.   ('admin','ROLE_USER');  
  5. insert into authorities(username,authority) values   
  6.   ('admin','ROLE_ADMIN');  
  7. insert into users(username, password, enabled) values   
  8.   ('guest','guest',true);  
  9. insert into authorities(username,authority) values   
  10.   ('guest','ROLE_USER');  
  11. commit;  

 接下来,我们需要添加这个SQL文件到嵌入式数据库配置中,它将会在启动时加载:

 

Xml代码   收藏代码
  1. <jdbc:embedded-database id="dataSource" type="HSQL">  
  2.   <jdbc:script location="classpath:security-schema.sql"/>  
  3.   <jdbc:script location="classpath:test-data.sql"/>  
  4. </jdbc:embedded-database>  

 在SQL添加到数据库配置后,我们应该能够启动应用并登录。Spring Security现在已经查找数据库的认证和GrantedAuthority信息。

 

基于数据库后台的认证是如何实现的(配置位于数据库上的认证存储的下级目录

你可能会记起在第二章中讲述的认证过程,AuthenticationManager委托AuthenticationProvider来校验安全实体的凭证信息以确定它是否能够访问系统。我们在第二章和第三章中使用的AuthenticationProvider为DaoAuthenticationProvider。这个provider又委托了一个UserDetailsService的实现来从凭证库中检索和校验安全实体的信息。我们能够通过以下的图来反应第二章的过程:


 

正如你可能预期的那样,数据库后台的认证存储和内存存储的唯一区别在于UserDetailsService的实现类。o.s.s.core.userdetails.jdbc.JdbcDaoImpl类提供了UserDetailsService的实现。不同于在内存中(通过Spring Security的配置文件添加)查找,JdbcDaoImpl在数据库中查找用户。



 你可能意识到我们根本没有引用这个实现类。这是因为在更新后的Spring Security配置中<jdbc-user-service>声明会自动配置JdbcDaoImpl并将其织入到AuthenticationProvider中。在本章接下类的内容中,我们将会介绍如何配置Spring Security使用我们自定义的JdbcDaoImpl实现,它继续包含了修改密码功能(在第三章中我们添加到InMemoryDaoImpl了)。让我们看一下如何实现自定义的支持修改密码功能的JdbcDaoImpl子类配置。

空目录
空目录

4.1.2 《Spring Security3》第四章第一部分翻译(下)(自定义UserDetailsService)

实现自定义的JDBC UserDetailsService

正如在前面章节中的那个练习,我们将以基本的JdbcDaoImpl作为起点,将其进行扩展以支持修改密码功能。

创建一个自定义的JDBC UserDetailsService

在com.packtpub.springsecurity.security包下创建如下的类:

 

Java代码   收藏代码
  1. public class CustomJdbcDaoImpl extends JdbcDaoImpl implements   
  2. IChangePassword {  
  3.   public void changePassword(String username, String password) {  
  4.     getJdbcTemplate()   
  5.     update("UPDATE USERS SET PASSWORD = ? WHERE USERNAME = ?",  
  6.             password, username);  
  7.   }  
  8. }  

 你可以看到这个类扩展了JdbcDaoImpl默认类,提供了按照用户请求更新数据库中密码的功能。我们使用标准的Spring JDBC模板完成这个功能。

 

为自定义的JDBC UserDetailsService添加Spring bean声明

在dogstore-base.xml配置文件中,添加如下的Spring Bean声明:

 

Xml代码   收藏代码
  1. <bean id="jdbcUserService"   
  2.       class="com.packtpub.springsecurity.security.CustomJdbcDaoImpl">  
  3.   <property name="dataSource" ref="dataSource"/>  
  4. </bean>  

 同样的,dataSource 的Bean引用指向了<embedded-database>声明,我们使用这个声明来安装HSQL内存数据库。

 

 

你会发现自定义的UserDetailsService允许我们与数据库直接交互。在接下来的例子中,我们将使用这个功能来扩展UserDetailsService的基本功能。在使用Spring Security的复杂应用中,这种类型的个性化是很常见的。

基于JDBC的内置用户管理

正如上面简单JdbcDaoImpl扩展所描述的那样,开发人员可能会扩展这个类,但同时也会保留基本的功能。而我们要实现更复杂功能时,如用户注册(online store所必须的)与用户管理功能、站点的管理员创建用户、更新密码等,又会怎样呢?

 

尽管这些功能借助JDBC语句都能相对容易的实现,但是Spring Security还是为我们提供了内置的功能以支持对数据库里的用户进行创建、读取、更新和删除的操作。这对简单的系统来说是很有用的,同时也为构建自定义需求的用户提供了很好的起点。

 

实现类o.s.s.provisioning.JdbcUserDetailsManager扩展了JdbcDaoImpl的功能,提供了一些很有用的与用户相关的方法,这些方法的一部分在o.s.s.provisioning.UserDetailsManager接口中进行了定义:

方法

描述

void createUser(UserDetails user)

根据给定的UserDetails创建一个新用户,并包含所有声明的GrantedAuthority。

void updateUser(final UserDetails user)

根据给定的UserDetails更新一个用户。更新其GrantedAuthority并将其从用户缓存中清除。

void deleteUser(String username)

根据给定的用户名删除用户,并将其从用户缓存中清除。

boolean userExists(String username)

根据给定的用户名判断用户是否存在(不管是否可用)。

void changePassword(String oldPassword, String newPassword)

修改当期登录用户的密码。为了使得操作成功,用户必须提供正确的当期密码。

 

正如你所见,JdbcUserDetailsManager的changePassword方法正好满足了我们CustomJdbcDaoImpl的不足——在修改之前,它会检验用户已存在密码。让我们看一下将CustomJdbcDaoImpl替换为JdbcUserDetailsManager需要怎样的配置步骤。

 

首先,我们需要在dogstore-base.xml中声明JdbcUserDetailsManager bean:

 

Xml代码   收藏代码
  1. <bean id="jdbcUserService"   
  2.       class="org.springframework.security   
  3.              .provisioning.JdbcUserDetailsManager">  
  4.   <property name="dataSource" ref="dataSource"/>  
  5.   <property name="authenticationManager"   
  6.             ref="authenticationManager"/>  
  7. </bean>  

 对AuthenticationManager的引用要匹配我们之前dogstore-security.xml文件中声明的<authentication-manager>的alias。不要忘记注释掉CustomJdbcDaoImpl的声明——我们暂时不会使用它。

 

 

接下来,我们需要对changePassword.jsp做一些小的调整:

 

Html代码   收藏代码
  1. <h1>Change Password</h1>  
  2. <form method="post">  
  3.   <label for="oldpassword">Old Password</label>:  
  4.   <input id="oldpassword" name="oldpassword"   
  5.          size="20" maxlength="50" type="password"/>  
  6.   <br />  
  7.   <label for="password">New Password</label>:  
  8.   <input id="password" name="password" size="20"   
  9.          maxlength="50" type="password"/>  
  10.   <br />  

 

 最后,需要简单调整AccountController。将@Autowired引用IChangePassword的实现替换为:

 

Java代码   收藏代码
  1. @Autowired  
  2. private UserDetailsManager userDetailsManager;  

 

 submitChangePasswordPage方法也会更加简单了,因为要依赖的当前用户信息将会由JdbcUserDetailsManager为我们确定:

 

Java代码   收藏代码
  1. public String submitChangePasswordPage(@RequestParam("oldpassword")   
  2.        String oldPassword,   
  3.   @RequestParam("password") String newPassword) {  
  4.   userDetailsManager.changePassword(oldPassword, newPassword);  
  5.   SecurityContextHolder.clearContext();  
  6.   return "redirect:home.do";  
  7. }  

 在这些修改完成后,你可以重启应用并尝试新的修改密码功能。

 

注意当你没有提供正确的密码时将会发生什么。试想一下会发生什么?并尝试思考怎样调整能使得对用户更友好。

 

尽管我们没有阐述JdbcUserDetailsManager提供的所有功能,但是可以看出它能很容易与简单的JSP页面结合在一起(当然要进行适当授权)以允许管理员来管理站点的用户——这对产品级别的应用是必要的。

空目录


4.2 《Spring Security3》第四章第二部分翻译 -- JdbcDaoImpl的高级配置

JdbcDaoImpl拥有众多的可配置选项使其可以在已存在的schema中使用,或对其功能进行更复杂地调整。在很多场景下,很可能我们只需调整内置UserDetailsService类的配置而不需要写自己的代码。

 

有一个很重要的功能就是在用户(User)和权限(GrantedAuthority)之间添加一个隔离层(a level of indirection——找不到更好的译法了),这通过将GrantedAuthority按照逻辑划分成集合即组(group)来实现。用户能够被分配到一个或多个组,而组的成员被赋予了一系列的GrantedAuthority声明。



 正如在图中所描述的那样,中间的隔离层使得我们可以将相同集合的角色分派给很多人,而这只需要指定新用户到存在的组中即可。将这与我们之前的做法对比,在以前的做法是将GrantedAuthority直接分配给单个的用户。

 

这种将权限进行打包处理的方式可能在以下的场景中用到:

 

 

l  要将用户分成不同的组,而组之间有些角色是重叠的;

l  想要全局地修改一类用户的权限。如,如果你拥有一个“供应商”的分组,而你想要修改他们能否访问应用特定区域的设置;

l  拥有大量的用户,你不需要用户级别的授权配置。

 

除非你的应用用户量很有限,否则很可能要使用基于组的访问控制。这种管理方式的简便性和扩展性带来的价值远远超过了它稍微增加的复杂性。这种将用户权限集中到组中的技术通常叫做基于组的访问控制(Group-Based Access Control ,GBAC)。

【基于组的访问控制几乎在市面上任何安全的操作系统和软件包中都能看到。微软的活动目录(Active Directory,AD)是大范围使用GBAC的典型实现,它把AD的用户纳入组中并给组授权权限。通过使用GBAC,能够指数级得简化对大量基于AD组织的权限管理。想一下你所使用软件的安全功能——用户、分组以及权限是如何管理的?这种方式编写安全功能的利弊是什么?】

 

让我们对JBCP Pets添加一层抽象,并将基于组的授权理念应用于这个站点。

配置基于组的授权

我们会为站点添加两个组——普通用户(我们将其称为“Users”)和管理员(我们将其称为“Administrators”)。通过修改用于启动数据库的SQL脚本,将已经存在的guest和admin账号分配到合适的组中。

配置JdbcDaoImpl以使用用户组

首先,我们需要为JdbcDaoImpl的自定义实现类设置属性以启用组的功能,并关闭对用户直接授权的功能。在dogstore-base.xml中添加如下的bean声明:

 

Xml代码   收藏代码
  1. <bean id="jdbcUserService"   
  2.       class="com.packtpub.springsecurity.security.CustomJdbcDaoImpl">  
  3.   <property name="dataSource" ref="dataSource"/>  
  4.   <property name="enableGroups" value="true"/>  
  5.   <property name="enableAuthorities" value="false"/>  
  6. </bean>  

 注意的是,如果你一直跟着我们的例子在做,并且使用了JdbcUserManager的代码和配置,请对其进行修改,因为在本章的剩余部分我们将使用CustomJdbcDaoImpl。

修改初始载入的SQL脚本

我们需要简单修改构建数据库的SQL语句:

l  定义我们的组信息;

l  指定GrantedAuthority声明到组中;

l  指定用户到组中。

 

简单起见,我们声明一个名为test-users-groups-data.sql的新SQL脚本。

首先,添加组:

 

Sql代码   收藏代码
  1. insert into groups(group_name) values ('Users');  
  2. insert into groups(group_name) values ('Administrators');  
 

接下来,指定角色到组中:

 

Sql代码   收藏代码
  1. insert into group_authorities(group_id, authority) select id,'ROLE_  
  2. USER' from groups where group_name='Users';  
  3. insert into group_authorities(group_id, authority) select id,'ROLE_  
  4. USER' from groups where group_name='Administrators';  
  5. insert into group_authorities(group_id, authority) select id,'ROLE_  
  6. ADMIN' from groups where group_name='Administrators';  

 接下来,创建用户:

 

Sql代码   收藏代码
  1. insert into users(username, password, enabled) values   
  2. ('admin','admin',true);  
  3. insert into users(username, password, enabled) values   
  4. ('guest','guest',true);  

 最后,指定用户到组中:

 

Sql代码   收藏代码
  1. insert into group_members(group_id, username) select id,'guest' from   
  2. groups where group_name='Users';  
  3. insert into group_members(group_id, username) select id,'admin' from   
  4. groups where group_name='Administrators';  
 
修改嵌入式的数据库创建声明

我们需要更新嵌入式HSQL数据库的创建配置指向这个脚本,而不是已经存在的test-data.sql脚本:

 

Xml代码   收藏代码
  1. <jdbc:embedded-database id="dataSource" type="HSQL">  
  2.   <jdbc:script location="classpath:security-schema.sql"/>  
  3.   <jdbc:script location="classpath:test-users-groups-data.sql"/>  
  4. </jdbc:embedded-database>  

 要注意的是,security-schema.sql脚本已经包含了支持组功能的表声明,所以我们不需要修改这个脚本了。

 

到这里,你可以重启JBCP Pets站点,它将与以前的表现完全一致,但是,我们在用户和权限间添加的抽象层使得我们能够更容易地开发开发复杂的用户管理功能。

 

让我们暂时离开JBCP Pets的场景,了解在这个方面上一个更为重要的配置。

使用遗留的或用户自定义的schame实现基于数据库的认证

通常来说,Spring Security的新用户可能需要适配用户、组和角色到已有的数据库schema中。尽管遗留的数据库并不匹配Spring Security要求的数据库schema,但我们还是可以通过配置JdbcDaoImpl来匹配它。

假设我们拥有一个如下图所示的遗留数据库schema,要基于它实现Spring Security:



 我们能够很容易地修改JdbcDaoImpl的配置来使用这个schema并重写我们在JBCP Pets中使用的默认Spring Security表定义和列。

确定正确的JDBC SQL查询

JdbcDaoImpl有三个SQL查询,它们有定义良好的参数和返回列的集合。我们必须机遇它们提供的功能,确定每个查询的SQL。JdbcDaoImpl的每个SQL查询都是使用登录时提供的用户名作为唯一的参数。

查询名

描述

期望得到的SQL列

usersByUsernameQuery

返回匹配用户名的一个或更多的用户。只有返回的第一个用户被使用。

Username (string)

Password (string)

Enabled (Boolean)

authoritiesByUsernameQuery

返回用户被直接授予的权限。一般在GBAC禁用时,被使用。

Username (string)

Granted Authority

(string)

groupAuthoritiesByUsernameQuery

返回用户作为组成员被授予的权限和组的详细信息。在GBAC功能启用时,被使用。

Group Primary Key

(any)

Group Name (any)

Granted Authority

(string)

要注意的是,在一些场景中返回的列在默认的JdbcDaoImpl实现中并没有用到,但我们依旧需要将这些值返回。在进入下一章节前,请花费一点时间尝试写一下基于前面数据库图表中的查询语句。

配置JdbcDaoImpl来使用自定义的SQL查询

给不规范的数据库使用自定义SQL查询,我们需要在Spring Bean的配置文件中修改JdbcDaoImpl的属性。要注意的一点是,为了给JdbcDaoImpl配置JDBC查询,我们不能使用<jdbc-user-service>声明。必要要明确实例化这个bean,如同我们在自定义JdbcDaoImpl实现时所作的那样:

 

Xml代码   收藏代码
  1. <bean id="jdbcUserService"   
  2.       class="com.packtpub.springsecurity.security.CustomJdbcDaoImpl">  
  3.   <property name="dataSource" ref="dataSource"/>  
  4.   <property name="enableGroups" value="true"/>  
  5.   <property name="enableAuthorities" value="false"/>  
  6.   <property name="usersByUsernameQuery">  
  7.     <value>SELECT LOGIN, PASSWORD,   
  8.            1 FROM USER_INFO WHERE LOGIN = ?   
  9.     </value>  
  10.   </property>  
  11.   <property name="groupAuthoritiesByUsernameQuery">  
  12.     <value>SELECT G.GROUP_ID, G.GROUP_NAME, P.NAME   
  13.       FROM USER_INFO U  
  14.       JOIN USER_GROUP UG on U.USER_INFO_ID = UG.USER_INFO_ID  
  15.       JOIN GROUP G ON UG.GROUP_ID = G.GROUP_ID  
  16.       JOIN GROUP_PERMISSION GP ON G.GROUP_ID = GP.GROUP_ID  
  17.       JOIN PERMISSION P ON GP.PERMISSION_ID = P.PERMISSION_ID  
  18.       WHERE U.LOGIN = ?   
  19.     </value>  
  20.   </property>  
  21. </bean>  

 这是Spring Security从已存在且不符合默认schema的数据库中读取设置时,唯一需要配置的地方。需要记住的是,在使用已存在的schema时,通常会需要扩展JdbcDaoImpl以支持修改密码、重命名用户账号以及其他的用户管理功能。

 

如果你使用JdbcUserDetailsManager来完成用户管理的任务,这个类使用了大约20个可配置的SQL查询。请参考Javadoc或源码来了解JdbcUserDetailsManager使用的默认查询。

空目录


4.3 《Spring Security3》第四章第三部分

4.3.1 《Spring Security3》第四章第三部分翻译(上)(配置安全的密码)

         我们回忆第一章:一个不安全应用的剖析中,审计人员认为密码以明文形式进行存储是最高优先级的安全风险。实际上,在任何安全系统中,密码安全都是保证已经经过认证的安全实体是真实可靠的重要方面。安全系统的设计人员必须保证密码存储时,任何恶意的用户想要进行破解都是非常困难的。

         在数据库存储时,需要遵守以下的准则:

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>声明我们能够很容易地配置密码编码:

 

Xml代码   收藏代码
  1. <authentication-manager alias="authenticationManager">  
  2.   <authentication-provider user-service-ref="jdbcUserService">  
  3.     <password-encoder hash="sha"/>  
  4.   </authentication-provider>  
  5. </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的实例

 

Xml代码   收藏代码
  1. <bean class="org.springframework.security.authentication.   
  2.              encoding.ShaPasswordEncoder" id="passwordEncoder"/>  

 你会发现我们使用了SHA-1的PasswordEncoder实现。这是一个高效的单向加密算法,在密码存储中经常用到。

配置AuthenticationProvider

         我们需要配置DaoAuthenticationProvider来持有一个对PasswordEncoder的引用,这样它就可以在用户登录时,编码并比较用户提供的密码。添加<password-encoder>声明并指向我们在前面定义的bean的ID:

 

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

 如果在此时重启应用,并尝试登录,你会发现前面合法的登录凭证现在都会被拒绝。这是因为数据库中存储的密码(在启动时通过test-users-groups-data.sql脚本加载的)并没有以加密的形式存储。我们需要用一些java代码对启动数据进行后置的处理。

编写数据库启动的密码编码器

         我们对SQL加载的数据进行编码的方式是使用了Spring bean的初始化方法,它将在embedded-database bean实例化完成后执行。这个bean, com.packtpub.springsecurity.security.DatabasePasswordSecurerBean很简单:

 

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

 

 

代码使用了JdbcTemplate功能来遍历所有的数据库中所有的用户并使用注入的PasswordEncoder引用对密码进行编码。每一个密码都进行了更新。

配置启动的密码编码

我们需要配置这个Spring bean的声明以使其在web应用启动时及<embedded-database>初始化后再进行该类的初始化。Spring bean的依赖跟踪机制保证DatabasePasswordSecurerBean能够在合适的时机执行:

 

Xml代码   收藏代码
  1. <bean class="com.packtpub.springsecurity.security.   
  2.              DatabasePasswordSecurerBean"   
  3.              init-method="secureDatabase" depends-on="dataSource">  
  4.   <property name="dataSource" ref="dataSource"/>  
  5. </bean>  

 如果你此时重启JBCP Pets应用,你会发现数据库中的密码已经进行了编码,登录功能可以正常使用了。


4.3.2 《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.     truetruetrue, 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, truetruetrue,   
  34.                AuthorityUtils.NO_AUTHORITIES, salt);  
  35.             }  
  36.         });  
  37.   }  
  38. }  

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

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

空目录

4.4 《Spring Security3》第四章第四部分翻译(Remember me后台存储和SSL)附前四章doc文件

到此,前四章翻译完成,欢迎朋友提些意见!

将Remember me功能迁移至数据库

         现在你可能会意识到我们remember me功能的实现,能够在应用重启前很好的使用,但在应用重启时用户的session会被丢失。这对用户来说会不太便利,他们不应该关心JBCP Pets的维护信息。

         幸运的是,Spring Security提供了将rememberme token持久化到任何存储的接口o.s.s.web.authentication.rememberme.PersistentTokenRepository,并提供了这个接口的JDBC实现。

配置基于数据库的remember me tokens

         在这里,修改remember me的配置以持久化到数据库是非常简单的。Spring Security配置的解析器能够识别出<remember-me>声明的data-source-ref新属性并为RememberMeServices切换实现类。让我们了解完成这个功能所需要的步骤。

添加SQL以创建remember me schema

         我们需要将包含期望schema定义的SQL文件放在classpath下(WEB-INF/classes中),它会与我们在前面使用的其它启动SQL脚本放在一起。我们将这个SQL脚本命名为remember-me-schema.sql:

 

Sql代码   收藏代码
  1. create table persistent_logins (  
  2.   username varchar_ignorecase(50) not null,   
  3.   series varchar(64) primary key,  
  4.   token varchar(64) not null,   
  5.   last_used timestamp not null);  
 

 

为嵌入式数据库声明添加新的SQL脚本

         接下来,在dogstore-security.xml文件的<embedded-database>声明中添加对新SQL脚本的引用:

 

Sql代码   收藏代码
  1. <jdbc:embedded-database id="dataSource" type="HSQL">  
  2.   <jdbc:script location="classpath:security-schema.sql"/>  
  3.   <jdbc:script location="classpath:remember-me-schema.sql"/>   
  4.   <jdbc:script location="classpath:test-users-groups-data.sql"/>  
  5. </jdbc:embedded-database>  
 

 

配置remember me服务持久化到数据库

         最后我们需要对<remember-me>声明做一些简单的配置修改使其指向我们使用的data source:

 

Xml代码   收藏代码
  1. <http auto-config="true" use-expressions="true"   
  2.       access-decision-manager-ref="affirmativeBased">  
  3.   <intercept-url pattern="/login.do" access="permitAll"/>  
  4.   <intercept-url pattern="/account/*.do"    
  5.              access="hasRole('ROLE_USER') and fullyAuthenticated"/>  
  6.   <intercept-url pattern="/*" access="hasRole('ROLE_USER')"/>  
  7.   <form-login login-page="/login.do" />  
  8.   <remember-me key="jbcpPetStore" token-validity-seconds="3600"   
  9.                data-source-ref="dataSource"/>  
  10.   <logout invalidate-session="true" logout-success-url=""   
  11.           logout-url="/logout"/>  
  12. </http>  
 这就是我们所有要做的。现在,如果你重启应用,将不会丢失之前合法用户设置的remember me cookie。

 

基于数据库后台的持久化tokens是不是更安全?

         你可能会回忆起我们在第三章实现的TokenBasedRememberMeServices,它用MD5哈希算法将一系列与用户相关的数据编码成安全的cookie,这种方式很难(但并非不可能)篡改。o.s.s.web.authentication.rememberme.PersistentTokenBasedRememberMeServices类实现了持久化tokens以及对token安全处理,它通过一个校验方法以稍微不同的方式处理潜在的篡改。

         PersistentTokenBasedRememberMeServices为每个用户创建一个唯一的序列号,用户在继续交互和认证时要使用序列号中唯一的tokens。序列号和token被存储在cookie中,在认证时要用来与存储的token进行对比。序列号和token都是基于配置的长度随机生成的,这使得恶意用户成功暴力破解的可能性很小了。

         与TokenBasedRememberMeServices类似,持久化的token也可能被cookie窃取或其它的man-in-the-middle技术。在使用持久化token时,依旧建议用自定义的子类将IP地址合并到持久化token中,以及对站点的敏感区域强制使用用户名和密码认证。

用SSL保护你的站点

         在日常使用在线站点时,你很可能已经听说或使用过SSL。安全套接字层(SSL)协议,以及其后续的传输层安全(TLS),被用来为网络上的HTTP事务提供传输层的安全——它们被称为安全的HTTP事务(HTTPS)。

         简而言之,SSL和TLS以一种对用户透明的方式保护原始的HTTP传输数据,这些数据在客户端浏览器和web服务器之间传输。但是作为开发人员,在设计安全站点时,规划使用SSL是很重要的。Spring Security提供了一系列的配置选项可以灵活的将SSL集成到web应用中。

【尽管SSL和TLS是不同的协议(TLS是更成熟的协议),单数大多数人更熟悉SSL这个术语,所以在本书的剩余部分,我们使用这个术语来代指SSL和TLS两个协议。】

详细介绍SSL协议的机制已经超出了本书的范围,有一些很好的书籍和技术论文很详细地介绍了其规范和协议(你可以从RFC:5246:传输安全协议(TLS)Version1.2开始,在以下地址http://tools.ietf.org/html/rfc5246)

配置Apache Tomcat以支持SSL

         首先且最重要的是,如果你计划执行如下SSL相关的例子,需要配置应用服务器以支持SSL连接。对于Apache Tomcat,这相对很容易。如果你在使用其它的应用服务器,请查看文档的相关部分。

生成server key store

         我们需要使用Java的keytool命令来生成一个key store。打开一个命令提示窗口,并输入以下的命令:

 

Java代码   收藏代码
  1. keytool -genkeypair -alias jbcpserver -keyalg RSA -validity 365   
  2.   -keystore tomcat.keystore -storetype JKS  

 

 按照提示进行如下的输入。输入密码password作为key store和个人密钥的密码。

 

Java代码   收藏代码
  1. What is your first and last name?  
  2.   [Unknown]:  JBCP Pets Admin  
  3. What is the name of your organizational unit?  
  4.   [Unknown]:  JBCP Pets  
  5. What is the name of your organization?  
  6.   [Unknown]:  JBCP Pets  
  7. What is the name of your City or Locality?  
  8.   [Unknown]:  Anywhere  
  9. What is the name of your State or Province?  
  10.   [Unknown]:  NH  
  11. What is the two-letter country code for this unit?  
  12.   [Unknown]:  US  
  13. Is CN=JBCP Pets Admin, OU=JBCP Pets, O=JBCP Pets, L=Anywhere, ST=NH, C=US   
  14. correct?  
  15.   [no]:  yes  
 这将会在当前目录下,生成一个名为tomcat.keystore的文件。这就是启用Tomcat SSL所使用的key store。

 

【注意的是要执行的是genkeypair命令(在早于java 6的释放版本中要使用keytool的genkey命令)】

         为了下一步的操作,需要记住这个文件的地址。

配置Tomcat的SSL Connector

         在Apache Tomcat的conf目录下,用XML编辑器(Eclipse或类似的都可以)打开server.xml,并取消注释或添加SSL Connector声明。应该如下所示:

 

Xml代码   收藏代码
  1. <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"  
  2.   maxThreads="150" scheme="https" secure="true"  
  3.   sslProtocol="TLS"   
  4.   keystoreFile="conf/tomcat.keystore"  
  5.   keystorePass="password"/>  
 确保在上一步中生成的tomcat.keystore文件被copy到了Tomcat安装路径的conf目录下。在配置后,Tomcat服务器可以重启,JBCP  Pets应用能够在一个安全的端口 https://localhost:8443/JBCPPets/上进行访问。

 

         取决于不同的浏览器,可能需要包含https而不是http。这样的问题可能会比较难发现,你可能会比较奇怪为什么不能看到JBCP  Pets的主页。

对站点进行自动的安全保护

         我们假设你在对客户的数据进行SSL保护时遇到了麻烦,你想把应用的特定部分置于SSL的保护之下。幸运的是,Spring Security让这一切变得很简单,只需要在<intercept-url>声明上添加一个配置属性。

         requires-channel属性能够添加到任何<intercept-url>声明中,以要求所有匹配的URL要以特定的协议(HTTP,HTTPS或都可以)进行传递。如果按照这种形式来增强JBCP Pets站点,配置可能如下所示:

 

Xml代码   收藏代码
  1. <http auto-config="true" use-expressions="true">  
  2.   <intercept-url pattern="/login.do" access="permitAll"   
  3.                  requires-channel="https"/>  
  4.   <intercept-url pattern="/account/*.do"   
  5.                  access="hasRole('ROLE_USER') and fullyAuthenticated"   
  6.                  requires-channel="https"/>  
  7.   <intercept-url pattern="/*" access="permitAll"   
  8.                  requires-channel="any"/>  
  9.   <!-- ... -->  
  10. </http>    
 如果此时重启应用,你将会发现:

 

l  现在访问登录页和账号页需要HTTPS,浏览器将会为用户自动从不安全的(HTTP)URL重定向到安全的URL。例如,尝试访问http://localhost:8080/JBCPPets/login.do将会被定向到https://localhost:8443/JBCPPets/login.do

l  如果用户被切换到了安全的HTTPS URL,如果他访问一个不必要使用HTTPS的URL,他能继续保留在HTTPS状态。

 

我们可以想象这种配置对于安全的好处——大多数的现代应用服务器使用一个secure标识session的cookie,所以强制要求登录页是安全的(如果这是应用的session被首次分配的地方)能够保证session的cookie能够被安全的传输,所以出现session劫持的可能性也更小。另外,直接将SSL加密配置在安全声明上的做法,能够很容易的保证应用中所有敏感的页面被适当和完整的保护。

为用户自动切换适当协议(HTTP或HTTPS)的功能,通过Spring Security过滤器链上的另外一个servlet过滤器来实现的(它的位置很靠前,在SecurityContextPersistenceFilter后面)。如果任何URL用requires-channel属性声明使用特定类型的协议,o.s.s.web.access.channel.ChannelProcessingFilter将会自动添加到过滤器链上

         ChannelProcessingFilter在请求时的交互过程如下图所示:



 如果你的应用需要超出内置功能的复杂逻辑,ChannelProcessingFilter的设计可以进行扩展和增强。注意我们尽管只在图中说明了SecureChannelProcessor和RetryWithHttpsEntryPoint的实现,但是有类似的类去校验和处理声明为要求HTTP的URL。

         注意,ChannelEntryPoint使用了HTTP 302的URL重写,这就不能使用这种技术去重定向POST的URL(尽管典型的POST请求不应该在安全协议和不安全协议间传递,因为大多数的应用都会对这种行为提出警告)。

安全的端口映射

         在一些特定的环境中,可能不会使用标准的HTTP和HTTPS端口,其默认为80/443或8080/8443。在这种情况下,你必须配置你的应用包含明确的端口映射,这样ChannelEntryPoint的实现能够确定当重定向用户到安全或不安全的URL时,使用什么端口。

         这仅需要增加额外的配置元素<port-mappings>,它能够指明除了默认的端口以外,额外的HTTP 的HTTPS端口:

 

Xml代码   收藏代码
  1. <port-mappings>  
  2.   <port-mapping http="9080" https="9443"/>  
  3. </port-mappings>  
 如果你的应用服务器在反向代理后的话,端口映射将会更加的重要。

 

小结

         在本章中,我们:

l  介绍了把安全数据存储在支持JDBC的数据库中是如何配置的;

l  配置JBCP Pets使用数据库来进行用户认证以及高安全性的密码存储,这里我们使用了密码加密和salting技术;

l  管理JDBC持久化到数据中的用户;

l  配置用户到安全组中。组被授予角色,而不是直接对用户进行角色的指定。这提高了站点和用户功能的可管理性;

l  介绍了Spring Security使用遗留的(非默认的)数据库schema;

l  讲解了HTTPS技术的配置及应用,它能够提高数据在访问应用敏感内容时的安全性。

         在接下来的章节中,我们将会介绍Spring Security一些高级的授权功能,并引入Spring Security的JSP标签以实现良好的授权。

 


空目录

空目录

5 第五章












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值