http协议之digest(摘要)认证,详细讲解并附Java SpringBoot源码

这篇具有很好参考价值的文章主要介绍了http协议之digest(摘要)认证,详细讲解并附Java SpringBoot源码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1.digest认证是什么?

2.digest认证过程

3.digest认证参数详解

4.基于SpringBoot实现digest认证

5.digest认证演示

6.digest认证完整项目

7.参考博客


1.digest认证是什么?

        HTTP通讯采用人类可阅读的文本格式进行数据通讯,其内容非常容易被解读。出于安全考虑,HTTP规范定义了几种认证方式以对访问者身份进行鉴权,最常见的认证方式之一是Digest认证。Digest是一种加密认证方式,通讯中不会传输密码信息,而仅采用校验方式对接入的请求进行验证。

        DIGEST 认证是使用质询 / 响应的方式(challenge/response),但不会像 BASIC 认证那样直接发送明文密码。质询响应方式是指,一开始一方会先发送认证要求给另一方,接着使用从另一方那接收到的质询码计算生成响应码。最后将响应码返回给对方进行认证的方式。

        Digest认证支持的加密算法有:SHA256,SHA512/256,MD5。上述这几种算法都是由哈希函数来生成散列值,其加密过程为单向计算,请求方无法反算出密码明文。
 

2.digest认证过程

http协议之digest(摘要)认证,详细讲解并附Java SpringBoot源码

 

步骤 1: 请求需认证的资源时,服务器会随着状态码 401Authorization Required,返回带WWW-Authenticate 首部字段的响应。该字段内包含质问响应方式认证所需的临时质询码(随机数,nonce)。首部字段 WWW-Authenticate 内必须包含realm 和nonce 这两个字段的信息。客户端就是依靠向服务器回送这两个值进行认证的。nonce 是一种每次随返回的 401 响应生成的任意随机字符串。该字符串通常推荐由Base64 编码的十六进制数的组成形式,但实际内容依赖服务器的具体实现。

步骤 2:接收到401状态码的客户端,返回的响应中包含 DIGEST 认证必须的首部字段 Authorization 信息。首部字段 Authorization 内必须包含 username、realm、nonce、uri 和response的字段信息。其中,realm 和 nonce 就是之前从服务器接收到的响应中的字段。
  username是realm 限定范围内可进行认证的用户名。uri(digest-uri)即Request-URI的值,但考虑到经代理转发后Request-URI的值可能被修改因此事先会复制一份副本保存在 uri内。

  response 也可叫做 Request-Digest,存放经过 MD5 运算后的密码字符串,形成响应码。

步骤 3:接收到包含首部字段 Authorization 请求的服务器,会确认认证信息的正确性。认证通过后则返回包含 Request-URI 资源的响应。并且这时会在首部字段 Authentication-Info 写入一些认证成功的相关信息。(不过我下面的例子没有去写这个Authentication-Info,而是直接返回的数据。因为我实在session里缓存的认证结果)。

3.digest认证参数详解

WWW-Authentication:用来定义使用何种方式(Basic、Digest、Bearer等)去进行认证以获取受保护的资源
realm:表示Web服务器中受保护文档的安全域(比如公司财务信息域和公司员工信息域),用来指示需要哪个域的用户名和密码
qop:保护质量,包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略,(可以为空,但是)不推荐为空值
nonce:服务端向客户端发送质询时附带的一个随机数,这个数会经常发生变化。客户端计算密码摘要时将其附加上去,使得多次生成同一用户的密码摘要各不相同,用来防止重放攻击
nc:nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量。例如,在响应的第一个请求中,客户端将发送“nc=00000001”。这个指示值的目的是让服务器保持这个计数器的一个副本,以便检测重复的请求
cnonce:客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令
Authorization-Info:用于返回一些与授权会话相关的附加信息
nextnonce:下一个服务端随机数,使客户端可以预先发送正确的摘要
rspauth:响应摘要,用于客户端对服务端进行认证
stale:当密码摘要使用的随机数过期时,服务器可以返回一个附带有新随机数的401响应,并指定stale=true,表示服务器在告知客户端用新的随机数来重试,而不再要求用户重新输入用户名和密码了

4.基于SpringBoot实现digest认证

DemoApplication.java
package com.digest.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages="com.digest")
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}
WebConfig.java
import com.digest.interceptor.DigestAuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        DigestAuthInterceptor requireAuthInterceptor = new DigestAuthInterceptor();
        registry.addInterceptor(requireAuthInterceptor);
    }

}
IndexController.java
package com.digest.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.digest.interceptor.DigestAuth;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
public class IndexController {

