Authenticating through a BASIC scheme is a popular solution for stateless applications like ours. Credentials are sent over with HTTP requests.
In this recipe, we complete the Spring Security configuration. We make it support the BASIC authentication scheme required for the application.
We slightly customize the generated response-headers, so they don't trigger the browser to show-up a native BASIC authentication form (which is not an optimal experience for our users).
cloudstreetmarket-api web.xml
:<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class> org.sfw.web.filter.DelegatingFilterProxy </filter- class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
cloudstreetmarket-api
module. This file hosts the following bean definitions:<bean id="authenticationEntryPoint" class="edu.zc.csm.api.authentication.CustomBasicAuthenticationEntryPoint"> <property name="realmName" value="cloudstreetmarket.com" /> </bean> <security:http create-session="stateless" authentication-manager-ref="authenticationManager" entry-point-ref="authenticationEntryPoint"> <security:custom-filter ref="basicAuthenticationFilter" after="BASIC_AUTH_FILTER" /> <security:csrf disabled="true"/> </security:http> <bean id="basicAuthenticationFilter" class="org.sfw.security.web.authentication.www.BasicAuthenticationFilter"> <constructor-arg name="authenticationManager" ref="authenticationManager" /> <constructor-arg name="authenticationEntryPoint" ref="authenticationEntryPoint" /> </bean> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider user-service-ref='communityServiceImpl'> <security:password-encoder ref="passwordEncoder"/> </security:authentication-provider> </security:authentication-manager> <security:global-method-security secured-annotations="enabled" pre-post-annotations="enabled" authentication-manager-ref="authenticationManager"/>
CustomBasicAuthenticationEntryPoint
class. This class has the following content:public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setHeader("WWW-Authenticate", "CSM_Basic realm= + getRealmName() + "); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage() ); } }
@ExceptionHandler
has been added to catch authentication Exceptions:@ExceptionHandler({BadCredentialsException.class, AuthenticationException.class, AccessDeniedException.class}) protected ResponseEntity<Object> handleBadCredentials(final RuntimeException ex, final WebRequest request) { return handleExceptionInternal(ex, "The attempted operation has been denied!", new HttpHeaders(), FORBIDDEN, request); } ...
IMarketService
interface in cloudstreetmarket-core
. Add the @Secured("ROLE_BASIC")
annotation to the Type
as follows:@Secured ("ROLE_BASIC") public interface IMarketService { Page<IndexOverviewDTO> getLastDayIndicesOverview( MarketCode market, Pageable pageable); Page<IndexOverviewDTO> getLastDayIndicesOverview( Pageable pageable); HistoProductDTO getHistoIndex(String code, MarketCode market, Date fromDate, Date toDate, QuotesInterval interval); }
403
status code (FORBIDDEN
).These queries have also returned the JSON response:
{"error":"Access is denied","message":"The attempted operation has been denied!","status":403","date":"2015-05-05 18:01:14.917"}
BASIC
role:Username: <userC> Password: <123456>
Basic dXNlckM6MTIzNDU2
. The encoded dXNlckM6MTIzNDU2
is the base64-encoded value for userC:123456
.The status is now 200 (OK)
and you should also have received the right JSON result:
WWW-Authenticate
header in the response to the value: CSM_Basic realm"="cloudstreetmarket.com"IMarketService
(in the 5th step).We are going to explore the concepts behind a BASIC authentication with Spring Security:
As always, a Spring configuration namespace brings a specific syntax that suits the needs and uses for a module. It lightens the overall Spring configuration with a better readability. Namespaces often come with configuration by default or auto configuration tools.
The Spring Security namespace comes with the spring-security-config dependency and can be defined as follows in a Spring configuration file:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xsi:schemaLocation"="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.0.xsd"> ... </beans>
The namespace stages three top-level components: <http>
(about web and HTTP security), <authentication-manager>,
and <global-method-security>
(service or controller restriction).
Then, other concepts are referenced by those top-level components as attribute or as child element: <authentication-provider>
, <access-decision-manager>
(provides access decisions for web and security methods), and <user-service>
(as UserDetailsService
implementations).
The <http>
component of the namespace provides an auto-config
attribute that we didn't use here. The <http auto-config"="true">
definition would have been a shortcut for the following definition:
<http> <form-login /> <http-basic /> <logout /> </http>
It isn't worth it for our REST API because we didn't plan to implement a server-side generated view for a form login. Also, the <logout>
component would have been useless for us since our API doesn't manage sessions.
Finally, the <http-basic>
element creates underlying BasicAuthenticationFilter
and BasicAuthenticationEntryPoint
to the configuration.
We have made use of our own BasicAuthenticationFilter
in order to customize the WWW-Authenticate
response's header value from Basic base64token
to CSM_Basic base64token
. This because the AJAX HTTP responses (from our API) containing a WWW-Authenticate
header with a value starting with a Basic keyword automatically trigger the web-browser to open a native Basic-form popup. It was not the type of user experience we wanted to set up.
In the very first step of the recipe, we have declared a filter named springSecurityFilterChain
in web.xml
:
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.sfw.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Here, springSecurityFilterChain
is also a Spring bean that is created internally by the Spring Security namespace (specifically the http
component). A DelegatingFilterProxy
is a Spring infrastructure that looks for a specific bean in the application context and invokes it. The targeted bean has to implement the Filter
interface.
The whole Spring Security machinery is hooked-up in this way through finally one single bean.
The configuration of the <http>
element plays a central-role in the definition of what the filter-chain is made of. It is directly the elements it defines, that create the related filters.
"Some core filters are always created in a filter chain and others will be added to the stack depending on the attributes and child elements which are present." | ||
--Spring Security |
It is important to distinguish between the configuration-dependant filters and the core filters that cannot be removed. As core filters, we can count SecurityContextPersistenceFilter
, ExceptionTranslationFilter,
and FilterSecurityInterceptor
. These three filters are natively bound to the <http>
element and can be found in the next table.
This table comes from the Spring Security reference document and it contains all the core filters (coming with the framework) that can be activated using specific elements or attributes. They are listed here in the order of their position in the chain.
Alias |
Filter Class |
Namespace Element or Attribute |
---|---|---|
|
ChannelProcessingFilter |
http/intercept-url@requires-channel |
|
SecurityContextPersistenceFilter |
http |
|
ConcurrentSessionFilter |
session-management/concurrency-control |
|
HeaderWriterFilter |
http/headers |
|
CsrfFilter |
http/csrf |
|
LogoutFilter |
http/logout |
|
X509AuthenticationFilter |
http/x509 |
|
AbstractPreAuthenticatedProcessingFilter Subclasses |
N/A |
|
CasAuthenticationFilter |
N/A |
|
UsernamePasswordAuthenticationFilter |
http/form-login |
|
BasicAuthenticationFilter |
http/http-basic |
|
SecurityContextHolderAwareRequestFilter |
http/@servlet-api-provision |
|
JaasApiIntegrationFilter |
http/@jaas-api-provision |
|
RememberMeAuthenticationFilter |
http/remember-me |
|
AnonymousAuthenticationFilter |
http/anonymous |
|
SessionManagementFilter |
session-management |
|
ExceptionTranslationFilter |
http |
|
FilterSecurityInterceptor |
http |
|
SwitchUserFilter |
N/A |
Remember that custom filters can be positioned relatively, or can replace any of these filters using the
custom-filter
element:
<security:custom-filter ref="myFilter" after="BASIC_AUTH_FILTER"/>
We have defined the following configuration for the <http>
'namespace's component:
<security:http create-session="stateless" entry-point-ref="authenticationEntryPoint" authentication-manager- ref="authenticationManager"> <security:custom-filter ref="basicAuthenticationFilter" after="BASIC_AUTH_FILTER" /> <security:csrf disabled="true"/> </security:http> <bean id="basicAuthenticationFilter" class="org.sfw.security.web.authentication.www.BasicAuthenticationFilter"> <constructor-arg name="authenticationManager" ref="authenticationManager" /> <constructor-arg name="authenticationEntryPoint" ref="authenticationEntryPoint" /> </bean> <bean id="authenticationEntryPoint" class="edu.zc.csm.api. authentication.CustomBasicAuthenticationEntryPoint"> <property name="realmName" value="${realm.name}" /> </bean>
Here, we tell Spring not to create sessions and to ignore incoming sessions using create-session=="stateless"
. We have done this to pursue the stateless and scalable Microservices design.
We have also disabled the Cross-Site Request Forgery (csrf) support for now, for the same reason. This feature has been enabled by default by the framework since the Version 3.2.
It has been necessary to define an entry-point-ref
because we didn't implement any authentication strategy preconfigured by the namespace (http-basic
or login-form
).
We have defined a custom filter BasicAuthenticationFilter
to be executed after the theoretical position of the core BASIC_AUTH_FILTER
.
We are now going to see which roles play the three references made to: authenticationEntryPoint
, authenticationManager
, and basicAuthenticationFilter
.
First of all, AuthenticationManager
is a single-method interface:
public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }
Spring Security provides one implementation: ProviderManager
. This implementation allows us to plug in several AuthenticationProviders
. The ProviderManager
tries all the AuthenticationProviders
in order, calling their authenticate
method. The code is as follows:
public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication); }
The ProviderManager
stops its iteration when it finds a non-null Authentication
object. Alternatively, it fails the Authentication
when an AuthenticationException
is thrown.
Using the namespace, a specific AuthenticationProviders
can be targeted using the ref
element as shown here:
<security:authentication-manager > <security:authentication-provider ref='myAuthenticationProvider'/> </security:authentication-manager>
Now, here is our configuration:
<security:authentication-manager alias"="authenticationManager""> <security:authentication-provider user-service-ref='communityServiceImpl'> <security:password-encoder ref="passwordEncoder"/> </security:authentication-provider> </security:authentication-manager>
There is no ref
element in our configuration. The namespace will by default instantiate a DaoAuthenticationProvider
. It will also inject our UserDetailsService
implementation: communityServiceImpl
, because we have specified it with user-service-ref
.
This DaoAuthenticationProvider
throws an AuthenticationException
when the password submitted in a UsernamePasswordAuthenticationToken
doesn't match the one which is loaded by UserDetailsService
(making use of the loadUserByUsername
method).
It exists a few other AuthenticationProviders
that could be used in our projects, for example,, RememberMeAuthenticationProvider
, LdapAuthenticationProvider
, CasAuthenticationProvider
, or JaasAuthenticationProvider
.
As we have said using a BASIC scheme is a great technique for REST applications. However when using it, it is critical to use an encrypted communication protocol (HTTPS) as the passwords are sent in plain text.
As demonstrated in the How to do it section, the principle is very simple. The HTTP requests are the same as usual with an extra header Authentication
. This header has the value made of the keyword Basic
followed by a space, followed by a String encoded in base 64.
We can find online a bunch of free services to quickly encode/decode in base 64 a String. The String to be encoded in base 64 has to be in the following form: <username>:<password>
.
To implement our Basic authentication, we have added BasicAuthenticationFilter
to our filter chain. This BasicAuthenticationFilter
(org.sfw.security.web.authentication.www.BasicAuthenticationFilter
) requires an authenticationManager
and optionally an authenticationEntryPoint
.
The optional configuration of an authenticationEntryPoint
drives the filter towards two different behaviours presented next.
Both starts the same way: the filter is triggered from its position in the chain. It looks for the authentication header in the request and delegates to the authenticationManager
, which then relies on the UserDetailsService
implementation to compare it with the user credentials from the database.
This is our configuration, which behaves in the following way:
Authentication
object is returned.authenticationEntryPoint
method is invoked in an interruption of the filter-chain. Our authentication entry-point sets a custom WWW-Authenticate
response header and a 401
status-code (FORBIDDEN
).This type of configuration provides a preauthentication where the Authentication Header
in the HTTP Request is checked to see whether or not the business services require an authorization (Secure Object).
This configuration allows a quick feedback with a potential native BASIC form prompted by the web browser. We have chosen this configuration for now in our application.
Without an authenticationEntryPoint, the filter behaves as follows:
Authentication
object is returned.This section has been largely inspired from the Spring rsecurity reference, which is again a great resource:
http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle
An appendix provides a very complete guide to the Spring Security namespace:
http://docs.spring.io/spring-security/site/docs/current/reference/html/appendix-namespace.html