是否可以验证从 WSO2 中的外部 OAuth2 服务器创建的 JWT 令牌?
Is it possible to validate a JWT token created from an external OAuth2 Server in WSO2?
我有一个 Spring 安全 OAuth2 服务器,它为前端应用程序生成 JWT 令牌。
此令牌将在对后端的调用中发送,通过 API 网关(WSO2 API 管理器)。
我想要的是在 WSO2 中注册后端 APIs 并能够验证外部生成的 JWT 令牌。
这可能吗?您能否提供 WSO2 APIM 需要配置以包含此逻辑的不同位置的示例?
注意:WSO2 永远不需要创建令牌,它之前总是已经创建,只需要验证它。
经过大量试验和错误以及 Whosebug 的一些帮助(感谢 Bee),这是我的工作解决方案。我希望它能帮助其他人,因为让它工作真的很棘手:
1.实施 JWTAuthHandler 以验证 JWT 令牌:
public class JwtAuthHandler extends AbstractHandler {
private final PublicKeyFactory pkf = new PublicKeyFactory();
private final JwtVerifier jwtVerifier = new JwtVerifier();
@Override
public boolean handleRequest(MessageContext messageContext) {
try {
final String jwtToken = getJwtTokenFromHeaders(messageContext).replace("Bearer ", "");
SignedJWT signedJwt = SignedJWT.parse(jwtToken);
final JSONObject payload = signedJwt.getPayload().toJSONObject();
final JSONObject environment = (JSONObject)payload.get("environment");
PublicKey publicKey = readPublicKey();
JWSVerifier verifier = new RSASSAVerifier(((RSAPublicKey) publicKey));
final boolean signatureVerification = signedJwt.verify(verifier)
if (signatureVerification) {
AuthenticationContext authContext = new AuthenticationContext();
authContext.setAuthenticated(true);
if (isProductionRequest(environment)) {
authContext.setKeyType(APIConstants.API_KEY_TYPE_PRODUCTION);
} else {
authContext.setKeyType(APIConstants.API_KEY_TYPE_SANDBOX);
}
APISecurityUtils.setAuthenticationContext(messageContext, authContext, "Authorization");
} else {
LOG.debug("handleRequest() - Sending 401 Unauthorized");
Utils.sendFault(messageContext, 401);
}
return signatureVerification;
} catch (Exception e) {
e.printStackTrace();
Utils.sendFault(messageContext, 500);
return false;
}
}
@Override
public boolean handleResponse(MessageContext messageContext) {
return true;
}
private String getJwtTokenFromHeaders(MessageContext messageContext) {
Map headers = (Map) ((Axis2MessageContext) messageContext).getAxis2MessageContext().
getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
return (String) headers.get("Authorization");
}
private boolean isProductionRequest(JSONObject environment) {
return environment != null && environment.equals("pro");
}
}
2。覆盖您的 API 定义(/repository/deployment/server/synapse-configs/default/api/yourapi.xml
)以使用 jwt 处理程序并删除 APIAuthenticationHandler 和 ThrottleHandler(后者需要删除,因为非 oauth2- 的一个众所周知的错误经过身份验证的 API):
应该是这样的:
<handlers>
<handler class="com.codependent.JwtAuthHandler"/>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.common.APIMgtLatencyStatsHandler"/>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.security.CORSRequestHandler">
<property name="apiImplementationType" value="ENDPOINT"/>
</handler>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.analytics.APIMgtUsageHandler"/>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.analytics.APIMgtGoogleAnalyticsTrackingHandler">
<property name="configKey" value="gov:/apimgt/statistics/ga-config.xml"/>
</handler>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.ext.APIManagerExtensionHandler"/>
</handlers>
重要提示:
处理后端通常从 OAuth2 访问令牌派生(在正常的 OAuth2 请求中)。由于我们在这里替换了它,WSO2 无法确定要调用哪个环境,因此它将调用 PRODUCTION 作为默认值。要解决此问题,请在您的 JWT 中插入一些额外的字段,在我的案例环境中,这有助于您做出决定。然后,如图所示创建具有适当环境的 AuthenticationContext
。就是这样!
如果您直接编辑 yourapi.xml 描述符,它将在您下次发布时被替换。要自动生成它,请编辑速度模板 (/repository/resources/api_templates/velocity_template.xml
)。在我的例子中,我只希望它适用于某些应用程序,所以我使用标签 (jwt-auth) 来 select 它们。
velocity_template.xml:
<handlers xmlns="http://ws.apache.org/ns/synapse">
#if($apiObj.tags.contains("jwt-auth"))
<handler class="com.codependent.JwtAuthHandler"/>
#end
#foreach($handler in $handlers)
#if((($handler.className != "org.wso2.carbon.apimgt.gateway.handlers.security.APIAuthenticationHandler") &&
($handler.className != "org.wso2.carbon.apimgt.gateway.handlers.throttling.ThrottleHandler")) ||
!($apiObj.tags.contains("jwt-auth")))
<handler xmlns="http://ws.apache.org/ns/synapse" class="$handler.className">
#if($handler.hasProperties())
#set ($map = $handler.getProperties() )
#foreach($property in $map.entrySet())
<property name="$!property.key" value="$!property.value"/>
#end
#end
</handler>
#end
#end
</handlers>
事实上,您可以编写一个自定义处理程序来自行进行身份验证(基本身份验证,jwt 承载)。干得好,很快就找到了。也许作为一种改进,您可以缓存经过验证的 jwt 令牌(或 jwt 哈希),因为验证可能需要一些时间和性能。
作为默认解决方案(无需任何自定义),您可以使用 JWT grant
从受信任的 IdP 交换令牌以获取内部 APIM 令牌。
我有一个 Spring 安全 OAuth2 服务器,它为前端应用程序生成 JWT 令牌。
此令牌将在对后端的调用中发送,通过 API 网关(WSO2 API 管理器)。
我想要的是在 WSO2 中注册后端 APIs 并能够验证外部生成的 JWT 令牌。
这可能吗?您能否提供 WSO2 APIM 需要配置以包含此逻辑的不同位置的示例?
注意:WSO2 永远不需要创建令牌,它之前总是已经创建,只需要验证它。
经过大量试验和错误以及 Whosebug 的一些帮助(感谢 Bee),这是我的工作解决方案。我希望它能帮助其他人,因为让它工作真的很棘手:
1.实施 JWTAuthHandler 以验证 JWT 令牌:
public class JwtAuthHandler extends AbstractHandler {
private final PublicKeyFactory pkf = new PublicKeyFactory();
private final JwtVerifier jwtVerifier = new JwtVerifier();
@Override
public boolean handleRequest(MessageContext messageContext) {
try {
final String jwtToken = getJwtTokenFromHeaders(messageContext).replace("Bearer ", "");
SignedJWT signedJwt = SignedJWT.parse(jwtToken);
final JSONObject payload = signedJwt.getPayload().toJSONObject();
final JSONObject environment = (JSONObject)payload.get("environment");
PublicKey publicKey = readPublicKey();
JWSVerifier verifier = new RSASSAVerifier(((RSAPublicKey) publicKey));
final boolean signatureVerification = signedJwt.verify(verifier)
if (signatureVerification) {
AuthenticationContext authContext = new AuthenticationContext();
authContext.setAuthenticated(true);
if (isProductionRequest(environment)) {
authContext.setKeyType(APIConstants.API_KEY_TYPE_PRODUCTION);
} else {
authContext.setKeyType(APIConstants.API_KEY_TYPE_SANDBOX);
}
APISecurityUtils.setAuthenticationContext(messageContext, authContext, "Authorization");
} else {
LOG.debug("handleRequest() - Sending 401 Unauthorized");
Utils.sendFault(messageContext, 401);
}
return signatureVerification;
} catch (Exception e) {
e.printStackTrace();
Utils.sendFault(messageContext, 500);
return false;
}
}
@Override
public boolean handleResponse(MessageContext messageContext) {
return true;
}
private String getJwtTokenFromHeaders(MessageContext messageContext) {
Map headers = (Map) ((Axis2MessageContext) messageContext).getAxis2MessageContext().
getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
return (String) headers.get("Authorization");
}
private boolean isProductionRequest(JSONObject environment) {
return environment != null && environment.equals("pro");
}
}
2。覆盖您的 API 定义(/repository/deployment/server/synapse-configs/default/api/yourapi.xml
)以使用 jwt 处理程序并删除 APIAuthenticationHandler 和 ThrottleHandler(后者需要删除,因为非 oauth2- 的一个众所周知的错误经过身份验证的 API):
应该是这样的:
<handlers>
<handler class="com.codependent.JwtAuthHandler"/>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.common.APIMgtLatencyStatsHandler"/>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.security.CORSRequestHandler">
<property name="apiImplementationType" value="ENDPOINT"/>
</handler>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.analytics.APIMgtUsageHandler"/>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.analytics.APIMgtGoogleAnalyticsTrackingHandler">
<property name="configKey" value="gov:/apimgt/statistics/ga-config.xml"/>
</handler>
<handler class="org.wso2.carbon.apimgt.gateway.handlers.ext.APIManagerExtensionHandler"/>
</handlers>
重要提示:
处理后端通常从 OAuth2 访问令牌派生(在正常的 OAuth2 请求中)。由于我们在这里替换了它,WSO2 无法确定要调用哪个环境,因此它将调用 PRODUCTION 作为默认值。要解决此问题,请在您的 JWT 中插入一些额外的字段,在我的案例环境中,这有助于您做出决定。然后,如图所示创建具有适当环境的
AuthenticationContext
。就是这样!如果您直接编辑 yourapi.xml 描述符,它将在您下次发布时被替换。要自动生成它,请编辑速度模板 (
/repository/resources/api_templates/velocity_template.xml
)。在我的例子中,我只希望它适用于某些应用程序,所以我使用标签 (jwt-auth) 来 select 它们。
velocity_template.xml:
<handlers xmlns="http://ws.apache.org/ns/synapse">
#if($apiObj.tags.contains("jwt-auth"))
<handler class="com.codependent.JwtAuthHandler"/>
#end
#foreach($handler in $handlers)
#if((($handler.className != "org.wso2.carbon.apimgt.gateway.handlers.security.APIAuthenticationHandler") &&
($handler.className != "org.wso2.carbon.apimgt.gateway.handlers.throttling.ThrottleHandler")) ||
!($apiObj.tags.contains("jwt-auth")))
<handler xmlns="http://ws.apache.org/ns/synapse" class="$handler.className">
#if($handler.hasProperties())
#set ($map = $handler.getProperties() )
#foreach($property in $map.entrySet())
<property name="$!property.key" value="$!property.value"/>
#end
#end
</handler>
#end
#end
</handlers>
事实上,您可以编写一个自定义处理程序来自行进行身份验证(基本身份验证,jwt 承载)。干得好,很快就找到了。也许作为一种改进,您可以缓存经过验证的 jwt 令牌(或 jwt 哈希),因为验证可能需要一些时间和性能。
作为默认解决方案(无需任何自定义),您可以使用 JWT grant 从受信任的 IdP 交换令牌以获取内部 APIM 令牌。