diff --git a/jap-core/src/main/java/com/fujieid/jap/core/JapUserService.java b/jap-core/src/main/java/com/fujieid/jap/core/JapUserService.java index e459d7c..4ae2c20 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/JapUserService.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/JapUserService.java @@ -94,12 +94,14 @@ public interface JapUserService { *
* It is suitable for the {@code jap-oauth2} module
*
- * @param platform oauth2 platform name
- * @param userInfo The basic user information returned by the OAuth platform
+ * @param platform oauth2 platform name
+ * @param userInfo The basic user information returned by the OAuth platform
+ * @param tokenInfo The token information returned by the OAuth platform, developers can store tokens
+ * , type {@code com.fujieid.jap.oauth2.helper.AccessToken}
* @return When saving successfully, return {@code JapUser}, otherwise return {@code null}
*/
- default JapUser createAndGetOauth2User(String platform, Map
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fujieid.jap.core;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fujieid.jap.core.exception.JapException;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * The tool class of Jap only provides static methods common to all modules
+ *
+ * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
+ * @version 1.0.0
+ * @date 2021-01-25 21:16
+ * @since 1.0.0
+ */
+public class JapUtil {
+
+ private static final String REDIRECT_ERROR = "JAP failed to redirect via HttpServletResponse.";
+
+ public static void redirect(String url, HttpServletResponse response) {
+ redirect(url, REDIRECT_ERROR, response);
+ }
+
+ public static void redirect(String url, String errorMessage, HttpServletResponse response) {
+ try {
+ response.sendRedirect(url);
+ } catch (IOException ex) {
+ throw new JapException(errorMessage, ex);
+ }
+ }
+
+ public static String convertToStr(Object o) {
+ if (ObjectUtil.isNull(o)) {
+ return null;
+ }
+ if (o instanceof String) {
+ return String.valueOf(o);
+ }
+ return o.toString();
+ }
+
+ public static Integer convertToInt(Object o) {
+ if (ObjectUtil.isNull(o)) {
+ return null;
+ }
+ if (o instanceof String) {
+ return Integer.parseInt(String.valueOf(o));
+ }
+ if (o instanceof Integer) {
+ return (Integer) o;
+ }
+ throw new ClassCastException(o + " cannot be converted to Integer type");
+ }
+}
diff --git a/jap-core/src/main/java/com/fujieid/jap/core/strategy/AbstractJapStrategy.java b/jap-core/src/main/java/com/fujieid/jap/core/strategy/AbstractJapStrategy.java
index 812b816..a9988ff 100644
--- a/jap-core/src/main/java/com/fujieid/jap/core/strategy/AbstractJapStrategy.java
+++ b/jap-core/src/main/java/com/fujieid/jap/core/strategy/AbstractJapStrategy.java
@@ -17,11 +17,7 @@ package com.fujieid.jap.core.strategy;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
-import com.fujieid.jap.core.AuthenticateConfig;
-import com.fujieid.jap.core.JapConfig;
-import com.fujieid.jap.core.JapUser;
-import com.fujieid.jap.core.JapUserService;
-import com.fujieid.jap.core.exception.JapException;
+import com.fujieid.jap.core.*;
import com.fujieid.jap.core.exception.JapSocialException;
import com.fujieid.jap.core.store.JapUserStore;
import com.fujieid.jap.core.store.JapUserStoreContextHolder;
@@ -31,7 +27,6 @@ import com.fujieid.jap.sso.JapSsoHelper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
/**
* General policy handling methods and parameters, policies of other platforms can inherit
@@ -97,23 +92,15 @@ public abstract class AbstractJapStrategy implements JapStrategy {
protected boolean checkSession(HttpServletRequest request, HttpServletResponse response) {
JapUser sessionUser = japUserStore.get(request, response);
if (null != sessionUser) {
- try {
- response.sendRedirect(japConfig.getSuccessRedirect());
- return true;
- } catch (IOException e) {
- throw new JapException("JAP failed to redirect via HttpServletResponse.", e);
- }
+ JapUtil.redirect(japConfig.getSuccessRedirect(), response);
+ return true;
}
return false;
}
protected void loginSuccess(JapUser japUser, HttpServletRequest request, HttpServletResponse response) {
japUserStore.save(request, response, japUser);
- try {
- response.sendRedirect(japConfig.getSuccessRedirect());
- } catch (IOException e) {
- throw new JapException("JAP failed to redirect via HttpServletResponse.", e);
- }
+ JapUtil.redirect(japConfig.getSuccessRedirect(), response);
}
/**
diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/OAuthConfig.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/OAuthConfig.java
index e1a604a..8f1f43c 100644
--- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/OAuthConfig.java
+++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/OAuthConfig.java
@@ -101,6 +101,20 @@ public class OAuthConfig extends AuthenticateConfig {
*/
private PkceCodeChallengeMethod codeChallengeMethod = PkceCodeChallengeMethod.S256;
+ /**
+ * The username in `Resource Owner Password Credentials Grant`
+ *
+ * @see https://tools.ietf.org/html/rfc6749#section-4.3
+ */
+ private String username;
+
+ /**
+ * The password in `Resource Owner Password Credentials Grant`
+ *
+ * @see https://tools.ietf.org/html/rfc6749#section-4.3
+ */
+ private String password;
+
public String getClientId() {
return clientId;
}
@@ -217,4 +231,22 @@ public class OAuthConfig extends AuthenticateConfig {
this.grantType = grantType;
return this;
}
+
+ public String getUsername() {
+ return username;
+ }
+
+ public OAuthConfig setUsername(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public OAuthConfig setPassword(String password) {
+ this.password = password;
+ return this;
+ }
}
diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Strategy.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Strategy.java
index cd9326d..74f84ef 100644
--- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Strategy.java
+++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Strategy.java
@@ -19,17 +19,14 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
-import com.fujieid.jap.core.AuthenticateConfig;
-import com.fujieid.jap.core.JapConfig;
-import com.fujieid.jap.core.JapUser;
-import com.fujieid.jap.core.JapUserService;
-import com.fujieid.jap.core.exception.JapException;
+import com.fujieid.jap.core.*;
import com.fujieid.jap.core.exception.JapOauth2Exception;
import com.fujieid.jap.core.exception.JapUserException;
import com.fujieid.jap.core.store.JapUserStore;
import com.fujieid.jap.core.strategy.AbstractJapStrategy;
+import com.fujieid.jap.oauth2.helper.AccessToken;
+import com.fujieid.jap.oauth2.helper.AccessTokenHelper;
import com.fujieid.jap.oauth2.pkce.PkceCodeChallengeMethod;
-import com.fujieid.jap.oauth2.pkce.PkceParams;
import com.fujieid.jap.oauth2.pkce.PkceUtil;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
@@ -38,7 +35,6 @@ import com.xkcoding.json.JsonUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
@@ -88,7 +84,7 @@ public class Oauth2Strategy extends AbstractJapStrategy {
@Override
public void authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) {
- checkErrorResopnse(request);
+ Oauth2Util.checkOauthCallbackRequest(request, "Oauth2strategy request failed.");
if (this.checkSession(request, response)) {
return;
@@ -99,11 +95,15 @@ public class Oauth2Strategy extends AbstractJapStrategy {
this.checkOauthConfig(oAuthConfig);
+ boolean isPasswordOrClientMode = oAuthConfig.getGrantType() == Oauth2GrantType.password
+ || oAuthConfig.getGrantType() == Oauth2GrantType.client_credentials;
+
// If it is not a callback request, it must be a request to jump to the authorization link
- if (!this.isCallback(request)) {
+ // If it is a password authorization request or a client authorization request, the token will be obtained directly
+ if (!this.isCallback(request, oAuthConfig) && !isPasswordOrClientMode) {
redirectToAuthorizationEndPoint(response, oAuthConfig);
} else {
- String accessToken = getAccessToken(request, oAuthConfig);
+ AccessToken accessToken = AccessTokenHelper.getToken(request, oAuthConfig);
JapUser japUser = getUserInfo(oAuthConfig, accessToken);
this.loginSuccess(japUser, request, response);
@@ -111,56 +111,42 @@ public class Oauth2Strategy extends AbstractJapStrategy {
}
- protected JapUser getUserInfo(OAuthConfig oAuthConfig, String accessToken) {
- String userinfoResponse = HttpUtil.post(oAuthConfig.getUserinfoUrl(), ImmutableMap.of("access_token", accessToken), false);
+ private JapUser getUserInfo(OAuthConfig oAuthConfig, AccessToken accessToken) {
+ String userinfoResponse = HttpUtil.post(oAuthConfig.getUserinfoUrl(),
+ ImmutableMap.of("access_token", accessToken.getAccessToken()), false);
Map
+ * 1. For {@code tokenUrl}, this configuration is indispensable for any mode
+ * 2. When responsetype = code:
+ * - {@code authorizationUrl} and {@code userinfoUrl} cannot be null
+ * - {@code clientId} cannot be null
+ * - {@code clientSecret} cannot be null when PKCE is not enabled
+ * 3. When responsetype = token:
+ * - {@code authorizationUrl} and {@code userinfoUrl} cannot be null
+ * - {@code clientId} cannot be null
+ * - {@code clientSecret} cannot be null
+ * 4. When GrantType = password:
+ * - {@code username} and {@code password} cannot be null
+ *
+ * @param oAuthConfig oauth config
+ */
private void checkOauthConfig(OAuthConfig oAuthConfig) {
- if (ObjectUtil.isNull(oAuthConfig.getClientId())) {
- throw new JapOauth2Exception("Oauth2Strategy requires a clientId option");
- }
-
- if (ObjectUtil.isNull(oAuthConfig.getAuthorizationUrl())) {
- throw new JapOauth2Exception("Oauth2Strategy requires a authorizationUrl option");
- }
-
if (ObjectUtil.isNull(oAuthConfig.getTokenUrl())) {
- throw new JapOauth2Exception("Oauth2Strategy requires a tokenUrl option");
+ throw new JapOauth2Exception("Oauth2Strategy requires a tokenUrl");
}
+ // For authorization code mode and implicit authorization mode
+ // refer to: https://tools.ietf.org/html/rfc6749#section-4.1
+ // refer to: https://tools.ietf.org/html/rfc6749#section-4.2
+ if (oAuthConfig.getResponseType() == Oauth2ResponseType.code ||
+ oAuthConfig.getResponseType() == Oauth2ResponseType.token) {
- if (!oAuthConfig.isEnablePkce() && ObjectUtil.isNull(oAuthConfig.getClientSecret())) {
- throw new JapOauth2Exception("Oauth2Strategy requires a clientSecret option when PKCE is not enabled.");
+ if (oAuthConfig.getResponseType() == Oauth2ResponseType.code) {
+ if (oAuthConfig.getGrantType() != Oauth2GrantType.authorization_code) {
+ throw new JapOauth2Exception("Invalid grantType `" + oAuthConfig.getGrantType() + "`. " +
+ "When using authorization code mode, grantType must be `authorization_code`");
+ }
+
+ if (!oAuthConfig.isEnablePkce() && ObjectUtil.isNull(oAuthConfig.getClientSecret())) {
+ throw new JapOauth2Exception("Oauth2Strategy requires a clientSecret when PKCE is not enabled.");
+ }
+ } else {
+ if (ObjectUtil.isNull(oAuthConfig.getClientSecret())) {
+ throw new JapOauth2Exception("Oauth2Strategy requires a clientSecret");
+ }
+
+ }
+ if (ObjectUtil.isNull(oAuthConfig.getClientId())) {
+ throw new JapOauth2Exception("Oauth2Strategy requires a clientId");
+ }
+
+ if (ObjectUtil.isNull(oAuthConfig.getAuthorizationUrl())) {
+ throw new JapOauth2Exception("Oauth2Strategy requires a authorizationUrl");
+ }
+
+ if (ObjectUtil.isNull(oAuthConfig.getUserinfoUrl())) {
+ throw new JapOauth2Exception("Oauth2Strategy requires a userinfoUrl");
+ }
}
- }
-
- private void checkErrorResopnse(HttpServletRequest request) {
- String error = request.getParameter("error");
- if (ObjectUtil.isNotNull(error)) {
- String errorDescription = request.getParameter("error_description");
- throw new JapOauth2Exception("Oauth2strategy request failed." + errorDescription);
+ // For password mode
+ // refer to: https://tools.ietf.org/html/rfc6749#section-4.3
+ else {
+ if (oAuthConfig.getGrantType() == Oauth2GrantType.password) {
+ if (!ObjectUtil.isAllNotEmpty(oAuthConfig.getUsername(), oAuthConfig.getPassword())) {
+ throw new JapOauth2Exception("Oauth2Strategy requires username and password in password certificate grant");
+ }
+ }
}
}
/**
* Whether it is the callback request after the authorization of the oauth platform is completed,
* the judgment basis is as follows:
- * - Code is not empty
+ * - When {@code response_type} is {@code code}, the {@code code} in the request parameter is empty
+ * - When {@code response_type} is {@code token}, the {@code access_token} in the request parameter is empty
*
- * @param request callback request
+ * @param request callback request
+ * @param oAuthConfig OAuthConfig
* @return When true is returned, the current request is a callback request
*/
- private boolean isCallback(HttpServletRequest request) {
- String code = request.getParameter("code");
- return !StrUtil.isEmpty(code);
+ private boolean isCallback(HttpServletRequest request, OAuthConfig oAuthConfig) {
+ if (oAuthConfig.getResponseType() == Oauth2ResponseType.code) {
+ String code = request.getParameter("code");
+ return !StrUtil.isEmpty(code);
+ } else if (oAuthConfig.getResponseType() == Oauth2ResponseType.token) {
+ String accessToken = request.getParameter("access_token");
+ return !StrUtil.isEmpty(accessToken);
+ }
+ return false;
}
}
diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Util.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Util.java
index b636217..3c3eb5e 100644
--- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Util.java
+++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Util.java
@@ -16,11 +16,17 @@
package com.fujieid.jap.oauth2;
import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
+import com.fujieid.jap.core.exception.JapOauth2Exception;
import com.fujieid.jap.oauth2.pkce.PkceCodeChallengeMethod;
import org.jose4j.base64url.Base64Url;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+import java.util.Optional;
+
/**
* OAuth Strategy Util
*
@@ -62,4 +68,20 @@ public class Oauth2Util {
return codeVerifier;
}
}
+
+ public static void checkOauthResponse(String responseStr, Map
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fujieid.jap.oauth2.helper;
+
+/**
+ * AccessToken entity class in OAuth 2.0 authorization process
+ *
+ * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
+ * @version 1.0.0
+ * @date 2021-01-25 22:06
+ * @since 1.0.0
+ */
+public class AccessToken {
+
+ private String accessToken;
+ private String tokenType;
+ private Integer expiresIn;
+ private String refreshToken;
+ private String idToken;
+ private String scope;
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public AccessToken setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ return this;
+ }
+
+ public String getTokenType() {
+ return tokenType;
+ }
+
+ public AccessToken setTokenType(String tokenType) {
+ this.tokenType = tokenType;
+ return this;
+ }
+
+ public Integer getExpiresIn() {
+ return expiresIn;
+ }
+
+ public AccessToken setExpiresIn(Integer expiresIn) {
+ this.expiresIn = expiresIn;
+ return this;
+ }
+
+ public String getRefreshToken() {
+ return refreshToken;
+ }
+
+ public AccessToken setRefreshToken(String refreshToken) {
+ this.refreshToken = refreshToken;
+ return this;
+ }
+
+ public String getIdToken() {
+ return idToken;
+ }
+
+ public AccessToken setIdToken(String idToken) {
+ this.idToken = idToken;
+ return this;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public AccessToken setScope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+}
diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/helper/AccessTokenHelper.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/helper/AccessTokenHelper.java
new file mode 100644
index 0000000..147baaa
--- /dev/null
+++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/helper/AccessTokenHelper.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com).
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.fujieid.jap.oauth2.helper;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.URLUtil;
+import com.fujieid.jap.core.JapUtil;
+import com.fujieid.jap.core.exception.JapOauth2Exception;
+import com.fujieid.jap.oauth2.*;
+import com.fujieid.jap.oauth2.pkce.PkceParams;
+import com.fujieid.jap.oauth2.pkce.PkceUtil;
+import com.google.common.collect.Maps;
+import com.xkcoding.http.HttpUtil;
+import com.xkcoding.json.JsonUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+/**
+ * Access token helper. Provides a unified access token method {@link AccessTokenHelper#getToken(HttpServletRequest, OAuthConfig)}
+ * for different authorization methods
+ *
+ * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
+ * @version 1.0.0
+ * @date 2021-01-26 10:44
+ * @since 1.0.0
+ */
+public class AccessTokenHelper {
+
+ /**
+ * get access_token
+ *
+ * @param request Current callback request
+ * @param oAuthConfig oauth config
+ * @return AccessToken
+ */
+ public static AccessToken getToken(HttpServletRequest request, OAuthConfig oAuthConfig) {
+ if (oAuthConfig.getResponseType() == Oauth2ResponseType.code) {
+ return getAccessTokenOfAuthorizationCodeMode(request, oAuthConfig);
+ }
+ if (oAuthConfig.getResponseType() == Oauth2ResponseType.token) {
+ return getAccessTokenOfImplicitMode(request);
+ }
+ if (oAuthConfig.getGrantType() == Oauth2GrantType.password) {
+ return getAccessTokenOfPasswordMode(request, oAuthConfig);
+ }
+ if (oAuthConfig.getGrantType() == Oauth2GrantType.client_credentials) {
+ return getAccessTokenOfClientMode(request, oAuthConfig);
+ }
+ throw new JapOauth2Exception("Oauth2Strategy failed to get AccessToken.");
+ }
+
+
+ /**
+ * 4.1. Authorization Code Grant
+ *
+ * @param request current callback request
+ * @param oAuthConfig oauth config
+ * @return token request url
+ * @see 4.1. Authorization Code Grant
+ */
+ private static AccessToken getAccessTokenOfAuthorizationCodeMode(HttpServletRequest request, OAuthConfig oAuthConfig) {
+ String code = request.getParameter("code");
+ Map