HTTP/1.1 使用的認證方式有
1)BASIC 認證(基本認證);
2)DIGEST 認證(摘要認證);
3)SSL 客戶端認證;
4)FormBase 認證(基於表單認證);
1、BASIC 認證(基本認證)的步驟
2、BASIC 認證的的缺點
3、Java + SpringBoot 實現 BASIC 認證的Demo
4、測試
5、注意事項
6、Java + SpringBoot + 自定義注解 + 攔截器實現 BASIC 認證
1、BASIC 認證(基本認證)的步驟 <-- 返回目錄
BASIC 認證(基本認證)是從HTTP/1. 1 就定義的認證方式,是Web服務器與通信客戶端之間進行的認證方式。
BASIC 認證的步驟:
步驟1:當請求的資源需要 BASIC 認證時,服務器會隨狀態碼 401 Authorization Required,返回 WWW-Authenticate 首部字段(響應頭)的響應。該字段內包含認證的方式(BASIC)及Request-URI 安全域字符串(realm)。
步驟2:接收到狀態碼 401 的客戶端為了通過 BASIC 認證,需要將用戶ID及密碼發送給服務器。發送的字符串內容是由用戶名 ID 和密碼構成,兩者中間以冒號(:)連接后,再經過 Base64 編碼處理。
假設用戶 ID 為 guest,密碼是 guest,連接起來形成 "guest:guest" 這樣的字符串。然后經過 Base64 編碼成 Z3Vlc3Q6Z3Vlc3Q=。把這個字符串前面拼接 Baisc 形成 "Basic Z3Vlc3Q6Z3Vlc3Q=",然后把這個字符串寫入首部字段 Authorization 后,發送請求。
當用戶端為瀏覽器時,一個僅需要輸入用戶 ID 和密碼即可,瀏覽器會自動完成 Base64 編碼的工作,並再前面添加 "Basic" 前綴。
步驟3:接收到包含首部字段 Authorization 請求的服務器,會對認證信息的正確性進行驗證。如果驗證通過,則返回一條包含 Request-URI 資源的響應。
2、BASIC 認證的的缺點 <-- 返回目錄
1)BASIC 認證雖然采用 Base64 密碼方式,但這不是加密處理。不需要任何附加信息即可對其進行解碼。換言之,由於明文解碼后就是用戶名 ID 和密碼,再 HTTP 等非加密通信線路上進行 BASIC 認證的過程中,如果被人竊聽,被盜的可能性極高。
所以,BASIC 認證需要配合HTTPS來保證信息傳輸的安全。
2)即使密碼被強加密,第三方仍可通過加密后的用戶名和密碼進行重放攻擊
3)如果想再進行一次 BASIC 認證時,一般的瀏覽器卻無法實現認證注銷操作。所以,BASIC 認證使用上不夠靈活,且達不到多數 Web 網站期望的安全性等級,因此並不常用。
3、Java + SpringBoot 實現 BASIC 認證的Demo <-- 返回目錄
application.properties
server.port=8089 server.servlet.context-path=/BootDemo
IndexController
package com.oy.controller; import java.util.Base64; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * @author oy * @version 1.0 * @date 2020年3月30日 * @time 下午1:37:36 */ @Controller public class IndexController { private static final Base64.Decoder decoder = Base64.getDecoder(); // private static final Base64.Encoder encoder = Base64.getEncoder(); @RequestMapping("/login") @ResponseBody public String login(HttpServletRequest req, HttpServletResponse res) { if (!isAuth(req, res)) { return "{code: 401, msg: \"no auth\"}"; } return "{code: 0, data: {username:\"test\"}}"; } @RequestMapping("/index") @ResponseBody public String index(HttpServletRequest req, HttpServletResponse res) { if (!isAuth(req, res)) { return "{code: 401, msg: \"no auth\"}"; } return "{code: 0, data: {xxx:\"xxx\"}}"; } private boolean isAuth(HttpServletRequest req, HttpServletResponse res) { String base6AuthStr = req.getHeader("Authorization"); System.out.println("base6AuthStr=" + base6AuthStr); // base6AuthStr=Basic YWFhOmFhYQ== if (base6AuthStr == null) { res.setStatus(401); res.addHeader("WWW-Authenticate", "basic realm=\"no auth\""); return false; } String authStr = new String(decoder.decode(base6AuthStr.substring(6).getBytes())); System.out.println("authStr=" + authStr); // authStr=xxx:xxx String[] arr = authStr.split(":"); if (arr != null && arr.length == 2) { String username = arr[0]; String password = arr[1]; // 校驗用戶名和密碼 if ("test".equals(username) && "123456".equals(password)) { return true; } } // 用戶名 ID 和密碼校驗失敗后,重新返回 401 和 WWW-Authenticate 響應頭,從而可以重復質詢 res.setStatus(401); res.addHeader("WWW-Authenticate", "basic realm=\"no auth\""); return false; } }
4、測試 <-- 返回目錄
瀏覽器輸入:http://localhost:8089/BootDemo/login,彈出下面的對話框,如果輸入錯誤會再次彈出該對話框
點擊取消,查看這次請求的信息,發現服務器響應 401,和 WWW-Authenticate 響應頭,告知瀏覽器需要 BASIC 認證,瀏覽器收到響應后彈出認證對話框。
用戶名 ID 和密碼輸入正確,結果(兩次請求,第二次瀏覽器發送包含認證信息的請求頭 Authorization)
認證成功后,訪問其他資源,瀏覽器自動添加 Authorization 請求頭
5、注意事項 <-- 返回目錄
1)發送 WWW-Authenticate 響應頭時,basic 是告訴瀏覽器使用 BASIC 認證,瀏覽器收到后彈出認證對話框;
realm
表示Web服務器中受保護文檔的安全域(比如公司財務信息域和公司員工信息域),用來指示需要哪個域的用戶名和密碼,最好用" "
包括起來。
res.addHeader("WWW-Authenticate", "basic realm=\"no auth\"");
2)用戶名 ID 和密碼校驗失敗后,重新返回 401 和 WWW-Authenticate 響應頭,從而可以重復質詢(用戶名和密碼輸入錯誤,重新彈出輸入框)。
6、Java + SpringBoot + 自定義注解 + 攔截器實現 BASIC 認證 <-- 返回目錄
application.properties
server.port=8089 server.servlet.context-path=/BootDemo
自定義注解 RequireAuth
package com.oy.interceptor; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // can be used to method @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequireAuth { }
配置 RequireAuth 注解使用的攔截器 RequireAuthInterceptor

