OkHttp3 之 Authorization处理认证(四)

处理验证

这部分和HTTP AUTH有关.

HTTP AUTH

使用HTTP AUTH需要在server端配置http auth信息, 其过程如下: 

  • 客户端发送http请求 

  • 服务器发现配置了http auth, 于是检查request里面有没有”Authorization”的http header 

  • 如果有, 则判断Authorization里面的内容是否在用户列表里面, Authorization header的典型数据为”Authorization: Basic jdhaHY0=”, 其中Basic表示基础认证, jdhaHY0=是base64编码的”user:passwd”字符串. 如果没有,或者用户密码不对,则返回http code 401页面给客户端. 

  • 标准的http浏览器在收到401页面之后, 应该弹出一个对话框让用户输入帐号密码; 并在用户点确认的时候再次发出请求, 这次请求里面将带上Authorization header.

一次典型的访问场景是:

浏览器发送http请求(没有Authorization header)
服务器端返回401页面
浏览器弹出认证对话框
用户输入帐号密码,并点确认
浏览器再次发出http请求(带着Authorization header)
服务器端认证通过,并返回页面
浏览器显示页面

OkHttp认证

OkHttp会自动重试未验证的请求. 当响应是401 Not Authorized时,Authenticator会被要求提供证书. Authenticator的实现中需要建立一个新的包含证书的请求. 如果没有证书可用, 返回null来跳过尝试. 
使用Response.challenges()来获得任何authentication challenges的 schemes 和 realms. 当完成一个Basic challenge, 使用Credentials.basic(username, password)来解码请求头.

Basic Authentication认证

private final OkHttpClient client;

public Demo() {
    client = new OkHttpClient.Builder()
            .authenticator(new Authenticator() {
                @Override
                public Request authenticate(Route route, Response response) {
                    System.out.println("Authenticating for response: " + response);
                    System.out.println("Challenges: " + response.challenges());
                    String credential = Credentials.basic("jesse", "password1");
                    return response.request().newBuilder()
                            .header("Authorization", credential)
                            .build();
                }
            })
            .build();
}

