👷 Please refer to CHANGELOGS.md for update details

This commit is contained in:
yadong.zhang 2021-05-12 17:04:35 +08:00
parent 7c74750dcc
commit 4f2e59d03d
23 changed files with 448 additions and 244 deletions

View File

@ -15,10 +15,8 @@
*/
package com.fujieid.jap.ids.config;
import com.fujieid.jap.ids.model.IdsConsts;
import com.fujieid.jap.ids.model.enums.ClientSecretAuthMethod;
import com.fujieid.jap.ids.model.enums.TokenAuthMethod;
import com.fujieid.jap.ids.util.ObjectUtils;
import java.util.Collections;
import java.util.List;
@ -40,53 +38,47 @@ public class IdsConfig {
* Get the password from request through {@code request.getParameter(`passwordField`)}, which defaults to "password"
*/
private String passwordField = "password";
private boolean enableDynamicIssuer;
/**
* Identity provider
*/
private String issuer;
/**
* Login url, the default is {@code issuer + /oauth/login}
* Login url, the default is {@code /oauth/login}
*/
private String loginUrl;
/**
* Login page url, the default is {@link com.fujieid.jap.ids.config.IdsConfig#loginUrl}
*/
private String loginPageUrl;
/**
* error url
*/
private String errorUrl;
/**
* The user confirms the authorized url, the default is {@code issuer + /oauth/confirm}
*/
private String confirmPageUrl;
/**
* Authorized url, the default is {@code issuer + /oauth/authorize}
* Authorized url, the default is {@code /oauth/authorize}
*/
private String authorizeUrl;
/**
* Automatically authorized url (do not display the authorization page), Must support get request method,
* the default is {@code issuer + /oauth/authorize/auto}
* the default is {@code /oauth/authorize/auto}
*/
private String authorizeAutoApproveUrl;
/**
* token url, the default is {@code issuer + /oauth/token}
* token url, the default is {@code /oauth/token}
*/
private String tokenUrl;
/**
* userinfo url, the default is {@code issuer + /oauth/userinfo}
* userinfo url, the default is {@code /oauth/userinfo}
*/
private String userinfoUrl;
/**
* Register the the client detail, the default is {@code issuer + /oauth/registration}
* Register the the client detail, the default is {@code /oauth/registration}
*/
private String registrationUrl;
/**
* logout url, the default is {@code issuer + /oauth/logout}
* logout url, the default is {@code /oauth/logout}
*/
private String endSessionUrl;
/**
* check session url, the default is {@code issuer + /oauth/check_session}
* check session url, the default is {@code /oauth/check_session}
*/
private String checkSessionUrl;
/**
@ -94,13 +86,30 @@ public class IdsConfig {
*/
private String logoutRedirectUrl;
/**
* public key url, the default is {@code issuer + /.well-known/jwks.json}
* public key url, the default is {@code /.well-known/jwks.json}
*/
private String jwksUrl;
/**
* Get open id provider metadata, the default is {@code issuer + /.well-known/openid-configuration}
* Get open id provider metadata, the default is {@code /.well-known/openid-configuration}
*/
private String discoveryUrl;
/**
* Login page url, the default is {@link com.fujieid.jap.ids.config.IdsConfig#loginUrl}
*/
private String loginPageUrl;
/**
* When the login page is not provided by an authorized service (the login page is hosted by other services), this configuration needs to be turned on
*/
private boolean externalLoginPageUrl;
/**
* The user confirms the authorized url, the default is {@code issuer + /oauth/confirm}
*/
private String confirmPageUrl;
/**
* When the authorization confirmation page is not provided by an authorized service (the authorization confirmation page is hosted by other services),
* this configuration needs to be turned on
*/
private boolean externalConfirmPageUrl;
/**
* When requesting api, the way to pass token
*/
@ -141,6 +150,15 @@ public class IdsConfig {
return this;
}
public boolean isEnableDynamicIssuer() {
return enableDynamicIssuer;
}
public IdsConfig setEnableDynamicIssuer(boolean enableDynamicIssuer) {
this.enableDynamicIssuer = enableDynamicIssuer;
return this;
}
public String getIssuer() {
return issuer;
}
@ -151,7 +169,7 @@ public class IdsConfig {
}
public String getLoginUrl() {
return null == loginUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + "oauth/login" : loginUrl;
return null == loginUrl ? "/oauth/login" : loginUrl;
}
public IdsConfig setLoginUrl(String loginUrl) {
@ -159,6 +177,105 @@ public class IdsConfig {
return this;
}
public String getErrorUrl() {
return null == errorUrl ? "/oauth/error" : errorUrl;
}
public IdsConfig setErrorUrl(String errorUrl) {
this.errorUrl = errorUrl;
return this;
}
public String getAuthorizeUrl() {
return null == authorizeUrl ? "/oauth/authorize" : authorizeUrl;
}
public IdsConfig setAuthorizeUrl(String authorizeUrl) {
this.authorizeUrl = authorizeUrl;
return this;
}
public String getAuthorizeAutoApproveUrl() {
return null == authorizeAutoApproveUrl ? "/oauth/authorize/auto" : authorizeAutoApproveUrl;
}
public IdsConfig setAuthorizeAutoApproveUrl(String authorizeAutoApproveUrl) {
this.authorizeAutoApproveUrl = authorizeAutoApproveUrl;
return this;
}
public String getTokenUrl() {
return null == tokenUrl ? "/oauth/token" : tokenUrl;
}
public IdsConfig setTokenUrl(String tokenUrl) {
this.tokenUrl = tokenUrl;
return this;
}
public String getUserinfoUrl() {
return null == userinfoUrl ? "/oauth/userinfo" : userinfoUrl;
}
public IdsConfig setUserinfoUrl(String userinfoUrl) {
this.userinfoUrl = userinfoUrl;
return this;
}
public String getRegistrationUrl() {
return null == registrationUrl ? "/oauth/registration" : registrationUrl;
}
public IdsConfig setRegistrationUrl(String registrationUrl) {
this.registrationUrl = registrationUrl;
return this;
}
public String getEndSessionUrl() {
return null == endSessionUrl ? "/oauth/logout" : endSessionUrl;
}
public IdsConfig setEndSessionUrl(String endSessionUrl) {
this.endSessionUrl = endSessionUrl;
return this;
}
public String getCheckSessionUrl() {
return null == checkSessionUrl ? "/oauth/check_session" : checkSessionUrl;
}
public IdsConfig setCheckSessionUrl(String checkSessionUrl) {
this.checkSessionUrl = checkSessionUrl;
return this;
}
public String getLogoutRedirectUrl() {
return null == logoutRedirectUrl ? "/" : logoutRedirectUrl;
}
public IdsConfig setLogoutRedirectUrl(String logoutRedirectUrl) {
this.logoutRedirectUrl = logoutRedirectUrl;
return this;
}
public String getJwksUrl() {
return null == jwksUrl ? "/.well-known/jwks.json" : jwksUrl;
}
public IdsConfig setJwksUrl(String jwksUrl) {
this.jwksUrl = jwksUrl;
return this;
}
public String getDiscoveryUrl() {
return null == discoveryUrl ? "/.well-known/openid-configuration" : discoveryUrl;
}
public IdsConfig setDiscoveryUrl(String discoveryUrl) {
this.discoveryUrl = discoveryUrl;
return this;
}
public String getLoginPageUrl() {
return null == loginPageUrl ? this.getLoginUrl() : loginPageUrl;
}
@ -168,17 +285,17 @@ public class IdsConfig {
return this;
}
public String getErrorUrl() {
return errorUrl;
public boolean isExternalLoginPageUrl() {
return externalLoginPageUrl;
}
public IdsConfig setErrorUrl(String errorUrl) {
this.errorUrl = errorUrl;
public IdsConfig setExternalLoginPageUrl(boolean externalLoginPageUrl) {
this.externalLoginPageUrl = externalLoginPageUrl;
return this;
}
public String getConfirmPageUrl() {
return null == confirmPageUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + "oauth/confirm" : confirmPageUrl;
return null == confirmPageUrl ? "/oauth/confirm" : confirmPageUrl;
}
public IdsConfig setConfirmPageUrl(String confirmPageUrl) {
@ -186,93 +303,12 @@ public class IdsConfig {
return this;
}
public String getAuthorizeUrl() {
return null == authorizeUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + "oauth/authorize" : authorizeUrl;
public boolean isExternalConfirmPageUrl() {
return externalConfirmPageUrl;
}
public IdsConfig setAuthorizeUrl(String authorizeUrl) {
this.authorizeUrl = authorizeUrl;
return this;
}
public String getAuthorizeAutoApproveUrl() {
return null == authorizeAutoApproveUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + "oauth/authorize/auto" : authorizeAutoApproveUrl;
}
public IdsConfig setAuthorizeAutoApproveUrl(String authorizeAutoApproveUrl) {
this.authorizeAutoApproveUrl = authorizeAutoApproveUrl;
return this;
}
public String getTokenUrl() {
return null == tokenUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + "oauth/token" : tokenUrl;
}
public IdsConfig setTokenUrl(String tokenUrl) {
this.tokenUrl = tokenUrl;
return this;
}
public String getUserinfoUrl() {
return null == userinfoUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + "oauth/userinfo" : userinfoUrl;
}
public IdsConfig setUserinfoUrl(String userinfoUrl) {
this.userinfoUrl = userinfoUrl;
return this;
}
public String getRegistrationUrl() {
return null == registrationUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + "oauth/registration" : registrationUrl;
}
public IdsConfig setRegistrationUrl(String registrationUrl) {
this.registrationUrl = registrationUrl;
return this;
}
public String getEndSessionUrl() {
return null == endSessionUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + "oauth/logout" : endSessionUrl;
}
public IdsConfig setEndSessionUrl(String endSessionUrl) {
this.endSessionUrl = endSessionUrl;
return this;
}
public String getCheckSessionUrl() {
return null == checkSessionUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + "oauth/check_session" : checkSessionUrl;
}
public IdsConfig setCheckSessionUrl(String checkSessionUrl) {
this.checkSessionUrl = checkSessionUrl;
return this;
}
public String getLogoutRedirectUrl() {
return null == logoutRedirectUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) : logoutRedirectUrl;
}
public IdsConfig setLogoutRedirectUrl(String logoutRedirectUrl) {
this.logoutRedirectUrl = logoutRedirectUrl;
return this;
}
public String getJwksUrl() {
return null == jwksUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + ".well-known/jwks.json" : jwksUrl;
}
public IdsConfig setJwksUrl(String jwksUrl) {
this.jwksUrl = jwksUrl;
return this;
}
public String getDiscoveryUrl() {
return null == discoveryUrl ? ObjectUtils.appendIfNotEndWith(issuer, IdsConsts.SLASH) + ".well-known/openid-configuration" : discoveryUrl;
}
public IdsConfig setDiscoveryUrl(String discoveryUrl) {
this.discoveryUrl = discoveryUrl;
public IdsConfig setExternalConfirmPageUrl(boolean externalConfirmPageUrl) {
this.externalConfirmPageUrl = externalConfirmPageUrl;
return this;
}

View File

@ -22,6 +22,7 @@ import com.fujieid.jap.ids.model.IdsResponse;
import com.fujieid.jap.ids.model.IdsScope;
import com.fujieid.jap.ids.provider.IdsRequestParamProvider;
import com.fujieid.jap.ids.provider.IdsScopeProvider;
import com.fujieid.jap.ids.util.EndpointUtil;
import com.fujieid.jap.ids.util.OauthUtil;
import com.fujieid.jap.ids.util.ObjectUtils;
@ -102,7 +103,7 @@ public class ApprovalEndpoint extends AbstractEndpoint {
builder.append("\" to access your protected resources?</p>");
builder.append("<form id=\"confirmationForm\" name=\"confirmationForm\" action=\"");
String requestPath = ObjectUtils.appendIfNotEndWith(JapIds.getIdsConfig().getAuthorizeUrl(), "?") + request.getQueryString();
String requestPath = ObjectUtils.appendIfNotEndWith(EndpointUtil.getAuthorizeUrl(request), "?") + request.getQueryString();
builder.append(requestPath).append("\" method=\"post\">");
builder.append("<input name=\"user_oauth_approval\" value=\"true\" type=\"hidden\"/>");

View File

@ -26,6 +26,7 @@ import com.fujieid.jap.ids.model.enums.ErrorResponse;
import com.fujieid.jap.ids.model.enums.ResponseType;
import com.fujieid.jap.ids.provider.IdsAuthorizationProvider;
import com.fujieid.jap.ids.provider.IdsRequestParamProvider;
import com.fujieid.jap.ids.util.EndpointUtil;
import com.fujieid.jap.ids.util.OauthUtil;
import com.xkcoding.json.util.StringUtil;
@ -70,12 +71,12 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
if (JapIds.isAuthenticated(request)) {
UserInfo userInfo = JapIds.getUserInfo(request);
String url = generateResponseUrl(param, param.getResponseType(), clientDetail, userInfo);
String url = generateResponseUrl(param, param.getResponseType(), clientDetail, userInfo, EndpointUtil.getIssuer(request));
return new IdsResponse<String, String>().data(url);
}
return new IdsResponse<String, String>()
.data(OauthUtil.createAuthorizeUrl(JapIds.getIdsConfig().getLoginPageUrl(), param));
.data(OauthUtil.createAuthorizeUrl(EndpointUtil.getLoginPageUrl(request), param));
}
/**
@ -106,7 +107,7 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
String responseType = param.getResponseType();
UserInfo userInfo = JapIds.getUserInfo(request);
String url = generateResponseUrl(param, responseType, clientDetail, userInfo);
String url = generateResponseUrl(param, responseType, clientDetail, userInfo, EndpointUtil.getIssuer(request));
return new IdsResponse<String, String>().data(url);
}
@ -118,27 +119,27 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
* @param clientDetail Currently authorized client
* @return Callback url
*/
private String generateResponseUrl(IdsRequestParam param, String responseType, ClientDetail clientDetail, UserInfo userInfo) {
private String generateResponseUrl(IdsRequestParam param, String responseType, ClientDetail clientDetail, UserInfo userInfo, String issuer) {
if (ResponseType.CODE.getType().equalsIgnoreCase(responseType)) {
return idsAuthorizationProvider.generateAuthorizationCodeResponse(userInfo, param, clientDetail);
}
if (ResponseType.TOKEN.getType().equalsIgnoreCase(responseType)) {
return idsAuthorizationProvider.generateImplicitGrantResponse(userInfo, param, clientDetail);
return idsAuthorizationProvider.generateImplicitGrantResponse(userInfo, param, clientDetail, issuer);
}
if (ResponseType.ID_TOKEN.getType().equalsIgnoreCase(responseType)) {
return idsAuthorizationProvider.generateIdTokenAuthorizationResponse(userInfo, param, clientDetail);
return idsAuthorizationProvider.generateIdTokenAuthorizationResponse(userInfo, param, clientDetail, issuer);
}
if (ResponseType.ID_TOKEN_TOKEN.getType().equalsIgnoreCase(responseType)) {
return idsAuthorizationProvider.generateIdTokenTokenAuthorizationResponse(userInfo, param, clientDetail);
return idsAuthorizationProvider.generateIdTokenTokenAuthorizationResponse(userInfo, param, clientDetail, issuer);
}
if (ResponseType.CODE_ID_TOKEN.getType().equalsIgnoreCase(responseType)) {
return idsAuthorizationProvider.generateCodeIdTokenAuthorizationResponse(userInfo, param, clientDetail);
return idsAuthorizationProvider.generateCodeIdTokenAuthorizationResponse(userInfo, param, clientDetail, issuer);
}
if (ResponseType.CODE_TOKEN.getType().equalsIgnoreCase(responseType)) {
return idsAuthorizationProvider.generateCodeTokenAuthorizationResponse(userInfo, param, clientDetail);
return idsAuthorizationProvider.generateCodeTokenAuthorizationResponse(userInfo, param, clientDetail, issuer);
}
if (ResponseType.CODE_ID_TOKEN_TOKEN.getType().equalsIgnoreCase(responseType)) {
return idsAuthorizationProvider.generateCodeIdTokenTokenAuthorizationResponse(userInfo, param, clientDetail);
return idsAuthorizationProvider.generateCodeIdTokenTokenAuthorizationResponse(userInfo, param, clientDetail, issuer);
}
// none
return idsAuthorizationProvider.generateNoneAuthorizationResponse(param);

View File

@ -18,6 +18,8 @@ package com.fujieid.jap.ids.endpoint;
import com.fujieid.jap.ids.model.OidcDiscoveryDto;
import com.fujieid.jap.ids.oidc.OidcUtil;
import javax.servlet.http.HttpServletRequest;
/**
* OpenID Provider Endpoint
*
@ -37,16 +39,15 @@ public class DiscoveryEndpoint extends AbstractEndpoint {
* <p>
* The issuer of idp is `http://localhost`, and the api in the idp is distinguished according to the user ID.
* <p>
* When {@code identity} is not empty, The endpoint generated by this method is actually `http://localhost/oauth/token/{identity}`
*
* @param identity identity
* @param request current HTTP request
* @return OpenID Provider Configuration
* @see <a href="https://tools.ietf.org/html/draft-ietf-oauth-discovery-06">https://tools.ietf.org/html/draft-ietf-oauth-discovery-06</a>
* @see <a href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata" target="_blank">OpenID Provider Metadata</a>
* @see <a href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest">OpenID Provider Configuration Response</a>
*/
public OidcDiscoveryDto getOidcDiscoveryInfo(String identity) {
return OidcUtil.getOidcDiscoveryInfo(identity);
public OidcDiscoveryDto getOidcDiscoveryInfo(HttpServletRequest request) {
return OidcUtil.getOidcDiscoveryInfo(request);
}
/**

View File

@ -18,7 +18,6 @@ package com.fujieid.jap.ids.endpoint;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fujieid.jap.ids.JapIds;
import com.fujieid.jap.ids.config.IdsConfig;
import com.fujieid.jap.ids.exception.IdsException;
import com.fujieid.jap.ids.model.ClientDetail;
import com.fujieid.jap.ids.model.IdsRequestParam;
@ -27,6 +26,7 @@ import com.fujieid.jap.ids.model.UserInfo;
import com.fujieid.jap.ids.model.enums.ErrorResponse;
import com.fujieid.jap.ids.pipeline.IdsPipeline;
import com.fujieid.jap.ids.provider.IdsRequestParamProvider;
import com.fujieid.jap.ids.util.EndpointUtil;
import com.fujieid.jap.ids.util.OauthUtil;
import com.fujieid.jap.ids.util.ObjectUtils;
@ -76,7 +76,7 @@ public class LoginEndpoint extends AbstractEndpoint {
+ " <body>\n"
+ " <div class=\"container\">\n");
String authenticationUrl = ObjectUtils.appendIfNotEndWith(JapIds.getIdsConfig().getLoginUrl(), "?") + request.getQueryString();
String authenticationUrl = ObjectUtils.appendIfNotEndWith(EndpointUtil.getLoginUrl(request), "?") + request.getQueryString();
sb.append(" <form class=\"form-signin\" method=\"post\" action=\"").append(authenticationUrl).append("\">\n")
.append(" <h2 class=\"form-signin-heading\">Please sign in</h2>\n")
.append(" <p>\n")
@ -98,7 +98,7 @@ public class LoginEndpoint extends AbstractEndpoint {
/**
* Login with account password
*
* @param servletRequest current HTTP request
* @param servletRequest current HTTP request
* @param servletResponse current HTTP response
* @return Confirm authorization page
*/
@ -108,15 +108,15 @@ public class LoginEndpoint extends AbstractEndpoint {
if (!idsSigninPipeline.preHandle(request, servletResponse)) {
throw new IdsException("IdsSigninPipeline<UserInfo>.preHandle returns false, the process is blocked.");
}
IdsRequestParam param = IdsRequestParamProvider.parseRequest(request);
UserInfo userInfo = idsSigninPipeline.postHandle(request, servletResponse);
if (null == userInfo) {
IdsConfig idsConfig = JapIds.getIdsConfig();
String username = request.getParameter(idsConfig.getUsernameField());
String password = request.getParameter(idsConfig.getPasswordField());
String username = param.getUsername();
String password = param.getPassword();
if (ObjectUtil.hasEmpty(username, password)) {
throw new IdsException(ErrorResponse.INVALID_USER_CERTIFICATE);
}
userInfo = JapIds.getContext().getUserService().loginByUsernameAndPassword(username, password);
userInfo = JapIds.getContext().getUserService().loginByUsernameAndPassword(username, password, param.getClientId());
if (null == userInfo) {
throw new IdsException(ErrorResponse.INVALID_USER_CERTIFICATE);
}
@ -124,7 +124,6 @@ public class LoginEndpoint extends AbstractEndpoint {
JapIds.saveUserInfo(userInfo, request);
IdsRequestParam param = IdsRequestParamProvider.parseRequest(request);
ClientDetail clientDetail = JapIds.getContext().getClientDetailService().getByClientId(param.getClientId());
OauthUtil.validClientDetail(clientDetail);
@ -132,9 +131,9 @@ public class LoginEndpoint extends AbstractEndpoint {
// When the client supports automatic authorization, it will judge whether the {@code autoapprove} function is enabled
if (null != clientDetail.getAutoApprove() && clientDetail.getAutoApprove() &&
StrUtil.isNotEmpty(param.getAutoapprove()) && "TRUE".equalsIgnoreCase(param.getAutoapprove())) {
redirectUri = JapIds.getIdsConfig().getAuthorizeAutoApproveUrl();
redirectUri = EndpointUtil.getAuthorizeAutoApproveUrl(request);
} else {
redirectUri = JapIds.getIdsConfig().getConfirmPageUrl();
redirectUri = EndpointUtil.getConfirmPageUrl(request);
}
String fullUrl = OauthUtil.createAuthorizeUrl(redirectUri, param);
return new IdsResponse<String, String>()

View File

@ -20,6 +20,7 @@ import com.fujieid.jap.ids.exception.IdsException;
import com.fujieid.jap.ids.model.IdsResponse;
import com.fujieid.jap.ids.model.UserInfo;
import com.fujieid.jap.ids.pipeline.IdsPipeline;
import com.fujieid.jap.ids.util.EndpointUtil;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
@ -43,6 +44,6 @@ public class LogoutEndpoint extends AbstractEndpoint {
logoutPipeline.afterHandle(request, response);
return new IdsResponse<String, String>()
.data(JapIds.getIdsConfig().getLogoutRedirectUrl());
.data(EndpointUtil.getLogoutRedirectUrl(request));
}
}

View File

@ -51,16 +51,16 @@ public class TokenEndpoint extends AbstractEndpoint {
throw new UnsupportedGrantTypeException(ErrorResponse.UNSUPPORTED_GRANT_TYPE);
}
if (GrantType.AUTHORIZATION_CODE.getType().equals(param.getGrantType())) {
return idsTokenProvider.generateAuthorizationCodeResponse(param);
return idsTokenProvider.generateAuthorizationCodeResponse(param, request);
}
if (GrantType.PASSWORD.getType().equals(param.getGrantType())) {
return idsTokenProvider.generatePasswordResponse(param, request);
}
if (GrantType.CLIENT_CREDENTIALS.getType().equals(param.getGrantType())) {
return idsTokenProvider.generateClientCredentialsResponse(param);
return idsTokenProvider.generateClientCredentialsResponse(param, request);
}
if (GrantType.REFRESH_TOKEN.getType().equals(param.getGrantType())) {
return idsTokenProvider.generateRefreshTokenResponse(param);
return idsTokenProvider.generateRefreshTokenResponse(param, request);
}
throw new UnsupportedGrantTypeException(ErrorResponse.UNSUPPORTED_GRANT_TYPE);
}

View File

@ -78,7 +78,6 @@ public class AbstractIdsFilter {
} else {
// Fault-tolerant processing
IdsConfig config = JapIds.getIdsConfig();
String issuer = config.getIssuer();
String authorizeUrl = config.getAuthorizeUrl();
String authorizeAutoApproveUrl = config.getAuthorizeAutoApproveUrl();
String loginUrl = config.getLoginUrl();
@ -95,9 +94,11 @@ public class AbstractIdsFilter {
String[] urls = {authorizeUrl, authorizeAutoApproveUrl, loginUrl, loginPageUrl, errorUrl, confirmPageUrl,
tokenUrl, registrationUrl, jwksUrl, discoveryUrl, logoutUrl, logoutRedirectUrl, checkSessionUrl};
for (String url : urls) {
if (StringUtil.isNotEmpty(url) && url.startsWith(issuer)) {
this.ignoreUrls.add(url.substring(issuer.length()));
if (StringUtil.isEmpty(url)) {
continue;
}
this.ignoreUrls.add(url);
}
}
this.ignoreUrls.add("/favicon.ico");

View File

@ -26,12 +26,13 @@ import com.fujieid.jap.ids.model.enums.ClientSecretAuthMethod;
import com.fujieid.jap.ids.model.enums.GrantType;
import com.fujieid.jap.ids.model.enums.ResponseType;
import com.fujieid.jap.ids.provider.IdsScopeProvider;
import com.fujieid.jap.ids.util.EndpointUtil;
import com.fujieid.jap.ids.util.JwtUtil;
import com.fujieid.jap.ids.util.ObjectUtils;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jwt.ReservedClaimNames;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;
@ -42,21 +43,22 @@ import java.util.stream.Collectors;
*/
public class OidcUtil {
public static OidcDiscoveryDto getOidcDiscoveryInfo(String identity) {
public static OidcDiscoveryDto getOidcDiscoveryInfo(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
List<String> scopes = IdsScopeProvider.getScopeCodes();
String issuer = EndpointUtil.getIssuer(request);
Map<String, Object> model = new HashMap<>();
String issuer = config.getIssuer();
model.put("issuer", issuer);
model.put("authorization_endpoint", ObjectUtils.appendIfNotEndWith(config.getAuthorizeUrl(), identity));
model.put("token_endpoint", ObjectUtils.appendIfNotEndWith(config.getTokenUrl(), identity));
model.put("userinfo_endpoint", ObjectUtils.appendIfNotEndWith(config.getUserinfoUrl(), identity));
model.put("registration_endpoint", ObjectUtils.appendIfNotEndWith(config.getRegistrationUrl(), identity));
model.put("end_session_endpoint", ObjectUtils.appendIfNotEndWith(config.getEndSessionUrl(), identity));
model.put("check_session_iframe", ObjectUtils.appendIfNotEndWith(config.getCheckSessionUrl(), identity));
model.put("jwks_uri", ObjectUtils.appendIfNotEndWith(config.getJwksUrl(), identity));
model.put("authorization_endpoint", EndpointUtil.getAuthorizeUrl(request));
model.put("token_endpoint", EndpointUtil.getTokenUrl(request));
model.put("userinfo_endpoint", EndpointUtil.getUserinfoUrl(request));
model.put("registration_endpoint", EndpointUtil.getRegistrationUrl(request));
model.put("end_session_endpoint", EndpointUtil.getEndSessionUrl(request));
model.put("check_session_iframe", EndpointUtil.getCheckSessionUrl(request));
model.put("jwks_uri", EndpointUtil.getJwksUrl(request));
model.put("grant_types_supported", GrantType.grantTypes());
model.put("response_modes_supported", Arrays.asList(
"fragment",

View File

@ -49,8 +49,8 @@ public class IdsAuthorizationProvider {
* @return String
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.2">4.2. Implicit Grant</a>
*/
public String generateImplicitGrantResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail) {
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), param.getScope(), param.getNonce());
public String generateImplicitGrantResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail, String issuer) {
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), param.getScope(), param.getNonce(), issuer);
Map<String, String> tokenResponse = new HashMap<>(9);
// https://tools.ietf.org/html/rfc6749#section-4.2.2
// The authorization server MUST NOT issue a refresh token.
@ -59,7 +59,7 @@ public class IdsAuthorizationProvider {
tokenResponse.put(IdsConsts.TOKEN_TYPE, IdsConsts.TOKEN_TYPE_BEARER);
tokenResponse.put(IdsConsts.SCOPE, param.getScope());
if (OauthUtil.isOidcProtocol(param.getScope())) {
tokenResponse.put(IdsConsts.ID_TOKEN, TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce()));
tokenResponse.put(IdsConsts.ID_TOKEN, TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce(), issuer));
}
if (StringUtil.isNotEmpty(param.getState())) {
tokenResponse.put(IdsConsts.STATE, param.getState());
@ -92,11 +92,12 @@ public class IdsAuthorizationProvider {
* @param userInfo Logged-in user information
* @param param Request parameter
* @param clientDetail Application information
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @return String
* @see <a href="https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations">Definitions of Multiple-Valued Response Type Combinations</a>
*/
public String generateCodeIdTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail) {
String params = "&id_token=" + TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce());
public String generateCodeIdTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail, String issuer) {
String params = "&id_token=" + TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce(), issuer);
return this.generateAuthorizationCodeResponse(userInfo, param, clientDetail) + params;
}
@ -107,10 +108,11 @@ public class IdsAuthorizationProvider {
* @param userInfo Logged-in user information
* @param param Request parameter
* @param clientDetail Application information
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @return String
*/
public String generateIdTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail) {
String params = "?id_token=" + TokenUtil.createIdToken(clientDetail, userInfo, param);
public String generateIdTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail, String issuer) {
String params = "?id_token=" + TokenUtil.createIdToken(clientDetail, userInfo, param, issuer);
return param.getRedirectUri() + params;
}
@ -121,12 +123,13 @@ public class IdsAuthorizationProvider {
* @param userInfo Logged-in user information
* @param param Request parameter
* @param clientDetail Application information
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @return String
* @see <a href="https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations">Definitions of Multiple-Valued Response Type Combinations</a>
*/
public String generateIdTokenTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail) {
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), param.getScope(), param.getNonce());
String params = "?access_token=" + accessToken.getAccessToken() + "&id_token=" + TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce());
public String generateIdTokenTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail, String issuer) {
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), param.getScope(), param.getNonce(), issuer);
String params = "?access_token=" + accessToken.getAccessToken() + "&id_token=" + TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce(), issuer);
return param.getRedirectUri() + params;
}
@ -136,11 +139,12 @@ public class IdsAuthorizationProvider {
* @param userInfo Logged-in user information
* @param param Request parameter
* @param clientDetail Application information
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @return String
* @see <a href="https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations">Definitions of Multiple-Valued Response Type Combinations</a>
*/
public String generateCodeTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail) {
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), param.getScope(), param.getNonce());
public String generateCodeTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail, String issuer) {
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), param.getScope(), param.getNonce(), issuer);
String params = "&access_token=" + accessToken.getAccessToken();
return this.generateAuthorizationCodeResponse(userInfo, param, clientDetail) + params;
}
@ -151,12 +155,13 @@ public class IdsAuthorizationProvider {
* @param userInfo Logged-in user information
* @param param Request parameter
* @param clientDetail Application information
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @return String
* @see <a href="https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations">Definitions of Multiple-Valued Response Type Combinations</a>
*/
public String generateCodeIdTokenTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail) {
String params = "&id_token=" + TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce());
return this.generateCodeTokenAuthorizationResponse(userInfo, param, clientDetail) + params;
public String generateCodeIdTokenTokenAuthorizationResponse(UserInfo userInfo, IdsRequestParam param, ClientDetail clientDetail, String issuer) {
String params = "&id_token=" + TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce(), issuer);
return this.generateCodeTokenAuthorizationResponse(userInfo, param, clientDetail, issuer) + params;
}
/**

View File

@ -16,6 +16,8 @@
package com.fujieid.jap.ids.provider;
import cn.hutool.core.util.ObjectUtil;
import com.fujieid.jap.ids.JapIds;
import com.fujieid.jap.ids.config.IdsConfig;
import com.fujieid.jap.ids.exception.InvalidRequestException;
import com.fujieid.jap.ids.model.ClientCertificate;
import com.fujieid.jap.ids.model.IdsConsts;
@ -64,8 +66,9 @@ public class IdsRequestParamProvider {
param.setAutoapprove(request.getParameter(IdsConsts.AUTOAPPROVE));
// Get username and password Applies to:<a href="https://tools.ietf.org/html/rfc6749#section-4.3" target="_blank">Resource Owner Password Credentials Grant</a>
param.setUsername(request.getParameter(IdsConsts.USERNAME));
param.setPassword(request.getParameter(IdsConsts.PASSWORD));
IdsConfig idsConfig = JapIds.getIdsConfig();
param.setUsername(request.getParameter(idsConfig.getUsernameField()));
param.setPassword(request.getParameter(idsConfig.getPasswordField()));
/*
* Applicable to open pkce enhanced protocol in authorization code mode

View File

@ -17,6 +17,7 @@ package com.fujieid.jap.ids.provider;
import cn.hutool.core.util.ObjectUtil;
import com.fujieid.jap.ids.model.IdsScope;
import com.xkcoding.json.util.StringUtil;
import java.util.*;
import java.util.stream.Collectors;
@ -39,6 +40,8 @@ public class IdsScopeProvider {
private static final List<IdsScope> SCOPES = new ArrayList<>();
static {
addScope(new IdsScope().setCode("read").setDescription("Allow users to read protected resources."));
addScope(new IdsScope().setCode("write").setDescription("Allow users to operate (add, delete, modify) protected resources."));
addScope(new IdsScope().setCode("openid").setDescription("OpenID connect must include scope."));
addScope(new IdsScope().setCode("profile").setDescription("Allow access to user's basic information."));
addScope(new IdsScope().setCode("email").setDescription("Allow access to user's mailbox."));
@ -48,11 +51,24 @@ public class IdsScopeProvider {
/**
* Add a single scope.
* Note: This method is to add data to the existing scope collection
* Note: This method is to add data to the existing scope collection.
* <p>
* If the scope to be added already exists, the new scope will overwrite the original scope
*
* @param idsScope single scope
*/
public static void addScope(IdsScope idsScope) {
if (null == idsScope || StringUtil.isEmpty(idsScope.getCode())) {
return;
}
long num = SCOPES.stream().filter(scope -> scope.getCode().equals(idsScope.getCode())).count();
if (num > 0) {
SCOPES.stream()
.filter(scope -> scope.getCode().equals(idsScope.getCode()))
.findFirst()
.map(scope -> scope.setDescription(idsScope.getDescription()));
return;
}
SCOPES.add(idsScope);
}

View File

@ -15,15 +15,13 @@
*/
package com.fujieid.jap.ids.provider;
import cn.hutool.core.util.ObjectUtil;
import com.fujieid.jap.ids.JapIds;
import com.fujieid.jap.ids.exception.IdsException;
import com.fujieid.jap.ids.exception.InvalidClientException;
import com.fujieid.jap.ids.exception.InvalidRequestException;
import com.fujieid.jap.ids.model.*;
import com.fujieid.jap.ids.model.enums.ErrorResponse;
import com.fujieid.jap.ids.model.enums.GrantType;
import com.fujieid.jap.ids.service.Oauth2Service;
import com.fujieid.jap.ids.util.EndpointUtil;
import com.fujieid.jap.ids.util.OauthUtil;
import com.fujieid.jap.ids.util.TokenUtil;
import com.xkcoding.json.util.StringUtil;
@ -48,11 +46,12 @@ public class IdsTokenProvider {
/**
* RFC6749 4.1. authorization code grant
*
* @param param request params
* @param param request params
* @param request current HTTP request
* @return IdsResponse
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.1" target="_blank">4.1. Authorization Code Grant</a>
*/
public IdsResponse<String, Object> generateAuthorizationCodeResponse(IdsRequestParam param) {
public IdsResponse<String, Object> generateAuthorizationCodeResponse(IdsRequestParam param, HttpServletRequest request) {
AuthCode codeInfo = oauth2Service.validateAndGetAuthrizationCode(param.getGrantType(), param.getCode());
String scope = codeInfo.getScope();
@ -70,7 +69,7 @@ public class IdsTokenProvider {
long expiresIn = OauthUtil.getAccessTokenExpiresIn(clientDetail.getAccessTokenExpiresIn());
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), scope, nonce);
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), scope, nonce, EndpointUtil.getIssuer(request));
IdsResponse<String, Object> response = new IdsResponse<String, Object>()
.add(IdsConsts.ACCESS_TOKEN, accessToken.getAccessToken())
.add(IdsConsts.REFRESH_TOKEN, accessToken.getRefreshToken())
@ -78,7 +77,7 @@ public class IdsTokenProvider {
.add(IdsConsts.TOKEN_TYPE, IdsConsts.TOKEN_TYPE_BEARER)
.add(IdsConsts.SCOPE, scope);
if (OauthUtil.isOidcProtocol(scope)) {
response.add(IdsConsts.ID_TOKEN, TokenUtil.createIdToken(clientDetail, userInfo, nonce));
response.add(IdsConsts.ID_TOKEN, TokenUtil.createIdToken(clientDetail, userInfo, nonce, EndpointUtil.getIssuer(request)));
}
return response;
}
@ -94,7 +93,8 @@ public class IdsTokenProvider {
public IdsResponse<String, Object> generatePasswordResponse(IdsRequestParam param, HttpServletRequest request) {
String username = param.getUsername();
String password = param.getPassword();
UserInfo userInfo = JapIds.getContext().getUserService().loginByUsernameAndPassword(username, password);
String clientId = param.getClientId();
UserInfo userInfo = JapIds.getContext().getUserService().loginByUsernameAndPassword(username, password, clientId);
if (null == userInfo) {
throw new IdsException(ErrorResponse.INVALID_USER_CERTIFICATE);
}
@ -110,7 +110,7 @@ public class IdsTokenProvider {
long expiresIn = OauthUtil.getAccessTokenExpiresIn(clientDetail.getAccessTokenExpiresIn());
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), requestScope, param.getNonce());
AccessToken accessToken = TokenUtil.createAccessToken(userInfo, clientDetail, param.getGrantType(), requestScope, param.getNonce(), EndpointUtil.getIssuer(request));
IdsResponse<String, Object> response = new IdsResponse<String, Object>()
.add(IdsConsts.ACCESS_TOKEN, accessToken.getAccessToken())
.add(IdsConsts.REFRESH_TOKEN, accessToken.getRefreshToken())
@ -119,7 +119,7 @@ public class IdsTokenProvider {
.add(IdsConsts.SCOPE, requestScope);
if (OauthUtil.isOidcProtocol(requestScope)) {
response.add(IdsConsts.ID_TOKEN, TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce()));
response.add(IdsConsts.ID_TOKEN, TokenUtil.createIdToken(clientDetail, userInfo, param.getNonce(), EndpointUtil.getIssuer(request)));
}
return response;
}
@ -127,11 +127,12 @@ public class IdsTokenProvider {
/**
* RFC6749 4.4. Client Credentials Grant
*
* @param param request params
* @param param request params
* @param request current HTTP request
* @return IdsResponse
* @see <a href="https://tools.ietf.org/html/rfc6749#section-4.4" target="_blank">4.4. Client Credentials Grant</a>
*/
public IdsResponse<String, Object> generateClientCredentialsResponse(IdsRequestParam param) {
public IdsResponse<String, Object> generateClientCredentialsResponse(IdsRequestParam param, HttpServletRequest request) {
String clientId = param.getClientId();
ClientDetail clientDetail = JapIds.getContext().getClientDetailService().getByClientId(clientId);
@ -144,7 +145,7 @@ public class IdsTokenProvider {
long expiresIn = OauthUtil.getAccessTokenExpiresIn(clientDetail.getAccessTokenExpiresIn());
AccessToken accessToken = TokenUtil.createClientCredentialsAccessToken(clientDetail, param.getGrantType(), requestScope, param.getNonce());
AccessToken accessToken = TokenUtil.createClientCredentialsAccessToken(clientDetail, param.getGrantType(), requestScope, param.getNonce(), EndpointUtil.getIssuer(request));
// https://tools.ietf.org/html/rfc6749#section-4.2.2
// The authorization server MUST NOT issue a refresh token.
@ -161,11 +162,12 @@ public class IdsTokenProvider {
/**
* RFC6749 6. Refreshing an Access Token
*
* @param param request params
* @param param request params
* @param request current HTTP request
* @return IdsResponse
* @see <a href="https://tools.ietf.org/html/rfc6749#section-6" target="_blank">6. Refreshing an Access Token</a>
*/
public IdsResponse<String, Object> generateRefreshTokenResponse(IdsRequestParam param) {
public IdsResponse<String, Object> generateRefreshTokenResponse(IdsRequestParam param, HttpServletRequest request) {
TokenUtil.validateRefreshToken(param.getRefreshToken());
try {
@ -183,7 +185,7 @@ public class IdsTokenProvider {
long expiresIn = OauthUtil.getRefreshTokenExpiresIn(clientDetail.getRefreshTokenExpiresIn());
AccessToken accessToken = TokenUtil.refreshAccessToken(user, clientDetail, token, param.getNonce());
AccessToken accessToken = TokenUtil.refreshAccessToken(user, clientDetail, token, param.getNonce(), EndpointUtil.getIssuer(request));
return new IdsResponse<String, Object>()
.add(IdsConsts.ACCESS_TOKEN, accessToken.getAccessToken())
.add(IdsConsts.REFRESH_TOKEN, accessToken.getRefreshToken())

View File

@ -28,14 +28,19 @@ import com.fujieid.jap.ids.model.UserInfo;
public interface IdsUserService {
/**
* Login with account and password
* Login with account and password.
* <p>
* In the business system, if it is a multi-tenant business architecture, a user may exist in multiple systems,
* <p>
* and the client id can distinguish the system where the user is located
*
* @param username account number
* @param password password
* @param clientId The unique code of the currently logged-in client
* @return UserInfo
*/
default UserInfo loginByUsernameAndPassword(String username, String password) {
throw new IdsException("Not implemented `IdsUserService.loginByUsernameAndPassword(String, String)`");
default UserInfo loginByUsernameAndPassword(String username, String password, String clientId) {
throw new IdsException("Not implemented `IdsUserService.loginByUsernameAndPassword(String, String, String)`");
}
/**
@ -50,11 +55,16 @@ public interface IdsUserService {
/**
* Get user info by username.
* <p>
* In the business system, if it is a multi-tenant business architecture, a user may exist in multiple systems,
* <p>
* and the client id can distinguish the system where the user is located
*
* @param username username of the business system
* @param clientId The unique code of the currently logged-in client
* @return UserInfo
*/
default UserInfo getByName(String username) {
throw new IdsException("Not implemented `IdsUserService.getByName(String)`");
default UserInfo getByName(String username, String clientId) {
throw new IdsException("Not implemented `IdsUserService.getByName(String, String)`");
}
}

View File

@ -0,0 +1,118 @@
/*
* 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.ids.util;
import com.fujieid.jap.core.util.RequestUtil;
import com.fujieid.jap.ids.JapIds;
import com.fujieid.jap.ids.config.IdsConfig;
import com.fujieid.jap.ids.exception.IdsException;
import javax.servlet.http.HttpServletRequest;
/**
* Get the request url of each api of the oauth endpoint
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0.0
* @since 1.0.2
*/
public class EndpointUtil {
public static String getIssuer(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
if (config.isEnableDynamicIssuer() && null == request) {
throw new IdsException("The second-level domain name verification has been enabled, the HTTP request cannot be empty");
}
return config.isEnableDynamicIssuer() ? RequestUtil.getFullDomainName(request) : config.getIssuer();
}
public static String getLoginUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getLoginUrl();
}
public static String getErrorUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getErrorUrl();
}
public static String getAuthorizeUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getAuthorizeUrl();
}
public static String getAuthorizeAutoApproveUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getAuthorizeAutoApproveUrl();
}
public static String getTokenUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getTokenUrl();
}
public static String getUserinfoUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getUserinfoUrl();
}
public static String getRegistrationUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getRegistrationUrl();
}
public static String getEndSessionUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getEndSessionUrl();
}
public static String getCheckSessionUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getCheckSessionUrl();
}
public static String getLogoutRedirectUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getLogoutRedirectUrl();
}
public static String getJwksUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getJwksUrl();
}
public static String getDiscoveryUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
return getIssuer(request) + config.getDiscoveryUrl();
}
public static String getLoginPageUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
if (config.isExternalLoginPageUrl()) {
return config.getLoginPageUrl();
}
return getIssuer(request) + config.getLoginPageUrl();
}
public static String getConfirmPageUrl(HttpServletRequest request) {
IdsConfig config = JapIds.getIdsConfig();
if (config.isExternalLoginPageUrl()) {
return config.getLoginPageUrl();
}
return getIssuer(request) + config.getConfirmPageUrl();
}
}

