參考:陳木鑫老師的《Spring Security 實戰》
創建spring boot項目
通過Intellij IDEA創建Spring Boot項目的方式有許多種,其中最簡單的方式就是使用Spring Initializr
工具。
Spring Initializr 允許我們提前選定一些常用的項目依賴,此處我們選擇 Security 作爲構建Spring
Security項目的最小依賴,選擇Web作爲Spring Boot構建Web應用的核心依賴。
Next :
Next:
創建項目的目錄結構:
maven 引用
在自動構建的Spring Security 項目中,Spring Initializr 爲我們引入了以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
我們點開spring-boot-starter-security
可以看到,其包含了以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.1.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<scope>compile</scope>
</dependency>
其中 spring-security-web
和spring-security-config
兩個核心模塊,正是官方建議引入的Spring Security最小依賴。
聲明controller
在項目中聲明一個測試路由TestController
:
package com.haan.springsecuritydemo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping
public String hello(){
return "Hello Spring Security!";
}
}
運行SpringsecuritydemoApplication
運行SpringsecuritydemoApplication
,默認啓動 8080
端口,打開瀏覽器,訪問localhost:8080
,我們發現頁面跳轉到了localhost:8080/login
:
在引入Spring Security項目之後,雖然沒有進行任何相關的配置或編碼,但Spring Security有一個默認的運行狀態,要求在經過表單基本認證後才能訪問對應的URL資源,其默認使用的用戶名爲 user
,密碼則是動態生成並打印到控制檯的一串隨機碼。翻看控制檯的打印信息:
輸入用戶名和密碼後,單擊“登錄”按鈕即可成功訪問:
修改登錄信息
基本表單認證中,用戶名和密碼都是可以配置的,最常見的就是在resources下的application
配置文件中修改:
spring.security.user.name=user02
spring.security.user.password=aaaaaa
重新啓動程序,發現控制檯不再打印默認密碼串了,此時使用我們自定義的用戶名和密碼即可登錄。
WebSecurityConfigurerAdapter
protected void configure(HttpSecurity http) throws Exception {
this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
}
可以看到 WebSecurityConfigurerAdapter
已經默認聲明瞭一些安全特性:
- 驗證所有請求。
- 允許用戶使用表單登錄進行身份驗證(Spring Security 提供了一個簡單的表單登錄頁面)。
- 允許用戶使用HTTP 基本認證。
spring boot 默認定義了DefaultConfigurerAdapter
,由@ConditionalOnMissingBean
可知當沒有其他WebSecurityConfigurerAdapter
被定義時,將使用DefaultConfigurerAdapter
:
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({WebSecurityConfigurerAdapter.class})
@ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
public class SpringBootWebSecurityConfiguration {
public SpringBootWebSecurityConfiguration() {
}
@Configuration(
proxyBeanMethods = false
)
@Order(2147483642)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
DefaultConfigurerAdapter() {
}
}
}
自定義表單登錄頁
Spring boot提供了WebSecurityConfigurerAdapter
的默認實現DefaultConfigurerAdapter
,能夠提供基本表單登錄認證。
雖然自動生成的表單登錄頁可以方便、快速地啓動,但是大多數應用程序更希望提供自己的表單登錄頁,此時就需要我們提供自己的WebSecurityConfigurerAdapter
來代替DefaultConfigurerAdapter
,覆寫WebSecurityConfigurerAdapter
的configure
方法:
@EnableWebSecurity
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/myLogin.html") //指明登錄頁面
.permitAll() //指明登錄頁允許所有進行訪問
.and()
.csrf().disable();
}
}
authorizeRequests()
方法實際上返回了一個 URL 攔截註冊器,我們可以調用它提供的anyRequest()
、antMatchers()
和regexMatchers()
等方法來匹配系統的URL,併爲其指定安全策略。formLogin()
方法和httpBasic()
方法都聲明瞭需要Spring Security提供的表單認證方式,分別返回對應的配置器。其中formLogin().loginPage("/myLogin.html")
指定自定義的登錄
頁/myLogin.html
,同時,Spring Security會用/myLogin.html
註冊一個POST
路由,用於接收登錄請求。csrf()
方法是Spring Security提供的跨站請求僞造防護功能,當我們繼承WebSecurityConfigurer Adapter
時會默認開啓csrf()
方法。
訪問localhost:8080
,我們發現,頁面就跳轉到了localhost:8080/myLogin.html
,由於我們靜態文件中並沒有myLogin.html
文件,所以提示了一個404的white page
:
我們在resources/static
文件夾下創建頁面myLogin.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello Security!</title>
</head>
<body>
<h3>登錄頁</h3>
<form action="/myLogin.html" method="post">
<input type="text" name="username"> <br/>
<input type="text" name="password"> <br/>
<input type="submit" value="login">
</form>
</body>
</html>
重啓服務,再次訪問localhost:8080
:
輸入上面的application.properties
中配置的用戶登錄賬戶和密碼進行登錄,登陸成功:
其他表單配置項
指定登錄處理URL
在自定義表單登錄頁之後,處理登錄請求的URL
也會相應改變,默認情況下,
如果只配置loginPage
而不配置loginProcessingUrl
的話那麼loginProcessingUrl
默認就是loginPage
,如果需要自定義登錄請求的URL
,需要配置loginProcessingUrl
:
重啓登錄,我們發現中間訪問了localhost:8080/myLogin
補充:
loginPage
和loginProcessingUrl
- 兩者都不配置:默認都是
/login
- 兩者都配置:按自己的來
- 只配置
loginProcessingUrl
:loginPage
默認/login
- 只配置
loginPage
:loginProcessingUrl
默認就是loginPage
設置登錄成功處理
此時,有些讀者可能會有疑問,因爲按照慣例,在發送登錄請求並認證成功之後,頁面會跳轉回原訪問頁。在某些系統中的確是跳轉回原訪問頁的,但在部分前後端完全分離、僅靠JSON完成所有交互的系統中,一般會在登錄時返回一段 JSON 數據,告知前端成功登錄成功與否,由前端決定如何處
理後續邏輯,而非由服務器主動執行頁面跳轉。這在Spring Security中同樣可以實現。
表單登錄配置模塊提供了successHandler()
和 failureHandler()
兩個方法,分別處理登錄成功和登錄失敗的邏輯。
-
successHandler()
方法帶有一個Authentication
參數,攜帶當前登錄用戶名及其角色等信息; -
failureHandler()
方法攜帶一個AuthenticationException
異常參數。具體處理方式需按照系統的情況自定義。
@EnableWebSecurity
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/myLogin.html") //指明登錄頁面
.loginProcessingUrl("/myLogin") //指明處理登陸的URL路徑,即登陸表單提交請求
.successHandler(new AuthenticationSuccessHandler() { // 設置登錄成功的處理器
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
PrintWriter responseWriter = httpServletResponse.getWriter();
String name = authentication.getName();
responseWriter.write(name+" login success!");
}
})
.failureHandler(new AuthenticationFailureHandler() { // 設置登錄失敗的處理器
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
PrintWriter responseWriter = httpServletResponse.getWriter();
responseWriter.write("login error!");
}
})
.permitAll() //指明登錄頁允許所有進行訪問
.and()
.csrf().disable();
}
}
正確的賬號密碼:
錯誤的賬號密碼: