1. 引言
本教程將重點介紹使用 Spring Security 進行登錄。我們將在此之前的 Spring MVC 示例基礎上進行擴展,因為該示例是設置 Web 應用程序以及登錄機制的必要組成部分。
2. Maven 依賴
在使用 Spring Boot 時,<a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security">spring-boot-starter-security</a> starter 會自動包含所有依賴項,例如 <em>spring-security-core</em>, <em>spring-security-web</em>, 和 <em>spring-security-config</em> 等:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>如果未使用 Spring Boot,請參閲 Spring Security with Maven 文章,其中介紹瞭如何添加所有必需的依賴項。標準 spring-security-web 和 spring-security-config 均需要包含。
3. Spring Security Java 配置
首先,我們創建一個 Spring Security 配置類,該類會創建一個 SecurityFilterChain 實例。
通過添加 @EnableWebSecurity 註解,我們獲得 Spring Security 與 MVC 集成支持:
@Configuration
@EnableWebSecurity
public class SecSecurityConfig {
@Bean
public InMemoryUserDetailsManager userDetailsService() {
// InMemoryUserDetailsManager (see below)
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// http builder configurations for authorize requests and form login (see below)
}
}在本示例中,我們使用了內存式身份驗證,並定義了三個用户。
接下來,我們將探討我們用於創建登錄配置表單的元素。
讓我們首先構建我們的身份驗證管理器。
3.1. InMemoryUserDetailsManager
身份提供者由一個簡單的、基於內存的實現 InMemoryUserDetailsManager 支持。這在尚未必要時,用於快速原型設計非常有用:
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user1 = User.withUsername("user1")
.password(passwordEncoder().encode("user1Pass"))
.roles("USER")
.build();
UserDetails user2 = User.withUsername("user2")
.password(passwordEncoder().encode("user2Pass"))
.roles("USER")
.build();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("adminPass"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user1, user2, admin);
}在這裏,我們將配置三個用户,用户名、密碼和角色都硬編碼。
從 Spring 5 開始,我們還需要定義密碼編碼器。
在我們的示例中,我們將使用 BCryptPasswordEncoder。
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}接下來,讓我們配置 HttpSecurity。
3.2. 配置請求授權
我們將首先進行必要的配置以授權請求。
在這裏,我們允許匿名訪問 /login</em/>,以便用户進行身份驗證。我們將限制對 /admin</em/> 的訪問至 ADMIN</em/> 角色,並對其他一切進行安全保護:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/anonymous*")
.anonymous()
.antMatchers("/login*")
.permitAll()
.anyRequest()
.authenticated()
.and()
// ...
}
請注意,antMatchers() 元素的順序很重要;更具體的規則需要先定義,然後才是更通用的規則。
3.3. 表單登錄配置
接下來,我們將擴展上述配置以支持表單登錄和登出:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/perform_login")
.defaultSuccessUrl("/homepage.html", true)
.failureUrl("/login.html?error=true")
.failureHandler(authenticationFailureHandler())
.and()
.logout()
.logoutUrl("/perform_logout")
.deleteCookies("JSESSIONID")
.logoutSuccessHandler(logoutSuccessHandler());
return http.build();
}<ul>
<li><em>loginPage()</em> – 自定義登錄頁面</li>
<li><em>loginProcessingUrl()</em> – 將用户名和密碼提交到的 URL</li>
<li><em>defaultSuccessUrl()</em> – 成功登錄後的目標頁面</li>
<li><em>failureUrl()</em> – 登錄失敗後的目標頁面</li>
<li><em>logoutUrl()</em> – 自定義註銷</li>
</ul>
4. 將 Spring Security 添加到 Web 應用程序
為了使用上述定義的 Spring Security 配置,我們需要將其附加到 Web 應用程序中。
我們將使用 <em >WebApplicationInitializer</em >>,因此我們無需提供 <em >web.xml</em >>。
public class AppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext sc) {
AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
root.register(SecSecurityConfig.class);
sc.addListener(new ContextLoaderListener(root));
sc.addFilter("securityFilter", new DelegatingFilterProxy("springSecurityFilterChain"))
.addMappingForUrlPatterns(null, false, "/*");
}
}請注意,如果我們在使用 Spring Boot 應用程序,則此初始化器並非必需。有關 Spring Boot 中安全配置如何加載的更多詳細信息,請參閲我們關於 Spring Boot 安全自動配置的文章。
5. Spring Security XML 配置
讓我們也來查看對應的 XML 配置。
整個項目使用 Java 配置,因此我們需要通過一個 Java @Configuration 類導入 XML 配置文件:
@Configuration
@ImportResource({ "classpath:webSecurityConfig.xml" })
public class SecSecurityConfig {
public SecSecurityConfig() {
super();
}
}Spring Security XML 配置,webSecurityConfig.xml:
<http use-expressions="true">
<intercept-url pattern="/login*" access="isAnonymous()" />
<intercept-url pattern="/**" access="isAuthenticated()"/>
<form-login login-page='/login.html'
default-target-url="/homepage.html"
authentication-failure-url="/login.html?error=true" />
<logout logout-success-url="/login.html" />
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user1" password="user1Pass" authorities="ROLE_USER" />
</user-service>
<password-encoder ref="encoder" />
</authentication-provider>
</authentication-manager>
<beans:bean id="encoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
</beans:bean>6. web.xml**
在 Spring 4 之前,我們通常在 web.xml 中配置 Spring Security,即為標準的 Spring MVC web.xml 添加一個額外的過濾器:
<display-name>Spring Secured Application</display-name>
<!-- Spring MVC -->
<!-- ... -->
<!-- Spring Security -->
<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>過濾器 – DelegatingFilterProxy – 僅會委託給由 Spring 管理的 Bean – 即 FilterChainProxy – 該 Bean 本身能夠受益於完整的 Spring Bean 生命週期管理以及相關功能。
7. 登錄表單
登錄表單頁面將使用 Spring MVC 的簡單機制,通過將視圖名稱映射到 URL 來進行註冊。 此外,不需要在視圖和控制器之間進行顯式的控制器:
registry.addViewController("/login.html");當然,這對應於 login.jsp:
<html>
<head></head>
<body>
<h1>Login</h1>
<form name='f' action="login" method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username' value=''></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password' /></td>
</tr>
<tr>
<td><input name="submit" type="submit" value="submit" /></td>
</tr>
</table>
</form>
</body>
</html>Spring 登錄表單包含以下相關工件:
- login – 表單通過 POST 請求發起的身份驗證流程的 URL
- username – 用户名
- password – 密碼
8. 進一步配置 Spring 登錄
我們在介紹上文的 Spring Security 配置時,簡要討論了一些登錄機制的配置。現在我們來更深入地探討一下。
覆蓋大多數 Spring Security 的默認配置的原因是為了 隱藏應用程序使用了 Spring Security。我們還希望儘量減少潛在攻擊者對應用程序所知的信息。
完全配置後,登錄元素如下所示:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/perform_login")
.defaultSuccessUrl("/homepage.html",true)
.failureUrl("/login.html?error=true")
return http.build();
}或者對應的XML配置:
<form-login
login-page='/login.html'
login-processing-url="/perform_login"
default-target-url="/homepage.html"
authentication-failure-url="/login.html?error=true"
always-use-default-target="true"/>8.1. 登錄頁面
接下來,我們將使用 loginPage() 方法 配置一個自定義登錄頁面:
http.formLogin()
.loginPage("/login.html")同樣,我們也可以使用XML配置:
login-page='/login.html'如果未指定此項,Spring Security將在login URL 生成一個非常基本的登錄表單。
8.2. 登錄 POST URL
Spring 登錄默認的 POST URL 用於觸發身份驗證過程,為 /login。在此之前,該 URL 為 /j_spring_security_check。這與 Spring Security 4 之前的版本不同(參見 從 Spring Security 3 遷移到 Spring Security 4)。
我們可以使用 loginProcessingUrl 方法來覆蓋此 URL。
http.formLogin()
.loginProcessingUrl("/perform_login")我們還可以使用XML配置:
login-processing-url="/perform_login"通過覆蓋默認 URL,我們隱藏了該應用程序實際上使用 Spring Security 加密的事實。該信息不應對外暴露。
8.3. 成功頁面
在成功登錄後,您將被重定向到一個頁面,默認情況下是 Web 應用程序的根目錄。
可以通過調用 defaultSuccessUrl() 方法來覆蓋此設置:
http.formLogin()
.defaultSuccessUrl("/homepage.html")或者使用XML配置:
default-target-url="/homepage.html"如果 always-use-default-target 屬性設置為 true,則用户始終會被重定向到該頁面。如果該屬性設置為 false,則用户將被重定向到他們之前想要訪問的上一頁,然後再提示進行身份驗證。
8.4. 登錄失敗頁面
類似於登錄頁面,登錄失敗頁面由 Spring Security 默認在 /login?error 路徑下自動生成。
要覆蓋此行為,可以使用 failureUrl() 方法:
http.formLogin()
.failureUrl("/login.html?error=true")或者使用XML:
authentication-failure-url="/login.html?error=true"9. 結論
在《Spring 登錄示例》中,我們配置了一個簡單的身份驗證流程。我們還討論了 Spring Security 登錄表單、安全配置以及一些高級自定義選項。
當項目在本地運行時,示例 HTML 可以通過以下地址訪問:
http://localhost:8080/spring-security-mvc-login/login.html