处理验证
这部分和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); }