实现修改密码管理
现在我们将要对基于内存的
UserDetailsService
进行简单的扩展以使其支持用户修改密码。因为这个功能对用户名和密码存于数据库的场景更有用,所以基于
o.s.s.core.userdetails.memory.InMemoryDaoImpl
扩展的实现不会关注存储机制,而是关注框架对这种方式扩展的整体流程和设计。在第四章中,我们将通过将其转移到数据库后台存储来进一步扩展我们的基本功能。
扩展基于内存的凭证存储以支持修改密码
Spring Security
框架提供的
InMemoryDaoImpl
内存凭证存储使用了一个简单的
map
来存储用户名以及关联的
UserDetails
。
InMemoryDaoImpl
使用的
UserDetails
实现类是
o.s.s.core.userdetails.User
,这个实现类将会在
Spring
Security API
中还会看到。
?
这个扩展的设计有意的进行了简化并省略了一些重要的细节,如需要用户在修改密码前提供他们的旧密码。添加这些功能将作为练习留给读者。
用
InMemoryChangePasswordDaoImpl
扩展
InMemoryDaoImpl
我们要首先写自定义的类来扩展基本的
InMemoryDaoImpl
,并提供允许用户修改密码的方法。因为用户是不可改变的对象,所以我们
copy
已经存在的
User
对象,只是将密码替换为用户提交的值。在这里我们定义一个接口在后面的章节中将会重用,这个接口提供了修改密码功能的一个方法:
?
Java代码 ?
-
package
?com.packtpub.springsecurity.security;??
-
??
-
public
?
interface
?IChangePassword?
extends
?UserDetailsService?{????
-
void
?changePassword(String?username,?String?password);??
-
}??
?以下的代码为基于内存的用户数据存储提供了修改密码功能:
?
Java代码 ?
-
package
?com.packtpub.springsecurity.security;??
-
public
?
class
?InMemoryChangePasswordDaoImpl?
extends
?InMemoryDaoImpl???
-
implements
?IChangePassword?{??
-
??@Override
??
-
??public
?
void
?changePassword(String?username,???
-
????????????????String?password)?{??
-
????
??
-
????User?userDetails?=???
-
????????(User)?getUserMap().getUser(username);??
-
????
??
-
????User?newUserDetails?=???
-
????????new
?User(userDetails.getUsername(),password,??
-
????????userDetails.isEnabled(),???
-
????????userDetails.isAccountNonExpired(),??
-
??????userDetails.isCredentialsNonExpired(),??
-
????????userDetails.isAccountNonLocked(),??
-
????????userDetails.getAuthorities());??
-
????
??
-
????getUserMap().addUser(newUserDetails);??
-
??}??
-
}??
?比较幸运的是,只有一点代码就能将这个简单的功能加到自定义的子类中了。我们接下来看看添加自定义
UserDetailsService
到
pet store
应用中会需要什么样的配置。
配置
Spring Security
来使用
InMemoryChangePasswordDaoImpl
现在,我们需要重新配置
Spring Security
的
XML
配置文件以使用新的
UserDetailsService
实现。这可能比我们预想的要困难一些,因为
<user-service>
元素在
Spring Security
的处理过程中有特殊的处理。需要明确声明我们的自定义
bean
并移除我们先前声明的
<user-service>
元素。我们需要把:
?
Xml代码 ?
-
<
authentication-manager
?
alias
=
"authenticationManager"
>
??
-
??<
authentication-provider
>
??
-
????<
user-service
?
id
=
"userService"
>
??
-
??????<
user
?
authorities
=
"ROLE_USER"
?
name
=
"guest"
?
password
=
"guest"
/>
??
-
????</
user-service
>
??
-
??</
authentication-provider
>
??
-
</
authentication-manager
>
??
?修改为:
?
Xml代码 ?
-
<
authentication-provider
?
user-service-ref
=
"userService"
/>
??
?在这里我们看到的
user-service-ref
属性,引用的是一个
id
为
userService
的
Spring Bean
。所以在
dogstore-base.xml Spring Beans
配置文件中,声明了如下的
bean
:
?
Xml代码 ?
-
<
bean
?
id
=
"userService"
?
class
="com.packtpub.springsecurity.security.??
-
InMemoryChangePasswordDaoImpl">
??
-
??<
property
?
name
=
"userProperties"
>
??
-
????<
props
>
??
-
??????<
prop
?
key
=
"guest"
>
guest,ROLE_USER
</
prop
>
??
-
????</
props
>
??
-
??</
property
>
??
-
</
bean
>
??
?你可能会发现,这里声明用户的语法不如
<user-service>
包含的
<user>
元素更易读。遗憾的是,
<user>
元素只能使用在默认的
InMemoryDaoImpl
实现类中,我们不能在自定义的
UserDetailsService
中使用了。在这里例子中,这个限制使得事情稍微复杂了一点,但是在实际中,没有人会愿意长期的将用户定义信息放在配置文件中。对于感兴趣的读者,
Spring Security 3
参考文档中的
6.2
节详细描述了以逗号分隔的提供用户信息的语法。
【高效使用基于内存的
UserDetailsService
。有一个常见的场景使用基于内存的
UserDetailsService
和硬编码的用户列表,那就是编写安全组件的单元测试。编写单元测试的人员经常编码或配置最简单的场景来测试组件的功能。使用基于内存的
UserDetailsService
以及定义良好的用户和
GrantedAuthority
值为测试编写人员提供了很可控的测试环境。】
?
到现在,你可以重启
JBCP Pets
应用,应该没有任何的配置错误报告。我们将在这个练习的最后的两步中,完成
UI
的功能。
构建一个修改密码的页面
我们接下来将会建立一个允许用户修改密码的简单页面。
?这个页面将会通过一个简单的链接添加到“
My Account
”页面。首先,我们在
/account/home.jsp
文件中添加一个链接:
?
Html代码 ?
-
<
p
>
??
-
??Please?find?account?functions?below...??
-
</
p
>
??
-
<
ul
>
??
-
??<
li
>
<
a
?
href
=
"changePassword.do"
>
Change?Password
</
a
>
</
li
>
??
-
</
ul
>
??
?接下来,在
/account/ changePassword.jsp
文件中建立“
Change Password
”页面本身:
?
Html代码 ?
-
<?
xml
?
version
=
"1.0"
?
encoding
=
"ISO-8859-1"
?
?>
??
-
<
%@?page?
language
=
"java"
?
contentType
=
"text/html;?charset=ISO-8859-1"
??
-
pageEncoding
=
"ISO-8859-1"
%
>
??
-
<
jsp:include
?
page
=
"../common/header.jsp"
>
??
-
??<
jsp:param
?
name
=
"pageTitle"
?
value
=
"Change?Password"
/>
??
-
</
jsp:include
>
??
-
<
h1
>
Change?Password
</
h1
>
??
-
<
form
?
method
=
"post"
>
??
-
??<
label
?
for
=
"password"
>
New?Password
</
label
>
:??
-
??<
input
?
id
=
"password"
?
name
=
"password"
?
size
=
"20"
?
maxlength
=
"50"
???
-
type
=
"password"
/>
??
-
??<
br
?
/>
??
-
??<
input
?
type
=
"submit"
?
value
=
"Change?Password"
/>
???
-
</
form
>
??
-
<
jsp:include
?
page
=
"../common/footer.jsp"
/>
??
?
?
最后我们还要添加基于
Spring MVC
的
AccountController
来处理密码修改的请求(在前面的章节中我们没有介绍
AccountController
,它是账号信息主页的简单处理类)。
为
AccountController
添加修改密码的处理
我们需要将对自定义
UserDetailsService
的应用注入到
com.packtpub.springsecurity.web.controller.AccountController
,这样我们就能使用修改密码的功能了。
Spring
的
@Autowired
注解实现了这一功能:
?
Java代码 ?
-
@Autowired
??
-
private
?IChangePassword?changePasswordDao;??
?两个接受请求的方法分别对应渲染
form
以及处理
POST
提交的
form
数据:
?
Java代码 ?
-
@RequestMapping
(value="/account/changePassword.??
-
do
",method=RequestMethod.GET)??
-
public
?
void
?showChangePasswordPage()?{????
-
}??
-
@RequestMapping
(value="/account/changePassword.??
-
do
",method=RequestMethod.POST)??
-
public
?String?submitChangePasswordPage(
@RequestParam
(
"password"
)???
-
String?newPassword)?{??
-
??Object?principal?=?SecurityContextHolder.getContext().??
-
getAuthentication().getPrincipal();??
-
??String?username?=?principal.toString();??
-
??if
?(principal?
instanceof
?UserDetails)?{??
-
username?=?((UserDetails)principal).getUsername();??
-
}??
-
??changePasswordDao.changePassword(username,?newPassword);??
-
??SecurityContextHolder.clearContext();??
-
??return
?
"redirect:home.do"
;??
-
}??
?完成这些配置后,重启应用,并在站点的“
My Account
”下找到“
Change Password
”功能。
练习笔记
比较精细的读者可能意识到这个修改密码的
form
相对于现实世界的应用来说太简单了。确实,很多的修改密码实现要复杂的多,并可能包含如下的功能:
l?
密码确认——通过两个文本框,确保用户输入的密码是正确的;
l?
旧密码确认——通过要求用户提供要修改的旧密码,增加安全性(这对使用
remember me
功能的场景特别重要);
l?
密码规则校验——检查密码的复杂性以及密码是否安全。
?
你可能也会注意到当你使用这个功能的时,会被自动退出。这是因为
SecurityContextHolder.clearContext()
调用导致的,它会移除用户的
SecurityContext
并要求他们重新认证。在练习中,我们需要给用户做出提示或者找到方法让用户免于再次认证。
小结
在本章中,我们更细节的了解了认证用户的生命周期并对
JBCP Pet?
Store
进行了结构性的修改。我们通过添加真正的登录和退出功能,进一步的满足了安全审计的要求,并提升了用户的体验。我们也学到了如下的技术:
l?
配置并使用基于
Spring
MVC
的自定义用户登录界面;
l?
配置
Spring
Security
的退出功能;
l?
使用
remember me
功能;
l?
通过记录
IP
地址,实现自定义的
remember
me
功能;
l?
实现修改密码功能;
l?
自定义
UserDetailsService
和
InMemoryDaoImpl
。
在第四章中,我们将会使用基于数据库的认证信息存储并学习怎样保证数据库中的密码和其他敏感数据的安全。