mirror of
https://gitee.com/fujieid/jap.git
synced 2024-12-02 03:17:33 +08:00
👽 OAuth supports multiple types of authorization; Improve the code
This commit is contained in:
parent
ad45f7756d
commit
6077c2cf54
@ -94,12 +94,14 @@ public interface JapUserService {
|
||||
* <p>
|
||||
* 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<String, Object> userInfo) {
|
||||
throw new JapUserException("JapUserService#createAndGetOauth2User(JSONObject) must be overridden by subclass");
|
||||
default JapUser createAndGetOauth2User(String platform, Map<String, Object> userInfo, Object tokenInfo) {
|
||||
throw new JapUserException("JapUserService#createAndGetOauth2User(String, Map<String, Object>, Object) must be overridden by subclass");
|
||||
}
|
||||
|
||||
}
|
||||
|
70
jap-core/src/main/java/com/fujieid/jap/core/JapUtil.java
Normal file
70
jap-core/src/main/java/com/fujieid/jap/core/JapUtil.java
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com).
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,6 +101,20 @@ public class OAuthConfig extends AuthenticateConfig {
|
||||
*/
|
||||
private PkceCodeChallengeMethod codeChallengeMethod = PkceCodeChallengeMethod.S256;
|
||||
|
||||
/**
|
||||
* The username in `Resource Owner Password Credentials Grant`
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.3" target="_blank">https://tools.ietf.org/html/rfc6749#section-4.3</a>
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* The password in `Resource Owner Password Credentials Grant`
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.3" target="_blank">https://tools.ietf.org/html/rfc6749#section-4.3</a>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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<String, Object> userinfo = JsonUtil.toBean(userinfoResponse, Map.class);
|
||||
if (userinfo.containsKey("error") && ObjectUtil.isNotEmpty(userinfo.get("error"))) {
|
||||
throw new JapOauth2Exception("Oauth2Strategy failed to get userinfo with accessToken." +
|
||||
userinfo.get("error_description") + " " + userinfoResponse);
|
||||
}
|
||||
JapUser japUser = this.japUserService.createAndGetOauth2User(oAuthConfig.getPlatform(), userinfo);
|
||||
|
||||
Oauth2Util.checkOauthResponse(userinfoResponse, userinfo, "Oauth2Strategy failed to get userinfo with accessToken.");
|
||||
|
||||
JapUser japUser = this.japUserService.createAndGetOauth2User(oAuthConfig.getPlatform(), userinfo, accessToken);
|
||||
if (ObjectUtil.isNull(japUser)) {
|
||||
throw new JapUserException("Unable to save user information");
|
||||
}
|
||||
return japUser;
|
||||
}
|
||||
|
||||
protected String getAccessToken(HttpServletRequest request, OAuthConfig oAuthConfig) {
|
||||
String code = request.getParameter("code");
|
||||
Map<String, String> params = Maps.newHashMap();
|
||||
params.put("grant_type", oAuthConfig.getGrantType().name());
|
||||
params.put("code", code);
|
||||
params.put("client_id", oAuthConfig.getClientId());
|
||||
params.put("client_secret", oAuthConfig.getClientSecret());
|
||||
if (StrUtil.isNotBlank(oAuthConfig.getCallbackUrl())) {
|
||||
params.put("redirect_uri", oAuthConfig.getCallbackUrl());
|
||||
private void redirectToAuthorizationEndPoint(HttpServletResponse response, OAuthConfig oAuthConfig) {
|
||||
String url = null;
|
||||
// 4.1. Authorization Code Grant https://tools.ietf.org/html/rfc6749#section-4.1
|
||||
// 4.2. Implicit Grant https://tools.ietf.org/html/rfc6749#section-4.2
|
||||
if (oAuthConfig.getResponseType() == Oauth2ResponseType.code ||
|
||||
oAuthConfig.getResponseType() == Oauth2ResponseType.token) {
|
||||
url = generateAuthorizationCodeGrantUrl(oAuthConfig);
|
||||
}
|
||||
// pkce 仅适用于授权码模式
|
||||
if (Oauth2ResponseType.code == oAuthConfig.getResponseType() && oAuthConfig.isEnablePkce()) {
|
||||
params.put(PkceParams.CODE_VERIFIER, PkceUtil.getCacheCodeVerifier());
|
||||
}
|
||||
String tokenResponse = HttpUtil.post(oAuthConfig.getTokenUrl(), params, false);
|
||||
Map<String, Object> accessToken = JsonUtil.toBean(tokenResponse, Map.class);
|
||||
if (accessToken.containsKey("error") && ObjectUtil.isNotEmpty(accessToken.get("error"))) {
|
||||
throw new JapOauth2Exception("Oauth2Strategy failed to get AccessToken." +
|
||||
accessToken.get("error_description") + " " + tokenResponse);
|
||||
}
|
||||
if (!accessToken.containsKey("access_token")) {
|
||||
throw new JapOauth2Exception("Oauth2Strategy failed to get AccessToken." + tokenResponse);
|
||||
}
|
||||
/*
|
||||
{
|
||||
"access_token":"2YotnFZFEjr1zCsicMWpAA",
|
||||
"token_type":"example",
|
||||
"expires_in":3600,
|
||||
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
|
||||
"example_parameter":"example_value"
|
||||
}
|
||||
*/
|
||||
return (String) accessToken.get("access_token");
|
||||
JapUtil.redirect(url, "JAP failed to redirect to " + oAuthConfig.getAuthorizationUrl() + " through HttpServletResponse.", response);
|
||||
}
|
||||
|
||||
protected void redirectToAuthorizationEndPoint(HttpServletResponse response, OAuthConfig oAuthConfig) {
|
||||
/**
|
||||
* It is suitable for authorization code mode(rfc6749#4.1) and implicit authorization mode(rfc6749#4.2).
|
||||
* When it is in authorization code mode, the callback requests return code and state;
|
||||
* when it is in implicit authorization mode, the callback requests return token related data
|
||||
*
|
||||
* @param oAuthConfig oauth config
|
||||
* @return authorize request url
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.1" target="_blank">4.1. Authorization Code Grant</a>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.2" target="_blank">4.2. Implicit Grant</a>
|
||||
*/
|
||||
private String generateAuthorizationCodeGrantUrl(OAuthConfig oAuthConfig) {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("response_type", oAuthConfig.getResponseType());
|
||||
params.put("client_id", oAuthConfig.getClientId());
|
||||
@ -179,49 +165,92 @@ public class Oauth2Strategy extends AbstractJapStrategy {
|
||||
.orElse(PkceCodeChallengeMethod.S256), params);
|
||||
}
|
||||
String query = URLUtil.buildQuery(params, StandardCharsets.UTF_8);
|
||||
try {
|
||||
response.sendRedirect(oAuthConfig.getAuthorizationUrl().concat("?").concat(query));
|
||||
} catch (IOException ex) {
|
||||
throw new JapException("JAP failed to redirect to " + oAuthConfig.getAuthorizationUrl() + " through HttpServletResponse.", ex);
|
||||
}
|
||||
return oAuthConfig.getAuthorizationUrl().concat("?").concat(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the validity of oauthconfig.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -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<String, ?> responseMap, String errorMsg) {
|
||||
if (responseMap.containsKey("error") && ObjectUtil.isNotEmpty(responseMap.get("error"))) {
|
||||
throw new JapOauth2Exception(Optional.ofNullable(errorMsg).orElse("") +
|
||||
responseMap.get("error_description") + " " + responseStr);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkOauthCallbackRequest(HttpServletRequest request, String errorMsg) {
|
||||
String error = request.getParameter("error");
|
||||
if (ObjectUtil.isNotNull(error)) {
|
||||
String errorDescription = request.getParameter("error_description");
|
||||
throw new JapOauth2Exception(Optional.ofNullable(errorMsg).orElse("") + errorDescription);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com).
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com).
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 <a href="https://tools.ietf.org/html/rfc6749#section-4.1" target="_blank">4.1. Authorization Code Grant</a>
|
||||
*/
|
||||
private static AccessToken getAccessTokenOfAuthorizationCodeMode(HttpServletRequest request, OAuthConfig oAuthConfig) {
|
||||
String code = request.getParameter("code");
|
||||
Map<String, String> params = Maps.newHashMap();
|
||||
params.put("grant_type", Oauth2GrantType.authorization_code.name());
|
||||
params.put("code", code);
|
||||
params.put("client_id", oAuthConfig.getClientId());
|
||||
params.put("client_secret", oAuthConfig.getClientSecret());
|
||||
if (StrUtil.isNotBlank(oAuthConfig.getCallbackUrl())) {
|
||||
params.put("redirect_uri", oAuthConfig.getCallbackUrl());
|
||||
}
|
||||
// Pkce is only applicable to authorization code mode
|
||||
if (Oauth2ResponseType.code == oAuthConfig.getResponseType() && oAuthConfig.isEnablePkce()) {
|
||||
params.put(PkceParams.CODE_VERIFIER, PkceUtil.getCacheCodeVerifier());
|
||||
}
|
||||
|
||||
String tokenResponse = HttpUtil.post(oAuthConfig.getTokenUrl(), params, false);
|
||||
Map<String, Object> tokenMap = JsonUtil.toBean(tokenResponse, Map.class);
|
||||
Oauth2Util.checkOauthResponse(tokenResponse, tokenMap, "Oauth2Strategy failed to get AccessToken.");
|
||||
|
||||
if (!tokenMap.containsKey("access_token")) {
|
||||
throw new JapOauth2Exception("Oauth2Strategy failed to get AccessToken." + tokenResponse);
|
||||
}
|
||||
|
||||
return mapToAccessToken(tokenMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 4.2. Implicit Grant
|
||||
*
|
||||
* @param request current callback request
|
||||
* @return token request url
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.2" target="_blank">4.2. Implicit Grant</a>
|
||||
*/
|
||||
private static AccessToken getAccessTokenOfImplicitMode(HttpServletRequest request) {
|
||||
Oauth2Util.checkOauthCallbackRequest(request, "Oauth2Strategy failed to get AccessToken.");
|
||||
|
||||
if (null == request.getParameter("access_token")) {
|
||||
throw new JapOauth2Exception("Oauth2Strategy failed to get AccessToken.");
|
||||
}
|
||||
|
||||
return new AccessToken()
|
||||
.setAccessToken(request.getParameter("access_token"))
|
||||
.setRefreshToken(request.getParameter("refresh_token"))
|
||||
.setIdToken(request.getParameter("id_token"))
|
||||
.setTokenType(request.getParameter("token_type"))
|
||||
.setScope(request.getParameter("scope"))
|
||||
.setExpiresIn(JapUtil.convertToInt(request.getParameter("expires_in")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 4.3. Resource Owner Password Credentials Grant
|
||||
*
|
||||
* @param oAuthConfig oauth config
|
||||
* @return token request url
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.3" target="_blank">4.3. Resource Owner Password Credentials Grant</a>
|
||||
*/
|
||||
private static AccessToken getAccessTokenOfPasswordMode(HttpServletRequest request, OAuthConfig oAuthConfig) {
|
||||
Map<String, String> params = Maps.newHashMap();
|
||||
params.put("grant_type", Oauth2GrantType.password.name());
|
||||
params.put("username", oAuthConfig.getUsername());
|
||||
params.put("password", oAuthConfig.getPassword());
|
||||
if (ArrayUtil.isNotEmpty(oAuthConfig.getScopes())) {
|
||||
params.put("scope", String.join(Oauth2Const.SCOPE_SEPARATOR, oAuthConfig.getScopes()));
|
||||
}
|
||||
String query = URLUtil.buildQuery(params, StandardCharsets.UTF_8);
|
||||
String url = oAuthConfig.getTokenUrl().concat("?").concat(query);
|
||||
String tokenResponse = HttpUtil.post(url, params, false);
|
||||
Map<String, Object> tokenMap = JsonUtil.toBean(tokenResponse, Map.class);
|
||||
Oauth2Util.checkOauthResponse(tokenResponse, tokenMap, "Oauth2Strategy failed to get AccessToken.");
|
||||
|
||||
if (!tokenMap.containsKey("access_token")) {
|
||||
throw new JapOauth2Exception("Oauth2Strategy failed to get AccessToken." + tokenResponse);
|
||||
}
|
||||
return mapToAccessToken(tokenMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 4.4. Client Credentials Grant
|
||||
*
|
||||
* @param oAuthConfig oauth config
|
||||
* @return token request url
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.4" target="_blank">4.4. Client Credentials Grant</a>
|
||||
*/
|
||||
private static AccessToken getAccessTokenOfClientMode(HttpServletRequest request, OAuthConfig oAuthConfig) {
|
||||
Map<String, String> params = Maps.newHashMap();
|
||||
params.put("grant_type", Oauth2GrantType.client_credentials.name());
|
||||
if (ArrayUtil.isNotEmpty(oAuthConfig.getScopes())) {
|
||||
params.put("scope", String.join(Oauth2Const.SCOPE_SEPARATOR, oAuthConfig.getScopes()));
|
||||
}
|
||||
String query = URLUtil.buildQuery(params, StandardCharsets.UTF_8);
|
||||
String url = oAuthConfig.getTokenUrl().concat("?").concat(query);
|
||||
|
||||
String tokenResponse = HttpUtil.post(url, params, false);
|
||||
Map<String, Object> tokenMap = JsonUtil.toBean(tokenResponse, Map.class);
|
||||
Oauth2Util.checkOauthResponse(tokenResponse, tokenMap, "Oauth2Strategy failed to get AccessToken.");
|
||||
|
||||
if (ObjectUtil.isEmpty(request.getParameter("access_token"))) {
|
||||
throw new JapOauth2Exception("Oauth2Strategy failed to get AccessToken.");
|
||||
}
|
||||
|
||||
return mapToAccessToken(tokenMap);
|
||||
}
|
||||
|
||||
private static AccessToken mapToAccessToken(Map<String, Object> tokenMap) {
|
||||
Object accessToken = tokenMap.get("access_token");
|
||||
Object refreshToken = tokenMap.get("refresh_token");
|
||||
Object idToken = tokenMap.get("id_token");
|
||||
Object tokenType = tokenMap.get("token_type");
|
||||
Object expiresIn = tokenMap.get("expires_in");
|
||||
Object scope = tokenMap.get("scope");
|
||||
return new AccessToken()
|
||||
.setAccessToken(JapUtil.convertToStr(accessToken))
|
||||
.setRefreshToken(JapUtil.convertToStr(refreshToken))
|
||||
.setIdToken(JapUtil.convertToStr(idToken))
|
||||
.setTokenType(JapUtil.convertToStr(tokenType))
|
||||
.setScope(JapUtil.convertToStr(scope))
|
||||
.setExpiresIn(JapUtil.convertToInt(expiresIn));
|
||||
}
|
||||
}
|
@ -19,11 +19,7 @@ import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
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.exception.JapUserException;
|
||||
import com.fujieid.jap.core.store.JapUserStore;
|
||||
@ -38,7 +34,6 @@ import me.zhyd.oauth.request.AuthRequest;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -117,12 +112,10 @@ public class SocialStrategy extends AbstractJapStrategy {
|
||||
|
||||
// If it is not a callback request, it must be a request to jump to the authorization link
|
||||
if (!this.isCallback(source, authCallback)) {
|
||||
try {
|
||||
response.sendRedirect(authRequest.authorize(socialConfig.getState()));
|
||||
return;
|
||||
} catch (IOException ex) {
|
||||
throw new JapException("JAP failed to redirect to " + source + " authorized endpoint through HttpServletResponse.", ex);
|
||||
}
|
||||
String url = authRequest.authorize(socialConfig.getState());
|
||||
String redirectErrorMsg = "JAP failed to redirect to " + source + " authorized endpoint through HttpServletResponse.";
|
||||
JapUtil.redirect(url, redirectErrorMsg, response);
|
||||
return;
|
||||
}
|
||||
|
||||
this.login(request, response, source, authRequest, authCallback);
|
||||
|
Loading…
Reference in New Issue
Block a user