HTTP的幾種認證方式之BASIC 認證(基本認證)


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;
    }

}
View Code

  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);
    }
    
}
View Code

  測試類,再需要校驗用戶的方法上面加 @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》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM