浏览器-基本认证(Basic Authentication)-摘要认证(digest authentication)=spring boot实现demo

23 篇文章 0 订阅
16 篇文章 0 订阅

平时开发的 java web 网站登录,都是通过表单提交登录信息。有时一些中间件登录是浏览器弹窗,没有看到表单实现代码。故通过查询,发现两种 HTTP 简单认证: 基本认证( Basic Authentication )、摘要认证( digest authentication )等,本次通过 java实现 spring boot 基本和摘要认证。
基本认证

  1. 客户端发送 HTTP 给服务器;
  2. 发送 Request 中 header 没含Authorization, 服务器返回401 Unauthozied ,并在Response 的 header中WWW-Authenticate添加信息(Basic realm="自定义内容");
  3. 客户端把用户名和密码(用户名+冒号+密码)用 BASE64 编码后,放在 Request 中 header Authorization 发送给服务器, 认证成功
  4. 服务器将 header 中Authorization的用户名密码取出验证, 如果验证通过,将根据请求,返回资源

摘要认证

  1. 客户端发送 HTTP 给服务器;
  2. 发送 Request 中 header 没含Authorization, 服务器产生(Digest 、认证的域realm="自定义内容"、认证的校验方式qop、随机数nonce、随机字符opaque、认证算法algorithm)字符串拼接一起,放在WWW-Authenticate响应头,一起发送给客户端;
  3. 客户端发现401响应,表示需认证,则弹出让用户输入用户名和密码的认证窗口,客户端选择算法,计算出密码和其他数据的摘要(response),放到Authorization的请求头中发送服务器,如客户端要对服务器也进行认证,这时可发送客户端随机数cnonce
  4. 服务接受摘要,选择算法,获取存储的用户名密码,计算摘要跟客户端传输的摘要进行比较,验证是否匹配

参考

HTTP基本认证(Basic Authentication)的JAVA示例
摘要认证及实现HTTP digest authentication
JAVA 调用 RFC2617摘要认证协议的接口
curl命令使用digest方式验证用户
http请求头和响应头
Java猿社区—Http digest authentication 请求代码最全示例
RFC 2617 - HTTP Authentication: Basic and Digest Access Authenti
HTTP Authentication: Basic and Digest Access Authentication
C# 摘要认证(digest authentication) IETF RFC 2617
http协议基本认证 Authorization
中文rfc2617-001
Java 基于 IETF RFC 2617 身份认证

代码

pom

<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.3.1.RELEASE</spring-boot.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-core</artifactId>
        <version>5.3.8</version>
    </dependency>
	<dependency>
	    <groupId>cn.hutool</groupId>
	    <artifactId>hutool-crypto</artifactId>
	    <version>5.3.8</version>
	</dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

接口代码


import cn.hutool.core.lang.UUID;
import org.apache.tomcat.util.security.MD5Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 测试认证接口
 *
 * @author z.y.l
 * @version v1.0
 * @date 2020-09-10
 */
