👽 OAuth supports multiple types of authorization; Improve the code

This commit is contained in:
yadong.zhang 2021-01-26 12:38:07 +08:00
parent ad45f7756d
commit 6077c2cf54
9 changed files with 530 additions and 111 deletions

View File

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

View 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");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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