在本教程中,我們將看到如何將GWT與Spring的安全模塊(即Spring Security)集成在一起。 我們將看到如何保護GWT入口點,如何檢索用戶的憑據以及如何記錄各種身份驗證事件。 此外,我們將實現自定義身份驗證提供程序,以便可以重用現有的身份驗證方案。
如果您是JavaCodeGeeks的普通讀者,那么現在您可能應該知道我們真的很喜歡GWT 。 過去,賈斯汀(Justin)在GWT上寫了一些殺手G的文章: 如何將GWT與Spring和Hibernate(JPA)集成以及如何在混合中添加Eclipse和Maven 。 此外,我已經寫了關于如何在GWT應用程序中添加JSON功能 , 如何為GWT添加CAPTCHA以及如何開始使用SmartGWT的文章 。 最后,Pat寫了有關構建自己的GWT Spring Maven原型并集成GWT,EJB3,Maven和JBoss的文章 。
因此,我們現在開始使用Spring的Security模塊就不足為奇了。 如官方站點所述, Spring Security是一個功能強大且高度可定制的身份驗證和訪問控制框架。 它是用于保護基于Spring的應用程序的實際標準 。 Spring Security是Acegi框架的演變,該框架在后臺使用Spring以便主要為Web應用程序提供安全性。 但是,Spring Security現在是一個完善的安全框架,它不僅包含針對Web的功能,而且還包含針對LDAP集成和ACL創建的功能。 在開始本教程之前,最好先閱讀一下Spring Security參考文檔并準備好Spring Security API Javadocs 。
在本教程中,我將使用GWT 2.1.0和Spring Security 3.0.5。 您可以在此處下載最新的生產版本。 您可能已經猜到了,還需要Spring核心框架中的一些庫。 您可以在此處下載框架。
讓我們開始在Eclipse中創建一個新的Web應用程序項目(我想您已經安裝了Eclipse的Google插件,并且已經部署了GWT)。 我為該項目的名稱選擇了深奧的名稱“ GwtSpringSecurityProject”。 Eclipse屏幕如下所示:
將Spring安全性添加到我們的項目的第一步是在“ web.xml”文件中聲明一個過濾器。 這個過濾器是FilterChainProxy類的實例,它將攔截所有傳入的請求,并將請求的控件委派給適當的Spring處理程序。 相關的Web聲明文件片段如下:
…
<filter><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter><filter-mapping><filter-name>springSecurityFilterChain</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
...
我們還必須在“ web.xml”中定義一個ContextLoaderListener以便引導Spring上下文。 這是通過以下代碼段完成的:
…<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>
...
接下來,我們在“ war / WEB-INF”文件夾中創建一個名為“ applicationContext.xml”的文件。 在那里,我們聲明了Spring Security相關信息。 最重要的元素是“ http ”,它可以用來定義應在哪些URL上應用安全性,以及用戶應具有哪些角色才能訪問特定資源。 在我們的示例中,代碼段如下:
…
<http auto-config="true"><intercept-url pattern="/gwtspringsecurityproject/**" access="ROLE_USER"/><intercept-url pattern="/gwt/**" access="ROLE_USER"/><intercept-url pattern="/**/*.html" access="ROLE_USER"/><intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
</http>
...
簡而言之,上述內容要求角色“ ROLE_USER”才能訪問“ gwt”和“ gwtspringsecurityproject”文件夾(與GWT相關的資源所在)下的文件。 同樣,所有HTML文件(如GWT的入口點)都需要相同的角色。 “ IS_AUTHENTICATED_ANONYMOUSLY”意味著所有用戶都可以訪問特定資源,而不必成為特定角色的一部分。 通過簡單地使用“ http ”元素,Spring將使用默認的登錄頁面和注銷URL。
所有身份驗證請求均由AuthenticationManager處理,因此必須在文件中聲明其實例。 更具體地說,通常將請求委托給AuthenticationProvider 。 可以使用一些已經創建的實現,例如DaoAuthenticationProvider (與DB中定義的角色和用戶一起使用時)或LdapAuthenticationProvider (根據LDAP服務器對用戶進行身份驗證)。 但是,出于本教程的目的,我們將創建一個自定義身份驗證提供程序,并將其與spring的安全基礎結構集成。
在深入研究應用程序的代碼之前,我們必須首先處理依賴項。 這是必須添加到項目的類路徑中的JAR:
- org.springframework.context-3.0.5.RELEASE.jar
- Spring安全核心-3.0.5.RELEASE.jar
- spring-security-web-3.0.5.RELEASE.jar
好的,現在我們準備好了。 我們的提供程序非常簡單,僅使用靜態Map來存儲用戶及其相應的密碼。 這是代碼:
package com.javacodegeeks.gwt.security.server.auth;import java.util.HashMap;
import java.util.Map;import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;public class CustomAuthenticationProvider implements AuthenticationProvider {private static Map<String, String> users = new HashMap<String, String>();static {users.put("fabrizio", "javacodegeeks");users.put("justin", "javacodegeeks");}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String username = (String) authentication.getPrincipal();String password = (String)authentication.getCredentials();if (users.get(username)==null)throw new UsernameNotFoundException("User not found");String storedPass = users.get(username);if (!storedPass.equals(password))throw new BadCredentialsException("Invalid password");Authentication customAuthentication = new CustomUserAuthentication("ROLE_USER", authentication);customAuthentication.setAuthenticated(true);return customAuthentication;}@Overridepublic boolean supports(Class<? extends Object> authentication) {return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);}}
讓我們從頭開始對該代碼進行詳細說明。 supports方法定義了此提供程序提供的身份驗證的類型。 在我們的例子中, UsernamePasswordAuthenticationToken是我們希望處理的那個。 該實現旨在簡化用戶名和密碼的顯示。
實現了authenticate方法,并在其中檢索登錄表單中提供的用戶名(通過getPrincipal方法)以及隨附的密碼(通過getCredentials方法)。 首先,我們檢查特定的用戶名是否存在,如果不存在,則拋出UsernameNotFoundException 。 同樣,如果用戶名存在但密碼不正確, 則會引發BadCredentialsException 。 請注意,這兩個異常都擴展了父AuthenticationException類。
如果用戶名和密碼均正確,我們將對用戶進行身份驗證。 為此,我們必須返回Authentication接口的具體實例。 在這種情況下,我們必須封裝已知的用戶信息(憑證等)以及用戶所具有的角色(權限)。 請注意,分配的角色(ROLE_USER)與“ applicationContext.xml”文件中聲明的角色匹配。 另外,必須調用setAuthenticated方法(以true作為參數),以向其余身份驗證鏈指示特定用戶已成功通過我們的模塊進行身份驗證。 讓我們看看在這種情況下如何定義自定義身份驗證對象:
package com.javacodegeeks.gwt.security.server.auth;import java.util.ArrayList;
import java.util.Collection;import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;public class CustomUserAuthentication implements Authentication {private static final long serialVersionUID = -3091441742758356129L;private boolean authenticated;private GrantedAuthority grantedAuthority;private Authentication authentication;public CustomUserAuthentication(String role, Authentication authentication) {this.grantedAuthority = new GrantedAuthorityImpl(role);this.authentication = authentication;}@Overridepublic Collection<GrantedAuthority> getAuthorities() {Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();authorities.add(grantedAuthority);return authorities;}@Overridepublic Object getCredentials() {return authentication.getCredentials();}@Overridepublic Object getDetails() {return authentication.getDetails();}@Overridepublic Object getPrincipal() {return authentication.getPrincipal();}@Overridepublic boolean isAuthenticated() {return authenticated;}@Overridepublic void setAuthenticated(boolean authenticated) throws IllegalArgumentException {this.authenticated = authenticated;}@Overridepublic String getName() {return this.getClass().getSimpleName();}}
在構造函數中,我們傳遞用戶的角色和原始的Authentication對象。 在已實現的方法中,最重要的一個是getAuthorities ,它返回已授予主體的權限。 該信息在GrantedAuthority對象的集合內提供。
現在讓我們看看“ applicationContext.xml”的樣子:
<?xml version="1.0" encoding="UTF-8"?><beans:beans xmlns="http://www.springframework.org/schema/security"xmlns:beans="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd"><beans:bean id="customAuthListener" class="com.javacodegeeks.gwt.security.server.auth.CustomAuthListener"/><http auto-config="true"><intercept-url pattern="/gwtspringsecurityproject/**" access="ROLE_USER"/><intercept-url pattern="/gwt/**" access="ROLE_USER"/><intercept-url pattern="/**/*.html" access="ROLE_USER"/><intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" /></http><beans:bean id="customAuthenticationProvider" class="com.javacodegeeks.gwt.security.server.auth.CustomAuthenticationProvider" /> <authentication-manager alias="authenticationManager"><authentication-provider ref="customAuthenticationProvider"/></authentication-manager></beans:beans>
除“ CustomAuthListener”外,聲明文件的每個元素均已定義。 作為Spring框架的一部分,Spring Security允??許應用程序開發人員提供回調,這些回調將在應用程序生命周期的特定部分被調用。 因此,當發生特定的身份驗證事件時,我們可以注冊要調用的方法。 在我們的例子中,我們將創建一個偵聽器,該偵聽器接收AbstractAuthorizationEvent ,即所有與安全攔截有關的事件。 讓我們看看這是如何實現的:
package com.javacodegeeks.gwt.security.server.auth;import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;public class CustomAuthListener implements ApplicationListener<AbstractAuthenticationEvent> {private static final Log logger = LogFactory.getLog(CustomAuthListener.class);@Overridepublic void onApplicationEvent(AbstractAuthenticationEvent event) {final StringBuilder builder = new StringBuilder();builder.append("Authentication event ");builder.append(event.getClass().getSimpleName());builder.append(": ");builder.append(event.getAuthentication().getName());builder.append("; details: ");builder.append(event.getAuthentication().getDetails());if (event instanceof AbstractAuthenticationFailureEvent) {builder.append("; exception: ");builder.append(((AbstractAuthenticationFailureEvent) event).getException().getMessage());}logger.warn(builder.toString());}}
在我們的實現中,我們僅記錄所有成功和不成功的身份驗證事件(基于LoggerListener類),但是在此處提供您自己的業務邏輯顯然很簡單。
最后,我們將創建一個GWT異步服務器端服務,該服務將向客戶端提供有關用戶及其登錄用戶名的信息。 如果您最熟悉GWT,那么理解代碼就不會有任何問題。 這是兩個接口以及該服務的具體實現:
驗證服務
package com.javacodegeeks.gwt.security.client;import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;/*** The client side stub for the RPC service.*/
@RemoteServiceRelativePath("auth")
public interface AuthService extends RemoteService {String retrieveUsername();
}
AuthServiceAsync
package com.javacodegeeks.gwt.security.client;import com.google.gwt.user.client.rpc.AsyncCallback;/*** The async counterpart of <code>AuthService</code>.*/
public interface AuthServiceAsync {void retrieveUsername(AsyncCallback<String> callback);
}
AuthServiceImpl
package com.javacodegeeks.gwt.security.server;import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.javacodegeeks.gwt.security.client.AuthService;@SuppressWarnings("serial")
public class AuthServiceImpl extends RemoteServiceServlet implements AuthService {@Overridepublic String retrieveUsername() {Authentication authentication =SecurityContextHolder.getContext().getAuthentication();if (authentication==null){System.out.println("Not logged in");return null;}else {return (String) authentication.getPrincipal();}}}
代碼很簡單。 我們使用SecurityContextHolder類來檢索當前的SecurityContext ,然后使用getAuthentication方法來獲取對基礎Authentication對象的引用。 然后,我們通過getPrincipal方法檢索用戶名(如果有)。
當然,我們必須在應用程序“ web.xml”文件中聲明特定的servlet。 這里是:
...
<servlet><servlet-name>authServlet</servlet-name><servlet-class>com.javacodegeeks.gwt.security.server.AuthServiceImpl</servlet-class>
</servlet><servlet-mapping><servlet-name>authServlet</servlet-name><url-pattern>/gwtspringsecurityproject/auth</url-pattern>
</servlet-mapping>
...
這是整個網絡聲明文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd"><web-app><filter><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>springSecurityFilterChain</filter-name><url-pattern>/*</url-pattern></filter-mapping><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- Servlets --><servlet><servlet-name>greetServlet</servlet-name><servlet-class>com.javacodegeeks.gwt.security.server.GreetingServiceImpl</servlet-class></servlet><servlet-mapping><servlet-name>greetServlet</servlet-name><url-pattern>/gwtspringsecurityproject/greet</url-pattern></servlet-mapping><servlet><servlet-name>authServlet</servlet-name><servlet-class>com.javacodegeeks.gwt.security.server.AuthServiceImpl</servlet-class></servlet><servlet-mapping><servlet-name>authServlet</servlet-name><url-pattern>/gwtspringsecurityproject/auth</url-pattern></servlet-mapping><!-- Default page to serve --><welcome-file-list><welcome-file>GwtSpringSecurityProject.html</welcome-file></welcome-file-list></web-app>
讓我們看看如何在應用程序的入口點中使用此服務。 我們在onModuleLoad方法結束之前添加以下代碼片段:
authService.retrieveUsername(new AsyncCallback<String>() {public void onFailure(Throwable caught) {dialogBox.setText("Remote Procedure Call - Failure");}public void onSuccess(String result) {nameField.setText(result);}}
);
啟動應用程序之前的最后一步是處理運行時依賴項。 Spring需要大量的庫來執行其DI魔術,因此,這是在“ war / WEB-INF / lib”文件夾中必須存在的JAR列表:
- org.springframework.aop-3.0.5.RELEASE.jar
- org.springframework.asm-3.0.5.RELEASE.jar
- org.springframework.beans-3.0.5.RELEASE.jar
- org.springframework.context-3.0.5.RELEASE.jar
- org.springframework.core-3.0.5.RELEASE.jar
- org.springframework.expression-3.0.5.RELEASE.jar
- org.springframework.web-3.0.5.RELEASE.jar
- 彈簧安全配置-3.0.5.RELEASE.jar
- Spring安全核心-3.0.5.RELEASE.jar
- spring-security-web-3.0.5.RELEASE.jar
復制以上所有內容之后,啟動Eclipse項目配置并嘗試訪問默認URL:
http://127.0.0.1:8888/GwtSpringSecurityProject.html?gwt.codesvr=127.0.0.1:9997
Spring Security將攔截該請求,并為您提供默認的登錄頁面。 提供如下有效憑證:
提交表單數據,您將被重定向到原始URL。 請注意,該文本字段將填充用于登錄的用戶名。
返回到Eclipse控制臺視圖,并檢查在那里打印的各種日志。 您應該看到類似以下的內容:
2010年12月12日8:45:49 PM com.javacodegeeks.gwt.security.server.auth.CustomAuthListener onApplicationEvent
警告:身份驗證事件AuthenticationSuccessEvent:CustomUserAuthentication; 詳細信息:org.springframework.security.web.authentication.WebAuthenticationDetails@fffdaa08:RemoteIpAddress:127.0.0.1; SessionId:im1fdjvdu7yw
2010年12月12日8:45:49 PM com.javacodegeeks.gwt.security.server.auth.CustomAuthListener onApplicationEvent
警告:身份驗證事件InteractiveAuthenticationSuccessEvent:CustomUserAuthentication; 詳細信息:org.springframework.security.web.authentication.WebAuthenticationDetails@fffdaa08:RemoteIpAddress:127.0.0.1; SessionId:im1fdjvdu7yw
那是所有人。 您可以在這里找到創建的Eclipse項目。 玩得開心!
- GWT 2 Spring 3 JPA 2 Hibernate 3.5教程
- SmartGWT入門,提供出色的GWT界面
- 建立自己的GWT Spring Maven原型
- GWT 2 Spring 3 JPA 2 Hibernate 3.5教程– Eclipse和Maven 2展示
- 使用Spring使用Java發送電子郵件– GMail SMTP服務器示例
翻譯自: https://www.javacodegeeks.com/2010/12/securing-gwt-apps-with-spring-security.html