    @DigestAuth
    @RequestMapping("/login")
    @ResponseBody
    public String login(HttpServletRequest req, HttpServletResponse res) {
        return "{code: 0, data: {username:\"test\"}}";
    }

    @DigestAuth
    @RequestMapping("/index")
    @ResponseBody
    public String index(HttpServletRequest req, HttpServletResponse res) {
        return "{code: 0, data: {xxx:\"xxx\"}}";
    }

}
DigestAuth.java
package com.digest.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 DigestAuth {

}
DigestAuthInterceptor.java
package com.digest.interceptor;

import java.text.MessageFormat;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.digest.model.DigestAuthInfo;
import com.digest.util.DigestUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;


public class DigestAuthInterceptor extends HandlerInterceptorAdapter {

    // 为了 测试Digest nc 值每次请求增加
    private int nc = 0;

    @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(DigestAuth.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 authStr = req.getHeader("Authorization");
        System.out.println("请求 Authorization 的内容:" + authStr);
        if (authStr == null || authStr.length() <= 7) {
            // 没有 Authorization 请求头,开启质询
            return challenge(res);
        }

        DigestAuthInfo authObject = DigestUtils.getAuthInfoObject(authStr);
        // System.out.println(authObject);

        /*
         * 生成 response 的算法:
         *  response = MD5(MD5(username:realm:password):nonce:nc:cnonce:qop:MD5(<request-method>:url))
         */
        // 这里密码固定为 123456, 实际应用需要根据用户名查询数据库或缓存获得
        String HA1 = DigestUtils.MD5(authObject.getUsername() + ":" + authObject.getRealm() + ":"+authObject.getUsername());
        String HD = String.format(authObject.getNonce() + ":" + authObject.getNc() + ":" + authObject.getCnonce() + ":"
                + authObject.getQop());
        String HA2 = DigestUtils.MD5(req.getMethod() + ":" + authObject.getUri());
        String responseValid = DigestUtils.MD5(HA1 + ":" + HD + ":" + HA2);

        // 如果 Authorization 中的 response(浏览器生成的) 与期望的 response(服务器计算的) 相同,则验证通过
        System.out.println("Authorization 中的 response: " + authObject.getResponse());
        System.out.println("期望的 response: " + responseValid);
        if (responseValid.equals(authObject.getResponse())) {
            /* 判断 nc 的值,用来防重放攻击 */
            // 判断此次请求的 Authorization 请求头里面的 nc 值是否大于之前保存的 nc 值
            // 大于,替换旧值,然后 return true
            // 否则,return false

            // 测试代码 start
            int newNc = Integer.parseInt(authObject.getNc(), 16);
            System.out.println("old nc: " + this.nc + ", new nc: " + newNc);
            if (newNc > this.nc) {
                this.nc = newNc;
                return true;
            }
            return false;
            // 测试代码 end
        }

        // 验证不通过,重复质询
        return challenge(res);
    }

    /**
     * 质询:返回状态码 401 和 WWW-Authenticate 响应头
     *
     * @param res 返回false,则表示拦截器拦截请求
     */
    private boolean challenge(HttpServletResponse res) {
        // 质询前,重置或删除保存的与该用户关联的 nc 值(nc:nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量)
        // 将 nc 置为初始值 0, 这里代码省略

        // 测试代码 start
        this.nc = 0;
        // 测试代码 end

        res.setStatus(401);
        String str = MessageFormat.format("Digest realm={0},nonce={1},qop={2}", "\"no auth\"",
                "\"" + DigestUtils.generateToken() + "\"", "\"auth\"");
        res.addHeader("WWW-Authenticate", str);
        return false;
    }

}
DigestAuthInfo.java
package com.digest.model;

import lombok.Data;

@Data
public class DigestAuthInfo {
    private String username;//认证的用户名
    private String realm;//Web服务器中受保护文档的安全域(比如公司财务信息域和公司员工信息域),用来指示需要哪个域的用户名和密码
    private String nonce;//服务端向客户端发送质询时附带的一个随机数
    private String uri;//请求的资源位置,访问地址,例如/index
    private String response;//由用户代理软件计算出的一个字符串,以证明用户知道口令
    private String qop;//保护质量,包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略,(可以为空,但是)不推荐为空值
    private String nc;//nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量。
    public String cnonce;//客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。

}
DigestUtils.java
package com.digest.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Random;

import com.digest.model.DigestAuthInfo;

public class DigestUtils {