@Controller
@RequestMapping("/auth")
public class AuthController {
    private final Logger logger = LoggerFactory.getLogger(AuthController.class);
    private final static String AUTH_B = "Basic ",AUTH_D = "Digest ";
    @GetMapping ("")
    @ResponseBody
    public String root(){
        return "Hello Spring Boot Auth Demo.By zyl.";
    }
    @GetMapping ("basic/info")
    @ResponseBody
    public String basicInfo(){
        return "Hello Basic Authentication.By zyl.";
    }
    @RequestMapping("basic")
    @ResponseBody
    public String basic(HttpServletRequest request, HttpServletResponse response){
        //基本认证(Basic Authentication)
        String auth = request.getHeader("Authorization");
        if( null == auth || auth.isEmpty() || !auth.startsWith(AUTH_B)){
            logger.info("测试浏览器原生弹窗登录认证-未登录");
            response.addHeader("WWW-Authenticate","Basic realm=\".\"");
            response.setStatus(401);
            //缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存
            response.setHeader("Cache-Control", "no-store");
            //0, 代表着过去的日期,即该资源已经过期
            response.setDateHeader("Expires", 0);
        }else {
            logger.info("测试浏览器原生弹窗登录认证-验证登录");
            logger.info("auth, {}",auth);
            auth = new String(Base64.getDecoder().decode(auth.substring(AUTH_B.length())));
            logger.info("auth-decode, {}",auth);
            String[] split = auth.split(":");
            String n = split[0],p = split[1];
            logger.info("auth-decode, name: {} , p: {}",n,p);
        }
        return "Hello Spring Boot Demo.by auth.";
    }
    @RequestMapping("digest")
    @ResponseBody
    public String digest(HttpServletRequest request, HttpServletResponse response){
        //摘要认证 digest authentication
        /*
        摘要认证及实现HTTP digest authentication https://www.jianshu.com/p/cf5a900d4ef7
        */
        String auth = request.getHeader("Authorization");
        if( null == auth || auth.isEmpty() || !auth.startsWith(AUTH_D)){
            logger.info("测试浏览器原生弹窗登录认证-摘要认证-未登录");
            Map<String, String> map = new HashMap<>(5);
            //值是一个简单的字符串
            map.put("realm","realm");
            //保护质量,是认证的(校验)方式.auth表示认证;auth-int表示完整性保护认证
            map.put("qop","auth,auth-int");
            //是随机数, 可以用GUID
            map.put("nonce",UUID.fastUUID().toString(true));
            //是个随机字符串,它只是透传而已,即客户端还会原样返回过来。
            map.put("opaque",UUID.fastUUID().toString(true));
            //是个字符串,用来指示用来产生分类及校验和的算法对。如果该域没指定,则认为是“MD5“算法。
            map.put("algorithm","MD5");
            StringBuilder sb = new StringBuilder(AUTH_D);
            map.forEach((k,v) -> sb.append(k).append('=').append('"').append(v).append('"').append(','));
            response.addHeader("WWW-Authenticate",sb.toString());
            logger.info("WWW-Authenticate: {}",sb.toString());
            response.setStatus(401);
        }else {
            logger.info("测试浏览器原生弹窗登录认证-摘要认证-验证登录");
            logger.info("auth, {}",auth);
            String[] split = auth.split(", ");
            String nc = "",cnonce="",nonce="",qop="",algorithm="";
            for (String sp : split) {
                logger.info("auth-ap, {}",sp);
                if(sp.startsWith("nc")){
                    nc = sp.substring("nc=".length());
                }else if(sp.startsWith("cnonce")){
                    cnonce = sp.substring("cnonce=".length());
                }else if(sp.startsWith("nonce")){
                    nonce = sp.substring("nonce=".length());
                }else if(sp.startsWith("qop")){
                    qop = sp.substring("qop=".length());
                }else if(sp.startsWith("algorithm")){
                    algorithm = sp.substring("algorithm=".length());
                }
            }
            String name = "asd", realm = "realm",pass = "123456";
            String a1 = name+":"+realm+":"+pass;
            MD5 md_5 = MD5.create();
            String ha1 = md_5.digestHex(a1,"UTF-8");
            logger.info("MD5 name:realm:pass > {} > {}",a1,ha1);

            boolean sess = "MD5-sess".equalsIgnoreCase(algorithm);
            if(sess){
                ha1 = ha1 + ":" + nonce + ":" + cnonce;
                logger.info("MD5-sess md5(name:realm:pass):nonce:cnonce > {} > {}",a1,ha1);
            }

            String a2 = "get:/hello/digest";
            String ha2 = md_5.digestHex(a2,"UTF-8");
            logger.info(" method:uri > {} > {}",a2,ha2);

            if("auth-int".equals(qop)){
                logger.info("如果 qop 值为“auth-int”,那么 HA2 为:HA2=MD5(A2)=MD5(method:digestURI:MD5(entityBody)),实现代码未找到");
            }
            if("auth".equalsIgnoreCase(qop) || "auth-int".equalsIgnoreCase(qop)){
                String md = ha1+":"+nonce+":"+nc+":"+cnonce+":"+qop+":"+ha2;
                String md5 = md_5.digestHex(md,"UTF-8");
                logger.info("response ha1:nonce:nc:cnonce:qop:ha2 > {} > {}",md,md5);
            }else{
                String md = ha1+":"+nonce+":"+ha2;
                String md5 = md_5.digestHex(md,"UTF-8");
                logger.info("response ha1:nonce:ha2 > {} > {}",md,md5);
            }
        }
        return "Hello Spring Boot Demo.by digest.";
    }
    @GetMapping ("digest/info")
    @ResponseBody
    public String digestInfo(){
        return "Hello Digest Authentication.By zyl.";
    }
}

