mirror of
https://github.com/czp3009/bilibili-api.git
synced 2025-02-19 20:50:28 +08:00
优化计算 sign 的过程
This commit is contained in:
parent
7b99019b6e
commit
6575e8d736
@ -139,6 +139,7 @@ public class BilibiliAPI implements BilibiliServiceProvider, LiveClientProvider
|
||||
ServerErrorCode.Common.UNAUTHORIZED,
|
||||
ServerErrorCode.Live.USER_NO_LOGIN,
|
||||
ServerErrorCode.Live.PLEASE_LOGIN,
|
||||
ServerErrorCode.Live.PLEASE_LOGIN0,
|
||||
ServerErrorCode.Live.NO_LOGIN
|
||||
))
|
||||
.addInterceptor(new AddAccessKeyInterceptor(bilibiliAccount))
|
||||
|
@ -5,24 +5,25 @@ 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 javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BilibiliSecurityHelper {
|
||||
private static String cipherPassword(BilibiliServiceProvider bilibiliServiceProvider,
|
||||
String password) throws IOException {
|
||||
private static String cipherPassword(@Nonnull BilibiliServiceProvider bilibiliServiceProvider,
|
||||
@Nonnull String password) throws IOException {
|
||||
KeyEntity keyEntity = bilibiliServiceProvider.getPassportService().getKey().execute().body();
|
||||
//服务器返回异常错误码
|
||||
if (keyEntity.getCode() != 0) {
|
||||
@ -60,17 +61,49 @@ public class BilibiliSecurityHelper {
|
||||
return cipheredPassword;
|
||||
}
|
||||
|
||||
public static LoginResponseEntity login(BilibiliServiceProvider bilibiliServiceProvider,
|
||||
String username,
|
||||
String password) throws IOException {
|
||||
//计算 sign
|
||||
//传入值为 name1=value1 形式
|
||||
//传入值必须已经排序
|
||||
//value 必须已经 URLEncode
|
||||
public static String calculateSign(@Nonnull List<String> nameAndValues, @Nonnull String appSecret) {
|
||||
return calculateSign(nameAndValues.stream().collect(Collectors.joining("&")), appSecret);
|
||||
}
|
||||
|
||||
public static String calculateSign(@Nonnull String encodedQuery, @Nonnull String appSecret) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||
messageDigest.update((encodedQuery + appSecret).getBytes());
|
||||
String md5 = new BigInteger(1, messageDigest.digest()).toString(16);
|
||||
//md5 不满 32 位时左边加 0
|
||||
return ("00000000000000000000000000000000" + md5).substring(md5.length());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
//直接生成添加了 sign 的 query
|
||||
//传入值为 name1=value1 形式
|
||||
//传入值必须已经排序
|
||||
//value 必须已经 URLEncode
|
||||
public static String addSignToQuery(@Nonnull List<String> nameAndValues, @Nonnull String appSecret) {
|
||||
return addSignToQuery(nameAndValues.stream().collect(Collectors.joining("&")), appSecret);
|
||||
}
|
||||
|
||||
public static String addSignToQuery(@Nonnull String encodedQuery, @Nonnull String appSecret) {
|
||||
return encodedQuery + String.format("&%s=%s", "sign", calculateSign(encodedQuery, appSecret));
|
||||
}
|
||||
|
||||
public static LoginResponseEntity login(@Nonnull BilibiliServiceProvider bilibiliServiceProvider,
|
||||
@Nonnull String username,
|
||||
@Nonnull String password) throws IOException {
|
||||
return login(bilibiliServiceProvider, username, password, null, null);
|
||||
}
|
||||
|
||||
public static LoginResponseEntity login(BilibiliServiceProvider bilibiliServiceProvider,
|
||||
String username,
|
||||
String password,
|
||||
String captcha,
|
||||
String cookie) throws IOException {
|
||||
public static LoginResponseEntity login(@Nonnull BilibiliServiceProvider bilibiliServiceProvider,
|
||||
@Nonnull String username,
|
||||
@Nonnull String password,
|
||||
@Nullable String captcha,
|
||||
@Nullable String cookie) throws IOException {
|
||||
return bilibiliServiceProvider.getPassportService()
|
||||
.login(
|
||||
username,
|
||||
@ -81,16 +114,16 @@ public class BilibiliSecurityHelper {
|
||||
.body();
|
||||
}
|
||||
|
||||
public static RefreshTokenResponseEntity refreshToken(BilibiliServiceProvider bilibiliServiceProvider,
|
||||
String accessToken,
|
||||
String refreshToken) throws IOException {
|
||||
public static RefreshTokenResponseEntity refreshToken(@Nonnull BilibiliServiceProvider bilibiliServiceProvider,
|
||||
@Nonnull String accessToken,
|
||||
@Nonnull String refreshToken) throws IOException {
|
||||
return bilibiliServiceProvider.getPassportService().refreshToken(accessToken, refreshToken)
|
||||
.execute()
|
||||
.body();
|
||||
}
|
||||
|
||||
public static LogoutResponseEntity logout(BilibiliServiceProvider bilibiliServiceProvider,
|
||||
String accessToken) throws IOException {
|
||||
public static LogoutResponseEntity logout(@Nonnull BilibiliServiceProvider bilibiliServiceProvider,
|
||||
@Nonnull String accessToken) throws IOException {
|
||||
return bilibiliServiceProvider.getPassportService().logout(accessToken)
|
||||
.execute()
|
||||
.body();
|
||||
|
@ -29,13 +29,18 @@ public class ServerErrorCode {
|
||||
}
|
||||
|
||||
//一些 API 未登录时返回 3, 一些返回 -101, 还有一些返回 401, 在网关上鉴权的 API 返回 -401
|
||||
//甚至有一些 API 返回 32205 这种奇怪的错误码
|
||||
public static class Live {
|
||||
//"user no login"
|
||||
public static final int USER_NO_LOGIN = 3;
|
||||
//"请登录"
|
||||
public static final int PLEASE_LOGIN = 401;
|
||||
//"请登录"
|
||||
public static final int PLEASE_LOGIN0 = 32205;
|
||||
//"请先登录"
|
||||
public static final int NO_LOGIN = -101;
|
||||
//"关键字不能小于2个字节或大于50字节"
|
||||
public static final int KEYWORD_CAN_NOT_LESS_THAN_2_BYTES_OR_GREATER_THAN_50_BYTES = -609;
|
||||
//已经领取过这个宝箱
|
||||
public static final int THIS_SILVER_TASK_ALREADY_TOOK = -903;
|
||||
//今天所有的宝箱已经领完
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.hiczp.bilibili.api.interceptor;
|
||||
|
||||
import com.hiczp.bilibili.api.BilibiliClientProperties;
|
||||
import com.hiczp.bilibili.api.BilibiliSecurityHelper;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Request;
|
||||
@ -8,21 +9,17 @@ import okhttp3.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SortParamsAndSignInterceptor implements Interceptor {
|
||||
private BilibiliClientProperties bilibiliClientDefinition;
|
||||
private BilibiliClientProperties bilibiliClientProperties;
|
||||
|
||||
public SortParamsAndSignInterceptor(BilibiliClientProperties bilibiliClientDefinition) {
|
||||
this.bilibiliClientDefinition = bilibiliClientDefinition;
|
||||
public SortParamsAndSignInterceptor(BilibiliClientProperties bilibiliClientProperties) {
|
||||
this.bilibiliClientProperties = bilibiliClientProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -43,31 +40,12 @@ public class SortParamsAndSignInterceptor implements Interceptor {
|
||||
)
|
||||
);
|
||||
Collections.sort(nameAndValues);
|
||||
nameAndValues.add(String.format("%s=%s", "sign", calculateSign(nameAndValues)));
|
||||
return chain.proceed(
|
||||
request.newBuilder()
|
||||
.url(httpUrl.newBuilder()
|
||||
.encodedQuery(generateQuery(nameAndValues))
|
||||
.encodedQuery(BilibiliSecurityHelper.addSignToQuery(nameAndValues, bilibiliClientProperties.getAppSecret()))
|
||||
.build()
|
||||
).build()
|
||||
);
|
||||
}
|
||||
|
||||
private String generateQuery(List<String> nameAndValues) {
|
||||
return nameAndValues.stream().collect(Collectors.joining("&"));
|
||||
}
|
||||
|
||||
//排序 params 并计算 sign
|
||||
//传入值为 name1=value1 形式
|
||||
private String calculateSign(List<String> nameAndValues) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||
messageDigest.update((generateQuery(nameAndValues) + bilibiliClientDefinition.getAppSecret()).getBytes());
|
||||
String md5 = new BigInteger(1, messageDigest.digest()).toString(16);
|
||||
//md5 不满 32 位时左边加 0
|
||||
return ("00000000000000000000000000000000" + md5).substring(md5.length());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ public interface LiveService {
|
||||
@GET("AppRoom/index")
|
||||
Call<LiveRoomInfoEntity> getRoomInfo(@Query("room_id") long roomId);
|
||||
|
||||
//未登录时返回 401
|
||||
@POST("feed/v1/feed/isFollowed")
|
||||
Call<IsFollowedResponseEntity> isFollowed(@Query("follow") long hostUserId);
|
||||
|
||||
@ -43,10 +44,11 @@ public interface LiveService {
|
||||
@GET("appUser/getTitle")
|
||||
Call<TitlesEntity> getTitle();
|
||||
|
||||
//这个 API 不是很明确, 所有房间都一样
|
||||
//这个 API 不是很明确, 所有房间都一样, 可能和什么活动有关
|
||||
@GET("SpecialGift/room/{roomId}")
|
||||
Call<SpecialGiftEntity> getSpecialGift(@Path("roomId") long roomId);
|
||||
|
||||
//未登录时返回 3
|
||||
@GET("mobile/getUser")
|
||||
Call<UserInfoEntity> getUserInfo();
|
||||
|
||||
@ -59,6 +61,7 @@ public interface LiveService {
|
||||
return getPlayUrl(cid, "json");
|
||||
}
|
||||
|
||||
//未登录时返回 3
|
||||
@POST("mobile/userOnlineHeart")
|
||||
@FormUrlEncoded
|
||||
Call<SendOnlineHeartResponseEntity> sendOnlineHeart(@Field("room_id") long roomId, @Field("scale") String scale);
|
||||
|
@ -0,0 +1,19 @@
|
||||
package com.hiczp.bilibili.api.test;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.hiczp.bilibili.api.BilibiliAPI;
|
||||
import com.hiczp.bilibili.api.passport.entity.LoginResponseEntity;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ManualLoginTool {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ManualLoginTool.class);
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
RuleSuite.init();
|
||||
Config config = Config.getInstance();
|
||||
LoginResponseEntity loginResponseEntity = new BilibiliAPI()
|
||||
.login(config.getUsername(), config.getPassword());
|
||||
LOGGER.info(new GsonBuilder().setPrettyPrinting().create().toJson(loginResponseEntity));
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.hiczp.bilibili.api.test;
|
||||
|
||||
import com.hiczp.bilibili.api.BilibiliClientProperties;
|
||||
import com.hiczp.bilibili.api.BilibiliSecurityHelper;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
//Insomnia 手动测试时使用
|
||||
//拷贝入整个 url, 它将自动计算出 sign
|
||||
public class ManualSignTool {
|
||||
public static void main(String[] args) {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
System.out.println("Please input url");
|
||||
while (true) {
|
||||
String input = scanner.nextLine().trim();
|
||||
if (input.equals("q")) {
|
||||
break;
|
||||
}
|
||||
if (input.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int index = input.indexOf("?");
|
||||
if (index == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> nameAndValues = Arrays.stream(input.substring(index + 1)
|
||||
.split("&"))
|
||||
.filter(param -> !param.startsWith("sign="))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
System.out.println(
|
||||
BilibiliSecurityHelper.calculateSign(nameAndValues, BilibiliClientProperties.defaultSetting().getAppSecret())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,10 @@ import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
@ -30,20 +33,21 @@ public class RuleSuite {
|
||||
}
|
||||
};
|
||||
|
||||
public static void init() {
|
||||
public static void init() throws Exception {
|
||||
//初始化 slf4j
|
||||
BasicConfigurator.configure();
|
||||
|
||||
//读取配置文件
|
||||
try {
|
||||
try (BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(Config.class.getResource("/config.json").toURI()))) {
|
||||
Config.setConfig(
|
||||
new Gson().fromJson(
|
||||
new BufferedReader(new InputStreamReader(Config.class.getResourceAsStream("/config.json"))),
|
||||
bufferedReader,
|
||||
Config.class
|
||||
)
|
||||
);
|
||||
} catch (NullPointerException e) {
|
||||
} catch (IOException e) {
|
||||
//抛出异常就可以取消测试
|
||||
throw new RuntimeException("Please create config file before tests");
|
||||
throw new FileNotFoundException("Please create config file before tests");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user