diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 6959e45e25924ab4d92c6f7f04e30b61249525a0..d4052a361ef6f6bb82fb45769bf0cf255052ebab 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -7,8 +7,10 @@ - 集成 Amazon 平台登录 - 集成 Slack 平台登录 - 集成 LINE 平台登录 + - 集成 Okta 平台登录 - 集成钉钉账号登录 - 修改 + - 【**重要**】 `AuthConfig`中的`codingGroupName`参数更名为`domainPrefix`,针对此类平台提供通用的配置。 - 修改 `AuthFacebookScope` 中的默认 scope,解决 justauth-demo 项目中使用 facebook 报错的问题 - 升级 facebook 的 api 到 v10.0 版本 - 优化部分代码 diff --git a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java index a9143055489f01d34d35e05692fc9bcf74d21ae3..2e0f67fb8b819ce91b15fd9a3dc3e6825523c5f7 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java +++ b/src/main/java/me/zhyd/oauth/config/AuthDefaultSource.java @@ -942,5 +942,38 @@ public enum AuthDefaultSource implements AuthSource { public String revoke() { return "https://api.line.me/oauth2/v2.1/revoke"; } - } + }, + /** + * Okta, + *
+ * 团队/组织的域名不同,此处通过配置动态组装 + * + * @since 1.16.0 + */ + OKTA { + @Override + public String authorize() { + return "https://%s.okta.com/oauth2/%s/v1/authorize"; + } + + @Override + public String accessToken() { + return "https://%s.okta.com/oauth2/%s/v1/token"; + } + + @Override + public String refresh() { + return "https://%s.okta.com/oauth2/%s/v1/token"; + } + + @Override + public String userInfo() { + return "https://%s.okta.com/oauth2/%s/v1/userinfo"; + } + + @Override + public String revoke() { + return "https://%s.okta.com/oauth2/%s/v1/revoke"; + } + }, } diff --git a/src/main/java/me/zhyd/oauth/enums/scope/AuthOktaScope.java b/src/main/java/me/zhyd/oauth/enums/scope/AuthOktaScope.java new file mode 100644 index 0000000000000000000000000000000000000000..087b1353caf6074270ffafdf9e9d20385c9b2ef6 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/enums/scope/AuthOktaScope.java @@ -0,0 +1,66 @@ +package me.zhyd.oauth.enums.scope; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Okta 平台 OAuth 授权范围 + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @since 1.16.0 + */ +@Getter +@AllArgsConstructor +public enum AuthOktaScope implements AuthScope { + + /** + * {@code scope} 含义,以{@code description} 为准 + */ + OPENID("openid", "Signals that a request is an OpenID request.", true), + PROFILE("profile", "The exact data varies based on what profile information you have provided, such as: name, time zone, picture, or birthday.", true), + EMAIL("email", "This allows the app to view your email address.", true), + ADDRESS("address", "This allows the app to view your address, such as: street address, city, state, and zip code.", true), + PHONE("phone", "This allows the app to view your phone number.", true), + OFFLINE_ACCESS("offline_access", "This keeps you signed in to the app, even when you are not using it.", true), + OKTA_USERS_MANAGE("okta.users.manage", "Allows the app to create and manage users and read all profile and credential information for users", false), + OKTA_USERS_READ("okta.users.read", "Allows the app to read any user's profile and credential information", false), + OKTA_USERS_MANAGE_SELF("okta.users.manage.self", "Allows the app to manage the currently signed-in user's profile. Currently only supports user profile attribute updates.", false), + OKTA_USERS_READ_SELF("okta.users.read.self", "Allows the app to read the currently signed-in user's profile and credential information", false), + OKTA_APPS_MANAGE("okta.apps.manage", "Allows the app to create and manage Apps in your Okta organization", false), + OKTA_APPS_READ("okta.apps.read", "Allows the app to read information about Apps in your Okta organization", false), + OKTA_AUTHORIZATIONSERVERS_MANAGE("okta.authorizationServers.manage", "Allows the app to manage authorization servers", false), + OKTA_AUTHORIZATIONSERVERS_READ("okta.authorizationServers.read", "Allows the app to read authorization server information", false), + OKTA_CLIENTS_MANAGE("okta.clients.manage", "Allows the app to manage all OAuth/OIDC clients and to create new clients", false), + OKTA_CLIENTS_READ("okta.clients.read", "Allows the app to read information for all OAuth/OIDC clients", false), + OKTA_CLIENTS_REGISTER("okta.clients.register", "Allows the app to register (create) new OAuth/OIDC clients (but not read information about existing clients)", false), + OKTA_EVENTHOOKS_MANAGE("okta.eventHooks.manage", "Allows the app to create and manage Event Hooks in your Okta organization", false), + OKTA_EVENTHOOKS_READ("okta.eventHooks.read", "Allows the app to read information about Event Hooks in your Okta organization", false), + OKTA_FACTORS_MANAGE("okta.factors.manage", "Allows the app to manage all admin operations for org factors (for example, activate, deactive, read)", false), + OKTA_FACTORS_READ("okta.factors.read", "Allows the app to read org factors information", false), + OKTA_GROUPS_MANAGE("okta.groups.manage", "Allows the app to manage groups in your Okta organization", false), + OKTA_GROUPS_READ("okta.groups.read", "Allows the app to read information about groups and their members in your Okta organization", false), + OKTA_IDPS_MANAGE("okta.idps.manage", "Allows the app to create and manage Identity Providers in your Okta organization", false), + OKTA_IDPS_READ("okta.idps.read", "Allows the app to read information about Identity Providers in your Okta organization", false), + OKTA_INLINEHOOKS_MANAGE("okta.inlineHooks.manage", "Allows the app to create and manage Inline Hooks in your Okta organization.", false), + OKTA_INLINEHOOKS_READ("okta.inlineHooks.read", "Allows the app to read information about Inline Hooks in your Okta organization.", false), + OKTA_LINKEDOBJECTS_MANAGE("okta.linkedObjects.manage", "Allows the app to manage Linked Object definitions in your Okta organization.", false), + OKTA_LINKEDOBJECTS_READ("okta.linkedObjects.read", "Allows the app to read Linked Object definitions in your Okta organization.", false), + OKTA_LOGS_READ("okta.logs.read", "Allows the app to read information about System Log entries in your Okta organization", false), + OKTA_ROLES_MANAGE("okta.roles.manage", "Allows the app to create and manage Administrator Roles in your Okta organization", false), + OKTA_ROLES_READ("okta.roles.read", "Allows the app to read information about Administrator Roles in your Okta organization", false), + OKTA_SCHEMAS_MANAGE("okta.schemas.manage", "Allows the app to create and manage Schemas in your Okta organization", false), + OKTA_SCHEMAS_READ("okta.schemas.read", "Allows the app to read information about Schemas in your Okta organization", false), + OKTA_SESSIONS_MANAGE("okta.sessions.manage", "Allows the app to manage all sessions in your Okta organization", false), + OKTA_SESSIONS_READ("okta.sessions.read", "Allows the app to read all sessions in your Okta organization", false), + OKTA_TEMPLATES_MANAGE("okta.templates.manage", "Allows the app to manage all custom templates in your Okta organization", false), + OKTA_TEMPLATES_READ("okta.templates.read", "Allows the app to read all custom templates in your Okta organization", false), + OKTA_TRUSTEDORIGINS_MANAGE("okta.trustedOrigins.manage", "Allows the app to manage all Trusted Origins in your Okta organization", false), + OKTA_TRUSTEDORIGINS_READ("okta.trustedOrigins.read", "Allows the app to read all Trusted Origins in your Okta organization", false), + OKTA_POLICIES_MANAGE("okta.policies.manage", "Allows the app to manage Policies in your Okta organization", false), + OKTA_POLICIES_READ("okta.policies.read", "Allows the app to read information about Policies in your Okta organization", false),; + + private final String scope; + private final String description; + private final boolean isDefault; + +} diff --git a/src/main/java/me/zhyd/oauth/request/AuthOktaRequest.java b/src/main/java/me/zhyd/oauth/request/AuthOktaRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..7c1e8101e0e0631bdf01785a862a8c6807472724 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthOktaRequest.java @@ -0,0 +1,159 @@ +package me.zhyd.oauth.request; + +import com.alibaba.fastjson.JSONObject; +import com.xkcoding.http.support.HttpHeader; +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.AuthOktaScope; +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.Base64Utils; +import me.zhyd.oauth.utils.HttpUtils; +import me.zhyd.oauth.utils.UrlBuilder; + +import java.util.HashMap; +import java.util.Map; + +/** + * Okta 登录 + *
+ * https://{domainPrefix}.okta.com/oauth2/default/.well-known/oauth-authorization-server
+ *
+ * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
+ * @since 1.16.0
+ */
+public class AuthOktaRequest extends AuthDefaultRequest {
+
+ public AuthOktaRequest(AuthConfig config) {
+ super(config, AuthDefaultSource.OKTA);
+ }
+
+ public AuthOktaRequest(AuthConfig config, AuthStateCache authStateCache) {
+ super(config, AuthDefaultSource.OKTA, authStateCache);
+ }
+
+ @Override
+ protected AuthToken getAccessToken(AuthCallback authCallback) {
+ String tokenUrl = accessTokenUrl(authCallback.getCode());
+ return getAuthToken(tokenUrl);
+ }
+
+ private AuthToken getAuthToken(String tokenUrl) {
+ HttpHeader header = new HttpHeader()
+ .add("accept", "application/json")
+ .add("content-type", "application/x-www-form-urlencoded")
+ .add("Authorization", "Basic " + Base64Utils.encode(config.getClientId().concat(":").concat(config.getClientSecret())));
+ String response = new HttpUtils(config.getHttpConfig()).post(tokenUrl, null, header, false);
+ JSONObject accessTokenObject = JSONObject.parseObject(response);
+ this.checkResponse(accessTokenObject);
+ return AuthToken.builder()
+ .accessToken(accessTokenObject.getString("access_token"))
+ .tokenType(accessTokenObject.getString("token_type"))
+ .expireIn(accessTokenObject.getIntValue("expires_in"))
+ .scope(accessTokenObject.getString("scope"))
+ .refreshToken(accessTokenObject.getString("refresh_token"))
+ .idToken(accessTokenObject.getString("id_token"))
+ .build();
+ }
+
+ @Override
+ public AuthResponse refresh(AuthToken authToken) {
+ if (null == authToken.getRefreshToken()) {
+ return AuthResponse.builder()
+ .code(AuthResponseStatus.ILLEGAL_TOKEN.getCode())
+ .msg(AuthResponseStatus.ILLEGAL_TOKEN.getMsg())
+ .build();
+ }
+ String refreshUrl = refreshTokenUrl(authToken.getRefreshToken());
+ return AuthResponse.builder()
+ .code(AuthResponseStatus.SUCCESS.getCode())
+ .data(this.getAuthToken(refreshUrl))
+ .build();
+ }
+
+ @Override
+ protected AuthUser getUserInfo(AuthToken authToken) {
+ HttpHeader header = new HttpHeader()
+ .add("Authorization", "Bearer " + authToken.getAccessToken());
+ String response = new HttpUtils(config.getHttpConfig()).post(userInfoUrl(authToken), null, header, false);
+ JSONObject object = JSONObject.parseObject(response);
+ this.checkResponse(object);
+ JSONObject address = object.getJSONObject("address");
+ return AuthUser.builder()
+ .rawUserInfo(object)
+ .uuid(object.getString("sub"))
+ .username(object.getString("name"))
+ .nickname(object.getString("nickname"))
+ .email(object.getString("email"))
+ .location(null == address ? null : address.getString("street_address"))
+ .gender(AuthUserGender.getRealGender(object.getString("sex")))
+ .token(authToken)
+ .source(source.toString())
+ .build();
+ }
+
+ @Override
+ public AuthResponse revoke(AuthToken authToken) {
+ Map