测试

请求:http://localhost:8080/auth
在这里插入图片描述

基本认证

浏览器访问

请求:http://localhost:8080/auth/basic,浏览器提示登录,请求报文显示了后端响应内容
在这里插入图片描述
输入账号密码登陆后,请求头会携带认证信息
在这里插入图片描述
日志
在这里插入图片描述

[AuthController.java:62] =.= 测试浏览器原生弹窗登录认证-验证登录
[AuthController.java:63] =.= auth, Basic YWRtaW46MTIzNDU2
[AuthController.java:65] =.= auth-decode, admin:123456
[AuthController.java:68] =.= auth-decode, name: admin , p: 123456

请求:http://localhost:8080/auth/basic/info,就会携带认证信息
在这里插入图片描述

Linux使用curl访问

在Linux服务器通过curl访问认证接口

curl -u test http://192.168.xxx.xxx:8080/auth/basic
Enter host password for user 'test':
Hello Spring Boot Demo.by auth.

在这里插入图片描述
接口打印日志
在这里插入图片描述

[AuthController.java:54] =.= 测试浏览器原生弹窗登录认证-验证登录
[AuthController.java:55] =.= auth, Basic dGVzdDo2NTQzMjE=
[AuthController.java:57] =.= auth-decode, test:654321
[AuthController.java:60] =.= auth-decode, name: test , p: 654321

摘要认证

浏览器访问

请求:http://localhost:8080/auth/digest,浏览器提示登录
在这里插入图片描述
输入账号密码登陆后,请求头会携带认证信息
在这里插入图片描述
在这里插入图片描述

[AuthController.java:91] =.= 测试浏览器原生弹窗登录认证-摘要认证-验证登录
[AuthController.java:92] =.= auth, Digest username="asd", realm="realm", nonce="d8df2efaecf74dd8a736147794eb2aff", uri="/auth/digest", algorithm=MD5, response="bfcdf3e596dace72c035de009f96458b", opaque="b4bccdce4a6b4ba1a42acf3d357c33bc", qop=auth, nc=00000004, cnonce="b2596a7472eee0df"
[AuthController.java:96] =.= auth-ap, Digest username="asd"
[AuthController.java:96] =.= auth-ap, realm="realm"
[AuthController.java:96] =.= auth-ap, nonce="d8df2efaecf74dd8a736147794eb2aff"
[AuthController.java:96] =.= auth-ap, uri="/auth/digest"
[AuthController.java:96] =.= auth-ap, algorithm=MD5
[AuthController.java:96] =.= auth-ap, response="bfcdf3e596dace72c035de009f96458b"
[AuthController.java:96] =.= auth-ap, opaque="b4bccdce4a6b4ba1a42acf3d357c33bc"
[AuthController.java:96] =.= auth-ap, qop=auth
[AuthController.java:96] =.= auth-ap, nc=00000004
[AuthController.java:96] =.= auth-ap, cnonce="b2596a7472eee0df"
[AuthController.java:113] =.= MD5 name:realm:pass > asd:realm:123456 > 41fd06d7b3a49c6fc0d44d338b3b2fb8
[AuthController.java:123] =.=  method:uri > get:/hello/digest > a422e85fbdc5fb6cb7d362a4207ff531
[AuthController.java:131] =.= response ha1:nonce:nc:cnonce:qop:ha2 > 41fd06d7b3a49c6fc0d44d338b3b2fb8:"d8df2efaecf74dd8a736147794eb2aff":00000004:"b2596a7472eee0df":auth:a422e85fbdc5fb6cb7d362a4207ff531 > f5faaf2b18a4ed555cdec05a02454398

