完成 sso API

This commit is contained in:
czp 2018-03-01 15:39:40 +08:00
parent 4694899ef9
commit 4e954af9d2
9 changed files with 238 additions and 27 deletions

View File

@ -10,7 +10,6 @@ apply plugin: 'signing'
sourceCompatibility = 1.8
repositories {
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
mavenCentral()
}

View File

@ -1,16 +1,17 @@
package com.hiczp.bilibili.api;
import com.hiczp.bilibili.api.cookie.SimpleCookieJar;
import com.hiczp.bilibili.api.interceptor.*;
import com.hiczp.bilibili.api.live.LiveService;
import com.hiczp.bilibili.api.live.socket.LiveClient;
import com.hiczp.bilibili.api.passport.PassportService;
import com.hiczp.bilibili.api.passport.SsoService;
import com.hiczp.bilibili.api.passport.entity.InfoEntity;
import com.hiczp.bilibili.api.passport.entity.LoginResponseEntity;
import com.hiczp.bilibili.api.passport.entity.LogoutResponseEntity;
import com.hiczp.bilibili.api.passport.entity.RefreshTokenResponseEntity;
import com.hiczp.bilibili.api.passport.exception.CaptchaMismatchException;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.*;
import okhttp3.logging.HttpLoggingInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -18,6 +19,7 @@ import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.security.auth.login.LoginException;
import java.io.IOException;
import java.text.SimpleDateFormat;
@ -25,8 +27,9 @@ import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider {
public class BilibiliAPI implements BilibiliServiceProvider, BilibiliSsoProvider, LiveClientProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(BilibiliAPI.class);
private final Long apiInitTime = Instant.now().getEpochSecond(); //记录当前类被实例化的时间
@ -72,8 +75,6 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider
public PassportService getPassportService(@Nonnull List<Interceptor> interceptors, @Nonnull HttpLoggingInterceptor.Level logLevel) {
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
interceptors.forEach(okHttpClientBuilder::addInterceptor);
okHttpClientBuilder
.addInterceptor(new AddFixedParamsInterceptor(
"build", bilibiliClientProperties.getBuild(),
@ -85,7 +86,11 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider
))
.addInterceptor(new AddAppKeyInterceptor(bilibiliClientProperties))
.addInterceptor(new SortParamsAndSignInterceptor(bilibiliClientProperties))
.addInterceptor(new ErrorResponseConverterInterceptor())
.addInterceptor(new ErrorResponseConverterInterceptor());
interceptors.forEach(okHttpClientBuilder::addInterceptor);
okHttpClientBuilder
.addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(logLevel));
return new Retrofit.Builder()
@ -107,8 +112,6 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider
public LiveService getLiveService(@Nonnull List<Interceptor> interceptors, @Nonnull HttpLoggingInterceptor.Level logLevel) {
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
interceptors.forEach(okHttpClientBuilder::addInterceptor);
okHttpClientBuilder
.addInterceptor(new AddFixedHeadersInterceptor(
"Buvid", bilibiliClientProperties.getBuvId(),
@ -144,7 +147,11 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider
))
.addInterceptor(new AddAccessKeyInterceptor(bilibiliAccount))
.addInterceptor(new SortParamsAndSignInterceptor(bilibiliClientProperties))
.addInterceptor(new ErrorResponseConverterInterceptor())
.addInterceptor(new ErrorResponseConverterInterceptor());
interceptors.forEach(okHttpClientBuilder::addInterceptor);
okHttpClientBuilder
.addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(logLevel));
return new Retrofit.Builder()
@ -155,6 +162,45 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider
.create(LiveService.class);
}
public SsoService getSsoService() {
return getSsoService(new SimpleCookieJar());
}
//sso 需要保存 cookie, 不对 SsoService 进行缓存
@Override
public SsoService getSsoService(CookieJar cookieJar) {
return getSsoService(cookieJar, Collections.emptyList(), HttpLoggingInterceptor.Level.BASIC);
}
public SsoService getSsoService(@Nonnull CookieJar cookieJar, @Nonnull List<Interceptor> interceptors, @Nonnull HttpLoggingInterceptor.Level logLevel) {
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
okHttpClientBuilder
.cookieJar(cookieJar)
.addInterceptor(new AddFixedParamsInterceptor(
"build", bilibiliClientProperties.getBuild(),
"mobi_app", "android",
"platform", "android"
))
.addInterceptor(new AddDynamicParamsInterceptor(
() -> "ts", () -> Long.toString(Instant.now().getEpochSecond())
))
.addInterceptor(new AddAccessKeyInterceptor(bilibiliAccount))
.addInterceptor(new AddAppKeyInterceptor(bilibiliClientProperties))
.addInterceptor(new SortParamsAndSignInterceptor(bilibiliClientProperties));
interceptors.forEach(okHttpClientBuilder::addInterceptor);
okHttpClientBuilder
.addNetworkInterceptor(new HttpLoggingInterceptor().setLevel(logLevel));
return new Retrofit.Builder()
.baseUrl(BaseUrlDefinition.PASSPORT)
.client(okHttpClientBuilder.build())
.build()
.create(SsoService.class);
}
public LoginResponseEntity login(@Nonnull String username, @Nonnull String password) throws IOException, LoginException, CaptchaMismatchException {
return login(username, password, null, null);
}
@ -270,6 +316,30 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider
return infoEntity;
}
@Override
public HttpUrl getSsoUrl(@Nullable String goUrl) {
CancelRequestInterceptor cancelRequestInterceptor = new CancelRequestInterceptor();
try {
getSsoService(new SimpleCookieJar(), Collections.singletonList(cancelRequestInterceptor), HttpLoggingInterceptor.Level.BASIC)
.sso(null)
.execute();
} catch (IOException ignored) {
}
return cancelRequestInterceptor.getRequest().url();
}
@Override
public Map<String, List<Cookie>> toCookies() throws IOException {
return toCookies(BaseUrlDefinition.PASSPORT + "api/oauth2/getKey");
}
public Map<String, List<Cookie>> toCookies(@Nullable String goUrl) throws IOException {
SimpleCookieJar simpleCookieJar = new SimpleCookieJar();
getSsoService(simpleCookieJar).sso(goUrl).execute();
return simpleCookieJar.getCookiesMap();
}
@Override
public LiveClient getLiveClient(long showRoomId) {
return bilibiliAccount.getUserId() == null ?

View File

@ -0,0 +1,20 @@
package com.hiczp.bilibili.api;
import com.hiczp.bilibili.api.passport.SsoService;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public interface BilibiliSsoProvider {
SsoService getSsoService(CookieJar cookieJar);
//获取用于进行 sso 登录的初始 URL
HttpUrl getSsoUrl(String goUrl);
//获取当前 token 对应的 cookies
Map<String, List<Cookie>> toCookies() throws IOException;
}

View File

@ -0,0 +1,67 @@
package com.hiczp.bilibili.api.cookie;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SimpleCookieJar implements CookieJar {
private Map<String, List<Cookie>> cookiesMap;
public SimpleCookieJar() {
cookiesMap = new HashMap<>();
}
public SimpleCookieJar(Map<String, List<Cookie>> cookiesMap) {
this.cookiesMap = new HashMap<>(cookiesMap);
}
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookies.forEach(cookie -> {
String domain = cookie.domain();
List<Cookie> savedCookies = cookiesMap.get(domain);
if (savedCookies == null) {
savedCookies = new ArrayList<>();
savedCookies.add(cookie);
cookiesMap.put(domain, savedCookies);
} else {
for (Cookie savedCookie : savedCookies) {
if (savedCookie.name().equals(cookie.name())) {
savedCookies.remove(savedCookie);
break;
}
}
savedCookies.add(cookie);
}
});
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
return getCookiesForHost(url.host());
}
public List<Cookie> getCookiesForHost(String host) {
List<Cookie> cookieList = new ArrayList<>();
cookiesMap.forEach((domain, cookies) -> {
if (host.endsWith(domain)) {
for (int i = cookies.size() - 1; i >= 0; i--) {
if (cookies.get(i).expiresAt() < System.currentTimeMillis()) {
cookies.remove(i);
}
}
cookieList.addAll(cookies);
}
});
return cookieList;
}
public Map<String, List<Cookie>> getCookiesMap() {
return cookiesMap;
}
}

