From 7e894f2fd6b630e8d397c7b2cd5e36b450c103f6 Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Sun, 19 Feb 2023 23:02:18 -0500 Subject: [PATCH] fixes #283 add SwtVerifyHandler and test case to support token info introspection (#284) --- openapi-security/pom.xml | 9 +- .../networknt/openapi/JwtVerifyHandler.java | 20 +- .../networknt/openapi/SwtVerifyHandler.java | 412 ++++++++++++++++++ .../openapi/UnifiedPathPrefixAuth.java | 9 + .../openapi/UnifiedSecurityConfig.java | 2 + .../openapi/UnifiedSecurityHandler.java | 50 ++- .../resources/config/openapi-security.yml | 10 +- .../networknt/openapi/SecurityConfigTest.java | 1 - .../openapi/SwtVerifyHandlerTest.java | 306 +++++++++++++ .../resources/config/openapi-security.yml | 81 ---- .../src/test/resources/config/openapi.yaml | 20 + .../resources/config/unified-security.yml | 4 +- .../src/test/resources/config/values.yml | 12 + 13 files changed, 811 insertions(+), 125 deletions(-) create mode 100644 openapi-security/src/main/java/com/networknt/openapi/SwtVerifyHandler.java create mode 100644 openapi-security/src/test/java/com/networknt/openapi/SwtVerifyHandlerTest.java delete mode 100644 openapi-security/src/test/resources/config/openapi-security.yml diff --git a/openapi-security/pom.xml b/openapi-security/pom.xml index 8a5c0ff2..10fdf9c0 100644 --- a/openapi-security/pom.xml +++ b/openapi-security/pom.xml @@ -38,6 +38,10 @@ com.networknt config + + com.networknt + client + com.networknt utility @@ -99,11 +103,6 @@ jose4j - - com.networknt - client - test - ch.qos.logback logback-classic diff --git a/openapi-security/src/main/java/com/networknt/openapi/JwtVerifyHandler.java b/openapi-security/src/main/java/com/networknt/openapi/JwtVerifyHandler.java index 9a7f1851..666bbd51 100644 --- a/openapi-security/src/main/java/com/networknt/openapi/JwtVerifyHandler.java +++ b/openapi-security/src/main/java/com/networknt/openapi/JwtVerifyHandler.java @@ -135,7 +135,7 @@ public boolean handleJwt(HttpServerExchange exchange, String pathPrefix, String boolean ignoreExpiry = config.isIgnoreJwtExpiry(); - String jwt = JwtVerifier.getJwtFromAuthorization(authorization); + String jwt = JwtVerifier.getTokenFromAuthorization(authorization); if (jwt != null) { @@ -172,7 +172,7 @@ public boolean handleJwt(HttpServerExchange exchange, String pathPrefix, String auditInfo.put(Constants.SUBJECT_CLAIMS, claims); auditInfo.put(Constants.CLIENT_ID_STRING, clientId); - if (!config.isEnableH2c() && this.checkForH2CRequest(headerMap)) { + if (!config.isEnableH2c() && jwtVerifier.checkForH2CRequest(headerMap)) { setExchangeStatus(exchange, STATUS_METHOD_NOT_ALLOWED); if (logger.isDebugEnabled()) logger.debug("JwtVerifyHandler.handleRequest ends with an error."); return false; @@ -202,7 +202,7 @@ public boolean handleJwt(HttpServerExchange exchange, String pathPrefix, String /* validate scope from operation */ String scopeHeader = headerMap.getFirst(HttpStringConstants.SCOPE_TOKEN); - String scopeJwt = JwtVerifier.getJwtFromAuthorization(scopeHeader); + String scopeJwt = JwtVerifier.getTokenFromAuthorization(scopeHeader); List secondaryScopes = new ArrayList<>(); if(!this.hasValidSecondaryScopes(exchange, scopeJwt, secondaryScopes, ignoreExpiry, pathPrefix, reqPath, jwkServiceIds, auditInfo)) { @@ -272,20 +272,6 @@ protected String getScopeToken(String authorization, HeaderMap headerMap) { return returnToken; } - /** - * Checks to see if the current exchange type is Upgrade. - * Two conditions required for a valid upgrade request. - * - 'Connection' header is set to 'upgrade'. - * - 'Upgrade' is present. - * - * @param headerMap - map containing all exchange headers - * @return - returns true if the request is an Upgrade request. - */ - protected boolean checkForH2CRequest(HeaderMap headerMap) { - return headerMap.getFirst(Headers.UPGRADE) != null - && headerMap.getFirst(Headers.CONNECTION) != null - && headerMap.getFirst(Headers.CONNECTION).equalsIgnoreCase("upgrade"); - } /** * Gets the operation from the spec. If not defined or defined incorrectly, return null. diff --git a/openapi-security/src/main/java/com/networknt/openapi/SwtVerifyHandler.java b/openapi-security/src/main/java/com/networknt/openapi/SwtVerifyHandler.java new file mode 100644 index 00000000..63801bb9 --- /dev/null +++ b/openapi-security/src/main/java/com/networknt/openapi/SwtVerifyHandler.java @@ -0,0 +1,412 @@ +package com.networknt.openapi; + +import com.networknt.config.Config; +import com.networknt.client.oauth.TokenInfo; +import com.networknt.exception.ClientException; +import com.networknt.handler.Handler; +import com.networknt.handler.MiddlewareHandler; +import com.networknt.handler.config.HandlerConfig; +import com.networknt.httpstring.AttachmentConstants; +import com.networknt.httpstring.HttpStringConstants; +import com.networknt.monad.Result; +import com.networknt.oas.model.Operation; +import com.networknt.oas.model.Path; +import com.networknt.oas.model.SecurityParameter; +import com.networknt.oas.model.SecurityRequirement; +import com.networknt.security.SwtVerifier; +import com.networknt.security.SecurityConfig; +import com.networknt.utility.Constants; +import com.networknt.utility.ModuleRegistry; +import io.undertow.Handlers; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HeaderMap; +import io.undertow.util.Headers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +/** + * This is a middleware handler used to verify simple web token with token introspection on + * the OAuth 2.0 provider. It does the scope verification against the openapi.yml file, and + * that is the reason it is located in the light-rest-4j. + * + * @author Steve Hu + */ +public class SwtVerifyHandler implements MiddlewareHandler { + static final Logger logger = LoggerFactory.getLogger(SwtVerifyHandler.class); + static final String OPENAPI_SECURITY_CONFIG = "openapi-security"; + static final String HANDLER_CONFIG = "handler"; + static final String STATUS_INVALID_AUTH_TOKEN = "ERR10000"; + static final String STATUS_AUTH_TOKEN_EXPIRED = "ERR10001"; + static final String STATUS_MISSING_AUTH_TOKEN = "ERR10002"; + static final String STATUS_INVALID_SCOPE_TOKEN = "ERR10003"; + static final String STATUS_SCOPE_TOKEN_EXPIRED = "ERR10004"; + static final String STATUS_AUTH_TOKEN_SCOPE_MISMATCH = "ERR10005"; + static final String STATUS_SCOPE_TOKEN_SCOPE_MISMATCH = "ERR10006"; + static final String STATUS_INVALID_REQUEST_PATH = "ERR10007"; + static final String STATUS_METHOD_NOT_ALLOWED = "ERR10008"; + static final String STATUS_CLIENT_EXCEPTION = "ERR10082"; + + + public static SwtVerifier swtVerifier; + + static SecurityConfig config; + private volatile HttpHandler next; + + String basePath; + + @Override + public HttpHandler getNext() { + return next; + } + + @Override + public MiddlewareHandler setNext(final HttpHandler next) { + Handlers.handlerNotNull(next); + this.next = next; + return this; + } + + @Override + public boolean isEnabled() { + return config.isEnableVerifySwt(); + } + + @Override + public void register() { + ModuleRegistry.registerModule(SwtVerifyHandler.class.getName(), Config.getInstance().getJsonMapConfigNoCache(OPENAPI_SECURITY_CONFIG), null); + } + + @Override + public void reload() { + config.reload(OPENAPI_SECURITY_CONFIG); + ModuleRegistry.registerModule(SwtVerifyHandler.class.getName(), Config.getInstance().getJsonMapConfigNoCache(OPENAPI_SECURITY_CONFIG), null); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + if (logger.isDebugEnabled()) + logger.debug("SwtVerifyHandler.handleRequest starts."); + + String reqPath = exchange.getRequestPath(); + + // if request path is in the skipPathPrefixes in the config, call the next handler directly to skip the security check. + if (config.getSkipPathPrefixes() != null && config.getSkipPathPrefixes().stream().anyMatch(reqPath::startsWith)) { + if(logger.isTraceEnabled()) + logger.trace("Skip request path base on skipPathPrefixes for " + reqPath); + Handler.next(exchange, next); + if (logger.isDebugEnabled()) + logger.debug("SwtVerifyHandler.handleRequest ends."); + return; + } + // only UnifiedSecurityHandler will have the jwkServiceIds as the third parameter. + if(handleSwt(exchange, reqPath, null)) { + if(logger.isDebugEnabled()) logger.debug("SwtVerifyHandler.handleRequest ends."); + Handler.next(exchange, next); + } + } + + + public boolean handleSwt(HttpServerExchange exchange, String reqPath, List jwkServiceIds) throws Exception { + Map auditInfo = null; + HeaderMap headerMap = exchange.getRequestHeaders(); + String authorization = headerMap.getFirst(Headers.AUTHORIZATION); + + if (logger.isTraceEnabled() && authorization != null && authorization.length() > 10) + logger.trace("Authorization header = " + authorization.substring(0, 10)); + // if an empty authorization header or a value length less than 6 ("Basic "), return an error + if(authorization == null ) { + setExchangeStatus(exchange, STATUS_MISSING_AUTH_TOKEN); + exchange.endExchange(); + if (logger.isDebugEnabled()) logger.debug("SwtVerifyHandler.handleRequest ends with an error."); + return false; + } else if(authorization.trim().length() < 6) { + setExchangeStatus(exchange, STATUS_INVALID_AUTH_TOKEN); + exchange.endExchange(); + if (logger.isDebugEnabled()) logger.debug("SwtVerifyHandler.handleRequest ends with an error."); + return false; + } else { + authorization = this.getScopeToken(authorization, headerMap); + String swt = SwtVerifier.getTokenFromAuthorization(authorization); + if (swt != null) { + if (logger.isTraceEnabled()) + logger.trace("parsed swt from authorization = " + swt.substring(0, 10)); + try { + Result tokenInfoResult = swtVerifier.verifySwt(swt, reqPath, jwkServiceIds); + if(tokenInfoResult.isFailure()) { + // return error status to the user. + setExchangeStatus(exchange, tokenInfoResult.getError()); + if (logger.isDebugEnabled()) logger.debug("SwtVerifyHandler.handleRequest ends with an error."); + return false; + } + TokenInfo tokenInfo = tokenInfoResult.getResult(); + /* if no auditInfo has been set previously, we populate here */ + auditInfo = exchange.getAttachment(AttachmentConstants.AUDIT_INFO); + if (auditInfo == null) { + auditInfo = new HashMap<>(); + exchange.putAttachment(AttachmentConstants.AUDIT_INFO, auditInfo); + } + String clientId = tokenInfo.getClientId(); + auditInfo.put(Constants.CLIENT_ID_STRING, clientId); + + if (!config.isEnableH2c() && swtVerifier.checkForH2CRequest(headerMap)) { + setExchangeStatus(exchange, STATUS_METHOD_NOT_ALLOWED); + if (logger.isDebugEnabled()) logger.debug("SwtVerifyHandler.handleRequest ends with an error."); + return false; + } + + String callerId = headerMap.getFirst(HttpStringConstants.CALLER_ID); + + if (callerId != null) + auditInfo.put(Constants.CALLER_ID_STRING, callerId); + + if (config != null && config.isEnableVerifyScope()) { + if (logger.isTraceEnabled()) + logger.trace("verify scope from the primary token when enableVerifyScope is true"); + + /* get openapi operation */ + OpenApiOperation openApiOperation = (OpenApiOperation) auditInfo.get(Constants.OPENAPI_OPERATION_STRING); + Operation operation = this.getOperation(exchange, openApiOperation, auditInfo); + if(operation == null) { + if(config.isSkipVerifyScopeWithoutSpec()) { + if (logger.isDebugEnabled()) logger.debug("SwtVerifyHandler.handleRequest ends without verifying scope due to spec."); + Handler.next(exchange, next); + } else { + // this will return an error message to the client. + } + return false; + } + + /* validate scope from operation */ + String scopeHeader = headerMap.getFirst(HttpStringConstants.SCOPE_TOKEN); + String scopeSwt = SwtVerifier.getTokenFromAuthorization(scopeHeader); + List secondaryScopes = new ArrayList<>(); + + if(!this.hasValidSecondaryScopes(exchange, scopeSwt, secondaryScopes, reqPath, jwkServiceIds, auditInfo)) { + return false; + } + if(!this.hasValidScope(exchange, scopeHeader, secondaryScopes, tokenInfo, operation)) { + return false; + } + } + if (logger.isTraceEnabled()) + logger.trace("complete SWT verification for request path = " + exchange.getRequestURI()); + + if (logger.isDebugEnabled()) + logger.debug("SwtVerifyHandler.handleRequest ends."); + + return true; + } catch (ClientException e) { + // client exception when accessing the token introspection endpoint. This is the only exception + // that can happen in the call stack. + logger.error("ClientException: ", e); + if (logger.isDebugEnabled()) + logger.debug("SwtVerifyHandler.handleRequest ends with an error."); + setExchangeStatus(exchange, STATUS_CLIENT_EXCEPTION, e.getMessage()); + exchange.endExchange(); + } + } else { + if (logger.isDebugEnabled()) + logger.debug("SwtVerifyHandler.handleRequest ends with an error."); + setExchangeStatus(exchange, STATUS_MISSING_AUTH_TOKEN); + exchange.endExchange(); + } + return true; + } + } + + /** + * Makes sure the provided scope in the JWT or SWT is valid for the main scope or secondary scopes. + * + * @param exchange - the current exchange + * @param scopeHeader - the scope header + * @param secondaryScopes - list of secondary scopes (can be empty) + * @param tokenInfo - TokenInfo returned from the introspection + * @param operation - the openapi operation + * @return - return true if scope is valid for endpoint + */ + protected boolean hasValidScope(HttpServerExchange exchange, String scopeHeader, List secondaryScopes, TokenInfo tokenInfo, Operation operation) { + // validate the scope against the scopes configured in the OpenAPI spec + if (config.isEnableVerifyScope()) { + // get scope defined in OpenAPI spec for this endpoint. + Collection specScopes = null; + Collection securityRequirements = operation.getSecurityRequirements(); + if (securityRequirements != null) { + for (SecurityRequirement requirement : securityRequirements) { + SecurityParameter securityParameter = null; + + for (String oauth2Name : OpenApiHandler.getHelper(exchange.getRequestPath()).oauth2Names) { + securityParameter = requirement.getRequirement(oauth2Name); + if (securityParameter != null) break; + } + + if (securityParameter != null) + specScopes = securityParameter.getParameters(); + + if (specScopes != null) + break; + } + } + + // validate scope + if (scopeHeader != null) { + if (logger.isTraceEnabled()) logger.trace("validate the scope with scope token"); + if (secondaryScopes == null || !matchedScopes(secondaryScopes, specScopes)) { + setExchangeStatus(exchange, STATUS_SCOPE_TOKEN_SCOPE_MISMATCH, secondaryScopes, specScopes); + exchange.endExchange(); + return false; + } + } else { + // no scope token, verify scope from auth token. + if (logger.isTraceEnabled()) logger.trace("validate the scope with primary token"); + List primaryScopes = null; + String scope = tokenInfo.getScope(); + if(scope != null) { + primaryScopes = Arrays.asList(scope.split(" ")); + } + + if (!matchedScopes(primaryScopes, specScopes)) { + setExchangeStatus(exchange, STATUS_AUTH_TOKEN_SCOPE_MISMATCH, primaryScopes, specScopes); + exchange.endExchange(); + return false; + } + } + } + return true; + } + + protected boolean matchedScopes(List tokenScopes, Collection specScopes) { + boolean matched = false; + if (specScopes != null && specScopes.size() > 0) { + if (tokenScopes != null && tokenScopes.size() > 0) { + for (String scope : specScopes) { + if (tokenScopes.contains(scope)) { + matched = true; + break; + } + } + } + } else { + matched = true; + } + return matched; + } + + /** + * Check is the request has secondary scopes, and they are valid. + * + * @param exchange - current exchange + * @param scopeSwt - the swt token that associate with a scope + * @param secondaryScopes - Initially an empty list that is then filled with the secondary scopes if there are any. + * @param reqPath - the request path as string + * @param jwkServiceIds - a list of serviceIds for jwk loading + * @param auditInfo - a map of audit info properties + * @return - return true if the secondary scopes are valid or if there are no secondary scopes. + */ + protected boolean hasValidSecondaryScopes(HttpServerExchange exchange, String scopeSwt, List secondaryScopes, String reqPath, List jwkServiceIds, Map auditInfo) { + if (scopeSwt != null) { + if (logger.isTraceEnabled()) + logger.trace("start verifying scope token = " + scopeSwt.substring(0, 10)); + try { + Result scopeTokenInfo = swtVerifier.verifySwt(scopeSwt, reqPath, jwkServiceIds); + if(scopeTokenInfo.isFailure()) { + setExchangeStatus(exchange, scopeTokenInfo.getError()); + exchange.endExchange(); + return false; + } + TokenInfo tokenInfo = scopeTokenInfo.getResult(); + String scope = tokenInfo.getScope(); + if(scope != null) { + secondaryScopes.addAll(Arrays.asList(scope.split(" "))); + auditInfo.put(Constants.SCOPE_CLIENT_ID_STRING, tokenInfo.getClientId()); + } + } catch (Exception e) { + // only the ClientException is possible here. + logger.error("Exception", e); + setExchangeStatus(exchange, STATUS_CLIENT_EXCEPTION, e.getMessage()); + exchange.endExchange(); + return false; + } + } + return true; + } + + /** + * Gets the operation from the spec. If not defined or defined incorrectly, return null. + * + * @param exchange - the current exchange + * @param openApiOperation - the openapi operation (from spec) + * @param auditInfo A map of audit info properties + * @return - return Operation + */ + protected Operation getOperation(HttpServerExchange exchange, OpenApiOperation openApiOperation, Map auditInfo) { + Operation operation; + if (openApiOperation == null) { + final NormalisedPath requestPath = new ApiNormalisedPath(exchange.getRequestURI(), basePath); + final Optional maybeApiPath = OpenApiHandler.getHelper(exchange.getRequestPath()).findMatchingApiPath(requestPath); + + if (maybeApiPath.isEmpty()) { + if(!config.isSkipVerifyScopeWithoutSpec()) { + setExchangeStatus(exchange, STATUS_INVALID_REQUEST_PATH); + } + return null; + } + + final NormalisedPath swaggerPathString = maybeApiPath.get(); + final Path swaggerPath = OpenApiHandler.getHelper(exchange.getRequestPath()).openApi3.getPath(swaggerPathString.original()); + final String httpMethod = exchange.getRequestMethod().toString().toLowerCase(); + + operation = swaggerPath.getOperation(httpMethod); + + if (operation == null) { + setExchangeStatus(exchange, STATUS_METHOD_NOT_ALLOWED, httpMethod, swaggerPathString.normalised()); + exchange.endExchange(); + return null; + } + + openApiOperation = new OpenApiOperation(swaggerPathString, swaggerPath, httpMethod, operation); + auditInfo.put(Constants.OPENAPI_OPERATION_STRING, openApiOperation); + auditInfo.put(Constants.ENDPOINT_STRING, swaggerPathString.normalised() + "@" + httpMethod); + + } else { + operation = openApiOperation.getOperation(); + } + return operation; + } + + /** + * Get authToken (JWT or SWT) from X-Scope-Token header. + * This covers situations where there is a secondary auth token. + * + * @param authorization - The auth token from authorization header + * @param headerMap - complete header map + * @return - return either x-scope-token or the initial auth token + */ + protected String getScopeToken(String authorization, HeaderMap headerMap) { + String returnToken = authorization; + // in the gateway case, the authorization header might be a basic header for the native API or other authentication headers. + // this will allow the Basic authentication be wrapped up with a JWT or SWT token between proxy client and proxy server for native. + if (returnToken != null && !returnToken.substring(0, 6).equalsIgnoreCase("Bearer")) { + + // get the swt token from the X-Scope-Token header in this case and allow the verification done with the secondary token. + returnToken = headerMap.getFirst(HttpStringConstants.SCOPE_TOKEN); + + if (logger.isTraceEnabled() && returnToken != null && returnToken.length() > 10) + logger.trace("The replaced authorization from X-Scope-Token header = " + returnToken.substring(0, 10)); + } + return returnToken; + } + + public SwtVerifyHandler() { + // at this moment, we assume that the OpenApiHandler is fully loaded with a single spec or multiple specs. + // And the basePath is the correct one from the OpenApiHandler helper or helperMap if multiple is used. + config = SecurityConfig.load(OPENAPI_SECURITY_CONFIG); + swtVerifier = new SwtVerifier(config); + // in case that the specification doesn't exist, get the basePath from the handler.yml for endpoint lookup. + HandlerConfig handlerConfig = (HandlerConfig) Config.getInstance().getJsonObjectConfig(HANDLER_CONFIG, HandlerConfig.class); + this.basePath = handlerConfig == null ? "/" : handlerConfig.getBasePath(); + } + +} diff --git a/openapi-security/src/main/java/com/networknt/openapi/UnifiedPathPrefixAuth.java b/openapi-security/src/main/java/com/networknt/openapi/UnifiedPathPrefixAuth.java index 1308ab83..834a9ba5 100644 --- a/openapi-security/src/main/java/com/networknt/openapi/UnifiedPathPrefixAuth.java +++ b/openapi-security/src/main/java/com/networknt/openapi/UnifiedPathPrefixAuth.java @@ -6,6 +6,7 @@ public class UnifiedPathPrefixAuth { String pathPrefix; boolean basic; boolean jwt; + boolean swt; boolean apikey; List jwkServiceIds; @@ -33,6 +34,14 @@ public void setJwt(boolean jwt) { this.jwt = jwt; } + public boolean isSwt() { + return swt; + } + + public void setSwt(boolean swt) { + this.swt = swt; + } + public boolean isApikey() { return apikey; } diff --git a/openapi-security/src/main/java/com/networknt/openapi/UnifiedSecurityConfig.java b/openapi-security/src/main/java/com/networknt/openapi/UnifiedSecurityConfig.java index a7a66024..d57b107b 100644 --- a/openapi-security/src/main/java/com/networknt/openapi/UnifiedSecurityConfig.java +++ b/openapi-security/src/main/java/com/networknt/openapi/UnifiedSecurityConfig.java @@ -20,6 +20,7 @@ public class UnifiedSecurityConfig { public static final String PREFIX = "prefix"; public static final String BASIC = "basic"; public static final String JWT = "jwt"; + public static final String SWT = "swt"; public static final String APIKEY = "apikey"; public static final String JWK_SERVICE_IDS = "jwkServiceIds"; @@ -147,6 +148,7 @@ private void setConfigList() { unifiedPathPrefixAuth.setPathPrefix((String)value.get(PREFIX)); unifiedPathPrefixAuth.setBasic(value.get(BASIC) == null ? false : (Boolean)value.get(BASIC)); unifiedPathPrefixAuth.setJwt(value.get(JWT) == null ? false : (Boolean)value.get(JWT)); + unifiedPathPrefixAuth.setSwt(value.get(SWT) == null ? false : (Boolean)value.get(SWT)); unifiedPathPrefixAuth.setApikey(value.get(APIKEY) == null ? false : (Boolean)value.get(APIKEY)); Object ids = value.get(JWK_SERVICE_IDS); if(ids instanceof String) { diff --git a/openapi-security/src/main/java/com/networknt/openapi/UnifiedSecurityHandler.java b/openapi-security/src/main/java/com/networknt/openapi/UnifiedSecurityHandler.java index 40aa504a..e4af7351 100644 --- a/openapi-security/src/main/java/com/networknt/openapi/UnifiedSecurityHandler.java +++ b/openapi-security/src/main/java/com/networknt/openapi/UnifiedSecurityHandler.java @@ -29,6 +29,7 @@ public class UnifiedSecurityHandler implements MiddlewareHandler { static final String BASIC_PREFIX = "BASIC"; static final String API_KEY = "apikey"; static final String JWT = "jwt"; + static final String SWT = "swt"; static final String MISSING_AUTH_TOKEN = "ERR10002"; static final String INVALID_AUTHORIZATION_HEADER = "ERR12003"; static final String HANDLER_NOT_FOUND = "ERR11200"; @@ -63,10 +64,10 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { found = true; if(logger.isTraceEnabled()) logger.trace("Found with requestPath = " + reqPath + " prefix = " + pathPrefixAuth.getPathPrefix()); // check jwt and basic first with authorization header, then check the apikey if it is enabled. - if(pathPrefixAuth.isBasic() || pathPrefixAuth.isJwt()) { + if(pathPrefixAuth.isBasic() || pathPrefixAuth.isJwt() || pathPrefixAuth.isSwt()) { String authorization = exchange.getRequestHeaders().getFirst(Headers.AUTHORIZATION); if(authorization == null) { - logger.error("Basic or JWT is enabled and authorization header is missing."); + logger.error("Basic or JWT or SWT is enabled and authorization header is missing."); setExchangeStatus(exchange, MISSING_AUTH_TOKEN); if(logger.isDebugEnabled()) logger.debug("UnifiedSecurityHandler.handleRequest ends with an error."); @@ -99,23 +100,42 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { } } else if (BEARER_PREFIX.equalsIgnoreCase(authorization.substring(0, 6))) { Map handlers = Handler.getHandlers(); - JwtVerifyHandler handler = (JwtVerifyHandler) handlers.get(JWT); - if(handler == null) { - logger.error("Cannot find JwtVerifyHandler with alias name jwt."); - setExchangeStatus(exchange, HANDLER_NOT_FOUND, "com.networknt.openapi.JwtVerifyHandler@jwt"); - exchange.endExchange(); - return; - } else { - // get the jwkServiceIds list. - if(handler.handleJwt(exchange, pathPrefixAuth.getPathPrefix(), reqPath, pathPrefixAuth.getJwkServiceIds())) { - // verification is passed, go to the next handler in the chain. - break; + if(pathPrefixAuth.isJwt()) { + JwtVerifyHandler handler = (JwtVerifyHandler) handlers.get(JWT); + if (handler == null) { + logger.error("Cannot find JwtVerifyHandler with alias name jwt."); + setExchangeStatus(exchange, HANDLER_NOT_FOUND, "com.networknt.openapi.JwtVerifyHandler@jwt"); + exchange.endExchange(); + return; } else { - // verification is not passed and an error is returned. Don't call the next handler. + // get the jwkServiceIds list. + if (handler.handleJwt(exchange, pathPrefixAuth.getPathPrefix(), reqPath, pathPrefixAuth.getJwkServiceIds())) { + // verification is passed, go to the next handler in the chain. + break; + } else { + // verification is not passed and an error is returned. Don't call the next handler. + return; + } + } + } else { + // this must be swt token + SwtVerifyHandler handler = (SwtVerifyHandler) handlers.get(SWT); + if (handler == null) { + logger.error("Cannot find SwtVerifyHandler with alias name swt."); + setExchangeStatus(exchange, HANDLER_NOT_FOUND, "com.networknt.openapi.SwtVerifyHandler@swt"); + exchange.endExchange(); return; + } else { + // get the jwkServiceIds list. + if (handler.handleSwt(exchange, reqPath, pathPrefixAuth.getJwkServiceIds())) { + // verification is passed, go to the next handler in the chain. + break; + } else { + // verification is not passed and an error is returned. Don't call the next handler. + return; + } } } - } else { String s = authorization.length() > 10 ? authorization.substring(0, 10) : authorization; logger.error("Invalid/Unsupported authorization header {}", s); diff --git a/openapi-security/src/main/resources/config/openapi-security.yml b/openapi-security/src/main/resources/config/openapi-security.yml index 57e969fa..a0ec40c9 100644 --- a/openapi-security/src/main/resources/config/openapi-security.yml +++ b/openapi-security/src/main/resources/config/openapi-security.yml @@ -9,6 +9,12 @@ # request path prefix in skipPathPrefixes. enableVerifyJwt: ${openapi-security.enableVerifyJwt:true} +# Enable the SWT verification flag. The SwtVerifierHandler will skip the SWT token verification +# if this flag is false. It should only be set to false on the dev environment for testing +# purposes. If you have some endpoints that want to skip the SWT verification, you can put the +# request path prefix in skipPathPrefixes. +enableVerifySwt: ${openapi-security.enableVerifySwt:true} + # Extract JWT scope token from the X-Scope-Token header and validate the JWT token enableExtractScopeToken: ${openapi-security.enableExtractScopeToken:true} @@ -27,10 +33,6 @@ enableVerifyScope: ${openapi-security.enableVerifyScope:true} # doesn't have a specification to retrieve the defined scopes, the handler will skip the scope verification. skipVerifyScopeWithoutSpec: ${openapi-security.skipVerifyScopeWithoutSpec:false} -# Enable JWT scope verification. -# Only valid when (enableVerifyJwt is true) AND (enableVerifyScope is true) -enableVerifyJwtScopeToken: ${openapi-security.enableVerifyJwtScopeToken:true} - # If set true, the JWT verifier handler will pass if the JWT token is expired already. Unless # you have a strong reason, please use it only on the dev environment if your OAuth 2 provider # doesn't support long-lived token for dev environment or test automation. diff --git a/openapi-security/src/test/java/com/networknt/openapi/SecurityConfigTest.java b/openapi-security/src/test/java/com/networknt/openapi/SecurityConfigTest.java index d0262c68..44556ff7 100644 --- a/openapi-security/src/test/java/com/networknt/openapi/SecurityConfigTest.java +++ b/openapi-security/src/test/java/com/networknt/openapi/SecurityConfigTest.java @@ -9,7 +9,6 @@ public class SecurityConfigTest { public void testLoadConfig() { SecurityConfig config = SecurityConfig.load("openapi-security"); Assert.assertTrue(config.isEnableVerifyJwt()); - Assert.assertFalse(config.isEnableVerifyJwtScopeToken()); Assert.assertEquals(2, config.getCertificate().size()); } diff --git a/openapi-security/src/test/java/com/networknt/openapi/SwtVerifyHandlerTest.java b/openapi-security/src/test/java/com/networknt/openapi/SwtVerifyHandlerTest.java new file mode 100644 index 00000000..f0b61ffd --- /dev/null +++ b/openapi-security/src/test/java/com/networknt/openapi/SwtVerifyHandlerTest.java @@ -0,0 +1,306 @@ +package com.networknt.openapi; + +import com.networknt.body.BodyConverter; +import com.networknt.client.Http2Client; +import com.networknt.config.Config; +import com.networknt.exception.ClientException; +import com.networknt.httpstring.HttpStringConstants; +import com.networknt.status.Status; +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.client.ClientConnection; +import io.undertow.client.ClientRequest; +import io.undertow.client.ClientResponse; +import io.undertow.server.HttpHandler; +import io.undertow.server.RoutingHandler; +import io.undertow.server.handlers.form.FormData; +import io.undertow.server.handlers.form.FormDataParser; +import io.undertow.server.handlers.form.FormParserFactory; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; +import org.apache.commons.text.StringEscapeUtils; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xnio.IoUtils; +import org.xnio.OptionMap; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +public class SwtVerifyHandlerTest { + static final Logger logger = LoggerFactory.getLogger(SwtVerifyHandlerTest.class); + + static Undertow server = null; + + @BeforeClass + public static void setUp() { + if (server == null) { + logger.info("starting server"); + HttpHandler handler = getTestHandler(); + SwtVerifyHandler swtVerifyHandler = new SwtVerifyHandler(); + swtVerifyHandler.setNext(handler); + OpenApiHandler openApiHandler = new OpenApiHandler(); + openApiHandler.setNext(swtVerifyHandler); + server = Undertow.builder() + .addHttpListener(7080, "localhost") + .setHandler(openApiHandler) + .build(); + server.start(); + } + } + + @AfterClass + public static void tearDown() throws Exception { + if (server != null) { + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + + } + server.stop(); + logger.info("The server is stopped."); + } + } + + static RoutingHandler getTestHandler() { + return Handlers.routing() + .add(Methods.POST, "/oauth/introspection", exchange -> { + FormParserFactory formParserFactory = FormParserFactory.builder().build(); + FormDataParser parser = formParserFactory.createParser(exchange); + String token = null; + if (parser != null) { + FormData formData = parser.parseBlocking(); + token = (String)BodyConverter.convert(formData).get("token"); + } + logger.trace("token = " + token); + String responseBody = "{\"active\":false}"; + switch (token) { + case "valid_token": + responseBody = "{ \"active\": true,\"client_id\": \"bb8293f6-ceef-4e7a-90c8-1492e97df19f\",\"token_type\": \"refresh_token\",\"scope\": \"openid profile read:pets\",\"sub\": \"cn=odicuser,dc=example,dc=com\",\"exp\": \"86400\",\"iat\": \"1506513918\",\"iss\": \"https://wa.example.com\" }"; + break; + case "invalid_scope": + responseBody = "{ \"active\": true,\"client_id\": \"bb8293f6-ceef-4e7a-90c8-1492e97df19f\",\"token_type\": \"refresh_token\",\"scope\": \"openid profile\",\"sub\": \"cn=odicuser,dc=example,dc=com\",\"exp\": \"86400\",\"iat\": \"1506513918\",\"iss\": \"https://wa.example.com\" }"; + break; + } + exchange.getResponseHeaders().add(new HttpString("Content-Type"), "application/json"); + exchange.getResponseSender().send(responseBody); + }) + .add(Methods.GET, "/v1/pets/{petId}", exchange -> { + Map examples = new HashMap<>(); + examples.put("application/xml", StringEscapeUtils.unescapeHtml4("<Pet> <id>123456</id> <name>doggie</name> <photoUrls> <photoUrls>string</photoUrls> </photoUrls> <tags> </tags> <status>string</status></Pet>")); + examples.put("application/json", StringEscapeUtils.unescapeHtml4("{ "photoUrls" : [ "aeiou" ], "name" : "doggie", "id" : 123456789, "category" : { "name" : "aeiou", "id" : 123456789 }, "tags" : [ { "name" : "aeiou", "id" : 123456789 } ], "status" : "aeiou"}")); + if (examples.size() > 0) { + exchange.getResponseHeaders().add(new HttpString("Content-Type"), "application/json"); + exchange.getResponseSender().send((String) examples.get("application/json")); + } else { + exchange.endExchange(); + } + }) + .add(Methods.GET, "/v1/pets", exchange -> exchange.getResponseSender().send("get")); + } + + @Test + public void testWithCorrectScopeInIdToken() throws Exception { + final Http2Client client = Http2Client.getInstance(); + final CountDownLatch latch = new CountDownLatch(1); + final ClientConnection connection; + try { + connection = client.connect(new URI("http://localhost:7080"), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.EMPTY).get(); + } catch (Exception e) { + throw new ClientException(e); + } + final AtomicReference reference = new AtomicReference<>(); + try { + ClientRequest request = new ClientRequest().setPath("/v1/pets/111").setMethod(Methods.GET); + request.getRequestHeaders().put(Headers.HOST, "localhost"); + request.getRequestHeaders().put(Headers.AUTHORIZATION, "Bearer valid_token"); + connection.sendRequest(request, client.createClientCallback(reference, latch)); + latch.await(); + } catch (Exception e) { + logger.error("Exception: ", e); + throw new ClientException(e); + } finally { + IoUtils.safeClose(connection); + } + int statusCode = reference.get().getResponseCode(); + Assert.assertEquals(200, statusCode); + if (statusCode == 200) { + Assert.assertNotNull(reference.get().getAttachment(Http2Client.RESPONSE_BODY)); + } + } + + @Test + public void testUnmatchedScopeInIdToken() throws Exception { + final Http2Client client = Http2Client.getInstance(); + final CountDownLatch latch = new CountDownLatch(1); + final ClientConnection connection; + try { + connection = client.connect(new URI("http://localhost:7080"), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.EMPTY).get(); + } catch (Exception e) { + throw new ClientException(e); + } + final AtomicReference reference = new AtomicReference<>(); + try { + ClientRequest request = new ClientRequest().setPath("/v1/pets/111").setMethod(Methods.GET); + request.getRequestHeaders().put(Headers.HOST, "localhost"); + request.getRequestHeaders().put(Headers.AUTHORIZATION, "Bearer invalid_scope"); + connection.sendRequest(request, client.createClientCallback(reference, latch)); + latch.await(); + } catch (Exception e) { + logger.error("Exception: ", e); + throw new ClientException(e); + } finally { + IoUtils.safeClose(connection); + } + int statusCode = reference.get().getResponseCode(); + Assert.assertEquals(403, statusCode); + if (statusCode == 403) { + Status status = Config.getInstance().getMapper().readValue(reference.get().getAttachment(Http2Client.RESPONSE_BODY), Status.class); + Assert.assertNotNull(status); + Assert.assertEquals("ERR10005", status.getCode()); + } + } + + @Test + public void testWithCorrectScopeInScopeToken() throws Exception { + final Http2Client client = Http2Client.getInstance(); + final CountDownLatch latch = new CountDownLatch(1); + final ClientConnection connection; + try { + connection = client.connect(new URI("http://localhost:7080"), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.EMPTY).get(); + } catch (Exception e) { + throw new ClientException(e); + } + final AtomicReference reference = new AtomicReference<>(); + try { + ClientRequest request = new ClientRequest().setPath("/v1/pets/111").setMethod(Methods.GET); + request.getRequestHeaders().put(Headers.HOST, "localhost"); + request.getRequestHeaders().put(Headers.AUTHORIZATION, "Bearer invalid_scope"); + request.getRequestHeaders().put(HttpStringConstants.SCOPE_TOKEN, "Bearer valid_token"); + connection.sendRequest(request, client.createClientCallback(reference, latch)); + latch.await(); + } catch (Exception e) { + logger.error("Exception: ", e); + throw new ClientException(e); + } finally { + IoUtils.safeClose(connection); + } + int statusCode = reference.get().getResponseCode(); + Assert.assertEquals(200, statusCode); + if (statusCode == 200) { + Assert.assertNotNull(reference.get().getAttachment(Http2Client.RESPONSE_BODY)); + } + } + + @Test + public void testUnmatchedScopeInScopeToken() throws Exception { + final Http2Client client = Http2Client.getInstance(); + final CountDownLatch latch = new CountDownLatch(1); + final ClientConnection connection; + try { + connection = client.connect(new URI("http://localhost:7080"), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.EMPTY).get(); + } catch (Exception e) { + throw new ClientException(e); + } + final AtomicReference reference = new AtomicReference<>(); + try { + ClientRequest request = new ClientRequest().setPath("/v1/pets/111").setMethod(Methods.GET); + request.getRequestHeaders().put(Headers.HOST, "localhost"); + request.getRequestHeaders().put(Headers.AUTHORIZATION, "Bearer invalid_scope"); + request.getRequestHeaders().put(HttpStringConstants.SCOPE_TOKEN, "Bearer invalid_scope"); + connection.sendRequest(request, client.createClientCallback(reference, latch)); + latch.await(); + } catch (Exception e) { + logger.error("Exception: ", e); + throw new ClientException(e); + } finally { + IoUtils.safeClose(connection); + } + int statusCode = reference.get().getResponseCode(); + Assert.assertEquals(403, statusCode); + if (statusCode == 403) { + Status status = Config.getInstance().getMapper().readValue(reference.get().getAttachment(Http2Client.RESPONSE_BODY), Status.class); + Assert.assertNotNull(status); + Assert.assertEquals("ERR10006", status.getCode()); + } + } + + @Test + public void testH2CDisabledRequest() throws Exception { + final Http2Client client = Http2Client.getInstance(); + final CountDownLatch latch = new CountDownLatch(1); + final ClientConnection connection; + try { + connection = client.connect(new URI("http://localhost:7080"), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.EMPTY).get(); + } catch (Exception e) { + throw new ClientException(e); + } + final AtomicReference reference = new AtomicReference<>(); + try { + ClientRequest request = new ClientRequest().setPath("/v1/pets/111").setMethod(Methods.GET); + request.getRequestHeaders().put(Headers.HOST, "localhost"); + request.getRequestHeaders().put(Headers.AUTHORIZATION, "Bearer valid_token"); + request.getRequestHeaders().put(HttpStringConstants.SCOPE_TOKEN, "Bearer valid_token"); + request.getRequestHeaders().put(Headers.CONNECTION, "upgrade"); + request.getRequestHeaders().put(Headers.UPGRADE, "foo/2"); + connection.sendRequest(request, client.createClientCallback(reference, latch)); + latch.await(); + } catch (Exception e) { + logger.error("Exception: ", e); + throw new ClientException(e); + } finally { + IoUtils.safeClose(connection); + } + int statusCode = reference.get().getResponseCode(); + Assert.assertEquals(405, statusCode); + if (statusCode == 405) { + Status status = Config.getInstance().getMapper().readValue(reference.get().getAttachment(Http2Client.RESPONSE_BODY), Status.class); + Assert.assertNotNull(status); + Assert.assertEquals("ERR10008", status.getCode()); + } + } + + @Test + public void testEmptyAuthorizationHeader() throws Exception { + final Http2Client client = Http2Client.getInstance(); + final CountDownLatch latch = new CountDownLatch(1); + final ClientConnection connection; + try { + connection = client.connect(new URI("http://localhost:7080"), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, OptionMap.EMPTY).get(); + } catch (Exception e) { + throw new ClientException(e); + } + final AtomicReference reference = new AtomicReference<>(); + try { + ClientRequest request = new ClientRequest().setPath("/v1/pets/111").setMethod(Methods.GET); + request.getRequestHeaders().put(Headers.HOST, "localhost"); + request.getRequestHeaders().put(Headers.AUTHORIZATION, ""); + connection.sendRequest(request, client.createClientCallback(reference, latch)); + latch.await(); + } catch (Exception e) { + logger.error("Exception: ", e); + throw new ClientException(e); + } finally { + IoUtils.safeClose(connection); + } + int statusCode = reference.get().getResponseCode(); + logger.debug("statusCode = " + statusCode); + String responseBody = reference.get().getAttachment(Http2Client.RESPONSE_BODY); + logger.debug("responseBody = " + responseBody); + Assert.assertEquals(401, statusCode); + if (statusCode == 401) { + Status status = Config.getInstance().getMapper().readValue(responseBody, Status.class); + Assert.assertNotNull(status); + Assert.assertEquals("ERR10000", status.getCode()); + } + } + +} diff --git a/openapi-security/src/test/resources/config/openapi-security.yml b/openapi-security/src/test/resources/config/openapi-security.yml deleted file mode 100644 index 7f920c2c..00000000 --- a/openapi-security/src/test/resources/config/openapi-security.yml +++ /dev/null @@ -1,81 +0,0 @@ -# Security configuration for openapi-security in light-rest-4j. It is a specific config -# for OpenAPI framework security. It is introduced to support multiple frameworks in the -# same server instance. If this file cannot be found, the generic security.yml will be -# loaded for backward compatibility. ---- -# Enable the JWT verification flag. The JwtVerifierHandler will skip the JWT token verification -# if this flag is false. It should only be set to false on the dev environment for testing -# purposes. If you have some endpoints that want to skip the JWT verification, you can put the -# request path prefix in skipPathPrefixes. -enableVerifyJwt: ${openapi-security.enableVerifyJwt:true} - -# Extract JWT scope token from the X-Scope-Token header and validate the JWT token -enableExtractScopeToken: ${openapi-security.enableExtractScopeToken:true} - -# Enable JWT scope verification. This flag is valid when enableVerifyJwt is true. When using the -# light gateway as a centralized gateway without backend API specifications, you can still enable -# this flag to allow the admin endpoints to have scopes verified. And all backend APIs without -# specifications skip the scope verification if the spec does not exist with the skipVerifyScopeWithoutSpec -# flag to true. Also, you need to have the openapi.yml specification file in the config folder to -# enable it, as the scope verification compares the scope from the JWT token and the scope in the -# endpoint specification. -enableVerifyScope: ${openapi-security.enableVerifyScope:true} - -# Users should only use this flag in a shared light gateway if the backend API specifications are -# unavailable in the gateway config folder. If this flag is true and the enableVerifyScope is true, -# the security handler will invoke the scope verification for all endpoints. However, if the endpoint -# doesn't have a specification to retrieve the defined scopes, the handler will skip the scope verification. -skipVerifyScopeWithoutSpec: ${openapi-security.skipVerifyScopeWithoutSpec:false} - -# Enable JWT scope verification. -# Only valid when (enableVerifyJwt is true) AND (enableVerifyScope is true) -enableVerifyJwtScopeToken: ${openapi-security.enableVerifyJwtScopeToken:true} - -# If set true, the JWT verifier handler will pass if the JWT token is expired already. Unless -# you have a strong reason, please use it only on the dev environment if your OAuth 2 provider -# doesn't support long-lived token for dev environment or test automation. -ignoreJwtExpiry: ${security.ignoreJwtExpiry:false} - -# User for test only. should be always be false on official environment. -enableMockJwt: ${openapi-security.enableMockJwt:false} - -# JWT signature public certificates. kid and certificate path mappings. -jwt: - certificate: ${openapi-security.certificate:100=primary.crt&101=secondary.crt} -# '100': primary.crt -# '101': secondary.crt - clockSkewInSeconds: ${openapi-security.clockSkewInSeconds:60} - # Key distribution server standard: JsonWebKeySet for other OAuth 2.0 provider| X509Certificate for light-oauth2 - keyResolver: ${openapi-security.keyResolver:X509Certificate} - -# Enable or disable JWT token logging -logJwtToken: ${openapi-security.logJwtToken:true} - -# Enable or disable client_id, user_id and scope logging. -logClientUserScope: ${openapi-security.logClientUserScope:false} - -# Enable JWT token cache to speed up verification. This will only verify expired time -# and skip the signature verification as it takes more CPU power and a long time. If -# each request has a different jwt token, like authorization code flow, this indicator -# should be turned off. Otherwise, the cached jwt will only be removed after 15 minutes -# and the cache can grow bigger if the number of requests is very high. This will cause -# memory kill in a Kubernetes pod if the memory setting is limited. -enableJwtCache: ${openapi-security.enableJwtCache:true} - -# If enableJwtCache is true, then an error message will be shown up in the log if the -# cache size is bigger than the jwtCacheFullSize. This helps the developers to detect -# cache problem if many distinct tokens flood the cache in a short period of time. If -# you see JWT cache exceeds the size limit in logs, you need to turn off the enableJwtCache -# or increase the cache full size to a bigger number from the default 100. -jwtCacheFullSize: ${openapi-security.jwtCacheFullSize:100} - -# If you are using light-oauth2, then you don't need to have oauth subfolder for public -# key certificate to verify JWT token, the key will be retrieved from key endpoint once -# the first token is arrived. Default to false for dev environment without oauth2 server -# or official environment that use other OAuth 2.0 providers. -bootstrapFromKeyService: ${openapi-security.bootstrapFromKeyService:false} - -# Used in light-oauth2 and oauth-kafka key service for federated deployment. Each instance -# will have a providerId, and it will be part of the kid to allow each instance to get the -# JWK from other instance based on the providerId in the kid. -providerId: ${openapi-security.providerId:} diff --git a/openapi-security/src/test/resources/config/openapi.yaml b/openapi-security/src/test/resources/config/openapi.yaml index cdc13c10..e77aa1eb 100644 --- a/openapi-security/src/test/resources/config/openapi.yaml +++ b/openapi-security/src/test/resources/config/openapi.yaml @@ -167,6 +167,26 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /oauth/introspection: + post: + summary: Introspect a token for SwtVerifyHandler + operationId: introspectToken + tags: + - token + security: + - petstore_auth: + - read:pets + - write:pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + components: securitySchemes: petstore_auth: diff --git a/openapi-security/src/test/resources/config/unified-security.yml b/openapi-security/src/test/resources/config/unified-security.yml index 6be44c04..b39349bf 100644 --- a/openapi-security/src/test/resources/config/unified-security.yml +++ b/openapi-security/src/test/resources/config/unified-security.yml @@ -6,8 +6,8 @@ enabled: ${unified-security.enabled:true} # first, and if any path is matched, all other security checks will be bypassed, and the request goes to # the next handler in the chain. You can use json array or string separated by comma or YAML format. anonymousPrefixes: - - /v1/dogs - /v1/cats + - /v1/dogs pathPrefixAuths: - prefix: /v1/salesforce @@ -22,4 +22,4 @@ pathPrefixAuths: - prefix: /v1/test1 apikey: true - prefix: /v1/pets - jwt: true \ No newline at end of file + jwt: true diff --git a/openapi-security/src/test/resources/config/values.yml b/openapi-security/src/test/resources/config/values.yml index 2bdf4f41..74d794c2 100644 --- a/openapi-security/src/test/resources/config/values.yml +++ b/openapi-security/src/test/resources/config/values.yml @@ -10,6 +10,7 @@ handler.basePath: / handler.handlers: - com.networknt.openapi.OpenApiHandler@specification - com.networknt.openapi.JwtVerifyHandler@jwt + - com.networknt.openapi.SwtVerifyHandler@swt - com.networknt.basicauth.BasicAuthHandler@basic - com.networknt.openapi.UnifiedSecurityHandler@unified - com.networknt.apikey.ApiKeyHandler@apikey @@ -40,3 +41,14 @@ apikey.pathPrefixAuths: - pathPrefix: /v1/test2 headerName: x-apikey apiKey: CRYPT:08eXg9TmK604+w06RaBlsPQbplU1F1Ez5pkBO/hNr8w= + +# client.yml +client.tokenKeyServerUrl: http://localhost:7080 +client.tokenKeyUri: /oauth/introspection +client.tokenKeyClientId: f7d42348-c647-4efb-a52d-4c5787421e72 +client.tokenKeyClientSecret: f6h1FTI8Q3-7UScPZDzfXA +client.tokenKeyEnableHttp2: true + +# openapi-security.yml +openapi-security.skipPathPrefixes: + - /oauth/introspection \ No newline at end of file