@Value("${app.twitter.login.consumer-key}")
private String consumerKey;
@Value("${app.twitter.login.consumer-secret}")
private String consumerSecret;
private static final String verifyCredentialsUrl = "https://api.twitter.com/1.1/account/verify_credentials.json";
private final Logger log = LoggerFactory.getLogger(this.getClass());
private static final String PREAMBLE = "OAuth";
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
private static final String HMAC_SHA1 = "HmacSHA1";
@Autowired
private AccountManager accountManager;
@Autowired
private UserForbiddenManager userForbiddenManager;
@Autowired
private SmsService smsService;
@Override
public AccountThirdBO process(LoginRequest req) {
if (StringUtils.isEmpty(req.getAccessToken())
|| StringUtils.isEmpty(req.getSecretToken())
|| StringUtils.isEmpty(req.getThirdId())) {
throw new BusinessException(ResultEnum.PARA_ERR);
}
String provider = AccountConstant.ProviderEnum.twitter.toString();
String thirdId = getTwitterUnionId(req.getThirdId(), req.getAccessToken(), req.getSecretToken());
AccountIndex accountIndex = accountManager.getAccountIndex(provider, thirdId);
boolean unregistered = accountIndex == null;
if (!unregistered) {
accountManager.checkPhoneValidation(accountIndex.getUid());
}
List<ThirdInfo> thirdInfoList = new ArrayList<>();
thirdInfoList.add(new ThirdInfo(provider, thirdId, thirdId, ""));
return new AccountThirdBO(thirdInfoList,
accountIndex == null ? null : accountIndex.getUid(),
unregistered,
false,
unregistered);
}
private String getTwitterUnionId(String userId, String accessToken, String secretToken) {
if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(secretToken)) {
throw new BusinessException(ResultEnum.PARA_ERR);
}
HashMap<String, String> headers = new HashMap<String, String>();
//应用口令
Map<String, String> queryParams = new HashMap<>();
queryParams.put("user_id", userId);
queryParams.put("include_entities", Boolean.toString(true));
Map<String, String> oauthParams = buildOauthParams();
oauthParams.put("oauth_token", accessToken);
Map<String, String> params = new HashMap<>(oauthParams);
params.putAll(queryParams);
oauthParams.put("oauth_signature", generateTwitterSignature(params, "GET", verifyCredentialsUrl, consumerSecret, secretToken));
String header = buildHeader(oauthParams);
headers.put("Authorization", header);
String result = HttpInvokeUtil.httpGetWithHeader(verifyCredentialsUrl, queryParams, headers);
log.info("getTwitterUnionId result:{}", result);
JSONObject jsonObject = JSON.parseObject(result);
return jsonObject.get("id_str").toString();
}
public static String generateTwitterSignature(Map<String, String> params, String method, String baseUrl, String apiSecret, String tokenSecret) {
TreeMap<String, String> map = new TreeMap<>(params);
String str = parseMapToString(map, true);
String baseStr = method.toUpperCase() + "&" + URLEncoder.encode(baseUrl) + "&" + URLEncoder.encode(str);
String signKey = apiSecret + "&" + (StringUtils.isEmpty(tokenSecret) ? "" : tokenSecret);
byte[] signature = sign(signKey.getBytes(DEFAULT_ENCODING), baseStr.getBytes(DEFAULT_ENCODING), HMAC_SHA1);
return new String(Base64Utils.encode(signature, false));
}
public static String parseMapToString(Map<String, String> params, boolean encode) {
if (null == params || params.isEmpty()) {
return "";
}
List<String> paramList = new ArrayList<>();
params.forEach((k, v) -> {
if (null == v) {
paramList.add(k + "=");
} else {
paramList.add(k + "=" + (encode ? URLEncoder.encode(v) : v));
}
});
return String.join("&", paramList);
}
private static byte[] sign(byte[] key, byte[] data, String algorithm) {
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data);
} catch (NoSuchAlgorithmException ex) {
throw new BusinessException(ResultEnum.PARA_ERR);
} catch (InvalidKeyException ex) {
throw new BusinessException(ResultEnum.PARA_ERR);
}
}
private Map<String, String> buildOauthParams() {
Map<String, String> params = new HashMap<>(5);
params.put("oauth_consumer_key", consumerKey);
params.put("oauth_nonce", WXPayUtil.generateNonceStr());
params.put("oauth_signature_method", "HMAC-SHA1");
params.put("oauth_timestamp", System.currentTimeMillis()/1000 + "");
params.put("oauth_version", "1.0");
return params;
}
private String buildHeader(Map<String, String> oauthParams) {
final StringBuilder sb = new StringBuilder(PREAMBLE + " ");
for (Map.Entry<String, String> param : oauthParams.entrySet()) {
sb.append(param.getKey()).append("=\"").append(URLEncoder.encode(param.getValue())).append('"').append(", ");
}
return sb.deleteCharAt(sb.length() - 2).toString();
}
相关工具类
public class Base64Utils {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
/**
* 标准编码表
*/
private static final byte[] STANDARD_ENCODE_TABLE = { //
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', //
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', //
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', //
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', //
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', //
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
'w', 'x', 'y', 'z', '0', '1', '2', '3', //
'4', '5', '6', '7', '8', '9', '+', '/' //
};
/**
* URL安全的编码表,将 + 和 / 替换为 - 和 _
*/
private static final byte[] URL_SAFE_ENCODE_TABLE = { //
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', //
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', //
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', //
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', //
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', //
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
'w', 'x', 'y', 'z', '0', '1', '2', '3', //
'4', '5', '6', '7', '8', '9', '-', '_' //
};
// -------------------------------------------------------------------- encode
/**
* 编码为Base64,非URL安全的
*
* @param arr 被编码的数组
* @param lineSep 在76个char之后是CRLF还是EOF
* @return 编码后的bytes
*/
public static byte[] encode(byte[] arr, boolean lineSep) {
return encode(arr, lineSep, false);
}
/**
* 编码为Base64,URL安全的
*
* @param arr 被编码的数组
* @param lineSep 在76个char之后是CRLF还是EOF
* @return 编码后的bytes
* @since 3.0.6
*/
public static byte[] encodeUrlSafe(byte[] arr, boolean lineSep) {
return encode(arr, lineSep, true);
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
*/
public static String encode(CharSequence source) {
return encode(source, DEFAULT_CHARSET);
}
/**
* base64编码,URL安全
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
* @since 3.0.6
*/
public static String encodeUrlSafe(CharSequence source) {
return encodeUrlSafe(source, DEFAULT_CHARSET);
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String encode(CharSequence source, Charset charset) {
return encode(StringUtils.bytes(source, charset));
}
/**
* base64编码,URL安全的
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @since 3.0.6
*/
public static String encodeUrlSafe(CharSequence source, Charset charset) {
return encodeUrlSafe(StringUtils.bytes(source, charset));
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
*/
public static String encode(byte[] source) {
return StringUtils.str(encode(source, false), DEFAULT_CHARSET);
}
/**
* base64编码,URL安全的
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
* @since 3.0.6
*/
public static String encodeUrlSafe(byte[] source) {
return StringUtils.str(encodeUrlSafe(source, false), DEFAULT_CHARSET);
}
/**
* 编码为Base64<br>
* 如果isMultiLine为<code>true</code>,则每76个字符一个换行符,否则在一行显示
*
* @param arr 被编码的数组
* @param isMultiLine 在76个char之后是CRLF还是EOF
* @param isUrlSafe 是否使用URL安全字符,一般为<code>false</code>
* @return 编码后的bytes
*/
public static byte[] encode(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
if (null == arr) {
return null;
}
int len = arr.length;
if (len == 0) {
return new byte[0];
}
int evenlen = (len / 3) * 3;
int cnt = ((len - 1) / 3 + 1) << 2;
int destlen = cnt + (isMultiLine ? (cnt - 1) / 76 << 1 : 0);
byte[] dest = new byte[destlen];
byte[] encodeTable = isUrlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
for (int s = 0, d = 0, cc = 0; s < evenlen; ) {
int i = (arr[s++] & 0xff) << 16 | (arr[s++] & 0xff) << 8 | (arr[s++] & 0xff);
dest[d++] = encodeTable[(i >>> 18) & 0x3f];
dest[d++] = encodeTable[(i >>> 12) & 0x3f];
dest[d++] = encodeTable[(i >>> 6) & 0x3f];
dest[d++] = encodeTable[i & 0x3f];
if (isMultiLine && ++cc == 19 && d < destlen - 2) {
dest[d++] = '\r';
dest[d++] = '\n';
cc = 0;
}
}
int left = len - evenlen;// 剩余位数
if (left > 0) {
int i = ((arr[evenlen] & 0xff) << 10) | (left == 2 ? ((arr[len - 1] & 0xff) << 2) : 0);
dest[destlen - 4] = encodeTable[i >> 12];
dest[destlen - 3] = encodeTable[(i >>> 6) & 0x3f];
if (isUrlSafe) {
// 在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉。
int urlSafeLen = destlen - 2;
if (2 == left) {
dest[destlen - 2] = encodeTable[i & 0x3f];
urlSafeLen += 1;
}
byte[] urlSafeDest = new byte[urlSafeLen];
System.arraycopy(dest, 0, urlSafeDest, 0, urlSafeLen);
return urlSafeDest;
} else {
dest[destlen - 2] = (left == 2) ? encodeTable[i & 0x3f] : (byte) '=';
dest[destlen - 1] = '=';
}
}
return dest;
}
}
public class StringUtils {
public static boolean isEmpty(String str) {
return null == str || str.isEmpty();
}
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
/**
* 如果给定字符串{@code str}中不包含{@code appendStr},则在{@code str}后追加{@code appendStr};
* 如果已包含{@code appendStr},则在{@code str}后追加{@code otherwise}
*
* @param str 给定的字符串
* @param appendStr 需要追加的内容
* @param otherwise 当{@code appendStr}不满足时追加到{@code str}后的内容
* @return 追加后的字符串
*/
public static String appendIfNotContain(String str, String appendStr, String otherwise) {
if (isEmpty(str) || isEmpty(appendStr)) {
return str;
}
if (str.contains(appendStr)) {
return str.concat(otherwise);
}
return str.concat(appendStr);
}
/**
* 编码字符串
*
* @param str 字符串
* @param charset 字符集,如果此字段为空,则解码的结果取决于平台
* @return 编码后的字节码
*/
public static byte[] bytes(CharSequence str, Charset charset) {
if (str == null) {
return null;
}
if (null == charset) {
return str.toString().getBytes();
}
return str.toString().getBytes(charset);
}
/**
* 解码字节码
*
* @param data 字符串
* @param charset 字符集,如果此字段为空,则解码的结果取决于平台
* @return 解码后的字符串
*/
public static String str(byte[] data, Charset charset) {
if (data == null) {
return null;
}
if (null == charset) {
return new String(data);
}
return new String(data, charset);
}
}
本文介绍了一个基于Twitter OAuth认证流程的应用程序实现,详细展示了如何通过OAuth参数构造签名并验证用户身份,进而获取用户的Twitter唯一标识。
1743





