写在前面: 本文是我看到Retrofit官方推荐的一个Retrofit技术文档,感觉收益匪浅,特此想把文档翻译一下,大家一起学习。
原文地址:https://futurestud.io/tutorials/retrofit-getting-started-and-android-client
本文不会深入探讨OAuth本身。他只会展示基础的原则和必要的信息来帮助理解验证流程。
OAuth基础
OAuth是一个基于token的验证方法,它通过提供一个访问token来为用户和api进行交互。OAuth通过几个步骤和请求来获得你的访问token。
1.在api上注册一个你想要开发的App。使用你想要去开发的开发者网站的公共api。
2.在你的app中存储客户端id和客户端密钥。
3.请求从你的app中访问用户数据。
4.使用验证代码来获得访问token。
5.使用访问token和api进行交互。
注册你的App
首先,你必须为你想要开发的服务/api注册App。一旦你登入你的应用,你将会获得客户端id和客户端密匙。这两个值都将用来验证你的app。
创建工程
我们假设你已经有了一个工程,如果你没有,那么就去创建一个。当你做好之后,移至下一节,准备coding。
集成 OAuth
因为我们在上几章中,使用了ServiceGenerator,那么接下来我们依旧以此为基础进行拓展,增加一个方法来处理OAuth访问Token。下面的代码块显示所要的方法。这不意味着你应该删除先前为基础验证所创建的方法,因为你也需要他们。
public class ServiceGenerator {
public static final String API_BASE_URL = "https://your.api-base.url";
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
public static <S> S createService(Class<S> serviceClass) {
return createService(serviceClass, null);
}
public static <S> S createService(Class<S> serviceClass, String username, String password) {
// we shortened this part, because it’s covered in
// the previous post on basic authentication with Retrofit
}
public static <S> S createService(Class<S> serviceClass, AccessToken token) {
if (token != null) {
httpClient.addInterceptor(new Interceptor() {
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.header("Accept", "application/json")
.header("Authorization",
token.getTokenType() + " " + token.getAccessToken())
.method(original.method(), original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
}
OkHttpClient client = httpClient.build();
Retrofit retrofit = builder.client(client).build();
return retrofit.create(serviceClass);
}
}
我们使用RequestInterceptor来设置HTTP请求头部的Authorization区域。这个区域包含两个部分:首先,OAuth请求的token类型,其次,访问token。
如你所见,这个方法请求一个AccessToken作为第三个参数,这个类如下所示:
public class AccessToken {
private String accessToken;
private String tokenType;
public String getAccessToken() {
return accessToken;
}
public String getTokenType() {
// OAuth requires uppercase Authorization HTTP header value for token type
if ( ! Character.isUpperCase(tokenType.charAt(0))) {
tokenType =
Character
.toString(tokenType.charAt(0))
.toUpperCase() + tokenType.substring(1);
}
return tokenType;
}
}
AccessToken类包含两个区域:accessToken和tokenType。因为OAuth API实现需要大写的token类型,我们首先检测类型。万一它不合适,我们将更新类型。例如,你的API返回bearer类型,那么任何这个类型的请求都会导致要么401无法验证,要么403拒绝,要么400请求失败。
当设置正确时,HTTP头部会如下例子所示。
Authorization: Bearer 12345
在你的App中集成OAuth
首先我们我们创建一个叫做LoginActivity的新Activity。你可以使用一个只有一个button的简单的view。下面是activity的代码。
public class LoginActivity extends Activity {
// you should either define client id and secret as constants or in string resources
private final String clientId = "your-client-id";
private final String clientSecret = "your-client-secret";
private final String redirectUri = "your://redirecturi";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
Button loginButton (Button) findViewById(R.id.loginbutton);
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(
Intent.ACTION_VIEW,
Uri.parse(ServiceGenerator.API_BASE_URL + "/login" + "?client_id=" + clientId + "&redirect_uri=" + redirectUri));
startActivity(intent);
}
});
}
}
你必须协调clientID,clientSecret,redirectUri的类属性值。同时也要保证url的登入不能是有/login的,不然,则将这部分更新至正确的部分。接下来,在oncreate方法中为登入按钮设置一个点击监听事件。一旦点击事件被触发,它就会显示URI所指向的webview。重要的一点是:在这个请求中,你必须提供你的client id和cilent 密匙。因为API需要这两个参数来对你的app进行其他的操作和处理。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login"
android:id="@+id/loginbutton"
android:gravity="center_vertical|center_horizontal"
/>
</RelativeLayout>
创建一个intent删选器,它能帮助你删选你想要的响应。
<activity
android:name="com.futurestudio.oauthexample.LoginActivity"
android:label="@string/app_name"
android:configChanges="keyboard|orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="redirecturi"
android:scheme="your" />
</intent-filter>
</activity>
抓取验证的代码
@Override
protected void onResume() {
super.onResume();
// the intent filter defined in AndroidManifest will handle the return from ACTION_VIEW intent
Uri uri = getIntent().getData();
if (uri != null && uri.toString().startsWith(redirectUri)) {
// use the parameter your API exposes for the code (mostly it's "code")
String code = uri.getQueryParameter("code");
if (code != null) {
// get access token
// we'll do that in a minute
} else if (uri.getQueryParameter("error") != null) {
// show an error message here
}
}
}
你的app在android的生命周期的onResume方法中进行操作,我们在这里使用了getIntent().getData()方法来检索intet的响应。
现在我们不想运行任何指针和检测值。接下来,我们从查询参数里组装处验证代码。设想一下,点击allow时,响应url如下所示。
your://redirecturi?code=1234
拒绝访问如下所示:
your://redirecturi?error=message
获得你的访问token
接下来,我们将通过传递client id,client secrest和验证代码给API来请求访问token。我们在上文的基础验证中的LoginService的基础上进行扩展,创建一个叫做getAccessToken的方法。
public interface LoginService {
@FormUrlEncoded
@POST("/token")
Call<AccessToken> getAccessToken(
@Field("code") String code,
@Field("grant_type") String grantType);
}
@Override
protected void onResume() {
super.onResume();
// the intent filter defined in AndroidManifest will handle the return from ACTION_VIEW intent
Uri uri = getIntent().getData();
if (uri != null && uri.toString().startsWith(redirectUri)) {
// use the parameter your API exposes for the code (mostly it's "code")
String code = uri.getQueryParameter("code");
if (code != null) {
// get access token
LoginService loginService =
ServiceGenerator.createService(LoginService.class, clientId, clientSecret);
Call<AccessToken> call = loginService.getAccessToken(code, "authorization_code");
AccessToken accessToken = call.execute().body();
} else if (uri.getQueryParameter("error") != null) {
// show an error message here
}
}
}
你做完之后,你可能必须为你请求的api调整许可类型。这个需求类型是通过getAccessToken的方法的第二个参数进行传递的。