keycloak-uncached

Merge pull request #615 from patriot1burke/master X-Frame-Options,

8/11/2014 6:45:26 PM

Changes

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 3bb7e83..dad5eac 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -52,6 +52,7 @@ public class RealmRepresentation {
     protected Map<String, List<ScopeMappingRepresentation>> applicationScopeMappings;
     protected List<ApplicationRepresentation> applications;
     protected List<OAuthClientRepresentation> oauthClients;
+    protected Map<String, String> browserSecurityHeaders;
     protected Map<String, String> socialProviders;
     protected Map<String, String> smtpServer;
     protected List<UserFederationProviderRepresentation> userFederationProviders;
@@ -291,6 +292,14 @@ public class RealmRepresentation {
         this.updateProfileOnInitialSocialLogin = updateProfileOnInitialSocialLogin;
     }
 
+    public Map<String, String> getBrowserSecurityHeaders() {
+        return browserSecurityHeaders;
+    }
+
+    public void setBrowserSecurityHeaders(Map<String, String> browserSecurityHeaders) {
+        this.browserSecurityHeaders = browserSecurityHeaders;
+    }
+
     public Map<String, String> getSocialProviders() {
         return socialProviders;
     }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
index 3e89af8..6ec3416 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -13,6 +13,7 @@ import org.keycloak.account.freemarker.model.SessionsBean;
 import org.keycloak.account.freemarker.model.TotpBean;
 import org.keycloak.account.freemarker.model.UrlBean;
 import org.keycloak.audit.Event;
+import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
 import org.keycloak.freemarker.FreeMarkerException;
 import org.keycloak.freemarker.FreeMarkerUtil;
 import org.keycloak.freemarker.Theme;
@@ -136,7 +137,9 @@ public class FreeMarkerAccountProvider implements AccountProvider {
 
         try {
             String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
-            return Response.status(status).type(MediaType.TEXT_HTML).entity(result).build();
+            Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
+            BrowserSecurityHeaderSetup.headers(builder, realm);
+            return builder.build();
         } catch (FreeMarkerException e) {
             logger.error("Failed to process template", e);
             return Response.serverError().build();
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/BrowserSecurityHeaderSetup.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/BrowserSecurityHeaderSetup.java
new file mode 100755
index 0000000..da8fc5d
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/BrowserSecurityHeaderSetup.java
@@ -0,0 +1,23 @@
+package org.keycloak.freemarker;
+
+import org.keycloak.models.BrowserSecurityHeaders;
+import org.keycloak.models.RealmModel;
+
+import javax.ws.rs.core.Response;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class BrowserSecurityHeaderSetup {
+
+    public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
+        for (Map.Entry<String, String> entry : realm.getBrowserSecurityHeaders().entrySet()) {
+            String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
+            if (headerName == null) continue;
+            builder.header(headerName, entry.getValue());
+        }
+        return builder;
+    }
+}
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
index 4f7b83a..4c434c2 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
@@ -675,16 +675,7 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmRevocationCtrl'
         })
