提交 173f7792 编写于 作者: M MaxKey

/authz/oauth/v20

上级 0325f76e
......@@ -207,8 +207,8 @@ maxkey.support.wsfederation.logoutUrl=https://adfs.maxkey.top/adfs/ls/?wa=wsigno
#############################################################################
# OIDC V1.0 METADATA configuration
maxkey.oidc.metadata.issuer=${maxkey.server.name}/maxkey
maxkey.oidc.metadata.authorizationEndpoint=${maxkey.server.name}/maxkey/oauth/v20/authorize
maxkey.oidc.metadata.tokenEndpoint=${maxkey.server.name}/maxkey/oauth/v20/token
maxkey.oidc.metadata.authorizationEndpoint=${maxkey.server.name}/maxkey/authz/oauth/v20/authorize
maxkey.oidc.metadata.tokenEndpoint=${maxkey.server.name}/maxkey/authz/oauth/v20/token
maxkey.oidc.metadata.userinfoEndpoint=${maxkey.server.name}/maxkey/api/connect/userinfo
#############################################################################
......
......@@ -64,7 +64,7 @@ public class AuthorizeEndpoint extends AuthorizeBaseEndpoint{
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.FORMBASED)){
modelAndView=WebContext.forward("/authz/formbased/"+id);
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.OAUTH20)){
modelAndView=WebContext.forward("/authz/oauthv20/"+application.getId());
modelAndView=WebContext.forward("/authz/oauth/v20/"+application.getId());
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.OPEN_ID_CONNECT)){
// modelAndView=new ModelAndView("openid connect");
}else if (application.getProtocol().equalsIgnoreCase(ConstantsProtocols.SAML20)){
......
package org.maxkey.authz.oauth2.common;
public class OAuth2Constants {
public static final class PARAMETER{
/**
* Constant to use while parsing and formatting parameter maps for OAuth2 requests
*/
public static final String CLIENT_ID = "client_id";
public static final String CLIENT_SECRET = "client_secret";
/**
* Constant to use while parsing and formatting parameter maps for OAuth2 requests
*/
public static final String STATE = "state";
/**
* Constant to use while parsing and formatting parameter maps for OAuth2 requests
*/
public static final String SCOPE = "scope";
public static final String CODE = "code";
public static final String EXPIRES_IN = "expires_in";
/**
* Constant to use while parsing and formatting parameter maps for OAuth2 requests
*/
public static final String REDIRECT_URI = "redirect_uri";
/**
* Constant to use while parsing and formatting parameter maps for OAuth2 requests
*/
public static final String RESPONSE_TYPE = "response_type";
/**
* Constant to use while parsing and formatting parameter maps for OAuth2 requests
*/
public static final String USER_OAUTH_APPROVAL = "user_oauth_approval";
/**
* Constant to use as a prefix for scope approval
*/
public static final String SCOPE_PREFIX = "scope.";
/**
* Constant to use while parsing and formatting parameter maps for OAuth2 requests
*/
public static final String GRANT_TYPE = "grant_type";
public static final String ACCESS_TOKEN = "access_token";
}
public static class ENDPOINT{
public final static String ENDPOINT_BASE = "/authz/oauth/v20";
public final static String ENDPOINT_AUTHORIZE = ENDPOINT_BASE + "/authorize";
public final static String ENDPOINT_TOKEN = ENDPOINT_BASE + "/token";
public final static String ENDPOINT_CHECK_TOKEN = ENDPOINT_BASE + "/check_token";
public final static String ENDPOINT_TOKEN_KEY = ENDPOINT_BASE + "/token_key";
public final static String ENDPOINT_APPROVAL_CONFIRM = ENDPOINT_BASE + "/approval_confirm";
public final static String ENDPOINT_ERROR = ENDPOINT_BASE + "/error";
public final static String ENDPOINT_USERINFO = "/api/oauth/v20/me";
public final static String ENDPOINT_OPENID_CONNECT_USERINFO = "/api/connect/v10/userinfo";
}
}
......@@ -22,6 +22,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import org.maxkey.authn.SigninPrincipal;
import org.maxkey.authz.endpoint.AuthorizeBaseEndpoint;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.common.util.OAuth2Utils;
import org.maxkey.authz.oauth2.provider.AuthorizationRequest;
import org.maxkey.authz.oauth2.provider.ClientDetailsService;
......@@ -73,7 +74,7 @@ public class OAuth20AccessConfirmationController {
* @return
* throws Exception
*/
@RequestMapping("/oauth/v20/approval_confirm")
@RequestMapping(OAuth2Constants.ENDPOINT.ENDPOINT_APPROVAL_CONFIRM)
public ModelAndView getAccessConfirmation(
@RequestParam Map<String, Object> model) throws Exception {
model.remove("authorizationRequest");
......@@ -123,7 +124,7 @@ public class OAuth20AccessConfirmationController {
* @return
* throws Exception
*/
@RequestMapping("/oauth/v20/error")
@RequestMapping(OAuth2Constants.ENDPOINT.ENDPOINT_ERROR)
public String handleError(Map<String, Object> model) throws Exception {
// We can add more stuff to the model here for JSP rendering. If the client was
// a machine then
......
......@@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.maxkey.authz.oauth2.common.OAuth2AccessToken;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.common.exceptions.InvalidClientException;
import org.maxkey.authz.oauth2.common.exceptions.InvalidRequestException;
import org.maxkey.authz.oauth2.common.exceptions.OAuth2Exception;
......@@ -99,7 +100,7 @@ import org.maxkey.authz.oauth2.provider.ClientDetailsService;
public class AuthorizationEndpoint extends AbstractEndpoint {
final static Logger _logger = LoggerFactory.getLogger(AuthorizationEndpoint.class);
private static final String OAUTH_V20_AUTHORIZATION_URL = "%s/oauth/v20/authorize?client_id=%s&response_type=code&redirect_uri=%s&approval_prompt=auto";
private static final String OAUTH_V20_AUTHORIZATION_URL = "%s" + OAuth2Constants.ENDPOINT.ENDPOINT_AUTHORIZE + "?client_id=%s&response_type=code&redirect_uri=%s&approval_prompt=auto";
@Autowired
@Qualifier("oauth20JdbcClientDetailsService")
......@@ -117,9 +118,9 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
private OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator();
private String userApprovalPage = "forward:/oauth/v20/approval_confirm";
private String userApprovalPage = "forward:" + OAuth2Constants.ENDPOINT.ENDPOINT_APPROVAL_CONFIRM;
private String errorPage = "forward:/oauth/error";
private String errorPage = "forward:" + OAuth2Constants.ENDPOINT.ENDPOINT_ERROR;
private Object implicitLock = new Object();
......@@ -132,7 +133,7 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
}
@ApiOperation(value = "OAuth 2.0 认证接口", notes = "传递参数client_id,response_type,redirect_uri等",httpMethod="GET")
@RequestMapping(value = "/oauth/v20/authorize", method = RequestMethod.GET)
@RequestMapping(value = OAuth2Constants.ENDPOINT.ENDPOINT_AUTHORIZE, method = RequestMethod.GET)
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
SessionStatus sessionStatus) {
Principal principal=(Principal)WebContext.getAuthentication();
......@@ -211,7 +212,7 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
}
@RequestMapping(value = "/oauth/v20/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
@RequestMapping(value = OAuth2Constants.ENDPOINT.ENDPOINT_AUTHORIZE, method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
SessionStatus sessionStatus) {
Principal principal=(Principal)WebContext.getAuthentication();
......@@ -514,7 +515,7 @@ public class AuthorizationEndpoint extends AbstractEndpoint {
}
@ApiOperation(value = "OAuth 2.0 认证接口", notes = "传递参数应用ID,自动完成跳转认证拼接",httpMethod="GET")
@RequestMapping("/authz/oauthv20/{id}")
@RequestMapping(OAuth2Constants.ENDPOINT.ENDPOINT_BASE + "/{id}")
public ModelAndView authorize(
HttpServletRequest request,
HttpServletResponse response,
......
......@@ -34,6 +34,7 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maxkey.authz.oauth2.common.OAuth2AccessToken;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.common.exceptions.InvalidTokenException;
import org.maxkey.authz.oauth2.common.exceptions.OAuth2Exception;
import org.maxkey.authz.oauth2.provider.OAuth2Authentication;
......@@ -81,7 +82,7 @@ public class CheckTokenEndpoint {
}
@ApiOperation(value = "OAuth 2.0 token检查接口", notes = "传递参数token",httpMethod="POST")
@RequestMapping(value = "/oauth/v20/check_token")
@RequestMapping(value = OAuth2Constants.ENDPOINT.ENDPOINT_CHECK_TOKEN)
@ResponseBody
public Map<String, ?> checkToken(@RequestParam("token") String value) {
......
......@@ -25,6 +25,7 @@ import java.util.Set;
import org.maxkey.authn.SigninPrincipal;
import org.maxkey.authz.oauth2.common.DefaultOAuth2AccessToken;
import org.maxkey.authz.oauth2.common.OAuth2AccessToken;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.common.exceptions.InvalidClientException;
import org.maxkey.authz.oauth2.common.exceptions.InvalidGrantException;
import org.maxkey.authz.oauth2.common.exceptions.InvalidRequestException;
......@@ -89,7 +90,7 @@ public class TokenEndpoint extends AbstractEndpoint {
* @throws HttpRequestMethodNotSupportedException
*/
@ApiOperation(value = "OAuth 2.0 获取AccessToken接口", notes = "传递参数token等",httpMethod="GET")
@RequestMapping(value = "/oauth/v20/token", method=RequestMethod.GET)
@RequestMapping(value = OAuth2Constants.ENDPOINT.ENDPOINT_TOKEN, method=RequestMethod.GET)
public ResponseEntity<OAuth2AccessToken> getAccessToken(@RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!allowedRequestMethods.contains(HttpMethod.GET)) {
......@@ -99,7 +100,7 @@ public class TokenEndpoint extends AbstractEndpoint {
}
@ApiOperation(value = "OAuth 2.0 获取AccessToken接口", notes = "传递参数token等",httpMethod="POST")
@RequestMapping(value = "/oauth/v20/token", method=RequestMethod.POST)
@RequestMapping(value = OAuth2Constants.ENDPOINT.ENDPOINT_TOKEN, method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(@RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
// TokenEndpointAuthenticationFilter
......
......@@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maxkey.authn.SigninPrincipal;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.common.util.OAuth2Utils;
import org.maxkey.authz.oauth2.provider.AuthorizationRequest;
import org.maxkey.authz.oauth2.provider.OAuth2Authentication;
......@@ -77,7 +78,7 @@ import org.springframework.web.HttpRequestMethodNotSupportedException;
* @author Dave Syer
*
*/
@WebFilter(filterName = "TokenEndpointAuthenticationFilter", urlPatterns = "/oauth/v20/token/*")
@WebFilter(filterName = "TokenEndpointAuthenticationFilter", urlPatterns = OAuth2Constants.ENDPOINT.ENDPOINT_TOKEN+"/*")
public class TokenEndpointAuthenticationFilter implements Filter {
private static final Log logger = LogFactory.getLog(TokenEndpointAuthenticationFilter.class);
......
......@@ -34,6 +34,7 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
......@@ -69,7 +70,7 @@ public class TokenKeyEndpoint {
* @param principal the currently authenticated user if there is one
* @return the key used to verify tokens
*/
@RequestMapping(value = "/oauth/token_key", method = RequestMethod.GET)
@RequestMapping(value = OAuth2Constants.ENDPOINT.ENDPOINT_TOKEN_KEY, method = RequestMethod.GET)
@ResponseBody
public Map<String, String> getKey(Principal principal) {
if ((principal == null || principal instanceof AnonymousAuthenticationToken) && !converter.isPublic()) {
......
/*
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.maxkey.authz.oauth2.provider.userinfo.endpoint;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Set;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.maxkey.authn.SigninPrincipal;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.common.exceptions.OAuth2Exception;
import org.maxkey.authz.oauth2.provider.ClientDetailsService;
import org.maxkey.authz.oauth2.provider.OAuth2Authentication;
import org.maxkey.authz.oauth2.provider.token.DefaultTokenServices;
import org.maxkey.constants.ContentType;
import org.maxkey.crypto.ReciprocalUtils;
import org.maxkey.crypto.jwt.encryption.service.JwtEncryptionAndDecryptionService;
import org.maxkey.crypto.jwt.encryption.service.impl.RecipientJwtEncryptionAndDecryptionServiceBuilder;
import org.maxkey.crypto.jwt.signer.service.JwtSigningAndValidationService;
import org.maxkey.crypto.jwt.signer.service.impl.SymmetricSigningAndValidationServiceBuilder;
import org.maxkey.domain.UserInfo;
import org.maxkey.domain.apps.oauth2.provider.ClientDetails;
import org.maxkey.persistence.service.AppsService;
import org.maxkey.persistence.service.UserInfoService;
import org.maxkey.util.JsonUtils;
import org.maxkey.util.StringGenerator;
import org.maxkey.web.HttpResponseAdapter;
import org.maxkey.web.WebConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTClaimsSet.Builder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import com.nimbusds.jwt.SignedJWT;
@Api(tags = "2-1-OAuth v2.0 API文档模块")
@Controller
public class OpenIdConnectUserInfoEndpoint {
final static Logger _logger = LoggerFactory.getLogger(OpenIdConnectUserInfoEndpoint.class);
@Autowired
@Qualifier("oauth20JdbcClientDetailsService")
private ClientDetailsService clientDetailsService;
@Autowired
@Qualifier("oauth20TokenServices")
private DefaultTokenServices oauth20tokenServices;
@Autowired
@Qualifier("userInfoService")
private UserInfoService userInfoService;
@Autowired
@Qualifier("appsService")
protected AppsService appsService;
@Autowired
@Qualifier("jwtSignerValidationService")
private JwtSigningAndValidationService jwtSignerValidationService;
@Autowired
@Qualifier("jwtEncryptionService")
private JwtEncryptionAndDecryptionService jwtEnDecryptionService;
private SymmetricSigningAndValidationServiceBuilder symmetricJwtSignerServiceBuilder
=new SymmetricSigningAndValidationServiceBuilder();
private RecipientJwtEncryptionAndDecryptionServiceBuilder recipientJwtEnDecryptionServiceBuilder
=new RecipientJwtEncryptionAndDecryptionServiceBuilder();
OAuthDefaultUserInfoAdapter defaultOAuthUserInfoAdapter=new OAuthDefaultUserInfoAdapter();
@Autowired
protected HttpResponseAdapter httpResponseAdapter;
@ApiOperation(value = "OIDC 用户信息接口", notes = "传递Authorization参数access_token",httpMethod="GET")
@RequestMapping(value=OAuth2Constants.ENDPOINT.ENDPOINT_OPENID_CONNECT_USERINFO)
@ResponseBody
public String connect10aUserInfo(
@RequestHeader(value = "Authorization", required = true) String access_token,
HttpServletRequest request,
HttpServletResponse response) {
String principal="";
if (!StringGenerator.uuidMatches(access_token)) {
return JsonUtils.gson2Json(accessTokenFormatError(access_token));
}
OAuth2Authentication oAuth2Authentication =null;
try{
oAuth2Authentication = oauth20tokenServices.loadAuthentication(access_token);
principal=((SigninPrincipal)oAuth2Authentication.getPrincipal()).getUsername();
Set<String >scopes=oAuth2Authentication.getOAuth2Request().getScope();
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(oAuth2Authentication.getOAuth2Request().getClientId());
UserInfo userInfo=queryUserInfo(principal);
String userJson="";
Builder jwtClaimsSetBuilder= new JWTClaimsSet.Builder();
SigninPrincipal authentication = (SigninPrincipal)oAuth2Authentication.getUserAuthentication().getPrincipal();
jwtClaimsSetBuilder.claim("sub", userInfo.getId());
jwtClaimsSetBuilder.claim(WebConstants.ONLINE_TICKET_NAME, authentication.getOnlineTicket().getTicketId());
if(scopes.contains("profile")){
jwtClaimsSetBuilder.claim("name", userInfo.getUsername());
jwtClaimsSetBuilder.claim("preferred_username", userInfo.getDisplayName());
jwtClaimsSetBuilder.claim("given_name", userInfo.getGivenName());
jwtClaimsSetBuilder.claim("family_name", userInfo.getFamilyName());
jwtClaimsSetBuilder.claim("middle_name", userInfo.getMiddleName());
jwtClaimsSetBuilder.claim("nickname", userInfo.getNickName());
jwtClaimsSetBuilder.claim("profile", "profile");
jwtClaimsSetBuilder.claim("picture", "picture");
jwtClaimsSetBuilder.claim("website", userInfo.getWebSite());
String gender;
switch(userInfo.getGender()){
case UserInfo.GENDER.MALE :
gender="male";break;
case UserInfo.GENDER.FEMALE :
gender="female";break;
default:
gender="unknown";
}
jwtClaimsSetBuilder.claim("gender", gender);
jwtClaimsSetBuilder.claim("zoneinfo", userInfo.getTimeZone());
jwtClaimsSetBuilder.claim("locale", userInfo.getLocale());
jwtClaimsSetBuilder.claim("updated_time", userInfo.getModifiedDate());
jwtClaimsSetBuilder.claim("birthdate", userInfo.getBirthDate());
}
if(scopes.contains("email")){
jwtClaimsSetBuilder.claim("email", userInfo.getWorkEmail());
jwtClaimsSetBuilder.claim("email_verified", false);
}
if(scopes.contains("phone")){
jwtClaimsSetBuilder.claim("phone_number", userInfo.getWorkPhoneNumber());
jwtClaimsSetBuilder.claim("phone_number_verified", false);
}
if(scopes.contains("address")){
HashMap<String, String> addressFields = new HashMap<String, String>();
addressFields.put("country", userInfo.getWorkCountry());
addressFields.put("region", userInfo.getWorkRegion());
addressFields.put("locality", userInfo.getWorkLocality());
addressFields.put("street_address", userInfo.getWorkStreetAddress());
addressFields.put("formatted", userInfo.getWorkAddressFormatted());
addressFields.put("postal_code", userInfo.getWorkPostalCode());
jwtClaimsSetBuilder.claim("address", addressFields);
}
jwtClaimsSetBuilder
.jwtID(UUID.randomUUID().toString())// set a random NONCE in the middle of it
.audience(Arrays.asList(clientDetails.getClientId()))
.issueTime(new Date())
.expirationTime(new Date(new Date().getTime()+clientDetails.getAccessTokenValiditySeconds()*1000));
JWTClaimsSet userInfoJWTClaims = jwtClaimsSetBuilder.build();
JWT userInfoJWT=null;
JWSAlgorithm signingAlg = jwtSignerValidationService.getDefaultSigningAlgorithm();
if (clientDetails.getUserInfoEncryptedAlgorithm() != null
&& !clientDetails.getUserInfoEncryptedAlgorithm().equals("none")
&& clientDetails.getUserInfoEncryptionMethod() != null
&& !clientDetails.getUserInfoEncryptionMethod().equals("none")
&&clientDetails.getJwksUri()!=null&&clientDetails.getJwksUri().length()>4
) {
//需要加密
response.setContentType(ContentType.APPLICATION_JWT_UTF8);
JwtEncryptionAndDecryptionService recipientJwtEnDecryptionService =
recipientJwtEnDecryptionServiceBuilder.serviceBuilder(clientDetails.getJwksUri());
if (recipientJwtEnDecryptionService != null) {
JWEAlgorithm jweAlgorithm=new JWEAlgorithm(clientDetails.getUserInfoEncryptedAlgorithm());
EncryptionMethod encryptionMethod=new EncryptionMethod(clientDetails.getUserInfoEncryptionMethod());
EncryptedJWT encryptedJWT = new EncryptedJWT(new JWEHeader(jweAlgorithm, encryptionMethod), userInfoJWTClaims);
recipientJwtEnDecryptionService.encryptJwt(encryptedJWT);
userJson=encryptedJWT.serialize();
}else{
_logger.error("Couldn't find encrypter for client: " + clientDetails.getClientId());
HashMap<String,Object>authzException=new HashMap<String,Object>();
authzException.put(OAuth2Exception.ERROR, "error");
authzException.put(OAuth2Exception.DESCRIPTION,"Couldn't find encrypter for client: " + clientDetails.getClientId());
return JsonUtils.gson2Json(authzException);
}
}else if (clientDetails.getUserInfoSigningAlgorithm()!=null
&& !clientDetails.getUserInfoSigningAlgorithm().equals("none")) {
//需要签名
response.setContentType(ContentType.APPLICATION_JWT_UTF8);
// signed ID token
if (signingAlg.equals(JWSAlgorithm.HS256)
|| signingAlg.equals(JWSAlgorithm.HS384)
|| signingAlg.equals(JWSAlgorithm.HS512)) {
// sign it with the client's secret
String client_secret=ReciprocalUtils.decoder(clientDetails.getClientSecret());
JwtSigningAndValidationService symmetricJwtSignerService =symmetricJwtSignerServiceBuilder.serviceBuilder(client_secret);
if(symmetricJwtSignerService!=null){
userInfoJWTClaims = new JWTClaimsSet.Builder(userInfoJWTClaims).claim("kid", "SYMMETRIC-KEY").build();
userInfoJWT = new SignedJWT(new JWSHeader(signingAlg), userInfoJWTClaims);
symmetricJwtSignerService.signJwt((SignedJWT) userInfoJWT);
}else{
_logger.error("Couldn't create symmetric validator for client " + clientDetails.getClientId() + " without a client secret");
}
} else {
userInfoJWTClaims = new JWTClaimsSet.Builder(userInfoJWTClaims).claim("kid", jwtSignerValidationService.getDefaultSignerKeyId()).build();
userInfoJWT = new SignedJWT(new JWSHeader(signingAlg), userInfoJWTClaims);
// sign it with the server's key
jwtSignerValidationService.signJwt((SignedJWT) userInfoJWT);
}
userJson=userInfoJWT.serialize();
}else {
//不需要加密和签名
response.setContentType(ContentType.APPLICATION_JSON_UTF8);
// unsigned ID token
//userInfoJWT = new PlainJWT(userInfoJWTClaims);
userJson=JsonUtils.gson2Json(jwtClaimsSetBuilder.getClaims());
}
return userJson;
}catch(OAuth2Exception e){
HashMap<String,Object>authzException=new HashMap<String,Object>();
authzException.put(OAuth2Exception.ERROR, e.getOAuth2ErrorCode());
authzException.put(OAuth2Exception.DESCRIPTION,e.getMessage());
return JsonUtils.object2Json(authzException);
}
}
public HashMap<String,Object> accessTokenFormatError(String access_token){
HashMap<String,Object>atfe=new HashMap<String,Object>();
atfe.put(OAuth2Exception.ERROR, "token Format Invalid");
atfe.put(OAuth2Exception.DESCRIPTION, "access Token Format Invalid , access_token : "+access_token);
return atfe;
}
public UserInfo queryUserInfo(String uid){
_logger.debug("uid : "+uid);
UserInfo userInfo = (UserInfo) userInfoService.loadByUsername(uid);
return userInfo;
}
public void setOauth20tokenServices(DefaultTokenServices oauth20tokenServices) {
this.oauth20tokenServices = oauth20tokenServices;
}
public void setUserInfoService(UserInfoService userInfoService) {
this.userInfoService = userInfoService;
}
//
//
// public void setJwtSignerValidationService(
// JwtSigningAndValidationService jwtSignerValidationService) {
// this.jwtSignerValidationService = jwtSignerValidationService;
// }
//
// public void setJwtEnDecryptionService(
// JwtEncryptionAndDecryptionService jwtEnDecryptionService) {
// this.jwtEnDecryptionService = jwtEnDecryptionService;
// }
}
......@@ -17,32 +17,24 @@
package org.maxkey.authz.oauth2.provider.userinfo.endpoint;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Set;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.maxkey.authn.SigninPrincipal;
import org.maxkey.authz.endpoint.adapter.AbstractAuthorizeAdapter;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.common.exceptions.OAuth2Exception;
import org.maxkey.authz.oauth2.provider.ClientDetailsService;
import org.maxkey.authz.oauth2.provider.OAuth2Authentication;
import org.maxkey.authz.oauth2.provider.token.DefaultTokenServices;
import org.maxkey.constants.Boolean;
import org.maxkey.constants.ContentType;
import org.maxkey.crypto.ReciprocalUtils;
import org.maxkey.crypto.jwt.encryption.service.JwtEncryptionAndDecryptionService;
import org.maxkey.crypto.jwt.encryption.service.impl.RecipientJwtEncryptionAndDecryptionServiceBuilder;
import org.maxkey.crypto.jwt.signer.service.JwtSigningAndValidationService;
import org.maxkey.crypto.jwt.signer.service.impl.SymmetricSigningAndValidationServiceBuilder;
import org.maxkey.domain.UserInfo;
import org.maxkey.domain.apps.Apps;
import org.maxkey.domain.apps.oauth2.provider.ClientDetails;
import org.maxkey.persistence.service.AppsService;
import org.maxkey.persistence.service.UserInfoService;
import org.maxkey.util.AuthorizationHeaderUtils;
......@@ -50,7 +42,6 @@ import org.maxkey.util.Instance;
import org.maxkey.util.JsonUtils;
import org.maxkey.util.StringGenerator;
import org.maxkey.web.HttpResponseAdapter;
import org.maxkey.web.WebConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -59,26 +50,11 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTClaimsSet.Builder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import com.nimbusds.jwt.SignedJWT;
@Api(tags = "2-1-OAuth v2.0 API文档模块")
@Controller
@RequestMapping(value = { "/api" })
public class UserInfoEndpoint {
final static Logger _logger = LoggerFactory.getLogger(UserInfoEndpoint.class);
@Autowired
......@@ -120,7 +96,7 @@ public class UserInfoEndpoint {
protected HttpResponseAdapter httpResponseAdapter;
@ApiOperation(value = "OAuth 2.0 用户信息接口", notes = "传递参数access_token",httpMethod="GET")
@RequestMapping(value="/oauth/v20/me")
@RequestMapping(value=OAuth2Constants.ENDPOINT.ENDPOINT_USERINFO)
public void apiV20UserInfo(
@RequestParam(value = "access_token", required = false) String access_token,
@RequestHeader(value = "authorization", required = false) String authorization_bearer,
......@@ -173,162 +149,6 @@ public class UserInfoEndpoint {
}
}
@ApiOperation(value = "OIDC 用户信息接口", notes = "传递Authorization参数access_token",httpMethod="GET")
@RequestMapping(value="/connect/v10/userinfo")
@ResponseBody
public String connect10aUserInfo(
@RequestHeader(value = "Authorization", required = true) String access_token,
HttpServletRequest request,
HttpServletResponse response) {
String principal="";
if (!StringGenerator.uuidMatches(access_token)) {
return JsonUtils.gson2Json(accessTokenFormatError(access_token));
}
OAuth2Authentication oAuth2Authentication =null;
try{
oAuth2Authentication = oauth20tokenServices.loadAuthentication(access_token);
principal=((SigninPrincipal)oAuth2Authentication.getPrincipal()).getUsername();
Set<String >scopes=oAuth2Authentication.getOAuth2Request().getScope();
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(oAuth2Authentication.getOAuth2Request().getClientId());
UserInfo userInfo=queryUserInfo(principal);
String userJson="";
Builder jwtClaimsSetBuilder= new JWTClaimsSet.Builder();
SigninPrincipal authentication = (SigninPrincipal)oAuth2Authentication.getUserAuthentication().getPrincipal();
jwtClaimsSetBuilder.claim("sub", userInfo.getId());
jwtClaimsSetBuilder.claim(WebConstants.ONLINE_TICKET_NAME, authentication.getOnlineTicket().getTicketId());
if(scopes.contains("profile")){
jwtClaimsSetBuilder.claim("name", userInfo.getUsername());
jwtClaimsSetBuilder.claim("preferred_username", userInfo.getDisplayName());
jwtClaimsSetBuilder.claim("given_name", userInfo.getGivenName());
jwtClaimsSetBuilder.claim("family_name", userInfo.getFamilyName());
jwtClaimsSetBuilder.claim("middle_name", userInfo.getMiddleName());
jwtClaimsSetBuilder.claim("nickname", userInfo.getNickName());
jwtClaimsSetBuilder.claim("profile", "profile");
jwtClaimsSetBuilder.claim("picture", "picture");
jwtClaimsSetBuilder.claim("website", userInfo.getWebSite());
String gender;
switch(userInfo.getGender()){
case UserInfo.GENDER.MALE :
gender="male";break;
case UserInfo.GENDER.FEMALE :
gender="female";break;
default:
gender="unknown";
}
jwtClaimsSetBuilder.claim("gender", gender);
jwtClaimsSetBuilder.claim("zoneinfo", userInfo.getTimeZone());
jwtClaimsSetBuilder.claim("locale", userInfo.getLocale());
jwtClaimsSetBuilder.claim("updated_time", userInfo.getModifiedDate());
jwtClaimsSetBuilder.claim("birthdate", userInfo.getBirthDate());
}
if(scopes.contains("email")){
jwtClaimsSetBuilder.claim("email", userInfo.getWorkEmail());
jwtClaimsSetBuilder.claim("email_verified", false);
}
if(scopes.contains("phone")){
jwtClaimsSetBuilder.claim("phone_number", userInfo.getWorkPhoneNumber());
jwtClaimsSetBuilder.claim("phone_number_verified", false);
}
if(scopes.contains("address")){
HashMap<String, String> addressFields = new HashMap<String, String>();
addressFields.put("country", userInfo.getWorkCountry());
addressFields.put("region", userInfo.getWorkRegion());
addressFields.put("locality", userInfo.getWorkLocality());
addressFields.put("street_address", userInfo.getWorkStreetAddress());
addressFields.put("formatted", userInfo.getWorkAddressFormatted());
addressFields.put("postal_code", userInfo.getWorkPostalCode());
jwtClaimsSetBuilder.claim("address", addressFields);
}
jwtClaimsSetBuilder
.jwtID(UUID.randomUUID().toString())// set a random NONCE in the middle of it
.audience(Arrays.asList(clientDetails.getClientId()))
.issueTime(new Date())
.expirationTime(new Date(new Date().getTime()+clientDetails.getAccessTokenValiditySeconds()*1000));
JWTClaimsSet userInfoJWTClaims = jwtClaimsSetBuilder.build();
JWT userInfoJWT=null;
JWSAlgorithm signingAlg = jwtSignerValidationService.getDefaultSigningAlgorithm();
if (clientDetails.getUserInfoEncryptedAlgorithm() != null
&& !clientDetails.getUserInfoEncryptedAlgorithm().equals("none")
&& clientDetails.getUserInfoEncryptionMethod() != null
&& !clientDetails.getUserInfoEncryptionMethod().equals("none")
&&clientDetails.getJwksUri()!=null&&clientDetails.getJwksUri().length()>4
) {
//需要加密
response.setContentType(ContentType.APPLICATION_JWT_UTF8);
JwtEncryptionAndDecryptionService recipientJwtEnDecryptionService =
recipientJwtEnDecryptionServiceBuilder.serviceBuilder(clientDetails.getJwksUri());
if (recipientJwtEnDecryptionService != null) {
JWEAlgorithm jweAlgorithm=new JWEAlgorithm(clientDetails.getUserInfoEncryptedAlgorithm());
EncryptionMethod encryptionMethod=new EncryptionMethod(clientDetails.getUserInfoEncryptionMethod());
EncryptedJWT encryptedJWT = new EncryptedJWT(new JWEHeader(jweAlgorithm, encryptionMethod), userInfoJWTClaims);
recipientJwtEnDecryptionService.encryptJwt(encryptedJWT);
userJson=encryptedJWT.serialize();
}else{
_logger.error("Couldn't find encrypter for client: " + clientDetails.getClientId());
HashMap<String,Object>authzException=new HashMap<String,Object>();
authzException.put(OAuth2Exception.ERROR, "error");
authzException.put(OAuth2Exception.DESCRIPTION,"Couldn't find encrypter for client: " + clientDetails.getClientId());
return JsonUtils.gson2Json(authzException);
}
}else if (clientDetails.getUserInfoSigningAlgorithm()!=null
&& !clientDetails.getUserInfoSigningAlgorithm().equals("none")) {
//需要签名
response.setContentType(ContentType.APPLICATION_JWT_UTF8);
// signed ID token
if (signingAlg.equals(JWSAlgorithm.HS256)
|| signingAlg.equals(JWSAlgorithm.HS384)
|| signingAlg.equals(JWSAlgorithm.HS512)) {
// sign it with the client's secret
String client_secret=ReciprocalUtils.decoder(clientDetails.getClientSecret());
JwtSigningAndValidationService symmetricJwtSignerService =symmetricJwtSignerServiceBuilder.serviceBuilder(client_secret);
if(symmetricJwtSignerService!=null){
userInfoJWTClaims = new JWTClaimsSet.Builder(userInfoJWTClaims).claim("kid", "SYMMETRIC-KEY").build();
userInfoJWT = new SignedJWT(new JWSHeader(signingAlg), userInfoJWTClaims);
symmetricJwtSignerService.signJwt((SignedJWT) userInfoJWT);
}else{
_logger.error("Couldn't create symmetric validator for client " + clientDetails.getClientId() + " without a client secret");
}
} else {
userInfoJWTClaims = new JWTClaimsSet.Builder(userInfoJWTClaims).claim("kid", jwtSignerValidationService.getDefaultSignerKeyId()).build();
userInfoJWT = new SignedJWT(new JWSHeader(signingAlg), userInfoJWTClaims);
// sign it with the server's key
jwtSignerValidationService.signJwt((SignedJWT) userInfoJWT);
}
userJson=userInfoJWT.serialize();
}else {
//不需要加密和签名
response.setContentType(ContentType.APPLICATION_JSON_UTF8);
// unsigned ID token
//userInfoJWT = new PlainJWT(userInfoJWTClaims);
userJson=JsonUtils.gson2Json(jwtClaimsSetBuilder.getClaims());
}
return userJson;
}catch(OAuth2Exception e){
HashMap<String,Object>authzException=new HashMap<String,Object>();
authzException.put(OAuth2Exception.ERROR, e.getOAuth2ErrorCode());
authzException.put(OAuth2Exception.DESCRIPTION,e.getMessage());
return JsonUtils.object2Json(authzException);
}
}
public HashMap<String,Object> accessTokenFormatError(String access_token){
HashMap<String,Object>atfe=new HashMap<String,Object>();
atfe.put(OAuth2Exception.ERROR, "token Format Invalid");
......
......@@ -24,6 +24,7 @@ import java.security.spec.InvalidKeySpecException;
import javax.servlet.Filter;
import javax.sql.DataSource;
import org.maxkey.authn.support.jwt.JwtLoginService;
import org.maxkey.authz.oauth2.common.OAuth2Constants;
import org.maxkey.authz.oauth2.provider.ClientDetailsService;
import org.maxkey.authz.oauth2.provider.OAuth2UserDetailsService;
import org.maxkey.authz.oauth2.provider.approval.TokenApprovalStore;
......@@ -79,7 +80,7 @@ public class Oauth20AutoConfiguration implements InitializingBean {
_logger.debug("TokenEndpointAuthenticationFilter init ");
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<Filter>();
registration.setFilter(new TokenEndpointAuthenticationFilter());
registration.addUrlPatterns("/oauth/v20/token/*");
registration.addUrlPatterns(OAuth2Constants.ENDPOINT.ENDPOINT_TOKEN + "/*");
registration.setName("TokenEndpointAuthenticationFilter");
registration.setOrder(1);
return registration;
......
......@@ -212,8 +212,8 @@ maxkey.saml.v20.sp.issuing.entity.id=client.maxkey.org
#OIDC V1.0 METADATA configuration #
#############################################################################
maxkey.oidc.metadata.issuer=https://${maxkey.server.domain}/maxkey
maxkey.oidc.metadata.authorizationEndpoint=${maxkey.server.name}/maxkey/oauth/v20/authorize
maxkey.oidc.metadata.tokenEndpoint=${maxkey.server.name}/maxkey/oauth/v20/token
maxkey.oidc.metadata.authorizationEndpoint=${maxkey.server.name}/maxkey/authz/oauth/v20/authorize
maxkey.oidc.metadata.tokenEndpoint=${maxkey.server.name}/maxkey/authz/oauth/v20/token
maxkey.oidc.metadata.userinfoEndpoint=${maxkey.server.name}/maxkey/api/connect/userinfo
#############################################################################
......
......@@ -180,8 +180,8 @@ public class MaxKeyMvcConfig implements WebMvcConfigurer {
.excludePathPatterns("/authz/cas/v1/tickets/*")
//OAuth
.addPathPatterns("/oauth/v20/authorize")
.addPathPatterns("/oauth/v20/authorize/*")
.addPathPatterns("/authz/oauth/v20/authorize")
.addPathPatterns("/authz/oauth/v20/authorize/*")
//online ticket Validate
.excludePathPatterns("/onlineticket/ticketValidate")
......
......@@ -287,8 +287,8 @@ maxkey.support.wsfederation.logoutUrl=https://adfs.maxkey.top/adfs/ls/?wa=wsigno
#OIDC V1.0 METADATA configuration #
#############################################################################
maxkey.oidc.metadata.issuer=${maxkey.server.name}/maxkey
maxkey.oidc.metadata.authorizationEndpoint=${maxkey.server.name}/maxkey/oauth/v20/authorize
maxkey.oidc.metadata.tokenEndpoint=${maxkey.server.name}/maxkey/oauth/v20/token
maxkey.oidc.metadata.authorizationEndpoint=${maxkey.server.name}/maxkey/authz/oauth/v20/authorize
maxkey.oidc.metadata.tokenEndpoint=${maxkey.server.name}/maxkey/authz/oauth/v20/token
maxkey.oidc.metadata.userinfoEndpoint=${maxkey.server.name}/maxkey/api/connect/userinfo
#############################################################################
......
......@@ -287,8 +287,8 @@ maxkey.support.wsfederation.logoutUrl=https://adfs.maxkey.top/adfs/ls/?wa=wsigno
#OIDC V1.0 METADATA configuration #
#############################################################################
maxkey.oidc.metadata.issuer=${maxkey.server.name}/maxkey
maxkey.oidc.metadata.authorizationEndpoint=${maxkey.server.name}/maxkey/oauth/v20/authorize
maxkey.oidc.metadata.tokenEndpoint=${maxkey.server.name}/maxkey/oauth/v20/token
maxkey.oidc.metadata.authorizationEndpoint=${maxkey.server.name}/maxkey/authz/oauth/v20/authorize
maxkey.oidc.metadata.tokenEndpoint=${maxkey.server.name}/maxkey/authz/oauth/v20/token
maxkey.oidc.metadata.userinfoEndpoint=${maxkey.server.name}/maxkey/api/connect/userinfo
#############################################################################
......
{
"issuer": "http://login.connsec.com",
"authorization_endpoint": "http://login.connsec.com/sec/oauth/v20/authorize",
"token_endpoint": "http://login.connsec.com/sec/oauth/v20/token",
"userinfo_endpoint": "http://login.connsec.com/sec/api/connect/v10/userinfo",
"issuer": "http://sso.maxkey.top",
"authorization_endpoint": "http://sso.maxkey.top/maxkey/authz/oauth/v20/authorize",
"token_endpoint": "http://sso.maxkey.top/maxkey/authz/oauth/v20/token",
"userinfo_endpoint": "http://sso.maxkey.top/maxkey/api/connect/v10/userinfo",
"revocation_endpoint": "",
"jwks_uri": "http://login.connsec.com/sec/key/jwk",
"jwks_uri": "http://sso.maxkey.top/maxkey/key/jwk",
"display_values_supported": [
"page",
"popup",
......
......@@ -40,7 +40,7 @@
</table>
<!--<p>You hereby authorize "${model.client.clientId!}" to access your protected resources.</p>-->
<form id="confirmationForm" name="confirmationForm" action="<@base/>/oauth/v20/authorize" method="post">
<form id="confirmationForm" name="confirmationForm" action="<@base/>/authz/oauth/v20/authorize" method="post">
<input id="user_oauth_approval" name="user_oauth_approval" value="true" type="hidden"/>
<label><input class="button btn btn-primary mr-3" name="authorize" value='<@locale code="apps.oauth.approval.authorize"/>' type="submit"/></label>
</form>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册