我已按照您的步骤开发我们的自定义协议。当我们迁移我们公司现有的身份验证协议时,我使用了 org.keycloak.adapters.authentication.ClientCredentialsProvider、org.keycloak.authentication.ClientAuthenticatorFactory、org.keycloak.authentication.ClientAuthenticator 类来定义我们的自定义协议。凭据选项卡仅在选择 oidc 和机密选项时可见。它是在 UI javascript 代码上定义的。所以我们选择 oidc 选项来设置自定义协议。之后,我们回到我们的自定义协议。
XyzClientAuthenticatorFactory
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
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
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;
}
}
Xyz登录协议
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
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) {
....
}