http的basic 认证方式

avatar
作者
猴君
阅读量:1

写在前面

在这里插入图片描述

本文看下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
<parent>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-parent</artifactId>     <version>2.2.1.RELEASE</version>     <relativePath/> </parent>  <dependencies>     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-web</artifactId>     </dependency> </dependencies> 

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

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequireAuth {      } 

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

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

注册拦截器:

@Configuration public class WebConfig implements WebMvcConfigurer {      @Override     public void addInterceptors(InterceptorRegistry registry) {         RequireAuthInterceptor requireAuthInterceptor = new RequireAuthInterceptor();         registry.addInterceptor(requireAuthInterceptor);     }      } 

接着定义接口:

@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重新弹出登录框,就像正文中所描述的那样。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!