如何在Pac4j 5.0.x中每次认证前动态更新Token Endpoint参数?
解决方案
在Pac4j 5.0.x的OIDC流程中,Token请求的构建逻辑位于OidcTokenGenerator类中,要动态更新Token Endpoint的fieldA参数,推荐通过自定义OidcTokenGenerator拦截Token请求构建过程,以下是具体实现步骤:
1. 自定义OidcTokenGenerator重写Token请求构建方法
创建自定义的Token生成器,在构建Token请求时动态修改Endpoint URI,添加或更新fieldA参数:
import org.pac4j.core.context.WebContext; import org.pac4j.core.exception.TechnicalException; import org.pac4j.core.http.HttpRequest; import org.pac4j.oidc.token.OidcTokenGenerator; import org.pac4j.oidc.config.OidcConfiguration; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; public class DynamicFieldAOidcTokenGenerator extends OidcTokenGenerator { public DynamicFieldAOidcTokenGenerator(OidcConfiguration configuration) { super(configuration); } @Override protected HttpRequest buildTokenRequest(String authorizationCode, WebContext context) { // 先调用父类方法构建原始请求 HttpRequest tokenRequest = super.buildTokenRequest(authorizationCode, context); // 从当前请求上下文获取动态的fieldA值(示例从会话中取,可根据实际场景调整) String dynamicFieldA = getDynamicFieldAFromSession(context); try { URI originalUri = tokenRequest.getUri(); String uriStr = originalUri.toString(); // 处理URI参数:替换原有fieldA或新增参数 if (uriStr.contains("fieldA=")) { uriStr = uriStr.replaceFirst("fieldA=[^&]*", "fieldA=" + URLEncoder.encode(dynamicFieldA, StandardCharsets.UTF_8.name())); } else { String separator = originalUri.getQuery() == null ? "?" : "&"; uriStr += separator + "fieldA=" + URLEncoder.encode(dynamicFieldA, StandardCharsets.UTF_8.name()); } // 更新请求URI tokenRequest.setUri(new URI(uriStr)); } catch (URISyntaxException e) { throw new TechnicalException("Failed to update token endpoint URI", e); } return tokenRequest; } // 从会话中获取之前存储的动态fieldA值 private String getDynamicFieldAFromSession(WebContext context) { return (String) context.getSessionStore().get(context, "dynamicFieldA"); } }
2. 在授权请求阶段存储动态fieldA值
如果fieldA是在用户发起认证请求时确定的,需要在授权跳转前将其存入会话,确保Token请求时能匹配到对应值。可以通过自定义OidcClient实现:
import org.pac4j.core.context.WebContext; import org.pac4j.core.redirect.RedirectAction; import org.pac4j.oidc.client.OidcClient; import org.pac4j.oidc.config.OidcConfiguration; public class DynamicFieldAOidcClient extends OidcClient { public DynamicFieldAOidcClient(OidcConfiguration configuration) { super(configuration); } @Override public RedirectAction redirect(WebContext context) { // 从当前请求参数中获取动态fieldA(可根据实际来源调整,比如请求头、业务参数等) String dynamicFieldA = context.getRequestParameter("dynamicFieldA"); // 将值存入会话 context.getSessionStore().set(context, "dynamicFieldA", dynamicFieldA); // 执行父类的授权跳转逻辑 return super.redirect(context); } }
3. 替换默认客户端和Token生成器
在项目的Pac4j配置代码中,使用自定义的客户端和Token生成器替换默认实现:
// 初始化OIDC配置 OidcConfiguration oidcConfiguration = new OidcConfiguration(); // 配置基础的Token Endpoint(不带fieldA参数) oidcConfiguration.setTokenEndpointURI(new URI(appConfig.getTokenEndpoint())); // 其他OIDC配置(客户端ID、密钥等) // oidcConfiguration.setClientId("your-client-id"); // oidcConfiguration.setSecret("your-client-secret"); // 使用自定义客户端 DynamicFieldAOidcClient oidcClient = new DynamicFieldAOidcClient(oidcConfiguration); // 设置自定义Token生成器 oidcClient.setTokenGenerator(new DynamicFieldAOidcTokenGenerator(oidcConfiguration)); // 将客户端添加到客户端工厂 Clients clients = new Clients("callback-url", oidcClient); // 后续配置Config、Filter等流程保持不变
备选方案:动态生成ProviderMetadata的TokenEndpointURI
如果不想修改Token生成器,也可以通过扩展OidcProviderMetadata,重写getTokenEndpointURI()方法动态生成带参数的URI,但需要借助ThreadLocal传递请求上下文,注意线程安全:
import org.pac4j.oidc.metadata.OidcProviderMetadata; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.function.Supplier; public class DynamicFieldAProviderMetadata extends OidcProviderMetadata { private final Supplier<String> fieldASupplier; private final URI originalTokenEndpoint; public DynamicFieldAProviderMetadata(OidcProviderMetadata original, Supplier<String> fieldASupplier) { // 复制原始元数据的所有属性 this.setAuthorizationEndpointURI(original.getAuthorizationEndpointURI()); this.setTokenEndpointURI(original.getTokenEndpointURI()); this.setUserInfoEndpointURI(original.getUserInfoEndpointURI()); this.setJwksURI(original.getJwksURI()); // 其他元数据字段按需复制 this.originalTokenEndpoint = original.getTokenEndpointURI(); this.fieldASupplier = fieldASupplier; } @Override public URI getTokenEndpointURI() { try { String dynamicFieldA = fieldASupplier.get(); String uriStr = originalTokenEndpoint.toString(); String separator = originalTokenEndpoint.getQuery() == null ? "?" : "&"; uriStr += separator + "fieldA=" + URLEncoder.encode(dynamicFieldA, StandardCharsets.UTF_8.name()); return new URI(uriStr); } catch (URISyntaxException e) { throw new TechnicalException("Failed to generate dynamic token endpoint URI", e); } } }
配置时需要结合ThreadLocal存储请求上下文,比如通过Filter将WebContext存入ThreadLocal,再在Supplier中获取。这种方式相对复杂,推荐优先使用第一种方案。
内容的提问来源于stack exchange,提问作者rithvik
相关产品推荐
相关产品推荐