View File

@ -0,0 +1,21 @@
package com.hiczp.bilibili.api.exception;
import java.io.IOException;
public class UserCancelRequestException extends IOException {
public UserCancelRequestException() {
}
public UserCancelRequestException(String message) {
super(message);
}
public UserCancelRequestException(String message, Throwable cause) {
super(message, cause);
}
public UserCancelRequestException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,22 @@
package com.hiczp.bilibili.api.interceptor;
import com.hiczp.bilibili.api.exception.UserCancelRequestException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class CancelRequestInterceptor implements Interceptor {
private Request request;
@Override
public Response intercept(Chain chain) throws IOException {
request = chain.request();
throw new UserCancelRequestException();
}
public Request getRequest() {
return request;
}
}

View File

@ -12,7 +12,6 @@ import retrofit2.http.POST;
import retrofit2.http.Query;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public interface PassportService {
//获取验证码
@ -50,9 +49,4 @@ public interface PassportService {
@POST("api/oauth2/revoke")
Call<LogoutResponseEntity> logout(@Query("access_token") String accessToken);
//TODO sso 尚不明确
@Deprecated
@GET("api/login/sso")
Call sso(@Query("access_token") String accessToken, @Nullable @Query("gourl") String goUrl);
}

View File

@ -0,0 +1,15 @@
package com.hiczp.bilibili.api.passport;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
import javax.annotation.Nullable;
//sso 很特别, 它可能返回的是一个 HTML 页面, 所以单独分出来
//sso 会经过两次 302 跳转, 需要保存其中的 cookie, 然后才能抵达最终页面并且进入 cookie 登录状态
public interface SsoService {
@GET("api/login/sso")
Call<ResponseBody> sso(@Nullable @Query("gourl") String goUrl);
}

View File

@ -1,25 +1,28 @@
package com.hiczp.bilibili.api.test;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.hiczp.bilibili.api.BilibiliAPI;
import org.junit.Ignore;
import okhttp3.Cookie;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
public class SsoTest {
private static final Logger LOGGER = LoggerFactory.getLogger(UserInfoTest.class);
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final Logger LOGGER = LoggerFactory.getLogger(SsoTest.class);
private static final BilibiliAPI BILIBILI_API = Config.getBilibiliAPI();
@Ignore
@Test
public void test() throws Exception {
// Object object = BILIBILI_API.getPassportService()
// .sso(BILIBILI_API.getBilibiliAccount().getAccessToken(), null)
// .execute()
// .body();
// LOGGER.info("{}", object.toString());
Map<String, List<Cookie>> cookiesMap = BILIBILI_API.toCookies();
StringBuilder stringBuilder = new StringBuilder();
cookiesMap.forEach((domain, cookies) -> {
stringBuilder.append("domain: ").append(domain).append("\n");
cookies.forEach(cookie ->
stringBuilder.append("\t").append(cookie.name()).append("=").append(cookie.value()).append("\n")
);
});
LOGGER.info("Cookies below: \n{}", stringBuilder.toString());
}
}