View File

@ -67,11 +67,12 @@ public class JwtUtil {
* @param clientId Client Identifier
* @param userinfo User Profile
* @param tokenExpireIn Id Token validity (seconds)
* @param nonce noncestr
* @param nonce Random string
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @return jwt token
*/
public static String createJwtToken(String clientId, UserInfo userinfo, Long tokenExpireIn, String nonce) {
return createJwtToken(clientId, userinfo, tokenExpireIn, nonce, null, null);
public static String createJwtToken(String clientId, UserInfo userinfo, Long tokenExpireIn, String nonce, String issuer) {
return createJwtToken(clientId, userinfo, tokenExpireIn, nonce, null, null, issuer);
}
/**
@ -83,10 +84,10 @@ public class JwtUtil {
* @param nonce Random string
* @param scopes Scopes
* @param responseType Response Type
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @return jwt token
*/
public static String createJwtToken(String clientId, UserInfo userinfo, Long tokenExpireIn, String nonce, Set<String> scopes, String responseType) {
String issuer = JapIds.getIdsConfig().getIssuer();
public static String createJwtToken(String clientId, UserInfo userinfo, Long tokenExpireIn, String nonce, Set<String> scopes, String responseType, String issuer) {
JwtClaims claims = new JwtClaims();
// required
@ -240,7 +241,7 @@ public class JwtUtil {
}
}
public static Map<String, Object> validateJwtToken(String clientId, String userId, String jwtToken) {
public static Map<String, Object> validateJwtToken(String clientId, String userId, String jwtToken, String jwksUrl) {
// Use JwtConsumerBuilder to construct an appropriate JwtConsumer, which will
// be used to validate and process the JWT.
// The specific validation requirements for a JWT are context dependent, however,
@ -261,7 +262,7 @@ public class JwtUtil {
// The HttpsJwks retrieves and caches keys from a the given HTTPS JWKS endpoint.
// Because it retains the JWKs after fetching them, it can and should be reused
// to improve efficiency by reducing the number of outbound calls the the endpoint.
VerificationKeyResolver verificationKeyResolver = new HttpsJwksVerificationKeyResolver(new HttpsJwks(idsConfig.getJwksUrl()));
VerificationKeyResolver verificationKeyResolver = new HttpsJwksVerificationKeyResolver(new HttpsJwks(jwksUrl));
jwtConsumerBuilder.setVerificationKeyResolver(verificationKeyResolver);
} else if (jwtVerificationType == JwtVerificationType.JWKS) {
// There's also a key resolver that selects from among a given list of JWKs using the Key ID

View File

@ -95,23 +95,23 @@ public class TokenUtil {
return RequestUtil.getCookieVal(request, IdsConsts.ACCESS_TOKEN);
}
public static String createIdToken(ClientDetail clientDetail, UserInfo user, String nonce) {
public static String createIdToken(ClientDetail clientDetail, UserInfo user, String nonce, String issuer) {
long idTokenExpiresIn = OauthUtil.getIdTokenExpiresIn(clientDetail.getIdTokenExpiresIn());
return JwtUtil.createJwtToken(clientDetail.getClientId(), user, idTokenExpiresIn, nonce);
return JwtUtil.createJwtToken(clientDetail.getClientId(), user, idTokenExpiresIn, nonce, issuer);
}
public static String createIdToken(ClientDetail clientDetail, UserInfo user, IdsRequestParam param) {
public static String createIdToken(ClientDetail clientDetail, UserInfo user, IdsRequestParam param, String issuer) {
long idTokenExpiresIn = OauthUtil.getIdTokenExpiresIn(clientDetail.getIdTokenExpiresIn());
return JwtUtil.createJwtToken(clientDetail.getClientId(), user, idTokenExpiresIn, param.getNonce(), OauthUtil.convertStrToList(param.getScope()), param.getResponseType());
return JwtUtil.createJwtToken(clientDetail.getClientId(), user, idTokenExpiresIn, param.getNonce(), OauthUtil.convertStrToList(param.getScope()), param.getResponseType(), issuer);
}
public static AccessToken createAccessToken(UserInfo user, ClientDetail clientDetail, String grantType, String scope, String nonce) {
public static AccessToken createAccessToken(UserInfo user, ClientDetail clientDetail, String grantType, String scope, String nonce, String issuer) {
String clientId = clientDetail.getClientId();
long accessTokenExpiresIn = OauthUtil.getAccessTokenExpiresIn(clientDetail.getAccessTokenExpiresIn());
long refreshTokenExpiresIn = OauthUtil.getAccessTokenExpiresIn(clientDetail.getRefreshTokenExpiresIn());
String accessTokenStr = JwtUtil.createJwtToken(clientId, user, accessTokenExpiresIn, nonce);
String accessTokenStr = JwtUtil.createJwtToken(clientId, user, accessTokenExpiresIn, nonce, issuer);
String refreshTokenStr = SecureUtil.sha256(clientId.concat(scope).concat(System.currentTimeMillis() + ""));
AccessToken accessToken = new AccessToken();
@ -138,9 +138,9 @@ public class TokenUtil {
return accessToken;
}
public static AccessToken refreshAccessToken(UserInfo user, ClientDetail clientDetail, AccessToken accessToken, String nonce) {
public static AccessToken refreshAccessToken(UserInfo user, ClientDetail clientDetail, AccessToken accessToken, String nonce, String issuer) {
String rawToken = accessToken.getAccessToken();
String accessTokenStr = JwtUtil.createJwtToken(clientDetail.getClientId(), user, clientDetail.getAccessTokenExpiresIn(), nonce);
String accessTokenStr = JwtUtil.createJwtToken(clientDetail.getClientId(), user, clientDetail.getAccessTokenExpiresIn(), nonce, issuer);
accessToken.setAccessToken(accessTokenStr);
accessToken.setAccessTokenExpiresIn(clientDetail.getAccessTokenExpiresIn());
@ -154,8 +154,8 @@ public class TokenUtil {
return accessToken;
}
public static AccessToken createClientCredentialsAccessToken(ClientDetail clientDetail, String grantType, String scope, String nonce) {
return createAccessToken(null, clientDetail, grantType, scope, nonce);
public static AccessToken createClientCredentialsAccessToken(ClientDetail clientDetail, String grantType, String scope, String nonce, String issuer) {
return createAccessToken(null, clientDetail, grantType, scope, nonce, issuer);
}

View File

@ -29,6 +29,7 @@ public class BaseIdsTest {
protected HttpServletResponse httpServletResponseMock;
@Mock
protected HttpSession httpsSessionMock;
protected String issuer = "http://www.baidu.com";
@Before
public void init() {
@ -42,7 +43,7 @@ public class BaseIdsTest {
.setClientDetailService(new IdsClientDetailServiceImpl())
.setIdentityService(new IdsIdentityServiceImpl())
.setIdsConfig(new IdsConfig()
.setIssuer("http://www.baidu.com")
.setIssuer(issuer)
.setJwtConfig(new JwtConfig()
.setJwksKeyId("jap-jwk-keyid")
.setJwksJson("{\n" +

View File

@ -23,8 +23,20 @@ public class IdsUserServiceImpl implements IdsUserService {
}
}
/**
* Login with account and password.
* <p>
* In the business system, if it is a multi-tenant business architecture, a user may exist in multiple systems,
* <p>
* and the client id can distinguish the system where the user is located
*
* @param username account number
* @param password password
* @param clientId The unique code of the currently logged-in client
* @return UserInfo
*/
@Override
public UserInfo loginByUsernameAndPassword(String username, String password) {
public UserInfo loginByUsernameAndPassword(String username, String password, String clientId) {
return userInfoList.stream().filter(userInfo -> userInfo.getUsername().equals(username)).findFirst().orElse(null);
}
@ -42,13 +54,16 @@ public class IdsUserServiceImpl implements IdsUserService {
/**
* Get user info by username.
* <p>
* It is suitable for the {@code jap-simple} module
* In the business system, if it is a multi-tenant business architecture, a user may exist in multiple systems,
* <p>
* and the client id can distinguish the system where the user is located
*
* @param username username of the business system
* @return JapUser
* @param clientId The unique code of the currently logged-in client
* @return UserInfo
*/
@Override
public UserInfo getByName(String username) {
public UserInfo getByName(String username, String clientId) {
return userInfoList.stream().filter(userInfo -> userInfo.getUsername().equals(username)).findFirst().orElse(null);
}
}

View File

@ -2,25 +2,16 @@ package com.fujieid.jap.ids.oidc;
import com.fujieid.jap.ids.BaseIdsTest;
import com.fujieid.jap.ids.JapIds;
import com.fujieid.jap.ids.exception.IdsException;
import com.fujieid.jap.ids.exception.InvalidJwksException;
import com.fujieid.jap.ids.model.OidcDiscoveryDto;
import com.xkcoding.json.JsonUtil;
import org.junit.Assert;
import org.junit.Test;
public class OidcUtilTest extends BaseIdsTest {
@Test
public void getOidcDiscoveryInfoNotNull() {
OidcDiscoveryDto discoveryDto = OidcUtil.getOidcDiscoveryInfo(null);
System.out.println(JsonUtil.toJsonString(discoveryDto));
Assert.assertNotNull(discoveryDto);
}
@Test
public void getOidcDiscoveryInfoEqual() {
OidcDiscoveryDto discoveryDto = OidcUtil.getOidcDiscoveryInfo(null);
Assert.assertNotNull(discoveryDto);
Assert.assertThrows(IdsException.class, () -> OidcUtil.getOidcDiscoveryInfo(null));
}
@Test

View File

@ -55,7 +55,7 @@ public class IdsAuthorizationProviderTest extends BaseIdsTest {
@Test
public void generateImplicitGrantResponse() {
String url = idsAuthorizationProvider.generateImplicitGrantResponse(userInfo, idsRequestParam, clientDetail);
String url = idsAuthorizationProvider.generateImplicitGrantResponse(userInfo, idsRequestParam, clientDetail, issuer);
System.out.println(url);
Assert.assertNotNull(url);
String params = url.substring(idsRequestParam.getRedirectUri().length() + 1);
@ -71,7 +71,7 @@ public class IdsAuthorizationProviderTest extends BaseIdsTest {
@Test
public void generateCodeIdTokenAuthorizationResponse() {
String url = idsAuthorizationProvider.generateCodeIdTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail);
String url = idsAuthorizationProvider.generateCodeIdTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail, issuer);
System.out.println(url);
Assert.assertNotNull(url);
String params = url.substring(idsRequestParam.getRedirectUri().length() + 1);
@ -84,7 +84,7 @@ public class IdsAuthorizationProviderTest extends BaseIdsTest {
@Test
public void generateIdTokenAuthorizationResponse() {
String url = idsAuthorizationProvider.generateIdTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail);
String url = idsAuthorizationProvider.generateIdTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail, issuer);
System.out.println(url);
Assert.assertNotNull(url);
String params = url.substring(idsRequestParam.getRedirectUri().length() + 1);
@ -95,7 +95,7 @@ public class IdsAuthorizationProviderTest extends BaseIdsTest {
@Test
public void generateIdTokenTokenAuthorizationResponse() {
String url = idsAuthorizationProvider.generateIdTokenTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail);
String url = idsAuthorizationProvider.generateIdTokenTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail, issuer);
System.out.println(url);
Assert.assertNotNull(url);
String params = url.substring(idsRequestParam.getRedirectUri().length() + 1);
@ -107,7 +107,7 @@ public class IdsAuthorizationProviderTest extends BaseIdsTest {
@Test
public void generateCodeTokenAuthorizationResponse() {
String url = idsAuthorizationProvider.generateCodeTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail);
String url = idsAuthorizationProvider.generateCodeTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail, issuer);
System.out.println(url);
Assert.assertNotNull(url);
String params = url.substring(idsRequestParam.getRedirectUri().length() + 1);
@ -120,7 +120,7 @@ public class IdsAuthorizationProviderTest extends BaseIdsTest {
@Test
public void generateCodeIdTokenTokenAuthorizationResponse() {
String url = idsAuthorizationProvider.generateCodeIdTokenTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail);
String url = idsAuthorizationProvider.generateCodeIdTokenTokenAuthorizationResponse(userInfo, idsRequestParam, clientDetail, issuer);
System.out.println(url);
Assert.assertNotNull(url);
String params = url.substring(idsRequestParam.getRedirectUri().length() + 1);

View File

@ -36,7 +36,7 @@ public class IdsTokenProviderTest extends BaseIdsTest {
@Test
public void generateAuthorizationCodeResponse() {
this.initParam();
Assert.assertThrows(InvalidCodeException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam));
Assert.assertThrows(InvalidCodeException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock));
}
@Test
@ -44,7 +44,7 @@ public class IdsTokenProviderTest extends BaseIdsTest {
this.initParam();
String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L);
idsRequestParam.setCode(code);
IdsResponse<String, Object> response = idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam);
IdsResponse<String, Object> response = idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock);
System.out.println(response);
Assert.assertNotNull(response);
}
@ -55,7 +55,7 @@ public class IdsTokenProviderTest extends BaseIdsTest {
String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L);
idsRequestParam.setCode(code);
idsRequestParam.setClientId("asdasd");
Assert.assertThrows(InvalidClientException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam));
Assert.assertThrows(InvalidClientException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock));
}
@Test
@ -64,7 +64,7 @@ public class IdsTokenProviderTest extends BaseIdsTest {
String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L);
idsRequestParam.setCode(code);
idsRequestParam.setGrantType("asdasd");
Assert.assertThrows(UnsupportedGrantTypeException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam));
Assert.assertThrows(UnsupportedGrantTypeException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock));
}
@Test
@ -73,7 +73,7 @@ public class IdsTokenProviderTest extends BaseIdsTest {
String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L);
idsRequestParam.setCode(code);
idsRequestParam.setRedirectUri("asdasd");
Assert.assertThrows(InvalidRedirectUriException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam));
Assert.assertThrows(InvalidRedirectUriException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock));
}
@Test
@ -82,7 +82,7 @@ public class IdsTokenProviderTest extends BaseIdsTest {
String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L);
idsRequestParam.setCode(code);
idsRequestParam.setClientSecret("asdasd");
Assert.assertThrows(InvalidClientException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam));
Assert.assertThrows(InvalidClientException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock));
}
@Test

View File

@ -139,7 +139,7 @@ public class JwtUtilTest extends BaseIdsTest {
@Test
public void createJwtTokenFromRs256() {
JapIds.getIdsConfig().getJwtConfig().setJwksJson(rs256JwksJson);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce, issuer);
System.out.println(jwtToken);
}
@ -157,7 +157,7 @@ public class JwtUtilTest extends BaseIdsTest {
.getJwtConfig()
.setJwksJson(rs384JwksJson)
.setTokenSigningAlg(TokenSigningAlg.RS384);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce, issuer);
System.out.println(jwtToken);
}
@ -179,7 +179,7 @@ public class JwtUtilTest extends BaseIdsTest {
.getJwtConfig()
.setJwksJson(rs512JwksJson)
.setTokenSigningAlg(TokenSigningAlg.RS512);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce, issuer);
System.out.println(jwtToken);
}
@ -200,7 +200,7 @@ public class JwtUtilTest extends BaseIdsTest {
.getJwtConfig()
.setJwksJson(es256JwksJson)
.setTokenSigningAlg(TokenSigningAlg.ES256);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce, issuer);
System.out.println(jwtToken);
}
@ -222,7 +222,7 @@ public class JwtUtilTest extends BaseIdsTest {
.getJwtConfig()
.setJwksJson(es384JwksJson)
.setTokenSigningAlg(TokenSigningAlg.ES384);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce, issuer);
System.out.println(jwtToken);
}
@ -244,7 +244,7 @@ public class JwtUtilTest extends BaseIdsTest {
.getJwtConfig()
.setJwksJson(es512JwksJson)
.setTokenSigningAlg(TokenSigningAlg.ES512);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce);
String jwtToken = JwtUtil.createJwtToken(clientId, userInfo, tokenExpireIn, nonce, issuer);
System.out.println(jwtToken);
}
@ -268,7 +268,7 @@ public class JwtUtilTest extends BaseIdsTest {
.setJwksJson(es512JwksJson)
.setTokenSigningAlg(TokenSigningAlg.ES512)
.setJwtVerificationType(JwtVerificationType.JWKS);
Map<String, Object> jwtInfo = JwtUtil.validateJwtToken(clientId, userInfo.getId(), jwt);
Map<String, Object> jwtInfo = JwtUtil.validateJwtToken(clientId, userInfo.getId(), jwt, EndpointUtil.getJwksUrl(null));
System.out.println(jwtInfo);
}
}