    /**
     * 根据当前时间戳生成一个随机字符串
     * @return
     */
    public static String generateToken() {
        String s = String.valueOf(System.currentTimeMillis() + new Random().nextInt());

        try {
            MessageDigest messageDigest = MessageDigest.getInstance("md5");
            byte[] digest = messageDigest.digest(s.getBytes());

            return Base64.getEncoder().encodeToString(digest);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException();
        }
    }


    public static String MD5(String inStr) {
        MessageDigest md5 = null;

        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }

        char[] charArray = inStr.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++) {
            byteArray[i] = (byte) charArray[i];
        }

        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();

        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16)
                hexValue.append("0");
            hexValue.append(Integer.toHexString(val));
        }

        return hexValue.toString();
    }

    /**
     * 该方法用于将 Authorization 请求头的内容封装成一个对象。
     *
     * Authorization 请求头的内容为:
     *     Digest username="aaa", realm="no auth", nonce="b2b74be03ff44e1884ba0645bb961b53",
     *     uri="/BootDemo/login", response="90aff948e6f2207d69ecedc5d39f6192", qop=auth,
     *     nc=00000002, cnonce="eb73c2c68543faaa"
     */
    public static DigestAuthInfo getAuthInfoObject(String authStr) {
        if (authStr == null || authStr.length() <= 7)
            return null;

        if (authStr.toLowerCase().indexOf("digest") >= 0) {
            // 截掉前缀 Digest
            authStr = authStr.substring(6);
        }

        // 将双引号去掉
        authStr = authStr.replaceAll("\"", "");

        DigestAuthInfo digestAuthObject = new DigestAuthInfo();
        String[] authArray = new String[8];
        authArray = authStr.split(",");
        // System.out.println(java.util.Arrays.toString(authArray));

        for (int i = 0, len = authArray.length; i < len; i++) {
            String auth = authArray[i];
            String key = auth.substring(0, auth.indexOf("=")).trim();
            String value = auth.substring(auth.indexOf("=") + 1).trim();
            switch (key) {
                case "username":
                    digestAuthObject.setUsername(value);
                    break;
                case "realm":
                    digestAuthObject.setRealm(value);
                    break;
                case "nonce":
                    digestAuthObject.setNonce(value);
                    break;
                case "uri":
                    digestAuthObject.setUri(value);
                    break;
                case "response":
                    digestAuthObject.setResponse(value);
                    break;
                case "qop":
                    digestAuthObject.setQop(value);
                    break;
                case "nc":
                    digestAuthObject.setNc(value);
                    break;
                case "cnonce":
                    digestAuthObject.setCnonce(value);
                    break;
            }
        }
        return digestAuthObject;
    }

}

application.properties

server.port=8080
server.servlet.context-path=/

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.6</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>SpringBootDigest</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.24</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

5.digest认证演示

启动项目后,访问http://localhost:8080/login,出现如下界面

http协议之digest(摘要)认证,详细讲解并附Java SpringBoot源码

 输入账号 user 密码 user,登录,出现如下界面

http协议之digest(摘要)认证,详细讲解并附Java SpringBoot源码

 项目后端打印信息如下所示:

请求 Authorization 的内容:Digest username="user", realm="no auth", nonce="Fy9j+28xS8co4LWob6LOAg==", uri="/login", response="516234f0509c3c81f9130ddfbcd95f50", qop=auth, nc=00000006, cnonce="b064dbc52ff14c0a"
Authorization 中的 response: 516234f0509c3c81f9130ddfbcd95f50
期望的 response: 516234f0509c3c81f9130ddfbcd95f50
old nc: 0, new nc: 6
请求 Authorization 的内容:Digest username="user", realm="no auth", nonce="Fy9j+28xS8co4LWob6LOAg==", uri="/login", response="3977b5c6e46b2941b61c835659dd904a", qop=auth, nc=00000007, cnonce="637df6c7b8be6edb"
Authorization 中的 response: 3977b5c6e46b2941b61c835659dd904a
期望的 response: 3977b5c6e46b2941b61c835659dd904a
old nc: 6, new nc: 7
请求 Authorization 的内容:null
请求 Authorization 的内容:Digest username="user", realm="no auth", nonce="T/46pI2qR2K0Cy6D4hb4jg==", uri="/login", response="ae9a759d6ac8af2d4a9e73a17cdd0859", qop=auth, nc=00000002, cnonce="3efb440863ffc450"
Authorization 中的 response: ae9a759d6ac8af2d4a9e73a17cdd0859
期望的 response: ae9a759d6ac8af2d4a9e73a17cdd0859
old nc: 0, new nc: 2

6.digest认证完整项目

基于SpringBoot完整项目 http://www.zrscsoft.com/sitepic/12152.html

7.参考博客

