如今咱們將要對基於內存的UserDetailsService進行簡單的擴展以使其支持用戶修改密碼。由於這個功能對用戶名和密碼存於數據庫的場景更有用,因此基於o.s.s.core.userdetails.memory.InMemoryDaoImpl擴展的實現不會關注存儲機制,而是關注框架對這種方式擴展的總體流程和設計。在第四章中,咱們將經過將其轉移到數據庫後臺存儲來進一步擴展咱們的基本功能。html
Spring Security框架提供的InMemoryDaoImpl內存憑證存儲使用了一個簡單的map來存儲用戶名以及關聯的UserDetails。InMemoryDaoImpl使用的UserDetails實現類是o.s.s.core.userdetails.User,這個實現類將會在Spring Security API中還會看到。java
這個擴展的設計有意的進行了簡化並省略了一些重要的細節,如須要用戶在修改密碼前提供他們的舊密碼。添加這些功能將做爲練習留給讀者。web
咱們要首先寫自定義的類來擴展基本的InMemoryDaoImpl,並提供容許用戶修改密碼的方法。由於用戶是不可改變的對象,因此咱們copy已經存在的User對象,只是將密碼替換爲用戶提交的值。在這裏咱們定義一個接口在後面的章節中將會重用,這個接口提供了修改密碼功能的一個方法:spring
package com.packtpub.springsecurity.security; // imports omitted public interface IChangePassword extends UserDetailsService { void changePassword(String username, String password); }
如下的代碼爲基於內存的用戶數據存儲提供了修改密碼功能:數據庫
package com.packtpub.springsecurity.security; public class InMemoryChangePasswordDaoImpl extends InMemoryDaoImpl implements IChangePassword { @Override public void changePassword(String username, String password) { // get the UserDetails User userDetails = (User) getUserMap().getUser(username); // create a new UserDetails with the new password User newUserDetails = new User(userDetails.getUsername(),password, userDetails.isEnabled(), userDetails.isAccountNonExpired(), userDetails.isCredentialsNonExpired(), userDetails.isAccountNonLocked(), userDetails.getAuthorities()); // add to the map getUserMap().addUser(newUserDetails); } }
比較幸運的是,只有一點代碼就能將這個簡單的功能加到自定義的子類中了。咱們接下來看看添加自定義UserDetailsService到pet store應用中會須要什麼樣的配置。安全
如今,咱們須要從新配置Spring Security的XML配置文件以使用新的UserDetailsService實現。這可能比咱們預想的要困難一些,由於<user-service>元素在Spring Security的處理過程當中有特殊的處理。須要明確聲明咱們的自定義bean並移除咱們先前聲明的<user-service>元素。咱們須要把:app
<authentication-manager alias="authenticationManager"> <authentication-provider> <user-service id="userService"> <user authorities="ROLE_USER" name="guest" password="guest"/> </user-service> </authentication-provider> </authentication-manager>
修改成:框架
<authentication-provider user-service-ref="userService"/>
在這裏咱們看到的user-service-ref屬性,引用的是一個id爲userService的Spring Bean。因此在dogstore-base.xml Spring Beans配置文件中,聲明瞭以下的bean:jsp
<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節詳細描述了以逗號分隔的提供用戶信息的語法。ide
【高效使用基於內存的UserDetailsService。有一個常見的場景使用基於內存的UserDetailsService和硬編碼的用戶列表,那就是編寫安全組件的單元測試。編寫單元測試的人員常常編碼或配置最簡單的場景來測試組件的功能。使用基於內存的UserDetailsService以及定義良好的用戶和GrantedAuthority值爲測試編寫人員提供了很可控的測試環境。】
到如今,你能夠重啓JBCP Pets應用,應該沒有任何的配置錯誤報告。咱們將在這個練習的最後的兩步中,完成UI的功能。
咱們接下來將會創建一個容許用戶修改密碼的簡單頁面。
這個頁面將會經過一個簡單的連接添加到「My Account」頁面。首先,咱們在/account/home.jsp文件中添加一個連接:
<p> Please find account functions below... </p> <ul> <li><a href="changePassword.do">Change Password</a></li> </ul>
接下來,在/account/ changePassword.jsp文件中創建「Change Password」頁面自己:
<?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,它是帳號信息主頁的簡單處理類)。
咱們須要將對自定義UserDetailsService的應用注入到com.packtpub.springsecurity.web.controller.AccountController,這樣咱們就能使用修改密碼的功能了。Spring的@Autowired註解實現了這一功能:
@Autowired private IChangePassword changePasswordDao;
兩個接受請求的方法分別對應渲染form以及處理POST提交的form數據:
@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。
在第四章中,咱們將會使用基於數據庫的認證信息存儲並學習怎樣保證數據庫中的密碼和其餘敏感數據的安全。