Spring Security – Two Security Realms in one Application
More specifically it is intending to show how to configure two different security realms in one web application.
First security realm is intended for the browser clients. It enables us to log in with in the login page and access protected resources.
Second security realm is intended for the REST web service requests coming from an android application. On each request, the REST client should send required information to the server and this information will be used to decide if the RESTfull request should be allowed to pass.
The two security realms (configurations) are distinguished by different URL patterns of resources in the web application. In both configurations we are able to reuse same authentication logic.
First security realm
We have a classic web application with some protected resources (pages). In order to access those resources the user should log in into the application on the login page. If the login was successful, the user is forwarded to the requested resource. If the user’s login process fails for some reason (i.e. bad username or password) then the user is not able to obtain the protected resource and he is redirected to the login page again with the corresponding message being presented.
The case I have just described in the section above might be considered as ‘classic web application behavior’. The average internet user has come across at least hundreds of online applications behaving like this. This kind of behavior is intended to work with clients (browsers). Since this kind of behavior is pretty common today, Spring security makes it really easy to implement this. It is obvious that the form based authentication mechanism suites us best. In Spring security when you wish to define actions which are related to the client’s authentication status you can define entry point. Here is a preview of our standard browser-client entry point:
<http entry-point-ref="loginUrlAuthenticationEntryPoint" use-expressions="true"> <intercept-url pattern="/includes/content/administration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" /> <intercept-url pattern="/includes/content/userAdministration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" /> <intercept-url pattern="/includes/content/groupAdministration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" /> <intercept-url pattern="/includes/content/departmentAdministration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" /> <intercept-url pattern="/includes/content/shiftAdministration.jsp" access="hasAnyRole('ROLE_100','ROLE_101','ROLE_1000') /> <custom-filter position="FORM_LOGIN_FILTER" ref="userAuthenticationProcessingFilter" /> <logout logout-url='/logout' /> </http> <beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <beans:property name="loginFormUrl" value="/login.jsp" /> </beans:bean>
Hopefully this is pretty self-explanatory. loginUrlAuthenticationEntryPoint is an entry point where you can configure the login page where you have implemented your login functionality. Then in the http element we have configured the behavior of this entry point to more details. First we defined list of intercept-url elements. This entry point will be activated only if one of those resources have been requested. We also replaced the default FORM_LOGIN_FILTER with our own customized version. Spring security functions by applying chain of filters that you define in your entry point. These are basically standard servlet filters. You can use the Spring predefined filters or you can extend them and plug in your custom filter. Here we used one of the Spring’s security filters.
It is a UsernamePasswordAuthenticationFilter. It is used in a situation where we have the login page with the user name and password fields. This filter allows us to incorporate our custom mechanism which will be used for authentication. It also allows us to define actions which will be taken in case of successful and unsuccessful authentication. Let’s see how this configuration looks like:
<beans:bean id="loginSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> <beans:property name="defaultTargetUrl" value="/main.jsp" /> </beans:bean> <beans:bean id="userAuthenticationProcessingFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <beans:property name="authenticationManager" ref="authenticationManager" /> <beans:property name="authenticationFailureHandler" ref="loginMappingFailureHandler" /> <beans:property name="authenticationSuccessHandler" ref="loginSuccessHandler" /> </beans:bean> <beans:bean id="loginMappingFailureHandler" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler"> <beans:property name="exceptionMappings" ref="failureUrlMap" /> </beans:bean> <util:map id="failureUrlMap" map-class="java.util.HashMap"> <beans:entry key="org.springframework.security.authentication.BadCredentialsException" value="/login.jsp?errorMessage=bad.credentials" /> <beans:entry key="org.springframework.security.authentication.DisabledException" value="/login.jsp?errorMessage=disabled.user" /> </util:map>
Let’s have a second and take a look in this configuration. I will explain what we just did here.
First thing, we defined our form login filter. Actually we defined three things for it. We gave it our custom authentication mechanism which will be used through out the application. This mechanism is plugged in to the filter via the authenticationManager. I will speak about authentication manager soon.
Second thing, we defined a login failure handler. Basically, this is a map of Spring’s exceptions and actions which are taken on these exceptions. The exceptions are thrown by an AuthenticationProvider which is described below. For example when the user enters wrong user name or password, BadCredentialsException is thrown. And when that happens, the user is redirected to the login page again. Also a certain parameter is appended to the URL of the login page to enable us to display the correct error message.
Third and final thing, we defined a successful authentication handler. And this is really obvious. We are defining what to do if the login succeeds. The user is sent to the main page.
Now let’s have a few words about authentication manager. This is just an interface that Spring uses. It can be anything. It can be a database of users, LDAP server or something else. Authentication manager does not do the work by it self. It only uses Authentication providers to do the actual authentication work for it. Authentication providers, when they are invoked, can do two things:
- Can return a successfully populated object (which is an instance of Spring’s Authentication interface)
- Can throw one of the appropriate Spring security exceptions
Here is how the authentication manager configuration looks like:
<authentication-manager alias="authenticationManager"> <authentication-provider ref="authenticationProvider" /> </authentication-manager> <beans:bean id="authenticationProvider" class="ba.codecentric.medica.security.UserAuthenticationProvider"> <beans:property name="userService" ref="userService"/> <beans:property name="licenseInformationWrapper" ref="licenseInformationWrapper"/> </beans:bean>
And here is the source code of my custom made authentication provider:
package ba.codecentric.medica.security; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import org.apache.commons.collections.CollectionUtils; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import ba.codecentric.medica.administration.service.UserService; import ba.codecentric.medica.model.Group; import ba.codecentric.medica.model.LicenseInformationWrapper; import ba.codecentric.medica.model.Role; import ba.codecentric.medica.model.User; public class UserAuthenticationProvider implements AuthenticationProvider { private static final String ROLE_PREFIX = "ROLE_"; private UserService userService; private LicenseInformationWrapper licenseInformationWrapper; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { User user = userService.getUserByUsernameAndPassword(authentication.getName(), authentication.getCredentials() .toString(), true); if (user != null) { Collection authorities = new ArrayList(buildRolesFromUser(user)); authorities.addAll(getActivatedModulesAsRoles()); return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), authorities); } else { throw new BadCredentialsException("Try again"); } } private Collection getActivatedModulesAsRoles() { List activatedModules = new ArrayList(); if(CollectionUtils.isNotEmpty(licenseInformationWrapper.getActivatedModules())) { for(String activatedModuleName: licenseInformationWrapper.getActivatedModules()) { activatedModules.add(new SimpleGrantedAuthority(ROLE_PREFIX + activatedModuleName)); } } return activatedModules; } private Collection buildRolesFromUser(User user) { Collection authorities = new HashSet(); for (Group group : user.getGroups()) { for (Role role : group.getRoles()) { authorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + role.getName())); } } return authorities; } @Override public boolean supports(Class authentication) { return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); } public UserService getUserService() { return userService; } public void setUserService(UserService userService) { this.userService = userService; } public LicenseInformationWrapper getLicenseInformationWrapper() { return licenseInformationWrapper; } public void setLicenseInformationWrapper(LicenseInformationWrapper licenseInformationWrapper) { this.licenseInformationWrapper = licenseInformationWrapper; } }
As you see the authentication process is really simple. My custom authentication provider implements the Spring AuthenticationProvider interface.
And it does the job just as we discussed before. It looks up for the username and password in the user table in the database. If such user is found then authentication object is created and returned. Otherwise, if no such user is found, the appropriate exception is thrown by the authenticate method. And one more thing. Spring uses a collection of GrantedAuthority objects to represent the roles which are given to the user. This is the reason why we build such a collection and attach it to the authentication object. Every role connected to the user in the database must be added to the collection of granted authorities in order for Spring to consider this as a role. And each role must have a ROLE_ prefix. We got one more thing to show. How is this filter invoked from the login web page? Here is the portion of the login.jsp:
<form id="loginForm" method="POST" action="j_spring_security_check"> <table> <tr> <td><b><fmt:message key="login.username.label" />:</b></td> <c:choose> <c:when test="${not empty param.j_username}" > <td><input type="text" name="j_username" id="username" value="${param.j_username }" class="loginInput" /></td> </c:when> <c:otherwise> <td><input type="text" name="j_username" id="username" class="loginInput"/></td> </c:otherwise> </c:choose> </tr> <tr> <td><b><fmt:message key="login.password.label" />:</b></td> <c:choose> <c:when test="${not empty param.j_password}" > <td><input type="password" name="j_password" id="password" value="${param.j_password }" class="loginInput" /></td> </c:when> <c:otherwise> <td><input type="password" name="j_password" id="password" class="loginInput" /></td> </c:otherwise> </c:choose> </tr> <tr> <td><b><fmt:message key="login.alphabet.label" /></b>:</td> <td><select id="alphabet" class="fullWidth" onchange="languageSelect();"> <option value="lat"> <fmt:message key="login.alphabet.lat" /> </option> <option value="cir"> <fmt:message key="login.alphabet.cyr" /> </option> </select></td> </tr> <tr> <td></td> <td><input type="submit" value="<fmt:message key="login.button.label" />" class="right"></td> </tr> </table></form>
Standard Spring security setup by default requires you to invoke the security chain from the login form by calling j_spring_security_check. The username and password filter will intercept this URL (buy default) but you can configure it to intercept any other URL. Well that’s all concerning the ‘browser based client’ security realm. If the user is not logged in and tries to access resource protected by this realm (entry point), then the realm is going to redirect the user to login page and ask him to log in. Only if the user logs in, then the protected resource is going to be available.
Second security realm
Now finally, let’s talk about the second security realm in the application. We only mentioned it in the introduction part of the blog. This application supports REST service calls. We had to implement the requirement to synchronize certain parts of the application with the simple android application running on mobile devices. We decided the simplest approach would be to make RESTfull calls from the mobile phone to the web application. And of course, we also need security here. We don’t want to allow users to always be able to connect to the application. The list of users and their roles is maintained in the database. For example, a user can be active today, but tomorrow administrator can decide that this user is not active any more and should not be able to connect to application (also should not be able to log in). As a consequence of this requirement, security has to be implemented in the REST services realm.
Let’s think about this realm for a second. How are these REST calls supposed to work. The Android application sends POST requests (RESTfull requests) to the web application to get certain data (doctor’s appointments, etc.). The application finds and returns the requested data. Android application then processes the obtained data and displays it to the user.
Now let’s add the security to this RESTfull concept and try to describe concept with security. Android application sends POST requests. The Android application sends a header containing the hashed user name and password as part of each request (see -> Basic Authentication).
Web application’s security realm (entry point) is supposed to receive this request and if the user name and password really present active user, then this request is allowed to reach the REST service in the web application and it will get processed. If by any chance the user name and password are invalid (or the user is inactive) then request should fail in the security entry point, meaning that we should immediately return properly formatted HTTP response which will notify the client application that the user with this user name and password is not allowed to access the REST service in the web application.
As we see in this case, the behavior of the previously defined entry point does not correspond for REST services. Previous entry point, redirects the user to the login page if he is not authenticated. That means if the user is not authenticated, the server actually returns a HTTP response containing the HTML code of the login page. We can not deal with this kind of behavior in the android application, as it does not display any HTML web pages. So what would it do when it receives HTML of the login page?
This is the main reason why we need actually a second security realm (security entry point) in the web application, which will work differently then our mechanism which deals with browser clients. This, new security realm will only return properly formatted HTTP response to the client application if the user could not be authenticated (it will set specific HTTP status and HTTP message on the response).
We know that in Java Server environment we have a type of security called Basic Authentication. It is based on sending the hashed user name and password as part of the request headers (headers have to be formatted in a specific way). Then if the user name and password can be found in the pool of user data request is allowed to pass. Otherwise HTTP response is returned with the corresponding status and message informing the client he is not permitted to access certain resource. Lucky for us, Spring supports this kind of authentication mechanism. We are going to add another entry point and a filter. This is how it will look like:
<http entry-point-ref="basicAuthEntryPoint" pattern="/ws/**" use-expressions="true"> <intercept-url pattern="/ws/schedule/patients" access="hasAnyRole('ROLE_1','ROLE_100','ROLE_300','ROLE_1000')" /> <custom-filter ref="basicAuthenticationFilter" after="BASIC_AUTH_FILTER" /> </http> <beans:bean id="basicAuthEntryPoint" class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint"> <beans:property name="realmName" value="REST Realm" /> </beans:bean> <beans:bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"> <beans:property name="authenticationManager" ref="authenticationManager"/> <beans:property name="authenticationEntryPoint" ref="basicAuthEntryPoint" /> </beans:bean>
Basically we have added a new entry point (security realm) which intercepts all requests on the URL path /ws/**. This is the path where all our REST service calls go through. We have used Springs BasicAuthenticationFilter which provides functionality of reading the request headers and calling the authentication manager. If the user name and password are found in the database (handled by the authentication manager) request will be allowed to pass further. If the user name and password are not found in the database, entry point will set the status 401 on the HTTP response and return this response to the client immediately. It’s just the behavior we need for the Android application.
And this is all the security configuration our application needs. Now the only thing left to do is to enable Spring security filters in web.xml file. I’ve already mentioned that Spring security works by invoking chains of filters on the request. This means that there is some kind of ‘main’ filter which is the basis for all other subsequent filters and services. This ‘main’ filter is enabled and configured in the web.xml. Here is my configuration:
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> <dispatcher>ERROR</dispatcher> <dispatcher>REQUEST</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping>
As you see the main Spring security filter is configured to intercept all requests to all resources in the application. But which resources are really protected and which resources are public is controlled by the entry points (via URL patterns in http elements). For example all resources located in the /css folder are considered public and do not require user to authenticate to be able to access them:
<http pattern="/css/**" security="none" />
On the other hand, resources like administration page are protected and require user not only to authenticate, but also to have certain roles assigned if the user wishes to access this page. Here is the example in this xml code snippet:
<!-- more xml --> <intercept-url pattern="/includes/content/administration.jsp" access="hasAnyRole('ROLE_100','ROLE_1000')" /> <!-- more xml -->
And two more very important things to say!
When you have multiple http elements in your security configuration, make sure that elements with most specific pattern attributes go before the ones which are less specific or maybe even have no pattern attributes. Otherwise you will see long stack traces in your log files when Spring starts to complain that the filter ordering in your application makes no sense.
After reading this blog you might start to think that it is enough to add Form based authentication or Basic authentication and your application will be safe. That however, is not totally true. Anyone with some ‘technical’ knowledge on HTTP protocol and networking can probably think of the way how to intercept HTTP streams of data inside a network. In the case of Basic authentication and Form based authentication, information like user name and password are sent directly through HTTP protocol. In the case of the Basic authentication they are sent as HTTP request headers. In the case of Form based authentication they are sent as request parameters. So, the person who can intercept and read these HTTP streams can easily read your headers and request parameters. Later this same person can manually create requests and attach those headers or parameters to the request. Of course this new request will now be authorized by the container because it contains your correct authentication details.
So what can we do to avoid these security attacks to our application?
The real answer would be: We should use the HTTPS protocol where ever we have protected resources in our application. Only by using the HTTPS protocol and the Java server’s authentication mechanisms we can claim with a big amount of certainty that our application is really secure.
Reference: Spring Security – Two Security Realms in one Application from our JCG partner Branislav Vidovi? at the Geek stuff :-) blog.