http的basic 认证方式

写在前面

本文看下http的basic auth认证方式。

1:什么是basic auth认证

basic auth是一种http协议规范中的一种认证方式,即一种证明你就是你的方式。更进一步的它是一种规范,这种规范是这样子,如果是服务端使用了basic auth认证方式来处理用户请求的话,会从header中获取Authorization的头信息,如果是没有该头信息或者是头信息不正确,则会返回401状态码,并添加WWW-Authenticate响应头。如下代码所示:

复制代码
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 ("test".equals(arr[0]) && "123456".equals(arr[1])) {
    res.setStatus(401);
    res.addHeader("WWW-Authenticate", "basic realm=\"no auth\"");
    return false;
}

如果是浏览器收到了服务端的401响应,并且判断有www-authenticate头信息的话则会弹出自带的用户名密码录入框让用户录入信息,用户录入后浏览器会对录入的信息按照格式base64(用户名:密码)处理得到一个base64的值,然后按照格式Authorization: basic base64值写到头Authorization,再次发起请求。

2:程序测试

使用springboot方式测试。首先添加依赖和配置文件:

  • application.properties

    server.port=8089
    server.servlet.context-path=/BootDemo

  • pom

    org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE org.springframework.boot spring-boot-starter-web

接着我们来定义注解,后面将会作为接口是否使用basic auth的标记来使用:

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequireAuth {
    
}

定义拦截器,解析目标接口方法上是否是否用了注解RequireAuth,以及使用时是否携带了正确的WWW-Authenticate头信息,源码如下:

java 复制代码
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\"");
//        res.addHeader("WWW-Authenticate", "basic realm=\"no auth\"");
        return false;
    }

}

注册拦截器:

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

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

接着定义接口:

java 复制代码
@Controller
public class IndexController {

    private static final Base64.Decoder decoder = Base64.getDecoder();
    // private static final Base64.Encoder encoder = Base64.getEncoder();

    @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\"}}";
    }

    @RequestMapping("/noBasicAuth")
    @ResponseBody
    public String noBasicAuth(HttpServletRequest req, HttpServletResponse res) {
        return "noBasicAuth res";
    }
}

以上代码我们定义了需要使用basic auth的接口login和index(标记了注解@RequireAuth),以及不需要basic auth的noBasicAuth接口。

启动服务后,首先访问需要basic auth的login接口:

可以看到浏览器弹出了自带的用户名密码输入框。同时来看下noBasicAuth接口可以是否需要录入用户名密码:

可以看到是可以的,说明noBasicAuth接口确实是不需要basic auth认证。接着我们回到主线再次访问index接口,输入错误的账号:

会再次弹出用户名密码录入框(重复质询),输入正确的用户名密码就可以访问接口了:

这是因为浏览器已经自动添加了Authorization头信息了,如下:

其值其实就是test:123456base64的结果,如下:

之后,浏览器就会记录basic auth的认证信息,下次访问会自动带上basic的头,比如访问index:

写在后面

参考文章列表

秒懂HTTP基本认证(Basic Authentication)

HTTP的几种认证方式之BASIC 认证(基本认证)

多知道一点

如何清除浏览器记录的basic auth认证信息

清除浏览器缓存即可。

重放攻击

认证的base64结果被盗窃后,不断的使用base64的结果来请求服务器接口。

重复质询

用户名和密码输入错误后,返回401重新弹出登录框,就像正文中所描述的那样。

相关推荐
虹科网络安全1 小时前
使用艾体宝 IOTA 10 CORE+ 监控企业网络中的 AI 流量
网络·人工智能
醉颜凉1 小时前
网络安全必学:粘性MAC地址(Sticky MAC)原理与应用全解析
运维·服务器·网络·安全·web安全
广州灵眸科技有限公司3 小时前
从手动三步到上电即连:4G模组systemd开机自连方案
linux·运维·服务器·开发语言·网络·人工智能·php
埃科光电3 小时前
16K万兆网口彩色线阵相机:四倍带宽 高速真彩检测新方案
网络·图像处理·数码相机·计算机视觉·制造·相机
运维全栈笔记4 小时前
Windows 安装 WSL2 并运行 Ubuntu 24.04 指南
linux·网络·windows·ubuntu
qq_452396234 小时前
第十篇:《eBPF 可观测性:零侵扰的深度内核追踪》
服务器·网络·php
迪康Defender4 小时前
政企内网终端安全建设:资产‑管控‑防护‑审计闭环能力拆解
运维·网络·安全·web安全·终端安全管理
晓天衡宇•评测社区5 小时前
Seedance 2.5 登顶图生视频榜单,Wan 3.0 在游戏与教育场景进入前列
前端·javascript·网络
charliejohn5 小时前
计算机考研 408 网络 以太网、ARP、NAT、DHCP概念
网络·计算机网络
商业白皮书5 小时前
企业大模型应用可获得哪些安全防护能力?Amazon Bedrock Guardrails 从内容过滤到规则验证搭建 AI 安全护栏
网络·人工智能·安全