public void run() throws Exception {
    Request request = new Request.Builder()
            .url("http://publicobject.com/secrets/hellosecret.txt")
            .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

喜欢使用lambda表达式如下

client = new OkHttpClient.Builder()
        .authenticator((route, response) -> {
            System.out.println("Authenticating for response: " + response);
            System.out.println("Challenges: " + response.challenges());
            String credential = Credentials.basic("jesse", "password1");
            return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
        })
        .build();

正确授权,打印结果如下

Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
Challenges: [Basic realm="OkHttp Secrets" charset="ISO-8859-1"]
authorization:null
                      @@@@@\
                     @@@@@@@@
                    @@@@@@@@@@@@@@@@@@@.
                    @/ \@@@@@@@@@@@@@@@@.
                    @   @@@@@@@@@@@@@@@@+
                    @\ /@@@@@@@@"*@@/^@/
                    \@@@@@@@@@@/  "   "
                     @@@@@@@@
                      @@@@@/
     :@@@.
   .@@@@@@@:   +@@       `@@      @@`   @@     @@
  .@@@@'@@@@:  +@@       `@@      @@`   @@     @@
  @@@     @@@  +@@       `@@      @@`   @@     @@
 .@@       @@: +@@   @@@ `@@      @@` @@@@@@ @@@@@@  @@;@@@@@
 @@@       @@@ +@@  @@@  `@@      @@` @@@@@@ @@@@@@  @@@@@@@@@
 @@@       @@@ +@@ @@@   `@@@@@@@@@@`   @@     @@    @@@   :@@
 @@@       @@@ +@@@@@    `@@@@@@@@@@`   @@     @@    @@#    @@+
 @@@       @@@ +@@@@@+   `@@      @@`   @@     @@    @@:    @@#
  @@:     .@@` +@@@+@@   `@@      @@`   @@     @@    @@#    @@+
  @@@.   .@@@  +@@  @@@  `@@      @@`   @@     @@    @@@   ,@@
   @@@@@@@@@   +@@   @@@ `@@      @@`   @@@@   @@@@  @@@@#@@@@
    @@@@@@@    +@@   #@@ `@@      @@`   @@@@:  @@@@: @@'@@@@@
                                                     @@:
                                                     @@:
                                                     @@:
耗时:2383

如果输入错误的授权账号,打印结果如下

.... 省略部分 ....
Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
Challenges: [Basic realm="OkHttp Secrets" charset="ISO-8859-1"]
authorization:Basic eHg6eHg=
Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
Challenges: [Basic realm="OkHttp Secrets" charset="ISO-8859-1"]
authorization:Basic eHg6eHg=
java.net.ProtocolException: Too many follow-up requests: 21
	at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:171)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
	at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:200)
	at okhttp3.RealCall.execute(RealCall.java:77)
	at com.tingfeng.guide.Test.run(Test.java:40)
	at com.tingfeng.guide.Test.main(Test.java:49)

为避免当验证不工作而导致许多重试,你可以返回null放弃,例如:当这些确切的证书已经尝试访问过时你可能想跳过重试:

client = new OkHttpClient.Builder()
        .authenticator((route, response) -> {
            System.out.println("Authenticating for response: " + response);
            System.out.println("Challenges: " + response.challenges());

            String credential = Credentials.basic("xx", "xx");
            if (credential.equals(response.request().header("Authorization"))) {
                System.out.println("验证失败,返回null");
                return null;
            }

            return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
        })
        .build();

打印结果如下,这种方式会在第三次认证失败返回null

Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
Challenges: [Basic realm="OkHttp Secrets" charset="ISO-8859-1"]
authorization:null
Authenticating for response: Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
Challenges: [Basic realm="OkHttp Secrets" charset="ISO-8859-1"]
authorization:Basic eHg6eHg=
认证失败,返回null
java.io.IOException: Unexpected code Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://publicobject.com/secrets/hellosecret.txt}
	at com.tingfeng.guide.Test.run(Test.java:41)
	at com.tingfeng.guide.Test.main(Test.java:49)

当你设置一个应用程序定义的限制时你也可以跳过重试

client = new OkHttpClient.Builder()
        .authenticator((route, response) -> {
            System.out.println("Authenticating for response: " + response);
            System.out.println("Challenges: " + response.challenges());

            String credential = Credentials.basic("xx", "xx");
            String authorization = response.request().header("Authorization");
            System.out.println("authorization:"+ authorization);
            
            // 第5次认证失败返回null
            if (responseCount(response) >= 5) {
                System.out.println("认证失败,返回null");
                return null; // If we've failed 5 times, give up.
            }

            return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
        })
        .build();

这上面的代码依赖于 responseCount() 方法:

private int responseCount(Response response) {
    int result = 1;
    while ((response = response.priorResponse()) != null) {
        result++;
    }
    return result;
}

Bearer Token认证

使用Header传递Authorization认证参数

public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
    RequestBody body = RequestBody.create(JSON, json);
    Request request = new Request.Builder()
            .url(url)
            .addHeader("Authorization","Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0aW5nZmVuZyIsInJvbGVzIjpbIk1FTUJFUiJdLCJpYXQiOjE1MjM0Mzg4ODYsImV4cCI6MTUyMzQ0MjQzMH0.5oQU1HUekYxP6BE534Vek_O6ZXhwPbUXQJuBB_da8r8")
            .post(body)
            .build();

    try (Response response = client.newCall(request).execute()) {
        return response.body().string();
    }
}

测试

public static void main(String[] args) throws IOException {
    PostExample example = new PostExample();
    String json = "{\"username\":\"tingfeng\",\"password\":\"tingfeng\"}";
    String response = example.post("http://localhost:8080/secure/user/roles", json);
    System.out.println(response);
}


赞(52) 打赏
未经允许不得转载:优客志 » JAVA开发
分享到:

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

支付宝扫一扫打赏

微信扫一扫打赏