This section describes how Spring Security is integrated with the Servlet API. The servletapi-xml sample application demonstrates the usage of each of these methods.
The HttpServletRequest.getRemoteUser() will return the result of SecurityContextHolder.getContext().getAuthentication().getName()
which is typically the current username.
This can be useful if you want to display the current username in your application.
Additionally, checking if this is null can be used to indicate if a user has authenticated or is anonymous.
Knowing if the user is authenticated or not can be useful for determining if certain UI elements should be shown or not (i.e. a log out link should only be displayed if the user is authenticated).
The HttpServletRequest.getUserPrincipal() will return the result of SecurityContextHolder.getContext().getAuthentication()
.
This means it is an Authentication
which is typically an instance of UsernamePasswordAuthenticationToken
when using username and password based authentication.
This can be useful if you need additional information about your user.
For example, you might have created a custom UserDetailsService
that returns a custom UserDetails
containing a first and last name for your user.
You could obtain this information with the following:
Authentication auth = httpServletRequest.getUserPrincipal(); // assume integrated custom UserDetails called MyCustomUserDetails // by default, typically instance of UserDetails MyCustomUserDetails userDetails = (MyCustomUserDetails) auth.getPrincipal(); String firstName = userDetails.getFirstName(); String lastName = userDetails.getLastName();
Note | |
---|---|
It should be noted that it is typically bad practice to perform so much logic throughout your application. Instead, one should centralize it to reduce any coupling of Spring Security and the Servlet API’s. |
The HttpServletRequest.isUserInRole(String) will determine if SecurityContextHolder.getContext().getAuthentication().getAuthorities()
contains a GrantedAuthority
with the role passed into isUserInRole(String)
.
Typically users should not pass in the "ROLE_" prefix into this method since it is added automatically.
For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the following:
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
This might be useful to determine if certain UI components should be displayed. For example, you might display admin links only if the current user is an admin.
The following section describes the Servlet 3 methods that Spring Security integrates with.
The HttpServletRequest.authenticate(HttpServletRequest,HttpServletResponse) method can be used to ensure that a user is authenticated. If they are not authenticated, the configured AuthenticationEntryPoint will be used to request the user to authenticate (i.e. redirect to the login page).
The HttpServletRequest.login(String,String) method can be used to authenticate the user with the current AuthenticationManager
.
For example, the following would attempt to authenticate with the username "user" and password "password":
try { httpServletRequest.login("user","password"); } catch(ServletException e) { // fail to authenticate }
Note | |
---|---|
It is not necessary to catch the ServletException if you want Spring Security to process the failed authentication attempt. |
The HttpServletRequest.logout() method can be used to log the current user out.
Typically this means that the SecurityContextHolder will be cleared out, the HttpSession will be invalidated, any "Remember Me" authentication will be cleaned up, etc. However, the configured LogoutHandler implementations will vary depending on your Spring Security configuration. It is important to note that after HttpServletRequest.logout() has been invoked, you are still in charge of writing a response out. Typically this would involve a redirect to the welcome page.
The AsynchContext.start(Runnable) method that ensures your credentials will be propagated to the new Thread. Using Spring Security’s concurrency support, Spring Security overrides the AsyncContext.start(Runnable) to ensure that the current SecurityContext is used when processing the Runnable. For example, the following would output the current user’s Authentication:
final AsyncContext async = httpServletRequest.startAsync(); async.start(new Runnable() { public void run() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); try { final HttpServletResponse asyncResponse = (HttpServletResponse) async.getResponse(); asyncResponse.setStatus(HttpServletResponse.SC_OK); asyncResponse.getWriter().write(String.valueOf(authentication)); async.complete(); } catch(Exception e) { throw new RuntimeException(e); } } });
If you are using Java Based configuration, you are ready to go. If you are using XML configuration, there are a few updates that are necessary. The first step is to ensure you have updated your web.xml to use at least the 3.0 schema as shown below:
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> </web-app>
Next you need to ensure that your springSecurityFilterChain is setup for processing asynchronous requests.
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class> org.springframework.web.filter.DelegatingFilterProxy </filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>ASYNC</dispatcher> </filter-mapping>
That’s it! Now Spring Security will ensure that your SecurityContext is propagated on asynchronous requests too.
So how does it work? If you are not really interested, feel free to skip the remainder of this section, otherwise read on. Most of this is built into the Servlet specification, but there is a little bit of tweaking that Spring Security does to ensure things work with asynchronous requests properly. Prior to Spring Security 3.2, the SecurityContext from the SecurityContextHolder was automatically saved as soon as the HttpServletResponse was committed. This can cause issues in an Async environment. For example, consider the following:
httpServletRequest.startAsync(); new Thread("AsyncThread") { @Override public void run() { try { // Do work TimeUnit.SECONDS.sleep(1); // Write to and commit the httpServletResponse httpServletResponse.getOutputStream().flush(); } catch (Exception e) { e.printStackTrace(); } } }.start();
The issue is that this Thread is not known to Spring Security, so the SecurityContext is not propagated to it. This means when we commit the HttpServletResponse there is no SecuriytContext. When Spring Security automatically saved the SecurityContext on committing the HttpServletResponse it would lose our logged in user.
Since version 3.2, Spring Security is smart enough to no longer automatically save the SecurityContext on commiting the HttpServletResponse as soon as HttpServletRequest.startAsync() is invoked.
The following section describes the Servlet 3.1 methods that Spring Security integrates with.
The HttpServletRequest.changeSessionId() is the default method for protecting against Session Fixation attacks in Servlet 3.1 and higher.
Spring Security provides Spring Data integration that allows referring to the current user within your queries. It is not only useful but necessary to include the user in the queries to support paged results since filtering the results afterwards would not scale.
To use this support, add org.springframework.security:spring-security-data
dependency and provide a bean of type SecurityEvaluationContextExtension
.
In Java Configuration, this would look like:
@Bean public SecurityEvaluationContextExtension securityEvaluationContextExtension() { return new SecurityEvaluationContextExtension(); }
In XML Configuration, this would look like:
<bean class="org.springframework.security.data.repository.query.SecurityEvaluationContextExtension"/>
Now Spring Security can be used within your queries. For example:
@Repository public interface MessageRepository extends PagingAndSortingRepository<Message,Long> { @Query("select m from Message m where m.to.id = ?#{ principal?.id }") Page<Message> findInbox(Pageable pageable); }
This checks to see if the Authentication.getPrincipal().getId()
is equal to the recipient of the Message
.
Note that this example assumes you have customized the principal to be an Object that has an id property.
By exposing the SecurityEvaluationContextExtension
bean, all of the Common Security Expressions are available within the Query.
In most environments, Security is stored on a per Thread
basis.
This means that when work is done on a new Thread
, the SecurityContext
is lost.
Spring Security provides some infrastructure to help make this much easier for users.
Spring Security provides low level abstractions for working with Spring Security in multi-threaded environments.
In fact, this is what Spring Security builds on to integration with the section called “AsyncContext.start(Runnable)” and Section 14.6.4, “Spring MVC Async Integration”.
One of the most fundamental building blocks within Spring Security’s concurrency support is the DelegatingSecurityContextRunnable
.
It wraps a delegate Runnable
in order to initialize the SecurityContextHolder
with a specified SecurityContext
for the delegate.
It then invokes the delegate Runnable ensuring to clear the SecurityContextHolder
afterwards.
The DelegatingSecurityContextRunnable
looks something like this:
public void run() { try { SecurityContextHolder.setContext(securityContext); delegate.run(); } finally { SecurityContextHolder.clearContext(); } }
While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another.
This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis.
For example, you might have used Spring Security’s the section called “<global-method-security>” support to secure one of your services.
You can now easily transfer the SecurityContext
of the current Thread
to the Thread
that invokes the secured service.
An example of how you might do this can be found below:
Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; SecurityContext context = SecurityContextHolder.getContext(); DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable, context); new Thread(wrappedRunnable).start();
The code above performs the following steps:
Runnable
that will be invoking our secured service.
Notice that it is not aware of Spring Security
SecurityContext
that we wish to use from the SecurityContextHolder
and initializes the DelegatingSecurityContextRunnable
DelegatingSecurityContextRunnable
to create a Thread
Since it is quite common to create a DelegatingSecurityContextRunnable
with the SecurityContext
from the SecurityContextHolder
there is a shortcut constructor for it.
The following code is the same as the code above:
Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable); new Thread(wrappedRunnable).start();
The code we have is simple to use, but it still requires knowledge that we are using Spring Security.
In the next section we will take a look at how we can utilize DelegatingSecurityContextExecutor
to hide the fact that we are using Spring Security.
In the previous section we found that it was easy to use the DelegatingSecurityContextRunnable
, but it was not ideal since we had to be aware of Spring Security in order to use it.
Let’s take a look at how DelegatingSecurityContextExecutor
can shield our code from any knowledge that we are using Spring Security.
The design of DelegatingSecurityContextExecutor
is very similar to that of DelegatingSecurityContextRunnable
except it accepts a delegate Executor
instead of a delegate Runnable
.
You can see an example of how it might be used below:
SecurityContext context = SecurityContextHolder.createEmptyContext(); Authentication authentication = new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER")); context.setAuthentication(authentication); SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor, context); Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; executor.execute(originalRunnable);
The code performs the following steps:
SecurityContext
to be used for our DelegatingSecurityContextExecutor
.
Note that in this example we simply create the SecurityContext
by hand.
However, it does not matter where or how we get the SecurityContext
(i.e. we could obtain it from the SecurityContextHolder
if we wanted).
Runnable
s
DelegatingSecurityContextExecutor
which is in charge of wrapping any Runnable that is passed into the execute method with a DelegatingSecurityContextRunnable
.
It then passes the wrapped Runnable to the delegateExecutor.
In this instance, the same SecurityContext
will be used for every Runnable submitted to our DelegatingSecurityContextExecutor
.
This is nice if we are running background tasks that need to be run by a user with elevated privileges.
SecurityContext
and the DelegatingSecurityContextExecutor
in our own code, we can inject an already initialized instance of DelegatingSecurityContextExecutor
.
@Autowired private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor public void submitRunnable() { Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; executor.execute(originalRunnable); }
Now our code is unaware that the SecurityContext
is being propagated to the Thread
, then the originalRunnable
is executed, and then the SecurityContextHolder
is cleared out.
In this example, the same user is being used to execute each Thread.
What if we wanted to use the user from SecurityContextHolder
at the time we invoked executor.execute(Runnable)
(i.e. the currently logged in user) to process originalRunnable
?
This can be done by removing the SecurityContext
argument from our DelegatingSecurityContextExecutor
constructor.
For example:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor);
Now anytime executor.execute(Runnable)
is executed the SecurityContext
is first obtained by the SecurityContextHolder
and then that SecurityContext
is used to create our DelegatingSecurityContextRunnable
.
This means that we are executing our Runnable
with the same user that was used to invoke the executor.execute(Runnable)
code.
Refer to the Javadoc for additional integrations with both the Java concurrent APIs and the Spring Task abstractions. They are quite self-explanatory once you understand the previous code.
Spring Security has added Jackson Support for persisting Spring Security related classes. This can improve the performance of serializing Spring Security related classes when working with distributed sessions (i.e. session replication, Spring Session, etc).
To use it, register the SecurityJackson2Modules.getModules(ClassLoader)
as Jackson Modules.
ObjectMapper mapper = new ObjectMapper(); ClassLoader loader = getClass().getClassLoader(); List<Module> modules = SecurityJackson2Modules.getModules(loader); mapper.registerModules(modules); // ... use ObjectMapper as normally ... SecurityContext context = new SecurityContextImpl(); // ... String json = mapper.writeValueAsString(context);
Spring Security supports localization of exception messages that end users are likely to see. If your application is designed for English-speaking users, you don’t need to do anything as by default all Security messages are in English. If you need to support other locales, everything you need to know is contained in this section.
All exception messages can be localized, including messages related to authentication failures and access being denied (authorization failures). Exceptions and logging messages that are focused on developers or system deployers (including incorrect attributes, interface contract violations, using incorrect constructors, startup time validation, debug-level logging) are not localized and instead are hard-coded in English within Spring Security’s code.
Shipping in the spring-security-core-xx.jar
you will find an org.springframework.security
package that in turn contains a messages.properties
file, as well as localized versions for some common languages.
This should be referred to by your ApplicationContext
, as Spring Security classes implement Spring’s MessageSourceAware
interface and expect the message resolver to be dependency injected at application context startup time.
Usually all you need to do is register a bean inside your application context to refer to the messages.
An example is shown below:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:org/springframework/security/messages"/> </bean>
The messages.properties
is named in accordance with standard resource bundles and represents the default language supported by Spring Security messages.
This default file is in English.
If you wish to customize the messages.properties
file, or support other languages, you should copy the file, rename it accordingly, and register it inside the above bean definition.
There are not a large number of message keys inside this file, so localization should not be considered a major initiative.
If you do perform localization of this file, please consider sharing your work with the community by logging a JIRA task and attaching your appropriately-named localized version of messages.properties
.
Spring Security relies on Spring’s localization support in order to actually lookup the appropriate message.
In order for this to work, you have to make sure that the locale from the incoming request is stored in Spring’s org.springframework.context.i18n.LocaleContextHolder
.
Spring MVC’s DispatcherServlet
does this for your application automatically, but since Spring Security’s filters are invoked before this, the LocaleContextHolder
needs to be set up to contain the correct Locale
before the filters are called.
You can either do this in a filter yourself (which must come before the Spring Security filters in web.xml
) or you can use Spring’s RequestContextFilter
.
Please refer to the Spring Framework documentation for further details on using localization with Spring.
The "contacts" sample application is set up to use localized messages.
Spring Security provides a number of optional integrations with Spring MVC. This section covers the integration in further detail.
Note | |
---|---|
As of Spring Security 4.0, |
To enable Spring Security integration with Spring MVC add the @EnableWebSecurity
annotation to your configuration.
Note | |
---|---|
Spring Security provides the configuration using Spring MVC’s WebMvcConfigurer.
This means that if you are using more advanced options, like integrating with |
Spring Security provides deep integration with how Spring MVC matches on URLs with MvcRequestMatcher
.
This is helpful to ensure your Security rules match the logic used to handle your requests.
In order to use MvcRequestMatcher
you must place the Spring Security Configuration in the same ApplicationContext
as your DispatcherServlet
.
This is necessary because Spring Security’s MvcRequestMatcher
expects a HandlerMappingIntrospector
bean with the name of mvcHandlerMappingIntrospector
to be registered by your Spring MVC configuration that is used to perform the matching.
For a web.xml
this means that you should place your configuration in the DispatcherServlet.xml
.
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- All Spring Configuration (both MVC and Security) are in /WEB-INF/spring/ --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/*.xml</param-value> </context-param> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- Load from the ContextLoaderListener --> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
Below WebSecurityConfiguration
in placed in the DispatcherServlet
s ApplicationContext
.
public class SecurityInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { RootConfiguration.class, WebMvcConfiguration.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
Note | |
---|---|
It is always recommended to provide authorization rules by matching on the Providing authorization rules by matching on |
Consider a controller that is mapped as follows:
@RequestMapping("/admin") public String admin() {
If we wanted to restrict access to this controller method to admin users, a developer can provide authorization rules by matching on the HttpServletRequest
with the following:
protected configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .antMatchers("/admin").hasRole("ADMIN") ); }
or in XML
<http> <intercept-url pattern="/admin" access="hasRole('ADMIN')"/> </http>
With either configuration, the URL /admin
will require the authenticated user to be an admin user.
However, depending on our Spring MVC configuration, the URL /admin.html
will also map to our admin()
method.
Additionally, depending on our Spring MVC configuration, the URL /admin/
will also map to our admin()
method.
The problem is that our security rule is only protecting /admin
.
We could add additional rules for all the permutations of Spring MVC, but this would be quite verbose and tedious.
Instead, we can leverage Spring Security’s MvcRequestMatcher
.
The following configuration will protect the same URLs that Spring MVC will match on by using Spring MVC to match on the URL.
protected configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorizeRequests -> authorizeRequests .mvcMatchers("/admin").hasRole("ADMIN") ); }
or in XML
<http request-matcher="mvc"> <intercept-url pattern="/admin" access="hasRole('ADMIN')"/> </http>
Spring Security provides AuthenticationPrincipalArgumentResolver
which can automatically resolve the current Authentication.getPrincipal()
for Spring MVC arguments.
By using @EnableWebSecurity
you will automatically have this added to your Spring MVC configuration.
If you use XML based configuration, you must add this yourself.
For example:
<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
Once AuthenticationPrincipalArgumentResolver
is properly configured, you can be entirely decoupled from Spring Security in your Spring MVC layer.
Consider a situation where a custom UserDetailsService
that returns an Object
that implements UserDetails
and your own CustomUser
Object
. The CustomUser
of the currently authenticated user could be accessed using the following code:
@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); CustomUser custom = (CustomUser) authentication == null ? null : authentication.getPrincipal(); // .. find messages for this user and return them ... }
As of Spring Security 3.2 we can resolve the argument more directly by adding an annotation. For example:
import org.springframework.security.core.annotation.AuthenticationPrincipal; // ... @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) { // .. find messages for this user and return them ... }
Sometimes it may be necessary to transform the principal in some way.
For example, if CustomUser
needed to be final it could not be extended.
In this situation the UserDetailsService
might returns an Object
that implements UserDetails
and provides a method named getCustomUser
to access CustomUser
.
For example, it might look like:
public class CustomUserUserDetails extends User { // ... public CustomUser getCustomUser() { return customUser; } }
We could then access the CustomUser
using a SpEL expression that uses Authentication.getPrincipal()
as the root object:
import org.springframework.security.core.annotation.AuthenticationPrincipal; // ... @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") CustomUser customUser) { // .. find messags for this user and return them ... }
We can also refer to Beans in our SpEL expressions. For example, the following could be used if we were using JPA to manage our Users and we wanted to modify and save a property on the current user.
import org.springframework.security.core.annotation.AuthenticationPrincipal; // ... @PutMapping("/users/self") public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") CustomUser attachedCustomUser, @RequestParam String firstName) { // change the firstName on an attached instance which will be persisted to the database attachedCustomUser.setFirstName(firstName); // ... }
We can further remove our dependency on Spring Security by making @AuthenticationPrincipal
a meta annotation on our own annotation.
Below we demonstrate how we could do this on an annotation named @CurrentUser
.
Note | |
---|---|
It is important to realize that in order to remove the dependency on Spring Security, it is the consuming application that would create |
@Target({ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @AuthenticationPrincipal public @interface CurrentUser {}
Now that @CurrentUser
has been specified, we can use it to signal to resolve our CustomUser
of the currently authenticated user.
We have also isolated our dependency on Spring Security to a single file.
@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) { // .. find messages for this user and return them ... }
Spring Web MVC 3.2+ has excellent support for Asynchronous Request Processing.
With no additional configuration, Spring Security will automatically setup the SecurityContext
to the Thread
that executes a Callable
returned by your controllers.
For example, the following method will automatically have its Callable
executed with the SecurityContext
that was available when the Callable
was created:
@RequestMapping(method=RequestMethod.POST) public Callable<String> processUpload(final MultipartFile file) { return new Callable<String>() { public Object call() throws Exception { // ... return "someView"; } }; }
Associating SecurityContext to Callable’s | |
---|---|
More technically speaking, Spring Security integrates with |
There is no automatic integration with a DeferredResult
that is returned by controllers.
This is because DeferredResult
is processed by the users and thus there is no way of automatically integrating with it.
However, you can still use Concurrency Support to provide transparent integration with Spring Security.
Spring Security will automatically include the CSRF Token within forms that use the Spring MVC form tag. For example, the following JSP:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:form="http://www.springframework.org/tags/form" version="2.0"> <jsp:directive.page language="java" contentType="text/html" /> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <!-- ... --> <c:url var="logoutUrl" value="/logout"/> <form:form action="${logoutUrl}" method="post"> <input type="submit" value="Log out" /> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form:form> <!-- ... --> </html> </jsp:root>
Will output HTML that is similar to the following:
<!-- ... --> <form action="/context/logout" method="post"> <input type="submit" value="Log out"/> <input type="hidden" name="_csrf" value="f81d4fae-7dec-11d0-a765-00a0c91e6bf6"/> </form> <!-- ... -->
Spring Security provides CsrfTokenArgumentResolver
which can automatically resolve the current CsrfToken
for Spring MVC arguments.
By using @EnableWebSecurity you will automatically have this added to your Spring MVC configuration.
If you use XML based configuraiton, you must add this yourself.
Once CsrfTokenArgumentResolver
is properly configured, you can expose the CsrfToken
to your static HTML based application.
@RestController public class CsrfController { @RequestMapping("/csrf") public CsrfToken csrf(CsrfToken token) { return token; } }
It is important to keep the CsrfToken
a secret from other domains.
This means if you are using Cross Origin Sharing (CORS), you should NOT expose the CsrfToken
to any external domains.
Spring Security 4 added support for securing Spring’s WebSocket support. This section describes how to use Spring Security’s WebSocket support.
Note | |
---|---|
You can find a complete working sample of WebSocket security at https://github.com/spring-projects/spring-session/tree/master/samples/boot/websocket. |
Spring Security 4.0 has introduced authorization support for WebSockets through the Spring Messaging abstraction.
To configure authorization using Java Configuration, simply extend the AbstractSecurityWebSocketMessageBrokerConfigurer
and configure the MessageSecurityMetadataSourceRegistry
.
For example:
@Configuration public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { messages .simpDestMatchers("/user/**").authenticated() } }
This will ensure that:
Any inbound CONNECT message requires a valid CSRF token to enforce Same Origin Policy | |
The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request. | |
Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in Section 14.7.3, “WebSocket Authorization” |
Spring Security also provides XML Namespace support for securing WebSockets. A comparable XML based configuration looks like the following:
<websocket-message-broker> <intercept-message pattern="/user/**" access="hasRole('USER')" /> </websocket-message-broker>
This will ensure that:
Any inbound CONNECT message requires a valid CSRF token to enforce Same Origin Policy | |
The SecurityContextHolder is populated with the user within the simpUser header attribute for any inbound request. | |
Our messages require the proper authorization. Specifically, any inbound message that starts with "/user/" will require ROLE_USER. Additional details on authorization can be found in Section 14.7.3, “WebSocket Authorization” |
WebSockets reuse the same authentication information that is found in the HTTP request when the WebSocket connection was made.
This means that the Principal
on the HttpServletRequest
will be handed off to WebSockets.
If you are using Spring Security, the Principal
on the HttpServletRequest
is overridden automatically.
More concretely, to ensure a user has authenticated to your WebSocket application, all that is necessary is to ensure that you setup Spring Security to authenticate your HTTP based web application.
Spring Security 4.0 has introduced authorization support for WebSockets through the Spring Messaging abstraction.
To configure authorization using Java Configuration, simply extend the AbstractSecurityWebSocketMessageBrokerConfigurer
and configure the MessageSecurityMetadataSourceRegistry
.
For example:
@Configuration public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { @Override protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { messages .nullDestMatcher().authenticated() .simpSubscribeDestMatchers("/user/queue/errors").permitAll() .simpDestMatchers("/app/**").hasRole("USER") .simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") .simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() .anyMessage().denyAll(); } }
This will ensure that:
Any message without a destination (i.e. anything other than Message type of MESSAGE or SUBSCRIBE) will require the user to be authenticated | |
Anyone can subscribe to /user/queue/errors | |
Any message that has a destination starting with "/app/" will be require the user to have the role ROLE_USER | |
Any message that starts with "/user/" or "/topic/friends/" that is of type SUBSCRIBE will require ROLE_USER | |
Any other message of type MESSAGE or SUBSCRIBE is rejected. Due to 6 we do not need this step, but it illustrates how one can match on specific message types. | |
Any other Message is rejected. This is a good idea to ensure that you do not miss any messages. |
Spring Security also provides XML Namespace support for securing WebSockets. A comparable XML based configuration looks like the following:
<websocket-message-broker> <intercept-message type="CONNECT" access="permitAll" /> <intercept-message type="UNSUBSCRIBE" access="permitAll" /> <intercept-message type="DISCONNECT" access="permitAll" /> <intercept-message pattern="/user/queue/errors" type="SUBSCRIBE" access="permitAll" /> <intercept-message pattern="/app/**" access="hasRole('USER')" /> <intercept-message pattern="/user/**" access="hasRole('USER')" /> <intercept-message pattern="/topic/friends/*" access="hasRole('USER')" /> <intercept-message type="MESSAGE" access="denyAll" /> <intercept-message type="SUBSCRIBE" access="denyAll" /> <intercept-message pattern="/**" access="denyAll" /> </websocket-message-broker>
This will ensure that:
Any message of type CONNECT, UNSUBSCRIBE, or DISCONNECT will require the user to be authenticated | |
Anyone can subscribe to /user/queue/errors | |
Any message that has a destination starting with "/app/" will be require the user to have the role ROLE_USER | |
Any message that starts with "/user/" or "/topic/friends/" that is of type SUBSCRIBE will require ROLE_USER | |
Any other message of type MESSAGE or SUBSCRIBE is rejected. Due to 6 we do not need this step, but it illustrates how one can match on specific message types. | |
Any other message with a destination is rejected. This is a good idea to ensure that you do not miss any messages. |
In order to properly secure your application it is important to understand Spring’s WebSocket support.
It is important to understand the distinction between SUBSCRIBE and MESSAGE types of messages and how it works within Spring.
Consider a chat application.
While we want clients to be able to SUBSCRIBE to "/topic/system/notifications", we do not want to enable them to send a MESSAGE to that destination. If we allowed sending a MESSAGE to "/topic/system/notifications", then clients could send a message directly to that endpoint and impersonate the system.
In general, it is common for applications to deny any MESSAGE sent to a destination that starts with the broker prefix (i.e. "/topic/" or "/queue/").
It is also is important to understand how destinations are transformed.
Consider a chat application.
SimpMessageSendingOperations.convertAndSendToUser("toUser", "/queue/messages", message)
.
With the application above, we want to allow our client to listen to "/user/queue" which is transformed into "/queue/user/messages-<sessionid>". However, we do not want the client to be able to listen to "/queue/*" because that would allow the client to see messages for every user.
In general, it is common for applications to deny any SUBSCRIBE sent to a message that starts with the broker prefix (i.e. "/topic/" or "/queue/"). Of course we may provide exceptions to account for things like
Spring contains a section titled Flow of Messages that describes how messages flow through the system.
It is important to note that Spring Security only secures the clientInboundChannel
.
Spring Security does not attempt to secure the clientOutboundChannel
.
The most important reason for this is performance. For every message that goes in, there are typically many more that go out. Instead of securing the outbound messages, we encourage securing the subscription to the endpoints.
It is important to emphasize that the browser does not enforce the Same Origin Policy for WebSocket connections. This is an extremely important consideration.
Consider the following scenario. A user visits bank.com and authenticates to their account. The same user opens another tab in their browser and visits evil.com. The Same Origin Policy ensures that evil.com cannot read or write data to bank.com.
With WebSockets the Same Origin Policy does not apply. In fact, unless bank.com explicitly forbids it, evil.com can read and write data on behalf of the user. This means that anything the user can do over the webSocket (i.e. transfer money), evil.com can do on that users behalf.
Since SockJS tries to emulate WebSockets it also bypasses the Same Origin Policy. This means developers need to explicitly protect their applications from external domains when using SockJS.
Fortunately, since Spring 4.1.5 Spring’s WebSocket and SockJS support restricts access to the current domain. Spring Security adds an additional layer of protection to provide defence in depth.
By default Spring Security requires the CSRF token in any CONNECT message type. This ensures that only a site that has access to the CSRF token can connect. Since only the Same Origin can access the CSRF token, external domains are not allowed to make a connection.
Typically we need to include the CSRF token in an HTTP header or an HTTP parameter. However, SockJS does not allow for these options. Instead, we must include the token in the Stomp headers
Applications can obtain a CSRF token by accessing the request attribute named _csrf.
For example, the following will allow accessing the CsrfToken
in a JSP:
var headerName = "${_csrf.headerName}"; var token = "${_csrf.token}";
If you are using static HTML, you can expose the CsrfToken
on a REST endpoint.
For example, the following would expose the CsrfToken
on the URL /csrf
@RestController public class CsrfController { @RequestMapping("/csrf") public CsrfToken csrf(CsrfToken token) { return token; } }
The JavaScript can make a REST call to the endpoint and use the response to populate the headerName and the token.
We can now include the token in our Stomp client. For example:
... var headers = {}; headers[headerName] = token; stompClient.connect(headers, function(frame) { ... }
If you want to allow other domains to access your site, you can disable Spring Security’s protection. For example, in Java Configuration you can use the following:
@Configuration public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { ... @Override protected boolean sameOriginDisabled() { return true; } }
SockJS provides fallback transports to support older browsers. When using the fallback options we need to relax a few security constraints to allow SockJS to work with Spring Security.
SockJS may use an transport that leverages an iframe. By default Spring Security will deny the site from being framed to prevent Clickjacking attacks. To allow SockJS frame based transports to work, we need to configure Spring Security to allow the same origin to frame the content.
You can customize X-Frame-Options with the frame-options element. For example, the following will instruct Spring Security to use "X-Frame-Options: SAMEORIGIN" which allows iframes within the same domain:
<http> <!-- ... --> <headers> <frame-options policy="SAMEORIGIN" /> </headers> </http>
Similarly, you can customize frame options to use the same origin within Java Configuration using the following:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // ... .headers(headers -> headers .frameOptions(frameOptions -> frameOptions .sameOrigin() ) ); } }
SockJS uses a POST on the CONNECT messages for any HTTP based transport. Typically we need to include the CSRF token in an HTTP header or an HTTP parameter. However, SockJS does not allow for these options. Instead, we must include the token in the Stomp headers as described in the section called “Adding CSRF to Stomp Headers”.
It also means we need to relax our CSRF protection with the web layer. Specifically, we want to disable CSRF protection for our connect URLs. We do NOT want to disable CSRF protection for every URL. Otherwise our site will be vulnerable to CSRF attacks.
We can easily achieve this by providing a CSRF RequestMatcher. Our Java Configuration makes this extremely easy. For example, if our stomp endpoint is "/chat" we can disable CSRF protection for only URLs that start with "/chat/" using the following configuration:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf // ignore our stomp endpoints since they are protected using Stomp headers .ignoringAntMatchers("/chat/**") ) .headers(headers -> headers // allow same origin to frame our site to support iframe SockJS .frameOptions(frameOptions -> frameOptions .sameOrigin() ) ) .authorizeRequests(authorizeRequests -> ... ) ...
If we are using XML based configuration, we can use the csrf@request-matcher-ref. For example:
<http ...> <csrf request-matcher-ref="csrfMatcher"/> <headers> <frame-options policy="SAMEORIGIN"/> </headers> ... </http> <b:bean id="csrfMatcher" class="AndRequestMatcher"> <b:constructor-arg value="#{T(org.springframework.security.web.csrf.CsrfFilter).DEFAULT_CSRF_MATCHER}"/> <b:constructor-arg> <b:bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher"> <b:bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher"> <b:constructor-arg value="/chat/**"/> </b:bean> </b:bean> </b:constructor-arg> </b:bean>
Spring Framework provides first class support for CORS.
CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the JSESSIONID
).
If the request does not contain any cookies and Spring Security is first, the request will determine the user is not authenticated (since there are no cookies in the request) and reject it.
The easiest way to ensure that CORS is handled first is to use the CorsFilter
.
Users can integrate the CorsFilter
with Spring Security by providing a CorsConfigurationSource
using the following:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // by default uses a Bean by the name of corsConfigurationSource .cors(withDefaults()) ... } @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("https://example.com")); configuration.setAllowedMethods(Arrays.asList("GET","POST")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
or in XML
<http> <cors configuration-source-ref="corsSource"/> ... </http> <b:bean id="corsSource" class="org.springframework.web.cors.UrlBasedCorsConfigurationSource"> ... </b:bean>
If you are using Spring MVC’s CORS support, you can omit specifying the CorsConfigurationSource
and Spring Security will leverage the CORS configuration provided to Spring MVC.
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // if Spring MVC is on classpath and no CorsConfigurationSource is provided, // Spring Security will use CORS configuration provided to Spring MVC .cors(withDefaults()) ... } }
or in XML
<http> <!-- Default to Spring MVC's CORS configuration --> <cors /> ... </http>
Spring Security has its own taglib which provides basic support for accessing security information and applying security constraints in JSPs.
To use any of the tags, you must have the security taglib declared in your JSP:
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
This tag is used to determine whether its contents should be evaluated or not.
In Spring Security 3.0, it can be used in two ways [11].
The first approach uses a web-security expression, specified in the access
attribute of the tag.
The expression evaluation will be delegated to the SecurityExpressionHandler<FilterInvocation>
defined in the application context (you should have web expressions enabled in your <http>
namespace configuration to make sure this service is available).
So, for example, you might have
<sec:authorize access="hasRole('supervisor')"> This content will only be visible to users who have the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s. </sec:authorize>
When used in conjuction with Spring Security’s PermissionEvaluator, the tag can also be used to check permissions. For example:
<sec:authorize access="hasPermission(#domain,'read') or hasPermission(#domain,'write')"> This content will only be visible to users who have read or write permission to the Object found as a request attribute named "domain". </sec:authorize>
A common requirement is to only show a particular link, if the user is actually allowed to click it. How can we determine in advance whether something will be allowed? This tag can also operate in an alternative mode which allows you to define a particular URL as an attribute. If the user is allowed to invoke that URL, then the tag body will be evaluated, otherwise it will be skipped. So you might have something like
<sec:authorize url="/admin"> This content will only be visible to users who are authorized to send requests to the "/admin" URL. </sec:authorize>
To use this tag there must also be an instance of WebInvocationPrivilegeEvaluator
in your application context.
If you are using the namespace, one will automatically be registered.
This is an instance of DefaultWebInvocationPrivilegeEvaluator
, which creates a dummy web request for the supplied URL and invokes the security interceptor to see whether the request would succeed or fail.
This allows you to delegate to the access-control setup you defined using intercept-url
declarations within the <http>
namespace configuration and saves having to duplicate the information (such as the required roles) within your JSPs.
This approach can also be combined with a method
attribute, supplying the HTTP method, for a more specific match.
The Boolean result of evaluating the tag (whether it grants or denies access) can be stored in a page context scope variable by setting the var
attribute to the variable name, avoiding the need for duplicating and re-evaluating the condition at other points in the page.
Hiding a link in a page for unauthorized users doesn’t prevent them from accessing the URL.
They could just type it into their browser directly, for example.
As part of your testing process, you may want to reveal the hidden areas in order to check that links really are secured at the back end.
If you set the system property spring.security.disableUISecurity
to true
, the authorize
tag will still run but will not hide its contents.
By default it will also surround the content with <span class="securityHiddenUI">…</span>
tags.
This allows you to display "hidden" content with a particular CSS style such as a different background colour.
Try running the "tutorial" sample application with this property enabled, for example.
You can also set the properties spring.security.securedUIPrefix
and spring.security.securedUISuffix
if you want to change surrounding text from the default span
tags (or use empty strings to remove it completely).
This tag allows access to the current Authentication
object stored in the security context.
It renders a property of the object directly in the JSP.
So, for example, if the principal
property of the Authentication
is an instance of Spring Security’s UserDetails
object, then using <sec:authentication property="principal.username" />
will render the name of the current user.
Of course, it isn’t necessary to use JSP tags for this kind of thing and some people prefer to keep as little logic as possible in the view.
You can access the Authentication
object in your MVC controller (by calling SecurityContextHolder.getContext().getAuthentication()
) and add the data directly to your model for rendering by the view.
This tag is only valid when used with Spring Security’s ACL module. It checks a comma-separated list of required permissions for a specified domain object. If the current user has all of those permissions, then the tag body will be evaluated. If they don’t, it will be skipped. An example might be
Caution | |
---|---|
In general this tag should be considered deprecated. Instead use the Section 14.9.2, “The authorize Tag”. |
<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}"> This will be shown if the user has all of the permissions represented by the values "1" or "2" on the given object. </sec:accesscontrollist>
The permissions are passed to the PermissionFactory
defined in the application context, converting them to ACL Permission
instances, so they may be any format which is supported by the factory - they don’t have to be integers, they could be strings like READ
or WRITE
.
If no PermissionFactory
is found, an instance of DefaultPermissionFactory
will be used.
The AclService
from the application context will be used to load the Acl
instance for the supplied object.
The Acl
will be invoked with the required permissions to check if all of them are granted.
This tag also supports the var
attribute, in the same way as the authorize
tag.
If CSRF protection is enabled, this tag inserts a hidden form field with the correct name and value for the CSRF protection token. If CSRF protection is not enabled, this tag outputs nothing.
Normally Spring Security automatically inserts a CSRF form field for any <form:form>
tags you use, but if for some reason you cannot use <form:form>
, csrfInput
is a handy replacement.
You should place this tag within an HTML <form></form>
block, where you would normally place other input fields.
Do NOT place this tag within a Spring <form:form></form:form>
block.
Spring Security handles Spring forms automatically.
<form method="post" action="/do/something"> <sec:csrfInput /> Name:<br /> <input type="text" name="name" /> ... </form>
If CSRF protection is enabled, this tag inserts meta tags containing the CSRF protection token form field and header names and CSRF protection token value. These meta tags are useful for employing CSRF protection within JavaScript in your applications.
You should place csrfMetaTags
within an HTML <head></head>
block, where you would normally place other meta tags.
Once you use this tag, you can access the form field name, header name, and token value easily using JavaScript.
JQuery is used in this example to make the task easier.
<!DOCTYPE html> <html> <head> <title>CSRF Protected JavaScript Page</title> <meta name="description" content="This is the description for this page" /> <sec:csrfMetaTags /> <script type="text/javascript" language="javascript"> var csrfParameter = $("meta[name='_csrf_parameter']").attr("content"); var csrfHeader = $("meta[name='_csrf_header']").attr("content"); var csrfToken = $("meta[name='_csrf']").attr("content"); // using XMLHttpRequest directly to send an x-www-form-urlencoded request var ajax = new XMLHttpRequest(); ajax.open("POST", "https://www.example.org/do/something", true); ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded data"); ajax.send(csrfParameter + "=" + csrfToken + "&name=John&..."); // using XMLHttpRequest directly to send a non-x-www-form-urlencoded request var ajax = new XMLHttpRequest(); ajax.open("POST", "https://www.example.org/do/something", true); ajax.setRequestHeader(csrfHeader, csrfToken); ajax.send("..."); // using JQuery to send an x-www-form-urlencoded request var data = {}; data[csrfParameter] = csrfToken; data["name"] = "John"; ... $.ajax({ url: "https://www.example.org/do/something", type: "POST", data: data, ... }); // using JQuery to send a non-x-www-form-urlencoded request var headers = {}; headers[csrfHeader] = csrfToken; $.ajax({ url: "https://www.example.org/do/something", type: "POST", headers: headers, ... }); <script> </head> <body> ... </body> </html>
If CSRF protection is not enabled, csrfMetaTags
outputs nothing.