@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); } }