mirror of
https://gitee.com/yadong.zhang/JustAuth.git
synced 2025-12-06 16:58:24 +08:00
🥚 添加 amazon 平台
This commit is contained in:
parent
c79b97a0d0
commit
132a7f4338
@ -131,4 +131,11 @@ public class AuthConfig {
|
|||||||
* @since 1.15.9
|
* @since 1.15.9
|
||||||
*/
|
*/
|
||||||
private String packId;
|
private String packId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启 PKCE 模式,该配置仅用于支持 PKCE 模式的平台,针对无服务应用,不推荐使用隐式授权,推荐使用 PKCE 模式
|
||||||
|
*
|
||||||
|
* @since 1.16.0
|
||||||
|
*/
|
||||||
|
private boolean pkce;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -832,5 +832,32 @@ public enum AuthDefaultSource implements AuthSource {
|
|||||||
public String refresh() {
|
public String refresh() {
|
||||||
return "https://oauth.aliyun.com/v1/token";
|
return "https://oauth.aliyun.com/v1/token";
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amazon
|
||||||
|
*
|
||||||
|
* @since 1.16.0
|
||||||
|
*/
|
||||||
|
AMAZON {
|
||||||
|
@Override
|
||||||
|
public String authorize() {
|
||||||
|
return "https://www.amazon.com/ap/oa";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String accessToken() {
|
||||||
|
return "https://api.amazon.com/auth/o2/token";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String userInfo() {
|
||||||
|
return "https://api.amazon.com/user/profile";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String refresh() {
|
||||||
|
return "https://api.amazon.com/auth/o2/token";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/main/java/me/zhyd/oauth/enums/scope/AuthAmazonScope.java
Normal file
28
src/main/java/me/zhyd/oauth/enums/scope/AuthAmazonScope.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package me.zhyd.oauth.enums.scope;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amazon平台 OAuth 授权范围
|
||||||
|
*
|
||||||
|
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.16.0
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum AuthAmazonScope implements AuthScope {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code scope} 含义,以{@code description} 为准
|
||||||
|
*/
|
||||||
|
R_LITEPROFILE("profile", "The profile scope includes a user's name and email address", true),
|
||||||
|
R_EMAILADDRESS("profile:user_id", "The profile:user_id scope only includes the user_id field of the profile", true),
|
||||||
|
W_MEMBER_SOCIAL("postal_code", "This includes the user's zip/postal code number from their primary shipping address", true);
|
||||||
|
|
||||||
|
private final String scope;
|
||||||
|
private final String description;
|
||||||
|
private final boolean isDefault;
|
||||||
|
|
||||||
|
}
|
||||||
182
src/main/java/me/zhyd/oauth/request/AuthAmazonRequest.java
Normal file
182
src/main/java/me/zhyd/oauth/request/AuthAmazonRequest.java
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package me.zhyd.oauth.request;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.xkcoding.http.constants.Constants;
|
||||||
|
import com.xkcoding.http.support.HttpHeader;
|
||||||
|
import com.xkcoding.http.util.UrlUtil;
|
||||||
|
import me.zhyd.oauth.cache.AuthStateCache;
|
||||||
|
import me.zhyd.oauth.config.AuthConfig;
|
||||||
|
import me.zhyd.oauth.config.AuthDefaultSource;
|
||||||
|
import me.zhyd.oauth.enums.AuthResponseStatus;
|
||||||
|
import me.zhyd.oauth.enums.AuthUserGender;
|
||||||
|
import me.zhyd.oauth.enums.scope.AuthAmazonScope;
|
||||||
|
import me.zhyd.oauth.exception.AuthException;
|
||||||
|
import me.zhyd.oauth.model.AuthCallback;
|
||||||
|
import me.zhyd.oauth.model.AuthResponse;
|
||||||
|
import me.zhyd.oauth.model.AuthToken;
|
||||||
|
import me.zhyd.oauth.model.AuthUser;
|
||||||
|
import me.zhyd.oauth.utils.AuthScopeUtils;
|
||||||
|
import me.zhyd.oauth.utils.HttpUtils;
|
||||||
|
import me.zhyd.oauth.utils.PkceUtil;
|
||||||
|
import me.zhyd.oauth.utils.UrlBuilder;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amazon登录
|
||||||
|
* Login with Amazon for Websites Overview: https://developer.amazon.com/zh/docs/login-with-amazon/register-web.html
|
||||||
|
* Login with Amazon SDK for JavaScript Reference Guide:https://developer.amazon.com/zh/docs/login-with-amazon/javascript-sdk-reference.html
|
||||||
|
*
|
||||||
|
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||||
|
* @since 1.16.0
|
||||||
|
*/
|
||||||
|
public class AuthAmazonRequest extends AuthDefaultRequest {
|
||||||
|
|
||||||
|
public AuthAmazonRequest(AuthConfig config) {
|
||||||
|
super(config, AuthDefaultSource.AMAZON);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthAmazonRequest(AuthConfig config, AuthStateCache authStateCache) {
|
||||||
|
super(config, AuthDefaultSource.AMAZON, authStateCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.amazon.com/zh/docs/login-with-amazon/authorization-code-grant.html#authorization-request
|
||||||
|
*
|
||||||
|
* @param state state 验证授权流程的参数,可以防止csrf
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String authorize(String state) {
|
||||||
|
UrlBuilder builder = UrlBuilder.fromBaseUrl(source.authorize())
|
||||||
|
.queryParam("client_id", config.getClientId())
|
||||||
|
.queryParam("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthAmazonScope.values())))
|
||||||
|
.queryParam("redirect_uri", config.getRedirectUri())
|
||||||
|
.queryParam("response_type", "code")
|
||||||
|
.queryParam("state", getRealState(state));
|
||||||
|
|
||||||
|
if (config.isPkce()) {
|
||||||
|
String cacheKey = this.source.getName().concat(":code_verifier:").concat(config.getClientId());
|
||||||
|
String codeVerifier = PkceUtil.generateCodeVerifier();
|
||||||
|
String codeChallengeMethod = "S256";
|
||||||
|
String codeChallenge = PkceUtil.generateCodeChallenge(codeChallengeMethod, codeVerifier);
|
||||||
|
builder.queryParam("code_challenge", codeChallenge)
|
||||||
|
.queryParam("code_challenge_method", codeChallengeMethod);
|
||||||
|
// 缓存 codeVerifier 十分钟
|
||||||
|
this.authStateCache.cache(cacheKey, codeVerifier, TimeUnit.MINUTES.toMillis(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.amazon.com/zh/docs/login-with-amazon/authorization-code-grant.html#access-token-request
|
||||||
|
*
|
||||||
|
* @return access token
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AuthToken getAccessToken(AuthCallback authCallback) {
|
||||||
|
Map<String, String> form = new HashMap<>(8);
|
||||||
|
form.put("grant_type", "authorization_code");
|
||||||
|
form.put("code", authCallback.getCode());
|
||||||
|
form.put("redirect_uri", config.getRedirectUri());
|
||||||
|
form.put("client_id", config.getClientId());
|
||||||
|
form.put("client_secret", config.getClientSecret());
|
||||||
|
|
||||||
|
if (config.isPkce()) {
|
||||||
|
String cacheKey = this.source.getName().concat(":code_verifier:").concat(config.getClientId());
|
||||||
|
String codeVerifier = this.authStateCache.get(cacheKey);
|
||||||
|
form.put("code_verifier", codeVerifier);
|
||||||
|
}
|
||||||
|
return getToken(form, this.source.accessToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthResponse refresh(AuthToken authToken) {
|
||||||
|
Map<String, String> form = new HashMap<>(6);
|
||||||
|
form.put("grant_type", "refresh_token");
|
||||||
|
form.put("refresh_token", authToken.getRefreshToken());
|
||||||
|
form.put("client_id", config.getClientId());
|
||||||
|
form.put("client_secret", config.getClientSecret());
|
||||||
|
return AuthResponse.builder()
|
||||||
|
.code(AuthResponseStatus.SUCCESS.getCode())
|
||||||
|
.data(getToken(form, this.source.refresh()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthToken getToken(Map<String, String> param, String url) {
|
||||||
|
HttpHeader httpHeader = new HttpHeader();
|
||||||
|
httpHeader.add("Host", "api.amazon.com");
|
||||||
|
httpHeader.add(Constants.CONTENT_TYPE, "application/x-www-form-urlencoded;charset=UTF-8");
|
||||||
|
String response = new HttpUtils(config.getHttpConfig()).post(url, param, httpHeader, false);
|
||||||
|
JSONObject jsonObject = JSONObject.parseObject(response);
|
||||||
|
this.checkResponse(jsonObject);
|
||||||
|
return AuthToken.builder()
|
||||||
|
.accessToken(jsonObject.getString("access_token"))
|
||||||
|
.tokenType(jsonObject.getString("token_type"))
|
||||||
|
.expireIn(jsonObject.getIntValue("expires_in"))
|
||||||
|
.refreshToken(jsonObject.getString("refresh_token"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验响应内容是否正确
|
||||||
|
*
|
||||||
|
* @param jsonObject 响应内容
|
||||||
|
*/
|
||||||
|
private void checkResponse(JSONObject jsonObject) {
|
||||||
|
if (jsonObject.containsKey("error")) {
|
||||||
|
throw new AuthException(jsonObject.getString("error_description").concat(" ") + jsonObject.getString("error_description"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.amazon.com/zh/docs/login-with-amazon/obtain-customer-profile.html#call-profile-endpoint
|
||||||
|
*
|
||||||
|
* @param authToken token信息
|
||||||
|
* @return AuthUser
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AuthUser getUserInfo(AuthToken authToken) {
|
||||||
|
String accessToken = authToken.getAccessToken();
|
||||||
|
this.checkToken(accessToken);
|
||||||
|
|
||||||
|
HttpHeader httpHeader = new HttpHeader();
|
||||||
|
httpHeader.add("Host", "api.amazon.com");
|
||||||
|
httpHeader.add("Authorization", "bearer " + accessToken);
|
||||||
|
String userInfo = new HttpUtils(config.getHttpConfig()).get(this.source.userInfo(), new HashMap<>(0), httpHeader, false);
|
||||||
|
JSONObject jsonObject = JSONObject.parseObject(userInfo);
|
||||||
|
this.checkResponse(jsonObject);
|
||||||
|
|
||||||
|
return AuthUser.builder()
|
||||||
|
.rawUserInfo(jsonObject)
|
||||||
|
.uuid(jsonObject.getString("user_id"))
|
||||||
|
.username(jsonObject.getString("name"))
|
||||||
|
.nickname(jsonObject.getString("name"))
|
||||||
|
.email(jsonObject.getString("email"))
|
||||||
|
.gender(AuthUserGender.UNKNOWN)
|
||||||
|
.source(source.toString())
|
||||||
|
.token(authToken)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkToken(String accessToken) {
|
||||||
|
String tokenInfo = new HttpUtils(config.getHttpConfig()).get("https://api.amazon.com/auth/o2/tokeninfo?access_token=" + UrlUtil.urlEncode(accessToken));
|
||||||
|
JSONObject jsonObject = JSONObject.parseObject(tokenInfo);
|
||||||
|
if (!config.getClientId().equals(jsonObject.getString("aud"))) {
|
||||||
|
throw new AuthException(AuthResponseStatus.ILLEGAL_TOKEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String userInfoUrl(AuthToken authToken) {
|
||||||
|
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||||
|
.queryParam("user_id", authToken.getUserId())
|
||||||
|
.queryParam("screen_name", authToken.getScreenName())
|
||||||
|
.queryParam("include_entities", true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/main/java/me/zhyd/oauth/utils/PkceUtil.java
Normal file
39
src/main/java/me/zhyd/oauth/utils/PkceUtil.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package me.zhyd.oauth.utils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该配置仅用于支持 PKCE 模式的平台,针对无服务应用,不推荐使用隐式授权,推荐使用 PKCE 模式
|
||||||
|
*
|
||||||
|
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public class PkceUtil {
|
||||||
|
|
||||||
|
public static String generateCodeVerifier() {
|
||||||
|
String randomStr = RandomUtil.randomString(50);
|
||||||
|
return Base64Utils.encodeUrlSafe(randomStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 适用于 OAuth 2.0 PKCE 增强协议
|
||||||
|
*
|
||||||
|
* @param codeChallengeMethod s256 / plain
|
||||||
|
* @param codeVerifier 客户端生产的校验码
|
||||||
|
* @return code challenge
|
||||||
|
*/
|
||||||
|
public static String generateCodeChallenge(String codeChallengeMethod, String codeVerifier) {
|
||||||
|
if ("S256".equalsIgnoreCase(codeChallengeMethod)) {
|
||||||
|
// https://tools.ietf.org/html/rfc7636#section-4.2
|
||||||
|
// code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
|
||||||
|
return newStringUsAscii(Base64Utils.encodeUrlSafe(Sha256.digest(codeVerifier), true));
|
||||||
|
} else {
|
||||||
|
return codeVerifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String newStringUsAscii(byte[] bytes) {
|
||||||
|
return new String(bytes, StandardCharsets.US_ASCII);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/main/java/me/zhyd/oauth/utils/RandomUtil.java
Normal file
38
src/main/java/me/zhyd/oauth/utils/RandomUtil.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package me.zhyd.oauth.utils;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机字符串
|
||||||
|
*
|
||||||
|
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.16.0
|
||||||
|
*/
|
||||||
|
public class RandomUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于随机选的字符和数字
|
||||||
|
*/
|
||||||
|
public static final String BASE_CHAR_NUMBER = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得一个随机的字符串
|
||||||
|
*
|
||||||
|
* @param length 字符串的长度
|
||||||
|
* @return 指定长度的随机字符串
|
||||||
|
*/
|
||||||
|
public static String randomString(int length) {
|
||||||
|
final StringBuilder sb = new StringBuilder(length);
|
||||||
|
|
||||||
|
if (length < 1) {
|
||||||
|
length = 1;
|
||||||
|
}
|
||||||
|
int baseLength = BASE_CHAR_NUMBER.length();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int number = ThreadLocalRandom.current().nextInt(baseLength);
|
||||||
|
sb.append(BASE_CHAR_NUMBER.charAt(number));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/me/zhyd/oauth/utils/Sha256.java
Normal file
27
src/main/java/me/zhyd/oauth/utils/Sha256.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package me.zhyd.oauth.utils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SHA256 加密
|
||||||
|
*
|
||||||
|
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.16.0
|
||||||
|
*/
|
||||||
|
public class Sha256 {
|
||||||
|
|
||||||
|
public static byte[] digest(String str) {
|
||||||
|
MessageDigest messageDigest;
|
||||||
|
try {
|
||||||
|
messageDigest = MessageDigest.getInstance("SHA-256");
|
||||||
|
messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return messageDigest.digest();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user