-        .when('/realms/:realm/sessions/brute-force', {
-            templateUrl : 'partials/session-brute-force.html',
-            resolve : {
-                realm : function(RealmLoader) {
-                    return RealmLoader();
-                }
-            },
-            controller : 'RealmBruteForceCtrl'
-        })
-        .when('/realms/:realm/sessions/realm', {
+         .when('/realms/:realm/sessions/realm', {
             templateUrl : 'partials/session-realm.html',
             resolve : {
                 realm : function(RealmLoader) {
@@ -761,6 +752,28 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'GenericUserFederationCtrl'
         })
+        .when('/realms/:realm/defense/headers', {
+            templateUrl : 'partials/defense-headers.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
+                }
+
+            },
+            controller : 'DefenseHeadersCtrl'
+        })
+        .when('/realms/:realm/defense/brute-force', {
+            templateUrl : 'partials/brute-force.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                }
+            },
+            controller : 'RealmBruteForceCtrl'
+        })
         .when('/logout', {
             templateUrl : 'partials/home.html',
             controller : 'LogoutCtrl'
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
index e89401d..8ce2c34 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
@@ -340,8 +340,6 @@ function genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $l
 
     var oldCopy = angular.copy($scope.realm);
 
-
-
     $scope.changed = false;
 
     $scope.$watch('realm', function() {
@@ -384,16 +382,20 @@ function genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $l
 
 }
 
+module.controller('DefenseHeadersCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
+    genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/defense/headers");
+});
+
 module.controller('RealmLoginSettingsCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
-    genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/login-settings")
+    genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/login-settings");
 });
 
 module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
-    genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/theme-settings")
+    genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/theme-settings");
 });
 
 module.controller('RealmCacheCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
-    genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/cache-settings")
+    genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/cache-settings");
 });
 
 module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, PasswordPolicy) {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/defense-headers.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/defense-headers.html
new file mode 100755
index 0000000..734efa9
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/defense-headers.html
@@ -0,0 +1,36 @@
+<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
+<div id="content-area" class="col-sm-9" role="main">
+    <ul class="nav nav-tabs nav-tabs-pf">
+        <li class="active"><a href="#/realms/{{realm.realm}}/defense/headers">Headers</a></li>
+        <li><a href="#/realms/{{realm.realm}}/defense/brute-force">Brute Force Detection</a></li>
+    </ul>
+    <div id="content">
+        <div data-ng-show="access.viewRealm">
+            <h2><span>{{realm.realm}}</span> Browser Security Headers</h2>
+            <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
+                <fieldset class="border-top">
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="xFrameOptions"><a href="http://tools.ietf.org/html/rfc7034">X-Frame-Options</a></label>
+                        <div class="col-sm-6">
+                            <input class="form-control" id="xFrameOptions" type="text" ng-model="realm.browserSecurityHeaders.xFrameOptions">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="contentSecurityPolicy"><a href="http://www.w3.org/TR/CSP/">Content-Security-Policy</a></label>
+                        <div class="col-sm-6">
+                            <input class="form-control" id="contentSecurityPolicy" type="text" ng-model="realm.browserSecurityHeaders.contentSecurityPolicy">
+                        </div>
+                    </div>
+                </fieldset>
+                <div class="pull-right form-actions" data-ng-show="access.manageRealm">
+                    <button kc-reset data-ng-show="changed">Clear changes</button>
+                    <button kc-save  data-ng-show="changed">Save</button>
+                </div>
+            </form>
+        </div>
+        <div data-ng-hide="access.viewRealm">
+            <h2 ><span>{{realm.realm}}</span></h2>
+        </div>
+
+    </div>
+</div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-detail.html
index f9c1e3b..ac43458 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-detail.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-detail.html
@@ -15,17 +15,16 @@
                 <fieldset class="border-top">
                     <div class="form-group">
                         <label class="col-sm-2 control-label" for="name">Name <span class="required" data-ng-show="createRealm">*</span></label>
-
                         <div class="col-sm-4">
                             <input class="form-control" type="text" id="name" name="name" data-ng-model="realm.realm" autofocus required>
                         </div>
                     </div>
                     <div class="form-group">
                         <label class="col-sm-2 control-label" for="enabled">Enabled</label>
+                        <span tooltip="Users and applications can only access a realm if it's enabled" class="pficon pficon-help"></span>
                         <div class="col-sm-4">
                             <input ng-model="realm.enabled" name="enabled" id="enabled" onoffswitch />
                         </div>
-                        <span tooltip="Users and applications can only access a realm if it's enabled" class="pficon pficon-help"></span>
                     </div>
                 </fieldset>
 
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html
index 1bc9c6b..b5dca73 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html
@@ -9,5 +9,6 @@
     <li data-ng-show="access.viewApplications" data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
     <li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'oauth-clients' || path[1] == 'oauth-client') && 'active'"><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
     <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions' || path[2] == 'token-settings') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm">Sessions and Tokens</a></li>
