您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)Spring Boot中如何實(shí)現(xiàn)HTTP認(rèn)證,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
HttpBasic 認(rèn)證有一定的局限性與安全隱患,因此在實(shí)際項(xiàng)目中使用并不多,但是,有的時(shí)候?yàn)榱藴y(cè)試方便,開啟 HttpBasic 認(rèn)證能方便很多。
今天還是來(lái)和大家簡(jiǎ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)勢(shì)在于使用非常簡(jiǎn)單,沒有復(fù)雜的頁(yè)面交互,只需要在請(qǐng)求頭中攜帶相應(yīng)的信息就可以認(rèn)證成功,而且它是一種無(wú)狀態(tài)登錄,也就是 session 中并不會(huì)記錄用戶的登錄信息。
HttpBasic 最大的問題在于安全性,因?yàn)橛脩裘?密碼只是簡(jiǎ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)證
我們先來(lái)看實(shí)現(xiàn),再來(lái)分析它的認(rèn)證流程。
首先創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,引入 Web 和 Spring Security 依賴,如下:
接下來(lái)創(chuàng)建一個(gè)測(cè)試接口:
@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í)瀏覽器中會(huì)有彈出框,讓我們輸入用戶名/密碼信息:
此時(shí)我們查看請(qǐng)求響應(yīng)頭,如下:
可以看到,瀏覽器響應(yīng)了 401,同時(shí)還攜帶了一個(gè) WWW-Authenticate 響應(yīng)頭,這個(gè)是用來(lái)描述認(rèn)證形式的,如果我們使用的是 HttpBasic 認(rèn)證,默認(rèn)響應(yīng)頭格式如圖所示。
接下來(lái)我們輸入用戶名密碼,點(diǎn)擊 Sign In 進(jìn)行登錄,登錄成功后,就可以成功訪問到 /hello 接口了。
我們查看第二次的請(qǐng)求,如下:
大家可以看到,在請(qǐng)求頭中,多了一個(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)過簡(jiǎn)單的 Base64 編碼之后就開始傳遞了,所以說(shuō),這種認(rèn)證方式比較危險(xiǎn)。
我們?cè)賮?lái)稍微總結(jié)一下 HttpBasic 認(rèn)證的流程:
瀏覽器發(fā)出請(qǐng)求,說(shuō)要訪問 /hello 接口。
服務(wù)端返回 401,表示未認(rèn)證。同時(shí)在響應(yīng)頭中攜帶 WWW-Authenticate 字段來(lái)描述認(rèn)證形式。
瀏覽器收到 401 響應(yīng)之后,彈出對(duì)話框,要求用戶輸入用戶名/密碼,用戶輸入完用戶名/密碼之后,瀏覽器會(huì)將之進(jìn)行 Base64 編碼,編碼完成后,發(fā)送到服務(wù)端。
服務(wù)端對(duì)瀏覽器傳來(lái)的信息進(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)在請(qǐng)求過程中。
Http 摘要認(rèn)證最重要的改進(jìn)是他不會(huì)在網(wǎng)絡(luò)上發(fā)送明文密碼。它的整個(gè)認(rèn)證流程是這樣的:
瀏覽器發(fā)出請(qǐng)求,說(shuō)要訪問 /hello 接口。
服務(wù)端返回 401,表示未認(rèn)證,同時(shí)在響應(yīng)頭中攜帶 WWW-Authenticate 字段來(lái)描述認(rèn)證形式。不同的是,這次服務(wù)端會(huì)計(jì)算出一個(gè)隨機(jī)字符串,一同返回前端,這樣可以防止重放攻擊(所謂重放攻擊就是別人嗅探到你的摘要信息,把摘要當(dāng)成密碼一次次發(fā)送服務(wù)端,加一個(gè)會(huì)變化的隨機(jī)字符串,生成的摘要信息就會(huì)變化,就可以防止重放攻擊),如下:
同時(shí),服務(wù)端返回的字段還有一個(gè) qop,表示保護(hù)級(jí)別,auth 表示只進(jìn)行身份驗(yàn)證;auth-int 表示還要校驗(yàn)內(nèi)容。
nonce 是服務(wù)端生成的隨機(jī)字符串,這是一個(gè)經(jīng)過 Base64 編碼的字符串,經(jīng)過解碼我們發(fā)現(xiàn),它是由過期時(shí)間和密鑰組成的。在以后的請(qǐng)求中 nonce 會(huì)原封不動(dòng)的再發(fā)回給服務(wù)端。
客戶端選擇一個(gè)算法,根據(jù)該算法計(jì)算出密碼以及其他數(shù)據(jù)的摘要,如下:
可以看到,客戶端發(fā)送到服務(wù)端的數(shù)據(jù)比較多。
nonce 就是服務(wù)端發(fā)來(lái)的隨機(jī)字符串。
response 是生成的摘要信息。
nc 表示請(qǐng)求此時(shí),可以防止重放攻擊。
cnonce 表示客戶端發(fā)送給服務(wù)端的隨機(jī)字符串。
服務(wù)端根據(jù)客戶端發(fā)送來(lái)的用戶名,可以查詢出用戶密碼,再根據(jù)用戶密碼可以計(jì)算出摘要信息,再將摘要信息和客戶端發(fā)送來(lái)的摘要信息進(jìn)行對(duì)比,就能確認(rèn)用戶身份。
這就是整個(gè)流程。
一言以蔽之,原本的用戶密碼被摘要信息代替了,為了安全,摘要信息會(huì)根據(jù)服務(wù)端返回的隨機(jī)字符串而發(fā)生變化,服務(wù)端根據(jù)用戶密碼,同樣算出密碼的摘要信息,再和客戶端傳來(lái)的摘要信息進(jìn)行對(duì)比,沒問題的話,用戶就算認(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ú)非就是兩方面,一方面是服務(wù)端隨機(jī)字符串的生成,另一方面就是客戶端摘要信息的校驗(yàn)。
首先提供 DigestAuthenticationEntryPoint 的實(shí)例,配置服務(wù)端隨機(jī)數(shù)生成的一些參數(shù),例如 nonce 有效期(多長(zhǎng)時(shí)間會(huì)變),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 過濾器,主要用來(lái)處理前端請(qǐng)求。過濾器的源碼比較長(zhǎng),我這里就不貼出來(lái)了,一個(gè)核心的思路就是從前端拿到用戶請(qǐng)求的摘要信息,服務(wù)端也根據(jù)一直的信息算出來(lái)一個(gè)摘要,再根據(jù)傳過來(lái)的摘要信息進(jìn)行比對(duì),進(jìn)而確認(rèn)用戶身份。
配置完成后,重啟服務(wù)端進(jìn)行測(cè)試。
測(cè)試效果其實(shí)和 HttpBasic 認(rèn)證是一樣的,所有的變化,只是背后的實(shí)現(xiàn)有所變化而已,用戶體驗(yàn)是一樣的。
4.小結(jié)
Http 摘要認(rèn)證的效果雖然比 HttpBasic 安全,但是其實(shí)大家看到,整個(gè)流程下來(lái)解決的安全問題其實(shí)還是非常有限。而且代碼也麻煩了很多,因此這種認(rèn)證方式并未廣泛流行開來(lái)。
Http 認(rèn)證小伙伴們作為一個(gè)了解即可,里邊的有一些思想還是挺有意思的,可以激發(fā)我們解決其他問題的思路,例如對(duì)于重放攻擊的的解決辦法,我們?nèi)绻胱约悍烙胤殴?,就可以參考這里的實(shí)現(xiàn)思路。
關(guān)于Spring Boot中如何實(shí)現(xiàn)HTTP認(rèn)證就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。