package com.oy.interceptor; import java.util.Base64; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; public class RequireAuthInterceptor extends HandlerInterceptorAdapter { final Base64.Decoder decoder = Base64.getDecoder(); // final Base64.Encoder encoder = Base64.getEncoder(); @Override public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception { // 請求目標為 method of controller,需要進行驗證 if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Object object = handlerMethod.getMethodAnnotation(RequireAuth.class); /* 方法沒有 @RequireAuth 注解, 放行 */ if (object == null) { return true; // 放行 } /* 方法有 @RequireAuth 注解,需要攔截校驗 */ // 沒有 Authorization 請求頭,或者 Authorization 認證信息不通過,攔截 if (!isAuth(req, res)) { return false; // 攔截 } // 驗證通過,放行 return true; } // 請求目標不是 mehod of controller, 放行 return true; } private boolean isAuth(HttpServletRequest req, HttpServletResponse res) { String base6AuthStr = req.getHeader("Authorization"); System.out.println("base6AuthStr=" + base6AuthStr); // base6AuthStr=Basic YWFhOmFhYQ== if (base6AuthStr == null) { res.setStatus(401); res.addHeader("WWW-Authenticate", "basic realm=\"no auth\""); return false; } String authStr = new String(decoder.decode(base6AuthStr.substring(6).getBytes())); System.out.println("authStr=" + authStr); // authStr=xxx:xxx String[] arr = authStr.split(":"); if (arr != null && arr.length == 2) { String username = arr[0]; String password = arr[1]; // 校驗用戶名和密碼 if ("test".equals(username) && "123456".equals(password)) { return true; } } res.setStatus(401); res.addHeader("WWW-Authenticate", "basic realm=\"no auth\""); return false; } }
SpringBoot 配置類WebConfig:注冊攔截器

package com.oy; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.oy.interceptor.RequireAuthInterceptor; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { RequireAuthInterceptor requireAuthInterceptor = new RequireAuthInterceptor(); registry.addInterceptor(requireAuthInterceptor); } }
測試類,再需要校驗用戶的方法上面加 @RequireAuth 注解
package com.oy.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.oy.interceptor.RequireAuth; @Controller public class IndexController { @RequireAuth @RequestMapping("/login") @ResponseBody public String login(HttpServletRequest req, HttpServletResponse res) { return "{code: 0, data: {username:\"test\"}}"; } @RequireAuth @RequestMapping("/index") @ResponseBody public String index(HttpServletRequest req, HttpServletResponse res) { return "{code: 0, data: {xxx:\"xxx\"}}"; } }
參考:
1)《圖解HTTP》