蕭山城區(qū)建設有限公司網(wǎng)站公司官網(wǎng)制作多少錢
關于csmall-passport項目
此項目主要用于實現(xiàn)“管理員”賬號的后臺管理功能,主要實現(xiàn):
- 管理員登錄
- 添加管理員
- 刪除管理員
- 顯示管理員列表
- 啟用 / 禁用管理員
關于RBAC
RBAC:Role-Based Access Control,基于角色的訪問控制
在涉及權限管理的應用軟件設計中,應該至少需要設計以下3張數(shù)據(jù)表:
- 用戶表
- 角色表
- 權限表
并且,還至少需要2張關聯(lián)表:
- 用戶與角色的關聯(lián)表
- 角色與權限的關聯(lián)表
關于Spring Security框架
Spring Security主要解決了認證與授權相關的問題。
認證:判斷某個賬號是否允許訪問某個系統(tǒng),簡單來說,就是驗證登錄
授權:判斷是否允許已經(jīng)通過認證的賬號訪問某個資源,簡單來說,就是判斷是否具有權限執(zhí)行某項操作
添加依賴
在基于Spring Boot的項目中,使用Spring Security需要添加依賴項:
<!-- Spring Boot Security依賴項,用于處理認證與授權相關的問題 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
當在項目中添加以上依賴項后,你的項目會發(fā)生以下變化(Spring Boot中的Spring Security的默認行為):
-
所有的請求都是必須要登錄才允許訪問的,包括錯誤的URL
-
提供了默認的登錄頁面,當未登錄時,會自動重定向到此登錄頁面
-
提供了臨時的登錄賬號,用戶名是
user
,密碼是啟動項目時在控制臺中的UUID值(每次重啟項目都會不同)
- 當?shù)卿洺晒?#xff0c;將自動重定向到此前嘗試訪問的URL,如果此前沒有嘗試訪問某個URL,則重定向到根路徑
- 可以通過
/logout
路徑訪問到“退出登錄”的頁面,以實現(xiàn)登出 - 當?shù)卿洺晒?#xff0c;
POST
請求都是不允許的,而GET
請求是允許的
關于Spring Security的配置類
在項目的根包下,創(chuàng)建config.SecurityConfiguration
類,繼承自WebSecurityConfigurerAdapter
類,在類上添加@Configuration
注解:
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
}
然后,在類中重寫void configure(HttpSecurity http)
方法:
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {}}
**注意:**在重寫的方法中,不要使用super
調(diào)用父類的此方法!
由于沒有調(diào)用父類此方法,再次重啟項目后,與此前將有些不同:
- 所有請求都不再要求登錄
- 登錄、登出的URL不可訪問
關于登錄表單
在Spring Security配置類的configure(HttpSecurity http)
方法中,根據(jù)是否調(diào)用了參數(shù)對象的formLogin()
方法,決定是否啟用登錄表單頁(/login
)和登出頁(/logout
),例如:
@Override
protected void configure(HttpSecurity http) throws Exception {// 調(diào)用formLogin()表示啟用登錄表單頁和登出頁,如果未調(diào)用此方法,則沒有登錄表單頁和登出頁http.formLogin();
}
關于URL的訪問控制
在Spring Security配置類的configure(HttpSecurity http)
方法中,
// 白名單
// 使用1個星號,表示通配此層級的任意資源,例如:/admin/*,可以匹配 /admin/delete、/admin/add-new
// 但是,不可以匹配多個層級,例如:/admin/*,不可以匹配 /admin/9527/delete
// 使用2個連續(xù)的星號,表示通配任何層級的任意資源,例如:/admin/**,可以匹配 /admin/delete、/admin/9527/delete
String[] urls = {"/doc.html","/**/*.js","/**/*.css","/swagger-resources","/v2/api-docs"
};// 配置URL的訪問控制
http.authorizeRequests() // 配置URL的訪問控制.mvcMatchers(urls) // 匹配某些URL.permitAll() // 直接許可,即:不需要通過認證就可以直接訪問.anyRequest() // 任何請求.authenticated(); // 以上配置的請求需要是通過認證的
使用臨時的自定義賬號實現(xiàn)登錄
可以自定義類,實現(xiàn)UserDetailsService
接口,并保證此類是組件類,則Spring Security框架會基于此實現(xiàn)類來處理認證。
在項目的根包下創(chuàng)建security.UserDetailsServiceImpl
類,實現(xiàn)UserDetailsService
接口,并在類上添加@Service
注解,重寫接口中定義的抽象方法:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {return null;}
}
當項目中存在UserDetailsService
類型的組件對象時,嘗試登錄時,Spring Security會自動使用登錄表單提交過來的用戶名來調(diào)用以上loadUserByUsername()
方法,并得到UserDetails
類型的對象,此對象中應該包含用戶的相關信息,例如密碼、賬號狀態(tài)等,接下來,Spring Security會自動使用登錄表單提交過來的密碼與UserDetails
中的密碼進行對比,且判斷賬號狀態(tài),以決定此賬號是否能夠通過認證。
所以,重寫以上方法:
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {// 假設存在可用的賬號信息:用戶名(root),密碼(123456)if ("root".equals(s)) {UserDetails userDetails = User.builder().username("root").password("123456").disabled(false).accountLocked(false).accountExpired(false).credentialsExpired(false).authorities("暫時給個山寨權限,暫時沒有作用,只是避免報錯而已").build();return userDetails;}return null;
}
**提示:**當項目中存在UserDetailsService
類型的組件對象時,Spring Security框架不再提供臨時的賬號(用戶名為user
密碼為啟動項目時的UUID值的賬號)!
**注意:**Spring Security在處理認證時,要求密碼必須經(jīng)過加密碼處理,即使你執(zhí)意不加密,也必須明確的表示出來!
在SecurityConfiguration
中,通過@Bean
方法配置PasswordEncoder
,并返回NoOpPasswordEncoder
的對象,表示“不對密碼進行加密處理”:
@Bean
public PasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();
}
完成后,重啟項目,通過/login
可以測試訪問。
使用數(shù)據(jù)庫中的賬號數(shù)據(jù)實現(xiàn)登錄
需要實現(xiàn)“根據(jù)用戶名查詢用戶的登錄信息”,需要執(zhí)行的SQL語句大致是:
select id, username, password, enable from ams_admin where username=?
在項目的根包下創(chuàng)建pojo.vo.AdminLoginInfoVO
類:
@Data
public class AdminLoginInfoVO implements Serializable {private Long id;private String username;private String password;private Integer enable;
}
在AdminMapper.java
接口中添加抽象方法:
AdminLoginInfoVO getLoginInfoByUsername(String username);
在AdminMapper.xml
中配置以上抽象方法映射的SQL:
<select ...></select><sql></sql><resultMap></resultMap>
在AdminMapperTests
中編寫并執(zhí)行測試:
接下來,在UserDetailsServiceImpl
中,先自動裝配AdminMapper
對象,然后,調(diào)整loadUserByUsername()
方法:
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {// 使用參數(shù)s作為參數(shù),調(diào)用AdminMapper對象的getLoginInfoByUsername()方法執(zhí)行查詢// 判斷查詢結果是否為null// 是:無此用戶名對應的賬號信息,返回null// 返回UserDetails對象// username:來自查詢結果// password:暫時寫死為123456,后續(xù)再改成來自查詢結果// disable:來自查詢結果中的enable,判斷enable是否為0// accountExpired等:參考此前的Demo,將各值寫死
}
完成后,可以使用數(shù)據(jù)庫中的賬號測試登錄(暫時不方便測試密碼)。
tLoginInfoByUsername()方法執(zhí)行查詢
// 判斷查詢結果是否為null
// 是:無此用戶名對應的賬號信息,返回null
// 返回UserDetails對象
// username:來自查詢結果
// password:暫時寫死為123456,后續(xù)再改成來自查詢結果
// disable:來自查詢結果中的enable,判斷enable是否為0
// accountExpired等:參考此前的Demo,將各值寫死
}
完成后,可以使用數(shù)據(jù)庫中的賬號測試登錄(暫時不方便測試密碼)。#### 密碼為什么需要加密如果未加密,將密碼的原文(原始密碼)直接存入到數(shù)據(jù)庫中,可以被輕松獲取賬戶的關鍵信息!以目前主流的網(wǎng)絡結構和技術,通常,密碼加密主要防范的是內(nèi)部工作人員(能夠接觸到服務器的人員)!需要注意:即使密碼加密了,也要防范相關的內(nèi)部工作人員,例如程序員!#### 如何對密碼進行加密直接使用現(xiàn)有的某種算法,也就是說,不會自行設計某個算法!#### 使用什么算法對密碼進行加密一定**不可以**使用**加密算法**!因為所有加密算法都是可以被逆向運算的,也就是說,可以根據(jù)加密得到的結果,進行反向運算,還原出原始密碼!通常,加密算法僅用于保障數(shù)據(jù)在傳輸過程中的安全!在對密碼進行加密處理并存入到數(shù)據(jù)庫中時,應該使用**不可逆**的算法!許多**哈希算法**,或基于哈希算法的**消息摘要算法**都是不可逆的!#### 關于消息摘要算法典型的消息摘要算法有:- SHA(Secure Hash Algorithm)家族算法- SHA-1(160位算法)- SHA-256(256位算法)- SHA-384(384位算法)- SHA-512(512位算法)
- MD(Message Digest)系列算法- MD2(128位算法)- MD4(128位算法)- MD5(128位算法)消息摘要算法原本是用于驗證接收方所接收的數(shù)據(jù)與發(fā)送方所發(fā)出的數(shù)據(jù)是否一致。消息摘要算法有幾個典型特征:- 如果消息相同,則摘要一定相同
- 如果消息不同,則摘要極大概率會不同- 必然存在n個不同的消息,摘要完全相同
- 使用同一種算法時,無論消息長度是多少,摘要的長度是固定的#### 在項目中使用MD5算法在Spring框架中,提供了`DigestUtils`,可以非常便利的使用MD5算法將消息處理為摘要:```java
public class Md5Tests {@Testvoid encode() {String rawPassword = "123456";String encodedPassword = DigestUtils.md5DigestAsHex(rawPassword.getBytes());System.out.println("原文:" + rawPassword);System.out.println("密文:" + encodedPassword);}}
算法位數(shù)對安全性的影響
以MD5算法為例,它是128位的算法,即其運算結果是由128個二進制位組成的,所以,其運算結果的排列組件有2的128次方種,這個數(shù)字轉(zhuǎn)換成十進制是:340282366920938463463374607431768211456。
理論上,使用MD5算法時,要想找到2個不同的消息運算出相同的摘要,概率應該是340282366920938463463374607431768211456分之1!或者,也可以認為,你至少需要運算340282366920938463463374607431768211456次,才可以找到2個不同的消息運算出相同的摘要。
相比之下,更高位數(shù)的算法,理論上,更難找出不同的消息運算出相同的摘要!
一般情況下,由于MD5的安全系數(shù)已經(jīng)較高,所以,不一定需要使用位數(shù)更高的算法!
關于消息摘要算法的破解 – 學術
當2個不同的消息,運算出相同的摘要,從學術上,稱之為“碰撞”。
理論上,128位的算法,其碰撞概率應該是2的128次方分之1。
關于消息算法的破解,主要是研究其碰撞概率,是否可以使用更少次數(shù)的運算實現(xiàn)碰撞!而不是嘗試根據(jù)摘要進行逆向運算還原出消息!
目前,SHA-1算法已經(jīng)被視為不安全的算法,它是160位算法,經(jīng)過研究,只需要經(jīng)過2的60幾次方的運算就可以發(fā)生碰撞,即SHA-1的安全系數(shù)與60幾位的算法幾乎相當。
關于消息摘要算法的“破解” – 根據(jù)摘要得到消息
網(wǎng)上有許多平臺可以做到“根據(jù)密文還原出原文”,這些平臺都是記錄大量的原文與密文的對應關系,當嘗試“破解”時,本質(zhì)上是在做查詢操作,大概是:
select 原文 from 數(shù)據(jù)表 where 密文=?
例如,某平臺明確的說明了:
本站針對md5、sha1、sha256等全球通用公開的加密算法進行反向查詢,通過窮舉字符組合的方式,創(chuàng)建了明文密文對應查詢數(shù)據(jù)庫,創(chuàng)建的記錄約90萬億條,占用硬盤超過500TB,查詢成功率95%以上,很多復雜密文只有本站才可查詢。本站專注于各種公開算法,已穩(wěn)定運行17年。
如果密碼可以使用全部的可打印字符,7位長度的密碼的排列組合有約70萬億種,8位長度的密碼的排列組件在此基礎上需要乘以95,則以上平臺不可能記錄8位長度的所有明文密文的對應關系!也就是說,只要原始密碼的長度達到8位,這些平臺就可能無法根據(jù)密文查詢出原文,原始密碼的長度越長,或原始密碼的強度越高(由多種元素組成,例如大小寫字母、數(shù)字、標點符號),被這些平臺收錄的可能性就越低!
如何進一步保障用戶的密碼安全 – 加鹽
鹽值的本質(zhì)就只是一個外部人員很難預測到的字符串,它將作用于處理加密過程中,例如:
// 以下1行定義了鹽值
String salt = "fsd4W87i78oiAsUu43IEF";String rawPassword = "123456";
String encodedPassword = DigestUtils.md5DigestAsHex((rawPassword + salt).getBytes());
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 將原始密碼和鹽值一起被處理System.out.println("原文:" + rawPassword);
System.out.println("密文:" + encodedPassword);
當然,鹽值應該如何使用,也沒有明確的規(guī)定,你可以:
String encodedPassword = DigestUtils.md5DigestAsHex((rawPassword + salt).getBytes());
或者:
String encodedPassword = DigestUtils.md5DigestAsHex((salt + rawPassword).getBytes());
甚至:
String encodedPassword = DigestUtils.md5DigestAsHex((salt + rawPassword + salt + rawPassword + salt + salt).getBytes());
總而言之,使用鹽的目的是”使得被MD5運算的原始數(shù)據(jù)變得更加復雜“。
你甚至可以使用隨機的鹽值,例如:
String salt = UUID.randomUUID().toString(); // 使用UUID作為鹽值
String rawPassword = "123456";
String encodedPassword = DigestUtils.md5DigestAsHex((rawPassword + salt).getBytes());
使用隨機鹽時必須注意:你需要將隨機的鹽值保存下來,否則,后續(xù)你將無法驗證密碼!
至于如何保存,方式有許多,例如在數(shù)據(jù)表中添加新的字段來保存鹽值,或者,把鹽值直接作為密碼的一部分,例如:
String salt = UUID.randomUUID().toString();
String rawPassword = "123456";
String encodedPassword = DigestUtils.md5DigestAsHex((rawPassword + salt).getBytes()) + salt;
System.out.println("鹽值:" + salt);
System.out.println("原文:" + rawPassword);
System.out.println("密文:" + encodedPassword);
密碼加密原則 – 小結
關于密碼加密處理:
- 不可以使用加密算法,只能使用消息摘要算法或其它哈希算法
- 不建議使用SHA-1
- 應該要求用戶使用更長的、強度更高的密碼,避免容易被反查(根據(jù)密文查詢得到原文)
- 應該進行加鹽處理
- 你還可以使用多重加密(使用同一個算法,或不同算法,對數(shù)據(jù)進行反復運算)
- 可以考慮使用位數(shù)更長的算法(在MD5的基礎上,改為使用SHA-256 / SHA-384 / SHA-512)
**注意:**無論你綜合使用以上哪些做法,最終,可能都無法避免內(nèi)部人員泄密(算法、加密參數(shù)、加密過程、密文都是破解時的已知條件)導致的窮舉式的暴力破解,而BCrypt算法是被設計得運算效率極低的算法,可以非常有效的避免被暴力破解。