This is an automated email from the ASF dual-hosted git repository.
smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 71a72e5e0 KNOX-3259 - Implement OAuth Refresh Token and Exchange Token
Grant Types (#1155)
71a72e5e0 is described below
commit 71a72e5e0697fa6331876de8e341caeb93b84cc4
Author: lmccay <[email protected]>
AuthorDate: Tue Feb 24 03:03:29 2026 -0500
KNOX-3259 - Implement OAuth Refresh Token and Exchange Token Grant Types
(#1155)
---
.../federation/jwt/filter/JWTFederationFilter.java | 39 +++++--
...st.java => OAuthFlowsFederationFilterTest.java} | 126 ++++++++++++++++++++-
2 files changed, 157 insertions(+), 8 deletions(-)
diff --git
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
index e3b0e9f2f..174928265 100644
---
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
+++
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
@@ -65,6 +65,10 @@ public class JWTFederationFilter extends AbstractJWTFilter {
public static final String CLIENT_SECRET = "client_secret";
public static final String CLIENT_ID = "client_id";
public static final String MISMATCHING_CLIENT_ID_AND_CLIENT_SECRET = "Client
credentials flow with mismatching client_id and client_secret";
+ public static final String REFRESH_TOKEN = "refresh_token";
+ public static final String REFRESH_TOKEN_PARAM = "refresh_token";
+ public static final String TOKEN_EXCHANGE = "token_exchange";
+ public static final String SUBJECT_TOKEN = "subject_token";
public enum TokenType {
JWT, Passcode;
@@ -308,7 +312,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
}
if (parsed == null) {
- parsed = parseFromClientCredentialsFlow(request);
+ parsed = parseFromGrantTypeFlow(request);
}
if (parsed == null) {
@@ -321,7 +325,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
return parsed;
}
- private Pair<TokenType, String>
parseFromClientCredentialsFlow(ServletRequest request) throws IOException {
+ private Pair<TokenType, String> parseFromGrantTypeFlow(ServletRequest
request) throws IOException {
/*
POST /{tenant}/oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com:443
@@ -338,18 +342,39 @@ public class JWTFederationFilter extends
AbstractJWTFilter {
if (clientSecretPresentAsQueryString) {
throw new SecurityException("client_secret must not be sent as a query
parameter");
}
- return getClientCredentialsFromRequestBody(request);
+ return getTokenFromRequestBody(request);
}
- private Pair<TokenType, String>
getClientCredentialsFromRequestBody(ServletRequest request) {
+ private Pair<TokenType, String> getTokenFromRequestBody(ServletRequest
request) {
final String grantType = request.getParameter(GRANT_TYPE);
if (CLIENT_CREDENTIALS.equals(grantType)) {
- // this is indeed a client credentials flow client_id and
- // client_secret are expected now the client_id will be in
- // the token as the token_id so we will get that later
+ // client credentials flow: client_id and client_secret are expected
+ // the client_id will be in the token as the token_id
final String clientSecret = request.getParameter(CLIENT_SECRET);
validateClientID((HttpServletRequest) request, clientSecret);
return Pair.of(TokenType.Passcode, clientSecret);
+ } else if (REFRESH_TOKEN.equals(grantType)) {
+ // refresh_token flow: the refresh_token parameter contains the
actual token
+ final String refreshToken =
request.getParameter(REFRESH_TOKEN_PARAM);
+ if (refreshToken != null) {
+ // determine if it's a JWT or passcode token
+ if (isJWT(refreshToken)) {
+ return Pair.of(TokenType.JWT, refreshToken);
+ } else {
+ return Pair.of(TokenType.Passcode, refreshToken);
+ }
+ }
+ } else if (TOKEN_EXCHANGE.equals(grantType)) {
+ // token_exchange flow: the subject_token parameter contains the
token to be exchanged
+ final String subjectToken = request.getParameter(SUBJECT_TOKEN);
+ if (subjectToken != null) {
+ // determine if it's a JWT or passcode token
+ if (isJWT(subjectToken)) {
+ return Pair.of(TokenType.JWT, subjectToken);
+ } else {
+ return Pair.of(TokenType.Passcode, subjectToken);
+ }
+ }
}
return null;
}
diff --git
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/ClientIdAndClientSecretFederationFilterTest.java
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
similarity index 62%
rename from
gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/ClientIdAndClientSecretFederationFilterTest.java
rename to
gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
index 710604a7e..064be121a 100644
---
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/ClientIdAndClientSecretFederationFilterTest.java
+++
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
@@ -38,7 +38,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-public class ClientIdAndClientSecretFederationFilterTest extends
TokenIDAsHTTPBasicCredsFederationFilterTest {
+public class OAuthFlowsFederationFilterTest extends
TokenIDAsHTTPBasicCredsFederationFilterTest {
@Override
protected void setTokenOnRequest(HttpServletRequest request, String
authUsername, String authPassword) {
EasyMock.expect((Object)request.getHeader("Authorization")).andReturn("");
@@ -243,4 +243,128 @@ public class ClientIdAndClientSecretFederationFilterTest
extends TokenIDAsHTTPBa
@Test
public void testUnableToParseJWT() throws Exception {
}
+
+ @Test
+ public void testGetWireTokenUsingRefreshTokenFlow() throws Exception {
+ final String refreshToken =
"WTJ4cFpXNTBMV2xrTFRFeU16UTE6OlkyeHBaVzUwTFhObFkzSmxkQzB4TWpNME5RPT0=";
+
+ final HttpServletRequest request =
EasyMock.createNiceMock(HttpServletRequest.class);
+
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+
EasyMock.expect(request.getParameter(JWTFederationFilter.GRANT_TYPE)).andReturn(JWTFederationFilter.REFRESH_TOKEN).anyTimes();
+
EasyMock.expect(request.getParameter(JWTFederationFilter.REFRESH_TOKEN_PARAM)).andReturn(refreshToken).anyTimes();
+ EasyMock.replay(request);
+
+ handler.init(new TestFilterConfig(getProperties()));
+ final Pair<TokenType, String> wireToken = ((TestJWTFederationFilter)
handler).getWireToken(request);
+
+ EasyMock.verify(request);
+
+ assertNotNull(wireToken);
+ assertEquals(TokenType.Passcode, wireToken.getLeft());
+ assertEquals(refreshToken, wireToken.getRight());
+ }
+
+ @Test
+ public void testGetWireTokenUsingTokenExchangeFlow() throws Exception {
+ final String subjectToken =
"WTJ4cFpXNTBMV2xrTFRFeU16UTE2OlkyeHBaVzUwTFhObFkzSmxkQzB4TWpNME5RPT0=";
+
+ final HttpServletRequest request =
EasyMock.createNiceMock(HttpServletRequest.class);
+
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+
EasyMock.expect(request.getParameter(JWTFederationFilter.GRANT_TYPE)).andReturn(JWTFederationFilter.TOKEN_EXCHANGE).anyTimes();
+
EasyMock.expect(request.getParameter(JWTFederationFilter.SUBJECT_TOKEN)).andReturn(subjectToken).anyTimes();
+ EasyMock.replay(request);
+
+ handler.init(new TestFilterConfig(getProperties()));
+ final Pair<TokenType, String> wireToken = ((TestJWTFederationFilter)
handler).getWireToken(request);
+
+ EasyMock.verify(request);
+
+ assertNotNull(wireToken);
+ assertEquals(TokenType.Passcode, wireToken.getLeft());
+ assertEquals(subjectToken, wireToken.getRight());
+ }
+
+ @Test
+ public void testVerifyRefreshTokenFlow() throws Exception {
+ final String topologyName = "jwt-topology";
+ final String tokenId = "4e0c548b-6568-4061-a3dc-62908087650b";
+ final String passcode = "0138aaed-ca2a-47f1-8ed8-e0c397596f96";
+ String passcodeToken =
"TkdVd1l6VTBPR0l0TmpVMk9DMDBNRFl4TFdFelpHTXROakk1TURnd09EYzJOVEJpOjpNREV6T0dGaFpXUXRZMkV5WVMwME4yWXhMVGhsWkRndFpUQmpNemszTlRrMlpqazI=";
+
+ final TokenStateService tokenStateService =
EasyMock.createNiceMock(TokenStateService.class);
+
EasyMock.expect(tokenStateService.getTokenExpiration(tokenId)).andReturn(Long.MAX_VALUE).anyTimes();
+
+ final TokenMetadata tokenMetadata =
EasyMock.createNiceMock(TokenMetadata.class);
+ EasyMock.expect(tokenMetadata.isEnabled()).andReturn(true).anyTimes();
+
EasyMock.expect(tokenMetadata.getPasscode()).andReturn(passcodeToken).anyTimes();
+
EasyMock.expect(tokenStateService.getTokenMetadata(EasyMock.anyString())).andReturn(tokenMetadata).anyTimes();
+
+ final Properties filterConfigProps = getProperties();
+ filterConfigProps.put(TokenStateService.CONFIG_SERVER_MANAGED,
Boolean.toString(true));
+ filterConfigProps.put(TestFilterConfig.TOPOLOGY_NAME_PROP,
topologyName);
+ final FilterConfig filterConfig = new
TestFilterConfig(filterConfigProps, tokenStateService);
+ handler.init(filterConfig);
+
+ final HttpServletRequest request =
EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(request.getRequestURL()).andReturn(new
StringBuffer(SERVICE_URL)).anyTimes();
+
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+
EasyMock.expect(request.getParameter(JWTFederationFilter.GRANT_TYPE)).andReturn(JWTFederationFilter.REFRESH_TOKEN).anyTimes();
+
EasyMock.expect(request.getParameter(JWTFederationFilter.REFRESH_TOKEN_PARAM)).andReturn(passcodeToken).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+
+ final HttpServletResponse response =
EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.replay(tokenStateService, tokenMetadata, request, response);
+
+ SignatureVerificationCache.getInstance(topologyName,
filterConfig).recordSignatureVerification(passcode);
+
+ final TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+
+ EasyMock.verify(response);
+ Assert.assertTrue(chain.doFilterCalled);
+ Assert.assertNotNull(chain.subject);
+ }
+
+ @Test
+ public void testVerifyTokenExchangeFlow() throws Exception {
+ final String topologyName = "jwt-topology";
+ final String tokenId = "4e0c548b-6568-4061-a3dc-62908087650c";
+ final String passcode = "0138aaed-ca2a-47f1-8ed8-e0c397596f97";
+ String passcodeToken =
"TkdVd1l6VTBPR0l0TmpVMk9DMDBNRFl4TFdFelpHTXROakk1TURnd09EYzJOVEJqOjpNREV6T0dGaFpXUXRZMkV5WVMwME4yWXhMVGhsWkRndFpUQmpNemszTlRrMlpqazM=";
+
+ final TokenStateService tokenStateService =
EasyMock.createNiceMock(TokenStateService.class);
+
EasyMock.expect(tokenStateService.getTokenExpiration(tokenId)).andReturn(Long.MAX_VALUE).anyTimes();
+
+ final TokenMetadata tokenMetadata =
EasyMock.createNiceMock(TokenMetadata.class);
+ EasyMock.expect(tokenMetadata.isEnabled()).andReturn(true).anyTimes();
+
EasyMock.expect(tokenMetadata.getPasscode()).andReturn(passcodeToken).anyTimes();
+
EasyMock.expect(tokenStateService.getTokenMetadata(EasyMock.anyString())).andReturn(tokenMetadata).anyTimes();
+
+ final Properties filterConfigProps = getProperties();
+ filterConfigProps.put(TokenStateService.CONFIG_SERVER_MANAGED,
Boolean.toString(true));
+ filterConfigProps.put(TestFilterConfig.TOPOLOGY_NAME_PROP,
topologyName);
+ final FilterConfig filterConfig = new
TestFilterConfig(filterConfigProps, tokenStateService);
+ handler.init(filterConfig);
+
+ final HttpServletRequest request =
EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(request.getRequestURL()).andReturn(new
StringBuffer(SERVICE_URL)).anyTimes();
+
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+
EasyMock.expect(request.getParameter(JWTFederationFilter.GRANT_TYPE)).andReturn(JWTFederationFilter.TOKEN_EXCHANGE).anyTimes();
+
EasyMock.expect(request.getParameter(JWTFederationFilter.SUBJECT_TOKEN)).andReturn(passcodeToken).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+
+ final HttpServletResponse response =
EasyMock.createNiceMock(HttpServletResponse.class);
+ EasyMock.replay(tokenStateService, tokenMetadata, request, response);
+
+ SignatureVerificationCache.getInstance(topologyName,
filterConfig).recordSignatureVerification(passcode);
+
+ final TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+
+ EasyMock.verify(response);
+ Assert.assertTrue(chain.doFilterCalled);
+ Assert.assertNotNull(chain.subject);
+ }
}