首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >KEYCLOAK -扩展OIDC协议|缺少凭证选项卡|在AccessTokenResponse中添加额外声明

KEYCLOAK -扩展OIDC协议|缺少凭证选项卡|在AccessTokenResponse中添加额外声明
EN

Stack Overflow用户
提问于 2020-12-03 01:53:34
回答 1查看 314关注 0票数 0

我们正在尝试在FHIR医疗保健授权协议规范上实现SMART。此规范是对OIDC (开放id连接协议)的扩展。在SMART on FHIR中,我们需要在OAUTH舞蹈期间在AccessTokenResponse对象中添加名为'patient‘的额外声明,值为'123’。

为了完成这个任务,我尝试扩展了OIDCLoginProtocol和OIDCLoginProtocolFactory类,并给这个协议起了一个新的名字,叫做'smart-openid-connect‘。我将其创建为SPI (服务提供者接口) JAR,并将其复制到/standalone/deployments文件夹。现在,我可以在UI中看到名为“smart-openid-connect”的新协议,但它不会在客户端创建屏幕中显示Access Type选项以选择作为机密客户端。因此,我无法创建客户端机密,因为此新协议的凭据菜单不会出现。

我有以下问题:

如何使用SPI为我创建的新协议启用客户端创建屏幕中的Credentials选项卡。我需要重写哪个类才能在AccessTokenResponse中添加额外的声明?请在这方面帮助我。

提前感谢您的帮助。

EN

回答 1

Stack Overflow用户

发布于 2020-12-13 06:03:19

我遵循了您的步骤来开发我们的自定义协议。当我们迁移公司现有的身份验证协议时,我使用了org.keycloak.adapters.authentication.ClientCredentialsProvider,org.keycloak.authentication.ClientAuthenticatorFactory,org.keycloak.authentication.ClientAuthenticator类来定义我们的自定义协议。仅当选择了oidc和机密选项时,证书选项卡才可见。它是在UI javascript代码上定义的。因此我们选择oidc选项来设置自定义协议。然后,我们返回到我们的自定义协议。

XyzClientAuthenticatorFactory

代码语言:javascript
复制
public class XyzClientAuthenticatorFactory implements ClientAuthenticatorFactory, ClientAuthenticator {
    public static final String PROVIDER_ID = "xyz-client-authenticator";
    public static final String DISPLAY_TEXT = "Xyz Client Authenticator";
    public static final String REFERENCE_CATEGORY = null;
    public static final String HELP_TEXT = null;

    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();

    private AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
            AuthenticationExecutionModel.Requirement.REQUIRED,
            AuthenticationExecutionModel.Requirement.ALTERNATIVE,
            AuthenticationExecutionModel.Requirement.DISABLED};

    static {
        ProviderConfigProperty property;

        property = new ProviderConfigProperty();
        property.setName(Constants.CLIENT_SETTINGS_APP_ID);
        property.setLabel("Xyz App Id");
        property.setType(ProviderConfigProperty.STRING_TYPE);
        configProperties.add(property);

        property = new ProviderConfigProperty();
        property.setName(Constants.CLIENT_SETTINGS_APP_KEY);
        property.setLabel("Xyz App Key");
        property.setType(ProviderConfigProperty.STRING_TYPE);
        configProperties.add(property);
    }

    @Override
    public void authenticateClient(ClientAuthenticationFlowContext context) {

    }

    @Override
    public String getDisplayType() {
        return DISPLAY_TEXT;
    }

    @Override
    public String getReferenceCategory() {
        return REFERENCE_CATEGORY;
    }

    @Override
    public ClientAuthenticator create() {
        return this;
    }

    @Override
    public boolean isConfigurable() {
        return false;
    }

    @Override
    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
        return REQUIREMENT_CHOICES;
    }

    @Override
    public boolean isUserSetupAllowed() {
        return false;
    }

    @Override
    public List<ProviderConfigProperty> getConfigPropertiesPerClient() {
        return configProperties;
    }

    @Override
    public Map<String, Object> getAdapterConfiguration(ClientModel client) {
        Map<String, Object> result = new HashMap<>();
        result.put(Constants.CLIENT_SETTINGS_APP_ID, client.getAttribute(Constants.CLIENT_SETTINGS_APP_ID));
        result.put(Constants.CLIENT_SETTINGS_APP_KEY, client.getAttribute(Constants.CLIENT_SETTINGS_APP_KEY));
        return result;
    }

    @Override
    public Set<String> getProtocolAuthenticatorMethods(String loginProtocol) {
        if (loginProtocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
            Set<String> results = new LinkedHashSet<>();
            results.add(Constants.CLIENT_SETTINGS_APP_ID);
            results.add(Constants.CLIENT_SETTINGS_APP_KEY);
            return results;
        } else {
            return Collections.emptySet();
        }
    }

    @Override
    public String getHelpText() {
        return HELP_TEXT;
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        return new LinkedList<>();
    }

    @Override
    public ClientAuthenticator create(KeycloakSession session) {
        return this;
    }

    @Override
    public void init(Config.Scope config) {
    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {
    }

    @Override
    public void close() {
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }
}