请求:http://localhost:8080/auth/digest/info,就会携带认证信息
在这里插入图片描述

Linux使用curl访问

在Linux服务器通过curl访问认证接口

curl -i http://192.168.xxx.xxx:8080/auth/digest -X GET --digest --user asd:12345
HTTP/1.1 401 
WWW-Authenticate: Digest realm="realm",qop="auth,auth-int",opaque="0f7db485912f42d5b3a22dbf0ecb771e",nonce="723bb303214c479496ab83afc54eedc3",algorithm="MD5",
Content-Type: text/plain;charset=UTF-8
Content-Length: 33
Date: Sun, 09 Oct 2022 08:34:42 GMT

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 33
Date: Sun, 09 Oct 2022 08:34:42 GMT

Hello Spring Boot Demo.by digest.

在这里插入图片描述
接口打印日志,两次请求
在这里插入图片描述

[AuthController.java:73] =.= 测试浏览器原生弹窗登录认证-摘要认证-未登录
[AuthController.java:88] =.= WWW-Authenticate: Digest realm="realm",qop="auth,auth-int",opaque="0f7db485912f42d5b3a22dbf0ecb771e",nonce="723bb303214c479496ab83afc54eedc3",algorithm="MD5",
[AuthController.java:91] =.= 测试浏览器原生弹窗登录认证-摘要认证-验证登录
[AuthController.java:92] =.= auth, Digest username="asd", realm="realm", nonce="723bb303214c479496ab83afc54eedc3", uri="/auth/digest", cnonce="ICAgICAgICAgICAgICAgICAgICAgICAgIDIxMDU1MTU=", nc=00000001, qop=auth, response="21d1f834c6c12b9baf6f87ffdaf76edf", opaque="0f7db485912f42d5b3a22dbf0ecb771e", algorithm="MD5"
[AuthController.java:96] =.= auth-ap, Digest username="asd"
[AuthController.java:96] =.= auth-ap, realm="realm"
[AuthController.java:96] =.= auth-ap, nonce="723bb303214c479496ab83afc54eedc3"
[AuthController.java:96] =.= auth-ap, uri="/auth/digest"
[AuthController.java:96] =.= auth-ap, cnonce="ICAgICAgICAgICAgICAgICAgICAgICAgIDIxMDU1MTU="
[AuthController.java:96] =.= auth-ap, nc=00000001
[AuthController.java:96] =.= auth-ap, qop=auth
[AuthController.java:96] =.= auth-ap, response="21d1f834c6c12b9baf6f87ffdaf76edf"
[AuthController.java:96] =.= auth-ap, opaque="0f7db485912f42d5b3a22dbf0ecb771e"
[AuthController.java:96] =.= auth-ap, algorithm="MD5"
[AuthController.java:113] =.= MD5 name:realm:pass > asd:realm:123456 > 41fd06d7b3a49c6fc0d44d338b3b2fb8
[AuthController.java:123] =.=  method:uri > get:/hello/digest > a422e85fbdc5fb6cb7d362a4207ff531
[AuthController.java:131] =.= response ha1:nonce:nc:cnonce:qop:ha2 > 41fd06d7b3a49c6fc0d44d338b3b2fb8:"723bb303214c479496ab83afc54eedc3":00000001:"ICAgICAgICAgICAgICAgICAgICAgICAgIDIxMDU1MTU=":auth:a422e85fbdc5fb6cb7d362a4207ff531 > 04b7f44c993d847534f97489c80f08b6

END

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值