unfinished working auth spi
This commit is contained in:
parent
143d176dcd
commit
a2718a889d
10 changed files with 172 additions and 5 deletions
|
@ -45,6 +45,9 @@ public class UrlBean {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLoginAction() {
|
public String getLoginAction() {
|
||||||
|
if (this.actionuri != null) {
|
||||||
|
return this.actionuri.toString();
|
||||||
|
}
|
||||||
return Urls.realmLoginAction(baseURI, realm).toString();
|
return Urls.realmLoginAction(baseURI, realm).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -279,6 +279,10 @@ public class AuthenticationProcessor {
|
||||||
super(message, cause, enableSuppression, writableStackTrace);
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
this.error = error;
|
this.error = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Error getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logUserFailure() {
|
public void logUserFailure() {
|
||||||
|
@ -389,6 +393,9 @@ public class AuthenticationProcessor {
|
||||||
if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) throw new AuthException(Error.INVALID_CREDENTIALS);
|
if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) throw new AuthException(Error.INVALID_CREDENTIALS);
|
||||||
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.ATTEMPTED);
|
clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.ATTEMPTED);
|
||||||
continue;
|
continue;
|
||||||
|
} else {
|
||||||
|
logger.error("Unknown result status");
|
||||||
|
throw new AuthException(Error.INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -398,7 +405,7 @@ public class AuthenticationProcessor {
|
||||||
|
|
||||||
public void validateUser(UserModel authenticatedUser) {
|
public void validateUser(UserModel authenticatedUser) {
|
||||||
if (authenticatedUser != null) {
|
if (authenticatedUser != null) {
|
||||||
if (!clientSession.getAuthenticatedUser().isEnabled()) throw new AuthException(Error.USER_DISABLED);
|
if (!authenticatedUser.isEnabled()) throw new AuthException(Error.USER_DISABLED);
|
||||||
}
|
}
|
||||||
if (realm.isBruteForceProtected()) {
|
if (realm.isBruteForceProtected()) {
|
||||||
if (protector.isTemporarilyDisabled(session, realm, authenticatedUser.getUsername())) {
|
if (protector.isTemporarilyDisabled(session, realm, authenticatedUser.getUsername())) {
|
||||||
|
|
|
@ -52,6 +52,7 @@ public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator {
|
||||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
context.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -53,6 +53,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
|
||||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
context.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,7 +63,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean configuredFor(UserModel user) {
|
public boolean configuredFor(UserModel user) {
|
||||||
return false;
|
return user.configuredForCredentialType(UserCredentialModel.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators;
|
||||||
|
|
||||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorContext;
|
import org.keycloak.authentication.AuthenticatorContext;
|
||||||
|
@ -51,7 +52,9 @@ public class LoginFormUsernameAuthenticator implements Authenticator {
|
||||||
|
|
||||||
protected boolean isActionUrl(AuthenticatorContext context) {
|
protected boolean isActionUrl(AuthenticatorContext context) {
|
||||||
URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
|
URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName());
|
||||||
return expected.getPath().equals(context.getUriInfo().getPath());
|
String current = context.getUriInfo().getAbsolutePath().getPath();
|
||||||
|
String expectedPath = expected.getPath();
|
||||||
|
return expectedPath.equals(current);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +74,11 @@ public class LoginFormUsernameAuthenticator implements Authenticator {
|
||||||
protected LoginFormsProvider loginForm(AuthenticatorContext context) {
|
protected LoginFormsProvider loginForm(AuthenticatorContext context) {
|
||||||
ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||||
code.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
code.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||||
|
URI action = LoginActionsService.authenticationFormProcessor(context.getUriInfo())
|
||||||
|
.queryParam(OAuth2Constants.CODE, code.getCode())
|
||||||
|
.build(context.getRealm().getName());
|
||||||
return context.getSession().getProvider(LoginFormsProvider.class)
|
return context.getSession().getProvider(LoginFormsProvider.class)
|
||||||
|
.setActionUri(action)
|
||||||
.setClientSessionCode(code.getCode());
|
.setClientSessionCode(code.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +105,7 @@ public class LoginFormUsernameAuthenticator implements Authenticator {
|
||||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
|
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
|
||||||
if (invalidUser(context, user)) return;
|
if (invalidUser(context, user)) return;
|
||||||
context.setUser(user);
|
context.setUser(user);
|
||||||
|
context.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean invalidUser(AuthenticatorContext context, UserModel user) {
|
public boolean invalidUser(AuthenticatorContext context, UserModel user) {
|
||||||
|
|
|
@ -58,6 +58,7 @@ public class OTPFormAuthenticator implements Authenticator {
|
||||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
context.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,15 @@ import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
|
import org.keycloak.authentication.authenticators.AuthenticationFlow;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
@ -244,6 +247,32 @@ public class AuthorizationEndpoint {
|
||||||
return buildRedirectToIdentityProvider(idpHint, accessCode);
|
return buildRedirectToIdentityProvider(idpHint, accessCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return oldBrowserAuthentication(accessCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response newBrowserAuthentication(String accessCode) {
|
||||||
|
String flowId = null;
|
||||||
|
for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
|
||||||
|
if (flow.getAlias().equals("browser")) {
|
||||||
|
flowId = flow.getId();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||||
|
processor.setClientSession(clientSession)
|
||||||
|
.setFlowId(flowId)
|
||||||
|
.setConnection(clientConnection)
|
||||||
|
.setEventBuilder(event)
|
||||||
|
.setProtector(authManager.getProtector())
|
||||||
|
.setRealm(realm)
|
||||||
|
.setSession(session)
|
||||||
|
.setUriInfo(uriInfo)
|
||||||
|
.setRequest(request);
|
||||||
|
|
||||||
|
return processor.authenticate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response oldBrowserAuthentication(String accessCode) {
|
||||||
Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
|
Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
|
||||||
if (response != null) return response;
|
if (response != null) return response;
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,10 @@ public class AuthenticationManager {
|
||||||
this.protector = protector;
|
this.protector = protector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BruteForceProtector getProtector() {
|
||||||
|
return protector;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
|
public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
|
||||||
if (userSession == null) {
|
if (userSession == null) {
|
||||||
logger.debug("No user session");
|
logger.debug("No user session");
|
||||||
|
|
|
@ -24,6 +24,7 @@ package org.keycloak.services.resources;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailProvider;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
@ -32,6 +33,7 @@ import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -118,7 +120,7 @@ public class LoginActionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder authenticationFormProcessor(UriInfo uriInfo) {
|
public static UriBuilder authenticationFormProcessor(UriInfo uriInfo) {
|
||||||
return loginActionsBaseUrl(uriInfo).path("auth-form");
|
return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "authForm");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
|
public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
|
||||||
|
@ -274,6 +276,116 @@ public class LoginActionsService {
|
||||||
* URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY!
|
* URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY!
|
||||||
*
|
*
|
||||||
* @param code
|
* @param code
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Path("auth-form")
|
||||||
|
@POST
|
||||||
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
|
public Response authForm(@QueryParam("code") String code) {
|
||||||
|
event.event(EventType.LOGIN);
|
||||||
|
if (!checkSsl()) {
|
||||||
|
event.error(Errors.SSL_REQUIRED);
|
||||||
|
return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!realm.isEnabled()) {
|
||||||
|
event.error(Errors.REALM_DISABLED);
|
||||||
|
return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
|
||||||
|
}
|
||||||
|
ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
|
||||||
|
if (clientCode == null) {
|
||||||
|
event.error(Errors.INVALID_CODE);
|
||||||
|
return ErrorPage.error(session, Messages.INVALID_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientSessionModel clientSession = clientCode.getClientSession();
|
||||||
|
event.detail(Details.CODE_ID, clientSession.getId());
|
||||||
|
|
||||||
|
if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
|
||||||
|
clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||||
|
event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
|
||||||
|
return session.getProvider(LoginFormsProvider.class)
|
||||||
|
.setError(Messages.EXPIRED_CODE)
|
||||||
|
.setClientSessionCode(clientCode.getCode())
|
||||||
|
.createLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientModel client = clientSession.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
event.error(Errors.CLIENT_NOT_FOUND);
|
||||||
|
return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
|
||||||
|
}
|
||||||
|
if (!client.isEnabled()) {
|
||||||
|
event.error(Errors.CLIENT_NOT_FOUND);
|
||||||
|
return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
String flowId = null;
|
||||||
|
for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
|
||||||
|
if (flow.getAlias().equals("browser")) {
|
||||||
|
flowId = flow.getId();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||||
|
processor.setClientSession(clientSession)
|
||||||
|
.setFlowId(flowId)
|
||||||
|
.setConnection(clientConnection)
|
||||||
|
.setEventBuilder(event)
|
||||||
|
.setProtector(authManager.getProtector())
|
||||||
|
.setRealm(realm)
|
||||||
|
.setSession(session)
|
||||||
|
.setUriInfo(uriInfo)
|
||||||
|
.setRequest(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return processor.authenticate();
|
||||||
|
} catch (AuthenticationProcessor.AuthException e) {
|
||||||
|
return handleError(e, code);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("failed authentication", e);
|
||||||
|
return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response handleError(AuthenticationProcessor.AuthException e, String code) {
|
||||||
|
logger.error("failed authentication: " + e.getError().toString(), e);
|
||||||
|
if (e.getError() == AuthenticationProcessor.Error.INVALID_USER) {
|
||||||
|
event.error(Errors.USER_NOT_FOUND);
|
||||||
|
return session.getProvider(LoginFormsProvider.class)
|
||||||
|
.setError(Messages.INVALID_USER)
|
||||||
|
.setClientSessionCode(code)
|
||||||
|
.createLogin();
|
||||||
|
|
||||||
|
} else if (e.getError() == AuthenticationProcessor.Error.USER_DISABLED) {
|
||||||
|
event.error(Errors.USER_DISABLED);
|
||||||
|
return session.getProvider(LoginFormsProvider.class)
|
||||||
|
.setError(Messages.ACCOUNT_DISABLED)
|
||||||
|
.setClientSessionCode(code)
|
||||||
|
.createLogin();
|
||||||
|
|
||||||
|
} else if (e.getError() == AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED) {
|
||||||
|
event.error(Errors.USER_TEMPORARILY_DISABLED);
|
||||||
|
return session.getProvider(LoginFormsProvider.class)
|
||||||
|
.setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
|
||||||
|
.setClientSessionCode(code)
|
||||||
|
.createLogin();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
event.error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
return session.getProvider(LoginFormsProvider.class)
|
||||||
|
.setError(Messages.INVALID_USER)
|
||||||
|
.setClientSessionCode(code)
|
||||||
|
.createLogin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY!
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
* @param formData
|
* @param formData
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,3 +3,4 @@ org.keycloak.protocol.ProtocolMapperSpi
|
||||||
org.keycloak.exportimport.ClientImportSpi
|
org.keycloak.exportimport.ClientImportSpi
|
||||||
org.keycloak.wellknown.WellKnownSpi
|
org.keycloak.wellknown.WellKnownSpi
|
||||||
org.keycloak.messages.MessagesSpi
|
org.keycloak.messages.MessagesSpi
|
||||||
|
org.keycloak.authentication.AuthenticatorSpi
|
Loading…
Reference in a new issue