XyzClientCredential

代码语言:javascript
复制
public class XyzClientCredential implements ClientCredentialsProvider {
    public static final String PROVIDER_ID = "xyz-client-credential";

    @Override
    public String getId() {
        return PROVIDER_ID;
    }

    @Override
    public void init(KeycloakDeployment deployment, Object config) {

    }

    @Override
    public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {

    }
}

XyzLoginProtocolFactory

代码语言:javascript
复制
public class XyzLoginProtocolFactory implements LoginProtocolFactory {

    static {

    }

    @Override
    public Map<String, ProtocolMapperModel> getBuiltinMappers() {
        return new HashMap<>();
    }

    @Override
    public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) {
        return new XyzLoginProtocolService(realm, event);
    }

    protected void addDefaultClientScopes(RealmModel realm, ClientModel newClient) {
        addDefaultClientScopes(realm, Arrays.asList(newClient));
    }

    protected void addDefaultClientScopes(RealmModel realm, List<ClientModel> newClients) {
        Set<ClientScopeModel> defaultClientScopes = realm.getDefaultClientScopes(true).stream()
                .filter(clientScope -> getId().equals(clientScope.getProtocol()))
                .collect(Collectors.toSet());
        for (ClientModel newClient : newClients) {
            for (ClientScopeModel defaultClientScopeModel : defaultClientScopes) {
                newClient.addClientScope(defaultClientScopeModel, true);
            }
        }

        Set<ClientScopeModel> nonDefaultClientScopes = realm.getDefaultClientScopes(false).stream()
                .filter(clientScope -> getId().equals(clientScope.getProtocol()))
                .collect(Collectors.toSet());
        for (ClientModel newClient : newClients) {
            for (ClientScopeModel nonDefaultClientScope : nonDefaultClientScopes) {
                newClient.addClientScope(nonDefaultClientScope, true);
            }
        }
    }

    @Override
    public void createDefaultClientScopes(RealmModel newRealm, boolean addScopesToExistingClients) {
        // Create default client scopes for realm built-in clients too
        if (addScopesToExistingClients) {
            addDefaultClientScopes(newRealm, newRealm.getClients());
        }
    }

    @Override
    public void setupClientDefaults(ClientRepresentation rep, ClientModel newClient) {

    }

    @Override
    public LoginProtocol create(KeycloakSession session) {
        return new XyzLoginProtocol().setSession(session);
    }

    @Override
    public void init(Config.Scope config) {
        log.infof("XyzLoginProtocolFactory init");
    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {
        factory.register(event -> {
            if (event instanceof RealmModel.ClientCreationEvent) {
                ClientModel client = ((RealmModel.ClientCreationEvent)event).getCreatedClient();
                addDefaultClientScopes(client.getRealm(), client);
                addDefaults(client);
            }
        });
    }

    protected void addDefaults(ClientModel client) {
    }

    @Override
    public void close() {

    }

    @Override
    public String getId() {
        return XyzLoginProtocol.LOGIN_PROTOCOL;
    }
}

