1. 建立項目
1.1 建立并啟動項目
建立 Spring Boot 項目,添加 Spring Web 和 Spring Security 依賴:

其中 Spring Security 依賴中主要的是這兩個:
項目建立成功後,添加一個測試接口:
package com.javaboy.vms;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author: gaoyang
* @date: 2021-04-14 17:47
* @description: 啟動類
*/
@SpringBootApplication
public class VmsServerApplication {
public static void main(String[] args) {
SpringApplication.run(VmsServerApplication.class, args);
}
}
然後啟動項目:
我們可以在控制台看到一串UUID,這個就是 Spring Security預設的登入密碼,預設使用者名是:user。
登入以後我們就可以看到傳回的資料了。
在 Spring Security 中,預設的登入頁面和登入接口,都是 /login ,隻不過一個是 get 請求(登入頁面),另一個是 post 請求(登入接口)。
1.2 預設使用者名和密碼是如何生成的
和使用者相關的自動化配置類在 UserDetailsServiceAutoConfiguration 裡,在該類的getOrDeducePassword 方法中,我們可以看到列印密碼的代碼:logger.info()
private String getOrDeducePassword(User user, PasswordEncoder encoder) {
String password = user.getPassword();
if (user.isPasswordGenerated()) {
logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
}
return encoder == null && !PASSWORD_ALGORITHM_PATTERN.matcher(password).matches() ? "{noop}" + password : password;
}
控制台的密碼就是從這列印來的,列印的條件是 isPasswordGenerated 方法傳回 true,即密碼是預設生成的。
擷取密碼的方法 user.getPassword() 在 SecurityProperties 類中,在 SecurityProperties 中我們可以看到 User 内部類有如下定義:
/**
* Default user name.
*/
private String name = "user";
/**
* Password for the default user name.
*/
private String password = UUID.randomUUID().toString();
private boolean passwordGenerated = true;
可以看到,預設的使用者名就是 user,預設的密碼則是 UUID,而預設情況下,passwordGenerated 也為 true。
2. 使用者配置
預設的密碼有一個明顯的缺陷就是:每次重新開機密碼都會改變,這很不友善,在未連接配接到資料庫以前,我們先來學習一下兩種非主流的使用者名/密碼配置方案,分别為配置檔案、配置類。
2.1 配置檔案配置使用者
我們可以在 application.yml 中配置預設的使用者名密碼。
spring:
security:
user:
name: javaBoy
password: 123
這裡是怎麼覆寫預設配置的呢?我可以看到 SecurityProperties 定義是這樣的:
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
而預設的使用者就定義在 SecurityProperties 的 User 靜态内部類裡,并通過 set 方法注入到屬性中去:
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
if (!StringUtils.hasLength(password)) {
return;
}
this.passwordGenerated = false;
this.password = password;
}
從這裡我們可以看出,如果傳輸的密碼長度 == 0,則傳回,此時用預設生成的 UUID 作為密碼;否則,則使用傳入的密碼,并且把 passwordGenerated 置為 false,此時控制台就不會列印密碼了。
再重新開機項目,就可以用配置好的使用者名/密碼登入。
2.2 配置類配置使用者
先上代碼,然後我們再一起解釋:
package com.javaboy.vms.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author: gaoyang
* @date: 2021-04-15 16:35
* @description: Spring Security 配置類
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("javaBoy_1024")
.password("123")
.roles("admin");
}
}
- 首先我們自定義 SecurityConfig 繼承自 WebSecurityConfigurerAdapter,重寫裡邊的configure 方法。
configure 方法中,我們通過 inMemoryAuthentication 來開啟在記憶體中定義使用者,withUser 中是使用者名,password 中則是使用者密碼,roles 中是使用者角色。
- 提供了一個 PasswordEncoder 的執行個體,因為目前的案例還比較簡單,是以我暫時先不給密碼進行加密,是以傳回NoOpPasswordEncoder 的執行個體即可。
PasswordEncoder 中提供了三個方法:
public interface PasswordEncoder {
String encode(CharSequence var1);
boolean matches(CharSequence var1, String var2);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
- encode 方法用來對明文密碼進行加密,傳回加密之後的密文。
- matches 方法是一個密碼校對方法,在使用者登入的時候,将使用者傳來的明文密碼和資料庫中儲存的密文密碼作為參數,傳入到這個方法中去,根據傳回的 Boolean 值判斷使用者密碼是否輸入正确。
- upgradeEncoding 是否還要進行再次加密,這個一般來說就不用了。
3. 密碼加密
3.1 密碼為什麼要加密?
2011 年 12 月 21 日,有人在網絡上公開了一個包含 600 萬個 CSDN 使用者資料的資料庫,資料全部為明文儲存,包含使用者名、密碼以及注冊郵箱。事件發生後 CSDN 在微網誌、官方網站等管道發出了聲明,解釋說此資料庫系 2009 年備份所用,因不明原因洩露,已經向警方報案,後又在官網發出了公開道歉信。在接下來的十多天裡,金山、網易、京東、當當、新浪等多家公司被卷入到這次事件中。整個事件中最觸目驚心的莫過于 CSDN 把使用者密碼明文存儲,由于很多使用者是多個網站共用一個密碼,是以一個網站密碼洩露就會造成很大的安全隐患。由于有了這麼多前車之鑒,我們現在做系統時,密碼都要加密處理。
這次洩密,也留下了一些有趣的事情,特别是對于廣大程式員設定密碼這一項。人們從 CSDN 洩密的檔案中,發現了一些好玩的密碼,例如如下這些:
- ppnn13%dkstFeb.1st 這段密碼的中文解析是:娉娉袅袅十三餘,豆蔻梢頭二月初。
-
csbt34.ydhl12s 這段密碼的中文解析是:池上碧苔三四點,葉底黃鹂一兩聲
…
等等不一而足,你會發現很多程式員的人文素養還是非常高的,讓人啧啧稱奇。
3.2 加密方案
密碼加密我們一般會用到散列函數,又稱雜湊演算法、哈希函數,這是一種從任何資料中建立數字“指紋”的方法。散列函數把消息或資料壓縮成摘要,使得資料量變小,将資料的格式固定下來,然後将資料打亂混合,重新建立一個散列值。散列值通常用一個短的随機字母和數字組成的字元串來代表。好的散列函數在輸入域中很少出現散列沖突。在散清單和資料進行中,不抑制沖突來差別資料,會使得資料庫記錄更難找到。我們常用的散列函數有 MD5 消息摘要算法、安全雜湊演算法(Secure Hash Algorithm)。
但是僅僅使用散列函數還不夠,為了增加密碼的安全性,一般在密碼加密過程中還需要加鹽,所謂的鹽可以是一個随機數也可以是使用者名,加鹽之後,即使密碼明文相同的使用者生成的密碼密文也不相同,這可以極大的提高密碼的安全性。但是傳統的加鹽方式需要在資料庫中有專門的字段來記錄鹽值,這個字段可能是使用者名字段(因為使用者名唯一),也可能是一個專門記錄鹽值的字段,這樣的配置比較繁瑣。
Spring Security 提供了多種密碼加密方案,官方推薦使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 強哈希函數,開發者在使用時可以選擇提供 strength 和 SecureRandom 執行個體。strength 越大,密鑰的疊代次數越多,密鑰疊代次數為 2^strength。strength 取值在 4~31 之間,預設為 10。
不同于 Shiro 中需要自己處理密碼加鹽,在 Spring Security 中,BCryptPasswordEncoder 就自帶了鹽,處理起來非常友善。
而 BCryptPasswordEncoder 就是 PasswordEncoder 接口的實作類。