KEYCLOAK-2651
No CSRF protection or general security headers on welcome page
This commit is contained in:
parent
dfa7c76331
commit
ff73e1a36a
3 changed files with 60 additions and 3 deletions
|
@ -17,11 +17,20 @@
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.util.MimeTypeUtil;
|
import org.keycloak.common.util.MimeTypeUtil;
|
||||||
|
import org.keycloak.models.BrowserSecurityHeaders;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.services.ForbiddenException;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
|
import org.keycloak.services.util.CookieHelper;
|
||||||
|
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
||||||
import org.keycloak.theme.FreeMarkerUtil;
|
import org.keycloak.theme.FreeMarkerUtil;
|
||||||
import org.keycloak.theme.Theme;
|
import org.keycloak.theme.Theme;
|
||||||
import org.keycloak.theme.ThemeProvider;
|
import org.keycloak.theme.ThemeProvider;
|
||||||
|
@ -35,8 +44,12 @@ import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.WebApplicationException;
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.Cookie;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||||
|
import javax.ws.rs.core.Response.Status;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -55,13 +68,20 @@ public class WelcomeResource {
|
||||||
|
|
||||||
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
|
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
|
||||||
|
|
||||||
|
private static final String KEYCLOAK_STATE_CHECKER = "KEYCLOAK_STATE_CHECKER";
|
||||||
|
|
||||||
private boolean bootstrap;
|
private boolean bootstrap;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected HttpHeaders headers;
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
private UriInfo uriInfo;
|
private UriInfo uriInfo;
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
protected KeycloakSession session;
|
private KeycloakSession session;
|
||||||
|
|
||||||
|
private String stateChecker;
|
||||||
|
|
||||||
public WelcomeResource(boolean bootstrap) {
|
public WelcomeResource(boolean bootstrap) {
|
||||||
this.bootstrap = bootstrap;
|
this.bootstrap = bootstrap;
|
||||||
|
@ -99,6 +119,9 @@ public class WelcomeResource {
|
||||||
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String stateChecker = formData.getFirst("stateChecker");
|
||||||
|
csrfCheck(stateChecker);
|
||||||
|
|
||||||
String username = formData.getFirst("username");
|
String username = formData.getFirst("username");
|
||||||
String password = formData.getFirst("password");
|
String password = formData.getFirst("password");
|
||||||
String passwordConfirmation = formData.getFirst("passwordConfirmation");
|
String passwordConfirmation = formData.getFirst("passwordConfirmation");
|
||||||
|
@ -159,6 +182,9 @@ public class WelcomeResource {
|
||||||
map.put("bootstrap", bootstrap);
|
map.put("bootstrap", bootstrap);
|
||||||
if (bootstrap) {
|
if (bootstrap) {
|
||||||
map.put("localUser", isLocal());
|
map.put("localUser", isLocal());
|
||||||
|
|
||||||
|
updateCsrfChecks();
|
||||||
|
map.put("stateChecker", stateChecker);
|
||||||
}
|
}
|
||||||
if (successMessage != null) {
|
if (successMessage != null) {
|
||||||
map.put("successMessage", successMessage);
|
map.put("successMessage", successMessage);
|
||||||
|
@ -168,7 +194,12 @@ public class WelcomeResource {
|
||||||
}
|
}
|
||||||
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
|
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
|
||||||
String result = freeMarkerUtil.processTemplate(map, "index.ftl", getTheme());
|
String result = freeMarkerUtil.processTemplate(map, "index.ftl", getTheme());
|
||||||
return Response.status(errorMessage == null ? Response.Status.OK : Response.Status.BAD_REQUEST).entity(result).cacheControl(CacheControlUtil.noCache()).build();
|
|
||||||
|
ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST)
|
||||||
|
.entity(result)
|
||||||
|
.cacheControl(CacheControlUtil.noCache());
|
||||||
|
BrowserSecurityHeaderSetup.headers(rb, BrowserSecurityHeaders.defaultHeaders);
|
||||||
|
return rb.build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
|
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
@ -199,4 +230,23 @@ public class WelcomeResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateCsrfChecks() {
|
||||||
|
RealmModel realm = session.realms().getRealmByName(Config.getAdminRealm());
|
||||||
|
Cookie cookie = headers.getCookies().get(KEYCLOAK_STATE_CHECKER);
|
||||||
|
if (cookie != null) {
|
||||||
|
stateChecker = cookie.getValue();
|
||||||
|
} else {
|
||||||
|
stateChecker = KeycloakModelUtils.generateSecret();
|
||||||
|
String cookiePath = uriInfo.getPath();
|
||||||
|
boolean secureOnly = uriInfo.getRequestUri().getScheme().equalsIgnoreCase("https");
|
||||||
|
CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, stateChecker, cookiePath, null, null, -1, secureOnly, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void csrfCheck(String stateChecker) {
|
||||||
|
if (!this.stateChecker.equals(stateChecker)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,11 @@ import java.util.Map;
|
||||||
public class BrowserSecurityHeaderSetup {
|
public class BrowserSecurityHeaderSetup {
|
||||||
|
|
||||||
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
|
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
|
||||||
for (Map.Entry<String, String> entry : realm.getBrowserSecurityHeaders().entrySet()) {
|
return headers(builder, realm.getBrowserSecurityHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, Map<String, String> headers) {
|
||||||
|
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||||
String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
|
String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
|
||||||
if (headerName != null && entry.getValue() != null && entry.getValue().length() > 0) {
|
if (headerName != null && entry.getValue() != null && entry.getValue().length() > 0) {
|
||||||
builder.header(headerName, entry.getValue());
|
builder.header(headerName, entry.getValue());
|
||||||
|
@ -38,4 +42,5 @@ public class BrowserSecurityHeaderSetup {
|
||||||
}
|
}
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,8 @@
|
||||||
<input id="passwordConfirmation" name="passwordConfirmation" type="password" />
|
<input id="passwordConfirmation" name="passwordConfirmation" type="password" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<input id="stateChecker" name="stateChecker" type="hidden" value="${stateChecker}" />
|
||||||
|
|
||||||
<button id="create-button" type="submit">Create</button>
|
<button id="create-button" type="submit">Create</button>
|
||||||
</form>
|
</form>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
Loading…
Reference in a new issue