這篇文章將為大家詳細(xì)講解有關(guān)Spring Boot中如何實(shí)現(xiàn)HTTP認(rèn)證,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
呼圖壁ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:13518219792(備注:SSL證書合作)期待與您的合作!
HttpBasic 認(rèn)證有一定的局限性與安全隱患,因此在實(shí)際項(xiàng)目中使用并不多,但是,有的時(shí)候?yàn)榱藴y試方便,開啟 HttpBasic 認(rèn)證能方便很多。
今天還是來和大家簡單聊一聊 Spring Security 中的 HttpBasic 認(rèn)證。
1.什么是 HttpBasic
Http Basic 認(rèn)證是 Web 服務(wù)器和客戶端之間進(jìn)行認(rèn)證的一種方式,最初是在 HTTP1.0 規(guī)范(RFC 1945)中定義,后續(xù)的有關(guān)安全的信息可以在 HTTP 1.1 規(guī)范(RFC 2616)和 HTTP 認(rèn)證規(guī)范(RFC 2617)中找到。
HttpBasic 最大的優(yōu)勢在于使用非常簡單,沒有復(fù)雜的頁面交互,只需要在請求頭中攜帶相應(yīng)的信息就可以認(rèn)證成功,而且它是一種無狀態(tài)登錄,也就是 session 中并不會記錄用戶的登錄信息。
HttpBasic 最大的問題在于安全性,因?yàn)橛脩裘?密碼只是簡單的通過 Base64 編碼之后就開始傳送了,很容易被工具嗅探到,進(jìn)而暴露用戶信息。
Spring Security 中既支持基本的 HttpBasic 認(rèn)證,也支持 Http 摘要認(rèn)證,Http 摘要認(rèn)證是在 HttpBasic 認(rèn)證的基礎(chǔ)上,提高了信息安全管理,但是代碼復(fù)雜度也提高了不少,所以 Http 摘要認(rèn)證使用并不多。
這里,和大家分享 Spring Security 中的這兩種認(rèn)證方式。
2.HttpBasic 認(rèn)證
我們先來看實(shí)現(xiàn),再來分析它的認(rèn)證流程。
首先創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,引入 Web 和 Spring Security 依賴,如下:
接下來創(chuàng)建一個(gè)測試接口:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
再開啟 HttpBasic 認(rèn)證:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
最后再在 application.properties 中配置基本的用戶信息,如下:
spring.security.user.password=123
spring.security.user.name=javaboy
配置完成后,啟動(dòng)項(xiàng)目,訪問 /hello 接口,此時(shí)瀏覽器中會有彈出框,讓我們輸入用戶名/密碼信息:
此時(shí)我們查看請求響應(yīng)頭,如下:
可以看到,瀏覽器響應(yīng)了 401,同時(shí)還攜帶了一個(gè) WWW-Authenticate 響應(yīng)頭,這個(gè)是用來描述認(rèn)證形式的,如果我們使用的是 HttpBasic 認(rèn)證,默認(rèn)響應(yīng)頭格式如圖所示。
接下來我們輸入用戶名密碼,點(diǎn)擊 Sign In 進(jìn)行登錄,登錄成功后,就可以成功訪問到 /hello 接口了。
我們查看第二次的請求,如下:
大家可以看到,在請求頭中,多了一個(gè) Authorization 字段,該字段的值為 Basic amF2YWJveToxMjM=,
amF2YWJveToxMjM= 是一個(gè)經(jīng)過 Base64 編碼之后的字符串,我們將該字符串解碼之后發(fā)現(xiàn),結(jié)果如下:
String x =
new String(Base64.getDecoder().decode("amF2YWJveToxMjM="),
"UTF-8");
解碼結(jié)果如下:
可以看到,這就是我們的用戶名密碼信息。用戶名/密碼只是經(jīng)過簡單的 Base64 編碼之后就開始傳遞了,所以說,這種認(rèn)證方式比較危險(xiǎn)。
我們再來稍微總結(jié)一下 HttpBasic 認(rèn)證的流程:
瀏覽器發(fā)出請求,說要訪問 /hello 接口。
服務(wù)端返回 401,表示未認(rèn)證。同時(shí)在響應(yīng)頭中攜帶 WWW-Authenticate 字段來描述認(rèn)證形式。
瀏覽器收到 401 響應(yīng)之后,彈出對話框,要求用戶輸入用戶名/密碼,用戶輸入完用戶名/密碼之后,瀏覽器會將之進(jìn)行 Base64 編碼,編碼完成后,發(fā)送到服務(wù)端。
服務(wù)端對瀏覽器傳來的信息進(jìn)行解碼,并校驗(yàn),當(dāng)沒問題的時(shí)候,給客戶端作出響應(yīng)。
大致的流程就是這樣。
3.Http 摘要認(rèn)證
Http 摘要認(rèn)證與 HttpBasic 認(rèn)證基本兼容,但是要復(fù)雜很多,這個(gè)復(fù)雜不僅體現(xiàn)在代碼上,也體現(xiàn)在請求過程中。
Http 摘要認(rèn)證最重要的改進(jìn)是他不會在網(wǎng)絡(luò)上發(fā)送明文密碼。它的整個(gè)認(rèn)證流程是這樣的:
瀏覽器發(fā)出請求,說要訪問 /hello 接口。
服務(wù)端返回 401,表示未認(rèn)證,同時(shí)在響應(yīng)頭中攜帶 WWW-Authenticate 字段來描述認(rèn)證形式。不同的是,這次服務(wù)端會計(jì)算出一個(gè)隨機(jī)字符串,一同返回前端,這樣可以防止重放攻擊(所謂重放攻擊就是別人嗅探到你的摘要信息,把摘要當(dāng)成密碼一次次發(fā)送服務(wù)端,加一個(gè)會變化的隨機(jī)字符串,生成的摘要信息就會變化,就可以防止重放攻擊),如下:
同時(shí),服務(wù)端返回的字段還有一個(gè) qop,表示保護(hù)級別,auth 表示只進(jìn)行身份驗(yàn)證;auth-int 表示還要校驗(yàn)內(nèi)容。
nonce 是服務(wù)端生成的隨機(jī)字符串,這是一個(gè)經(jīng)過 Base64 編碼的字符串,經(jīng)過解碼我們發(fā)現(xiàn),它是由過期時(shí)間和密鑰組成的。在以后的請求中 nonce 會原封不動(dòng)的再發(fā)回給服務(wù)端。
客戶端選擇一個(gè)算法,根據(jù)該算法計(jì)算出密碼以及其他數(shù)據(jù)的摘要,如下:
可以看到,客戶端發(fā)送到服務(wù)端的數(shù)據(jù)比較多。
nonce 就是服務(wù)端發(fā)來的隨機(jī)字符串。
response 是生成的摘要信息。
nc 表示請求此時(shí),可以防止重放攻擊。
cnonce 表示客戶端發(fā)送給服務(wù)端的隨機(jī)字符串。
服務(wù)端根據(jù)客戶端發(fā)送來的用戶名,可以查詢出用戶密碼,再根據(jù)用戶密碼可以計(jì)算出摘要信息,再將摘要信息和客戶端發(fā)送來的摘要信息進(jìn)行對比,就能確認(rèn)用戶身份。
這就是整個(gè)流程。
一言以蔽之,原本的用戶密碼被摘要信息代替了,為了安全,摘要信息會根據(jù)服務(wù)端返回的隨機(jī)字符串而發(fā)生變化,服務(wù)端根據(jù)用戶密碼,同樣算出密碼的摘要信息,再和客戶端傳來的摘要信息進(jìn)行對比,沒問題的話,用戶就算認(rèn)證成功了。當(dāng)然,在此基礎(chǔ)上還加了一些過期限制、重放攻擊防范機(jī)制等。
好了,那這個(gè)在 Spring Security 代碼中該怎么實(shí)現(xiàn)呢?
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(digestAuthenticationEntryPoint())
.and()
.addFilter(digestAuthenticationFilter());
}
@Bean
DigestAuthenticationEntryPoint
digestAuthenticationEntryPoint() {
DigestAuthenticationEntryPoint entryPoint =
new DigestAuthenticationEntryPoint();
entryPoint.setKey("javaboy");
entryPoint.setRealmName("myrealm");
entryPoint.setNonceValiditySeconds(1000);
return entryPoint;
}
@Bean
DigestAuthenticationFilter
digestAuthenticationFilter() {
DigestAuthenticationFilter filter =
new DigestAuthenticationFilter();
filter.setAuthenticationEntryPoint(digestAuthenticationEntryPoint());
filter.setUserDetailsService(userDetailsService());
return filter;
}
@Override
@Bean
protected UserDetailsService
userDetailsService() {
InMemoryUserDetailsManager manager =
new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("javaboy").password("123").roles("admin").build());
return manager;
}
@Bean
PasswordEncoder
passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
配置無非就是兩方面,一方面是服務(wù)端隨機(jī)字符串的生成,另一方面就是客戶端摘要信息的校驗(yàn)。
首先提供 DigestAuthenticationEntryPoint 的實(shí)例,配置服務(wù)端隨機(jī)數(shù)生成的一些參數(shù),例如 nonce 有效期(多長時(shí)間會變),realm 的名字,以及生成 nonce 時(shí)所需要的 key。nonce 的具體生成邏輯在 DigestAuthenticationEntryPoint#commence 方法中:
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
HttpServletResponse httpResponse = response;
long expiryTime = System.currentTimeMillis() + (nonceValiditySeconds *
1000);
String signatureValue = DigestAuthUtils.md5Hex(expiryTime +
":" + key);
String nonceValue = expiryTime +
":" + signatureValue;
String nonceValueBase64 =
new String(Base64.getEncoder().encode(nonceValue.getBytes()));
String authenticateHeader =
"Digest realm=\"" + realmName +
"\", "
+
"qop=\"auth\", nonce=\"" + nonceValueBase64 +
"\"";
if (authException
instanceof NonceExpiredException) {
authenticateHeader = authenticateHeader +
", stale=\"true\"";
}
if (logger.isDebugEnabled()) {
logger.debug("WWW-Authenticate header sent to user agent: "
+ authenticateHeader);
}
httpResponse.addHeader("WWW-Authenticate", authenticateHeader);
httpResponse.sendError(HttpStatus.UNAUTHORIZED.value(),
HttpStatus.UNAUTHORIZED.getReasonPhrase());
}
在這段代碼中,首先獲取到過期時(shí)間,然后給過期時(shí)間和 key 一起計(jì)算出消息摘要,再將 nonce 和消息摘要共同作為 value,計(jì)算出一個(gè) Base64 編碼字符,再將該編碼字符寫回到前端。
配置 DigestAuthenticationFilter 過濾器,主要用來處理前端請求。過濾器的源碼比較長,我這里就不貼出來了,一個(gè)核心的思路就是從前端拿到用戶請求的摘要信息,服務(wù)端也根據(jù)一直的信息算出來一個(gè)摘要,再根據(jù)傳過來的摘要信息進(jìn)行比對,進(jìn)而確認(rèn)用戶身份。
配置完成后,重啟服務(wù)端進(jìn)行測試。
測試效果其實(shí)和 HttpBasic 認(rèn)證是一樣的,所有的變化,只是背后的實(shí)現(xiàn)有所變化而已,用戶體驗(yàn)是一樣的。
4.小結(jié)
Http 摘要認(rèn)證的效果雖然比 HttpBasic 安全,但是其實(shí)大家看到,整個(gè)流程下來解決的安全問題其實(shí)還是非常有限。而且代碼也麻煩了很多,因此這種認(rèn)證方式并未廣泛流行開來。
Http 認(rèn)證小伙伴們作為一個(gè)了解即可,里邊的有一些思想還是挺有意思的,可以激發(fā)我們解決其他問題的思路,例如對于重放攻擊的的解決辦法,我們?nèi)绻胱约悍烙胤殴?,就可以參考這里的實(shí)現(xiàn)思路。
關(guān)于Spring Boot中如何實(shí)現(xiàn)HTTP認(rèn)證就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。