http协议之digest(摘要)认证_red-fly的博客-CSDN博客_digest认证

HTTP的几种认证方式之DIGEST 认证(摘要认证) - wenbin_ouyang - 博客园文章来源地址https://www.toymoban.com/news/detail-417614.html

到了这里,关于http协议之digest(摘要)认证,详细讲解并附Java SpringBoot源码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Qt HTTP 摘要认证(海康球机摄像机ISAPI开发)

    接到一个需求是开发下海康的球机,控制云台,给到我的是一个开发手册,当然了是海康的私有协议 ISAPI开发手册 https://download.csdn.net/download/qq_37059136/88547425 关于开发这块读文档就可以理解了,海康使用的是摘要认证,当然了海康已经给出使用范例 通过libcurl就可以直接连接上海康的

    2024年02月04日
    浏览(47)
  • 身份认证——AAA与Radius协议讲解

    目录 AAA介绍 AAA的基本架构 AAA的认证 授权 计费方式 AAA通过本地和远端实现的大致流程 Radius协议 Radius架构 Radius报文结构 Radius如何对用户进行认证 Radius协议报文交互过程 AAA(Authentication Authorization and Accounting)又称为认证、授权和计费,是一种管理网络安全的机制(架构),

    2024年02月02日
    浏览(40)
  • HTTP协议分析--第5关:HTTP 认证

    任务描述 本关任务:分析 HTTP 认证过程及 HTTP 协议中认证相关的字段。 相关知识 为了完成本关任务,你需要掌握: HTTP 协议中如何完成认证; 识别 HTTP 协议中认证相关的字段。 HTTP认证方式 HTTP 中有如下常用认证方式: Basic 认证; Digest 认证; SSL Client 认证; 表单认证。

    2024年02月04日
    浏览(45)
  • [玩转AIGC]如何训练LLaMA2(模型训练、推理、代码讲解,并附可直接运行的kaggle连接)

    Llama 2,基于优化的 Transformer 架构,是Meta AI正式发布的最新一代开源大模型,一系列模型(7b、13b、70b)均开源可商用,效果直逼gpt3.5。 下面我们来介绍如何使用Llama 2来训练一个故事生成模型。 如果迫不及待想爽一把先,请直接跳到这里,可直接运行:llama2-c, 学习不就是

    2024年02月12日
    浏览(45)
  • HTTP、HTTPS、SSL协议以及相关报文讲解

    目录 HTTP/HTTPS介绍 HTTP/HTTPS基本信息 HTTP如何实现有状态 HTTP请求与应答报文 HTTP请求报文 HTTP响应报文 SSL协议 SSL单向认证 SSL双向认证 HTTP连接建立与传输步骤 HTTP访问全过程相关报文(以访问www.download.cucdccom为例子) DNS报文解析 TCP三次握手连接 进行HTTP交互(明文) HTTPS连接

    2024年02月04日
    浏览(59)
  • HTTP 协议与HTTP S协议对比与详细内容

    阅读须知 : 以下所有内容均来源于菜鸟教程,详细内容请点击这里查看 HTTP 协议一般指 HTTP(超文本传输协议)基于 TCP/IP 通信协议来传递数据。 超文本传输协议定义(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议,是因

    2024年02月03日
    浏览(51)
  • 【计算机网络】TCP协议超详细讲解

    TCP协议广泛应用于可靠性要求较高的应用场景,如网页浏览、文件传输、电子邮件等。它提供了可靠的数据传输和流控制机制,能够确保数据的完整性和有序性。然而,由于TCP协议在传输过程中引入了较多的控制信息,因此相比于UDP协议,TCP的传输速度较慢。 TCP UDP 有连接

    2024年02月14日
    浏览(37)
  • 网络协议之HTTP详细解释

    可能我接下的讲述可能有些赘述,但这些东西都是我们要了解的内容,循序渐进才行,不能一蹴而就,话不多说,我们开始吧!永远在路上. 在了解什么是协议之前,我们可以先来看看,web的发展阶段, 1.静态 Web 页面阶段:Web 诞生之初,主要是以静态页面为主,HTML 作为页面标记语言,网

    2024年02月06日
    浏览(41)
  • 最详细的signal 通信协议讲解,双棘轮复杂加密

    目录 了解signal的意思 起源: 涉及算法: signal技术优势: 使用signal的热门产品 Signal protocol 为了快速入门,先来了解一下signal protocol两端协议的演变: DH协议(迪菲-赫尔曼密钥交换协议(Diffie–Hellman key exchange): X3DH 双棘轮算法 KDF棘轮 DH棘轮 Signal Protocol的群组聊天设计 首

    2024年02月12日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包