XyzLoginProtocol

代码语言:javascript
复制
public class XyzLoginProtocol implements LoginProtocol {
    public static final String LOGIN_PROTOCOL = "xyz";

    protected KeycloakSession session;

    protected RealmModel realm;

    protected UriInfo uriInfo;

    protected HttpHeaders headers;

    protected EventBuilder event;

    public XyzLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
        this.session = session;
        this.realm = realm;
        this.uriInfo = uriInfo;
        this.headers = headers;
        this.event = event;
    }

    public XyzLoginProtocol() {

    }

    @Override
    public XyzLoginProtocol setSession(KeycloakSession session) {
        this.session = session;
        return this;
    }

    @Override
    public XyzLoginProtocol setRealm(RealmModel realm) {
        this.realm = realm;
        return this;
    }

    @Override
    public XyzLoginProtocol setUriInfo(UriInfo uriInfo) {
        this.uriInfo = uriInfo;
        return this;
    }

    @Override
    public XyzLoginProtocol setHttpHeaders(HttpHeaders headers) {
        this.headers = headers;
        return this;
    }

    @Override
    public XyzLoginProtocol setEventBuilder(EventBuilder event) {
        this.event = event;
        return this;
    }

    @Override
    public Response authenticated(AuthenticationSessionModel authSession, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
        log.debugf("Authenticated.. User: %s, Session Id: %s", userSession.getUser().getUsername(), userSession.getId());
        try {
            ....
        } catch (Exception ex) {
            // TODO handle TokenNotFoundException exception
            log.error(ex.getMessage(), ex);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }


    @Override
    public Response sendError(AuthenticationSessionModel authSession, Error error) {
        new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, true);

        String redirect = authSession.getRedirectUri();
        try {
            URI uri = new URI(redirect);
            return Response.status(302).location(uri).build();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            return Response.noContent().build();
        }
    }

    @Override
    public void backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
        ClientModel client = clientSession.getClient();
        new ResourceAdminManager(session).logoutClientSession(realm, client, clientSession);
    }

    @Override
    public Response frontchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
        throw new RuntimeException("NOT IMPLEMENTED");
    }

    @Override
    public Response finishLogout(UserSessionModel userSession) {
        return Response.noContent().build();
    }

    @Override
    public boolean requireReauthentication(UserSessionModel userSession, AuthenticationSessionModel authSession) {
        return false;
    }

    @Override
    public boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
        PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), notBefore);
        String token = session.tokens().encode(adminAction);
        log.tracev("pushRevocation resource: {0} url: {1}", resource.getClientId(), managementUrl);
        URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
        try {
            int status = session.getProvider(HttpClientProvider.class).postText(target.toString(), token);
            boolean success = status == 204 || status == 200;
            log.tracef("pushRevocation success for %s: %s", managementUrl, success);
            return success;
        } catch (IOException e) {
            ServicesLogger.LOGGER.failedToSendRevocation(e);
            return false;
        }
    }

    @Override
    public void close() {

    }
}

XyzLoginProtocolService

代码语言:javascript
复制
public class XyzLoginProtocolService {
    private final RealmModel realm;
    private final EventBuilder event;

    @Context
    private KeycloakSession session;

    @Context
    private HttpHeaders headers;

    @Context
    private HttpRequest request;

    @Context
    private ClientConnection clientConnection;

    public XyzLoginProtocolService(RealmModel realm, EventBuilder event) {
        this.realm = realm;
        this.event = event;
        this.event.realm(realm);
    }

    @POST
    @Path("request")
    @Produces(MediaType.APPLICATION_JSON)
    @NoCache
    public Response request(ApipmLoginRequest loginRequest) {
        ....
    }
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65113522

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档