+    <li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'defense') && 'active'"><a href="#/realms/{{realm.realm}}/defense/headers">Security Defenses</a></li>
     <li data-ng-show="access.viewAudit" data-ng-class="(path[2] == 'audit' || path[2] == 'audit-settings') && 'active'"><a href="#/realms/{{realm.realm}}/audit">Audit</a></li>
 </ul>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html
index 866b2f7..f5f164a 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/session-realm.html
@@ -4,7 +4,6 @@
         <li class="active"><a href="#/realms/{{realm.realm}}/sessions/realm">Realm Sessions</a></li>
         <li><a href="#/realms/{{realm.realm}}/token-settings">Timeout Settings</a></li>
         <li><a href="#/realms/{{realm.realm}}/sessions/revocation">Revocation</a></li>
-        <li><a href="#/realms/{{realm.realm}}/sessions/brute-force">Brute Force Protection</a></li>
     </ul>
     <div id="content">
         <ol class="breadcrumb">
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index fcabeb1..34168cd 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
+import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
 import org.keycloak.freemarker.FreeMarkerException;
 import org.keycloak.freemarker.FreeMarkerUtil;
 import org.keycloak.freemarker.Theme;
@@ -216,7 +217,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
         try {
             String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
-            return Response.status(status).type(MediaType.TEXT_HTML).entity(result).build();
+            Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
+            BrowserSecurityHeaderSetup.headers(builder, realm);
+            return builder.build();
         } catch (FreeMarkerException e) {
             logger.error("Failed to process template", e);
             return Response.serverError().build();
diff --git a/model/api/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java b/model/api/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
new file mode 100755
index 0000000..e7d479f
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
@@ -0,0 +1,27 @@
+package org.keycloak.models;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class BrowserSecurityHeaders {
+    public static final Map<String, String> headerAttributeMap;
+    public static final Map<String, String> defaultHeaders;
+
+    static {
+        Map<String, String> headerMap = new HashMap<String, String>();
+        headerMap.put("xFrameOptions", "X-Frame-Options");
+        headerMap.put("contentSecurityPolicy", "Content-Security-Policy");
+
+        Map<String, String> dh = new HashMap<String, String>();
+        dh.put("xFrameOptions", "SAMEORIGIN");
+        dh.put("contentSecurityPolicy", "frame-src 'self'");
+
+        defaultHeaders = Collections.unmodifiableMap(dh);
+        headerAttributeMap = Collections.unmodifiableMap(headerMap);
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 8d3779b..fdba16a 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -52,6 +52,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
     private List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
 
+    private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
     private Map<String, String> smtpConfig = new HashMap<String, String>();
     private Map<String, String> socialConfig = new HashMap<String, String>();
 
@@ -317,6 +318,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
         this.requiredCredentials = requiredCredentials;
     }
 
+    public Map<String, String> getBrowserSecurityHeaders() {
+        return browserSecurityHeaders;
+    }
+
+    public void setBrowserSecurityHeaders(Map<String, String> browserSecurityHeaders) {
+        this.browserSecurityHeaders = browserSecurityHeaders;
+    }
+
     public Map<String, String> getSmtpConfig() {
         return smtpConfig;
     }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 4ae280f..b863e32 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -151,6 +151,9 @@ public interface RealmModel extends RoleContainerModel {
 
     List<OAuthClientModel> getOAuthClients();
 
+    Map<String, String> getBrowserSecurityHeaders();
+    void setBrowserSecurityHeaders(Map<String, String> headers);
+
     Map<String, String> getSmtpConfig();
 
     void setSmtpConfig(Map<String, String> smtpConfig);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 4499465..8e34e15 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -105,6 +105,7 @@ public class ModelToRepresentation {
         rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
         rep.setSmtpServer(realm.getSmtpConfig());
         rep.setSocialProviders(realm.getSocialConfig());
+        rep.setBrowserSecurityHeaders(realm.getBrowserSecurityHeaders());
         rep.setAccountTheme(realm.getAccountTheme());
         rep.setLoginTheme(realm.getLoginTheme());
         rep.setAdminTheme(realm.getAdminTheme());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index c7d7c26..b79d6c2 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -4,6 +4,7 @@ import net.iharder.Base64;
 import org.jboss.logging.Logger;
 import org.keycloak.enums.SslRequired;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.BrowserSecurityHeaders;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
@@ -199,6 +200,12 @@ public class RepresentationToModel {
             newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
         }
 
+        if (rep.getBrowserSecurityHeaders() != null) {
+            newRealm.setBrowserSecurityHeaders(rep.getBrowserSecurityHeaders());
+        } else {
+            newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders);
+        }
+
         if (rep.getSocialProviders() != null) {
             newRealm.setSocialConfig(new HashMap(rep.getSocialProviders()));
         }
@@ -266,6 +273,10 @@ public class RepresentationToModel {
             realm.setSocialConfig(new HashMap(rep.getSocialProviders()));
         }
 
+        if (rep.getBrowserSecurityHeaders() != null) {
+            realm.setBrowserSecurityHeaders(rep.getBrowserSecurityHeaders());
+        }
+
         if (rep.getUserFederationProviders() != null) {
             List<UserFederationProviderModel> providerModels = convertFederationProviders(rep.getUserFederationProviders());
             realm.setUserFederationProviders(providerModels);
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index cacc084..a6256b0 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -66,6 +66,7 @@ public class CachedRealm {
     private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
     private List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
 
+    private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
     private Map<String, String> smtpConfig = new HashMap<String, String>();
     private Map<String, String> socialConfig = new HashMap<String, String>();
 
@@ -123,6 +124,7 @@ public class CachedRealm {
 
         smtpConfig.putAll(model.getSmtpConfig());
         socialConfig.putAll(model.getSocialConfig());
+        browserSecurityHeaders.putAll(model.getBrowserSecurityHeaders());
 
         auditEnabled = model.isAuditEnabled();
         auditExpiration = model.getAuditExpiration();
@@ -287,6 +289,10 @@ public class CachedRealm {
         return socialConfig;
     }
 
+    public Map<String, String> getBrowserSecurityHeaders() {
+        return browserSecurityHeaders;
+    }
+
     public String getLoginTheme() {
         return loginTheme;
     }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 9fb8a1e..fac8410 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -558,6 +558,19 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public Map<String, String> getBrowserSecurityHeaders() {
+        if (updated != null) return updated.getBrowserSecurityHeaders();
+        return cached.getBrowserSecurityHeaders();
+    }
+
+    @Override
+    public void setBrowserSecurityHeaders(Map<String, String> headers) {
+        getDelegateForUpdate();
+        updated.setBrowserSecurityHeaders(headers);
+
+    }
+
+    @Override
     public Map<String, String> getSmtpConfig() {
         if (updated != null) return updated.getSmtpConfig();
         return cached.getSmtpConfig();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 4b44180..612d3ec 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -196,6 +196,7 @@ public class RealmAdapter implements RealmModel {
     }
 
     public Map<String, String> getAttributes() {
+        // should always return a copy
         Map<String, String> result = new HashMap<String, String>();
         for (RealmAttributeEntity attr : realm.getAttributes()) {
             result.put(attr.getName(), attr.getValue());
@@ -711,6 +712,27 @@ public class RealmAdapter implements RealmModel {
         return list;
     }
 
+    private static final String BROWSER_HEADER_PREFIX = "_browser_header.";
+
+    @Override
+    public Map<String, String> getBrowserSecurityHeaders() {
+        Map<String, String> attributes = getAttributes();
+        Map<String, String> headers = new HashMap<String, String>();
+        for (Map.Entry<String, String> entry : attributes.entrySet()) {
+            if (entry.getKey().startsWith(BROWSER_HEADER_PREFIX)) {
+                headers.put(entry.getKey().substring(BROWSER_HEADER_PREFIX.length()), entry.getValue());
+            }
+        }
+        return headers;
+    }
+
+    @Override
+    public void setBrowserSecurityHeaders(Map<String, String> headers) {
+        for (Map.Entry<String, String> entry : headers.entrySet()) {
+            setAttribute(BROWSER_HEADER_PREFIX + entry.getKey(), entry.getValue());
+        }
+    }
+
     @Override
     public Map<String, String> getSmtpConfig() {
         return realm.getSmtpConfig();
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 893f52e..0c6eda2 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -735,6 +735,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
+    public Map<String, String> getBrowserSecurityHeaders() {
+        return realm.getBrowserSecurityHeaders();
+    }
+
+    @Override
+    public void setBrowserSecurityHeaders(Map<String, String> headers) {
+        realm.setBrowserSecurityHeaders(headers);
+        updateRealm();
+    }
+
+    @Override
     public Map<String, String> getSmtpConfig() {
         return realm.getSmtpConfig();
     }
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index c8b613c..c6ccb76 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -7,6 +7,7 @@ import org.keycloak.exportimport.util.ImportUtils;
 import org.keycloak.models.AccountRoles;
 import org.keycloak.models.AdminRoles;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.BrowserSecurityHeaders;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
@@ -117,6 +118,8 @@ public class RealmManager {
 
 
     protected void setupRealmDefaults(RealmModel realm) {
+        realm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders);
+
         // brute force
         realm.setBruteForceProtected(false); // default settings off for now todo set it on
         realm.setMaxFailureWaitSeconds(900);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
index 2d7c0ca..cce5a0b 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
@@ -8,6 +8,7 @@ import org.jboss.resteasy.spi.HttpResponse;
 import org.jboss.resteasy.spi.NotFoundException;
 import org.keycloak.ClientConnection;
 import org.keycloak.Config;
+import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
 import org.keycloak.freemarker.Theme;
 import org.keycloak.freemarker.ThemeProvider;
 import org.keycloak.models.AdminRoles;
@@ -325,7 +326,9 @@ public class AdminConsole {
                 cacheControl.setNoTransform(false);
                 cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
 
-                return Response.ok(resource).type(contentType).cacheControl(cacheControl).build();
+                Response.ResponseBuilder builder = Response.ok(resource).type(contentType).cacheControl(cacheControl);
+                BrowserSecurityHeaderSetup.headers(builder, realm);
+                return builder.build();
             } else {
                 return Response.status(Response.Status.NOT_FOUND).build();
             }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 0d0c170..9ebdaff 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -25,6 +25,7 @@ import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.audit.Details;
@@ -158,12 +159,12 @@ public class AccountTest {
         });
     }
 
-    /*
+    @Ignore
     @Test
     public void forever() throws Exception{
         while (true) Thread.sleep(5000);
     }
-    */
+
 
     @Test
     public void returnToAppFromQueryParam() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
index e22c9d1..4942411 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
@@ -278,6 +278,9 @@ public class AdminAPITest {
         if (rep.getSocialProviders() != null) {
             Assert.assertEquals(rep.getSocialProviders(), storedRealm.getSocialProviders());
         }
+        if (rep.getBrowserSecurityHeaders() != null) {
+            Assert.assertEquals(rep.getBrowserSecurityHeaders(), storedRealm.getBrowserSecurityHeaders());
+        }
 
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index c406bfb..4447019 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -27,6 +27,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.audit.Details;
+import org.keycloak.models.BrowserSecurityHeaders;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
@@ -42,6 +43,11 @@ import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
 
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.core.Response;
+import java.util.Map;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -86,6 +92,20 @@ public class LoginTest {
     private static String userId;
 
     @Test
+    public void testBrowserSecurityHeaders() {
+        Client client = ClientBuilder.newClient();
+        Response response = client.target(oauth.getLoginFormUrl()).request().get();
+        Assert.assertEquals(200, response.getStatus());
+        for (Map.Entry<String, String> entry : BrowserSecurityHeaders.defaultHeaders.entrySet()) {
+            String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
+            String headerValue = response.getHeaderString(headerName);
+            Assert.assertNotNull(headerValue);
+            Assert.assertEquals(headerValue, entry.getValue());
+        }
+        response.close();
+    }
+
+    @Test
     public void loginInvalidPassword() {
         loginPage.open();
         loginPage.login("login-test", "invalid");