Spring Security 用戶名密碼認(rèn)證實(shí)例
1. 前言
上一節(jié),我們介紹了 Spring Security 的基本認(rèn)證組件,本節(jié)我們介紹最常見的認(rèn)證方式「密碼認(rèn)證」的實(shí)現(xiàn)方法。
用戶名、密碼認(rèn)證被廣泛應(yīng)用于 PC 端的 Web 應(yīng)用和客戶端應(yīng)用,比如登陸網(wǎng)站,又比如 QQ 桌面客戶端。
- 表單登錄;
- 基本認(rèn)證;
- 數(shù)字認(rèn)證。
從認(rèn)證數(shù)據(jù)源角度分類,也可以將認(rèn)證分為:
- 使用內(nèi)存存儲;
- 使用關(guān)系型數(shù)據(jù)庫存儲;
- 自定義存儲;
- LDAP 存儲。
本小節(jié)實(shí)例開發(fā)環(huán)境:
本小節(jié)所使用的實(shí)例代碼是基于 Spring 官網(wǎng)中提供的最小化 HelloWorld 模板創(chuàng)建,請點(diǎn)此下載完整的 HelloWorld 模板壓縮包。
-
編譯環(huán)境:JDK 1.8,點(diǎn)此下載;
-
構(gòu)建工具:Maven 3.5.3,點(diǎn)此下載;
-
開發(fā)工具:VS Code,點(diǎn)此下載、控制臺;
-
其他依賴性:無
2. 實(shí)例講解
2.1 創(chuàng)建 Spring Security 項(xiàng)目
- 修改 Hello World 模板工程的目錄名稱為 UsernamePasswordSample;
- 修改 pom.xml 文件,將基礎(chǔ)信息部分改為如下形式:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
略...
<groupId>imooc.springsecurity</groupId>
<artifactId>UsernamePasswordSample</artifactId>
<version>0.0.1-SNAPSHOT</version>
略...
</project>
-
修改啟動類,修改其包名為
imooc.springsecurity.usernamepassword
,修改類名為UsernamePasswordSample
; -
創(chuàng)建測試頁面,返回登錄用戶信息
新建 src/main/java/imooc/springsecurity/usernamepassword/controller/UserController.java
。
package imooc.springsecurity.usernamepassword.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
@RequestMapping("user")
public class UserController {
@RequestMapping("me")
private String showMe(Principal principal) {
return String.format("當(dāng)前登錄用戶為:「%s」", principal.getName());
}
}
- 在項(xiàng)目根目錄控制臺輸入命令
mvn spring-boot:run
,如看到以下輸出代表配置正確。
2.2 表單認(rèn)證
2.2.1 表單認(rèn)證的過程說明
Spring Security 支持從 HTML 的 Form 表單形式提交登錄用戶信息。
表單認(rèn)證可分為以下步驟:
- 用戶請求受保護(hù)資源;
- Spring Security 的
FilterSecurityInterceptor
對象,檢測到當(dāng)前用戶認(rèn)證未通過,應(yīng)予以拒絕,并拋出AccessDeniedException
; - 當(dāng)
AccessDeniedException
被ExceptionTranslationFilter
接收后,其認(rèn)定需要發(fā)起認(rèn)證流程,此時(shí)用戶被要求登錄,認(rèn)證服務(wù)器將登錄地址(默認(rèn)由LoginUrlAuthenticationEntryPoint
)返回給客戶端; - 客戶端瀏覽重定向到登錄頁面;
- 登錄頁面有服務(wù)端渲染生成。
當(dāng)用戶提交登錄信息,認(rèn)證服務(wù)器端的 UsernamePasswordAuthenticationFilter
就會被執(zhí)行。
此過程的具體執(zhí)行過程如下:
UsernamePasswordAuthenticationFilter
產(chǎn)生UsernamePasswordAuthenticationToken
,并存入從請求中獲取的用戶名、密碼等信息;- 創(chuàng)建出的 Token 被傳遞給
AuthenticationManager
用于認(rèn)證; - 認(rèn)證成功或失敗的后續(xù)流程同上一小節(jié)中關(guān)于
AbstractAuthenticationProcessingFilter
的執(zhí)行過程一致。
2.2.2 表單認(rèn)證的開啟
默認(rèn)情況下,Spring Security 開啟了表單認(rèn)證功能。如果我們需要顯式配置,可用如下方式實(shí)現(xiàn)。
創(chuàng)建 Security 配置文件: src/main/java/imooc/springsecurity/usernamepassword/config/WebSecurityConfig.java
,并在其中添加 http.formLogin(withDefaults())
的配置,完整代碼如下:
package imooc.springsecurity.usernamepassword.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.formLogin(Customizer.withDefaults());
}
}
訪問 http://localhost:8080/user/me ,網(wǎng)頁會自動跳轉(zhuǎn)到登錄頁面。
提交登錄后,通過認(rèn)證,我們將在瀏覽器看到當(dāng)前登錄的用戶名。
當(dāng)前登錄用戶為:「user」
2.2.3 表單認(rèn)證的配置
默認(rèn)情況下,表單登錄的跳轉(zhuǎn)地址是 /login
,登錄參數(shù)中用戶名變量名為 username
,密碼變量名為 password
。如果我們需要修改這些配置信息,可以通過如下方式實(shí)現(xiàn):
在 configure(HttpSecurity http)
方法中,為 http
的 formLogin
項(xiàng)修改配置。
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll() // 表單認(rèn)證頁面不需要權(quán)限
.anyRequest().authenticated(); // 其他頁面需要登錄用戶才能訪問
http.formLogin()
.loginPage("/login") // 自定義表單認(rèn)證頁面地址
.usernameParameter("user")
.passwordParameter("pass");
http.csrf().disable(); // 關(guān)閉 csrf 以通過認(rèn)證,注意,這不是最好的做法,后續(xù)章節(jié)會有介紹。
}
當(dāng)然這一步中配置 /login
頁面需要我們自己去實(shí)現(xiàn)。這里有幾個(gè)需要注意的地方:
- 自定義表單提交地址為
/login
,提交方法僅支持POST
; - 表單需要支持 CSRF 票據(jù),即附帶
_csrf
參數(shù); - 用戶名字段需要命名為
user
; - 密碼字段需要命名為
pass
; - 當(dāng)認(rèn)證失敗時(shí),表單頁面會收到
error
參數(shù); - 當(dāng)用戶退出成功時(shí),表單頁面會收到
logout
參數(shù)。
為了測試上述配置,我們創(chuàng)建一個(gè)測試登錄頁:
- 新建
src/main/java/imooc/springsecurity/usernamepassword/controller/LoginController.java
。
package imooc.springsecurity.usernamepassword.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
@RequestMapping("/login")
public String viewLogin2() {
return "/login.html";
}
}
- 新建 src/main/resources/templates/login.html
<form method="post" action="/login">
<input type="text" name="user">
<input type="password" name="pass">
<input type="submit" value="登錄">
</form>
訪問測試:http://localhost:8080/user/me ,此時(shí)跳轉(zhuǎn)到我們新建的登錄頁面。
2.3 基本認(rèn)證
2.3.1 基本認(rèn)證的流程
基本認(rèn)證也是常用的認(rèn)證方式?;菊J(rèn)證分兩種場景:
- 如果直接在瀏覽器里訪問頁面,瀏覽器會彈出登錄窗口,如下圖:
- 如果發(fā)送未經(jīng)認(rèn)證的 http 請求,服務(wù)端會返回 401 錯(cuò)誤。
實(shí)現(xiàn)基本認(rèn)證有兩種方式:
- 在請求頭中添加
Authorization: "Basic Base64(用戶名+密碼)"
; - 在請求參數(shù)中增加用戶名和密碼。
在 Spring Security 中,具體的認(rèn)證過程如下:
- 用戶請求受保護(hù)資源;(與表單認(rèn)證相同)
- Spring Security 的
FilterSecurityInterceptor
對象,檢測到當(dāng)前用戶認(rèn)證未通過,應(yīng)予以拒絕,并拋出AccessDeniedException
;(與表單認(rèn)證相同) - 當(dāng)
AccessDeniedException
被ExceptionTranslationFilter
接收后,其認(rèn)定需要發(fā)起認(rèn)證流程,此時(shí)用戶被要求登錄,認(rèn)證服務(wù)器將認(rèn)證頭WWW-Authenticate
(默認(rèn)由BasicAuthenticationEntryPoint
提供)返回給客戶端。
當(dāng)客戶端收到 WWW-Authenticate
頭后,客戶端提供用戶名和密碼參數(shù)用于認(rèn)證。
2.3.2 基本認(rèn)證的配置
默認(rèn)情況下,Spring Security 開啟了基本認(rèn)證功能。如果我們需要顯式配置,可用如下方式實(shí)現(xiàn)。
protected void configure(HttpSecurity http) {
http
// ...
.httpBasic(withDefaults());
}
2.4 數(shù)字認(rèn)證
數(shù)字認(rèn)證在新型應(yīng)用中已不建議使用,因?yàn)檫@種方式下,用戶的敏感信息,比如密碼等都需要以明文形式存在,因此數(shù)字認(rèn)證方式并不安全。
數(shù)字認(rèn)證對應(yīng)的認(rèn)證過濾器為:DigestAuthenticationFilter
。
2.5 在內(nèi)存中配置用戶名密碼
內(nèi)存認(rèn)證是將用戶名密碼信息存儲在內(nèi)存之中,通過 InMemoryUserDetailsManager
方式完成認(rèn)證。
內(nèi)存認(rèn)證添加用戶的方式如下,在 WebSecurityConfig.java 類(非必須)中添加以下 Bean 定義。
@Bean
public UserDetailsService users() {
// 用戶1 user 用戶
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
// 用戶2 admin 用戶
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
注意,其中的密碼字段需要符合系統(tǒng)加密規(guī)則。比較簡單的生成方式是通過 Spring Boot CLI 工具,在控制臺將密碼轉(zhuǎn)換為密文。
然后我們可以在登錄表單中,用這里配置的用戶信息完成認(rèn)證。
2.6 使用數(shù)據(jù)庫管理用戶名密碼
Spring Security 支持使用數(shù)據(jù)庫作為認(rèn)證數(shù)據(jù)源,并且提供了默認(rèn)數(shù)據(jù)模型。
2.6.1 默認(rèn)的數(shù)據(jù)模型
使用 JDBC 數(shù)據(jù)源最簡單直接的方法就是使用 Spring Security 提供的默認(rèn)數(shù)據(jù)模型「users.ddl」構(gòu)建認(rèn)證數(shù)據(jù)庫。
users.ddl 的定義如下:
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(50) not null,
enabled boolean not null
);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);
使用此數(shù)據(jù)庫描述文本,在我們的數(shù)據(jù)庫中創(chuàng)建「用戶表」和「權(quán)限表」,并在 Spring Security 項(xiàng)目中配置 JDBC 數(shù)據(jù)源。
2.6.2 配置 JDBC 數(shù)據(jù)源
@Autowired
private DataSource dataSource;
@Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
如此,我們便可以用數(shù)據(jù)庫中存儲的用戶名和密碼進(jìn)行登錄校驗(yàn)了。
3. 小結(jié)
本節(jié)我們介紹了用戶名密碼認(rèn)證的實(shí)現(xiàn)方式,主要知識點(diǎn)如下:
Spring Security 用戶名密碼認(rèn)證有多種實(shí)現(xiàn)方式,從認(rèn)證方式角度看,可以分為表單認(rèn)證、基本認(rèn)證和數(shù)字證書認(rèn)證,其中數(shù)字證書認(rèn)證已經(jīng)不適用現(xiàn)代的互聯(lián)網(wǎng)技術(shù)。從認(rèn)證數(shù)據(jù)源角度,也可以分為內(nèi)存認(rèn)證、JDBC 數(shù)據(jù)庫認(rèn)證和 LDAP 認(rèn)證。
用戶名密碼認(rèn)證是 Spring Security 框架中最基本的認(rèn)證形式。下節(jié)開始我們將討論一種在移動應(yīng)用被廣泛應(yīng)用的認(rèn)證方式:OAuth2.0 認(rèn)證。