HttpBasic 認證有必定的侷限性與安全隱患,所以在實際項目中使用並很少,可是,有的時候爲了測試方便,開啓 HttpBasic 認證能方便不少。前端
所以鬆哥今天仍是來和你們簡單聊一聊 Spring Security 中的 HttpBasic 認證。java
本文是 Spring Security 系列第 29 篇,閱讀前面文章有助於更好理解本文:算法
Http Basic 認證是 Web 服務器和客戶端之間進行認證的一種方式,最初是在 HTTP1.0 規範(RFC 1945)中定義,後續的有關安全的信息能夠在 HTTP 1.1 規範(RFC 2616)和 HTTP 認證規範(RFC 2617)中找到。spring
HttpBasic 最大的優點在於使用很是簡單,沒有複雜的頁面交互,只須要在請求頭中攜帶相應的信息就能夠認證成功,並且它是一種無狀態登陸,也就是 session 中並不會記錄用戶的登陸信息。數據庫
HttpBasic 最大的問題在於安全性,由於用戶名/密碼只是簡單的經過 Base64 編碼以後就開始傳送了,很容易被工具嗅探到,進而暴露用戶信息。後端
Spring Security 中既支持基本的 HttpBasic 認證,也支持 Http 摘要認證,Http 摘要認證是在 HttpBasic 認證的基礎上,提升了信息安全管理,可是代碼複雜度也提升了很多,因此 Http 摘要認證使用並很少。跨域
這裏,鬆哥將和你們分享 Spring Security 中的這兩種認證方式。瀏覽器
咱們先來看實現,再來分析它的認證流程。安全
首先建立一個 Spring Boot 項目,引入 Web 和 Spring Security 依賴,以下:服務器
接下來建立一個測試接口:
@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello"; } }
再開啓 HttpBasic 認證:
@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
配置完成後,啓動項目,訪問 /hello
接口,此時瀏覽器中會有彈出框,讓咱們輸入用戶名/密碼信息:
此時咱們查看請求響應頭,以下:
能夠看到,瀏覽器響應了 401,同時還攜帶了一個 WWW-Authenticate 響應頭,這個是用來描述認證形式的,若是咱們使用的是 HttpBasic 認證,默認響應頭格式如圖所示。
接下來咱們輸入用戶名密碼,點擊 Sign In 進行登陸,登陸成功後,就能夠成功訪問到 /hello
接口了。
咱們查看第二次的請求,以下:
你們能夠看到,在請求頭中,多了一個 Authorization 字段,該字段的值爲 Basic amF2YWJveToxMjM=
,
amF2YWJveToxMjM=
是一個通過 Base64 編碼以後的字符串,咱們將該字符串解碼以後發現,結果以下:
String x = new String(Base64.getDecoder().decode("amF2YWJveToxMjM="), "UTF-8");
解碼結果以下:
能夠看到,這就是咱們的用戶名密碼信息。用戶名/密碼只是通過簡單的 Base64 編碼以後就開始傳遞了,因此說,這種認證方式比較危險⚠️。
咱們再來稍微總結一下 HttpBasic 認證的流程:
/hello
接口。大體的流程就是這樣。
Http 摘要認證與 HttpBasic 認證基本兼容,可是要複雜不少,這個複雜不只體如今代碼上,也體如今請求過程當中。
Http 摘要認證最重要的改進是他不會在網絡上發送明文密碼。它的整個認證流程是這樣的:
/hello
接口。同時,服務端返回的字段還有一個 qop,表示保護級別,auth 表示只進行身份驗證;auth-int 表示還要校驗內容。
nonce 是服務端生成的隨機字符串,這是一個通過 Base64 編碼的字符串,通過解碼咱們發現,它是由過時時間和密鑰組成的。在之後的請求中 nonce 會原封不動的再發回給服務端。
能夠看到,客戶端發送到服務端的數據比較多。
這就是整個流程。
一言以蔽之,本來的用戶密碼被摘要信息代替了,爲了安全,摘要信息會根據服務端返回的隨機字符串而發生變化,服務端根據用戶密碼,一樣算出密碼的摘要信息,再和客戶端傳來的摘要信息進行對比,沒問題的話,用戶就算認證成功了。固然,在此基礎上還加了一些過時限制、重放攻擊防範機制等。
好了,那這個在 Spring Security 代碼中該怎麼實現呢?
@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(); } }
配置無非就是兩方面,一方面是服務端隨機字符串的生成,另外一方面就是客戶端摘要信息的校驗。
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()); }
在這段代碼中,首先獲取到過時時間,而後給過時時間和 key 一塊兒計算出消息摘要,再將 nonce 和消息摘要共同做爲 value,計算出一個 Base64 編碼字符,再將該編碼字符寫回到前端。
配置完成後,重啓服務端進行測試。
測試效果其實和 HttpBasic 認證是同樣的,全部的變化,只是背後的實現有所變化而已,用戶體驗是同樣的。
Http 摘要認證的效果雖然比 HttpBasic 安全,可是其實你們看到,整個流程下來解決的安全問題其實仍是很是有限。並且代碼也麻煩了不少,所以這種認證方式並未普遍流行開來。
Http 認證小夥伴們做爲一個瞭解便可,裏邊的有一些思想仍是挺有意思的,能夠激發咱們解決其餘問題的思路,例如對於重放攻擊的的解決辦法,咱們若是想本身防護重放攻擊,就能夠參考這裏的實現思路。
好啦,小夥伴們若是有收穫,記得點個在看鼓勵下鬆哥哦~