Spring Security for a REST API
Further reading:
Spring Security Authentication Tutorial
How to build a production-grade Registration process for new users, and Login flow for existing users.
Granted Authority Versus Role in Spring Security
A quick guide to the difference between a granted authority and a role in Spring Security.
Spring Security Basic Authentication
Set up Basic Authentication in Spring – the XML Configuration, the Error Messages, and example of consuming the secured URLs with curl.
2. Enable Spring Security
The architecture of Spring Security is based entirely on Servlet Filters. Therefore, this comes before Spring MVC in regards to the processing of HTTP requests.
The simplest option to register the Spring security filter is by annotating our config class with @EnableWebSecurity:
@Config
@EnableWebSecurity
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
// ...
}
For a non-Spring Boot application, we can extend the AbstractSecurityWebApplicationInitializer and pass our config class in its constructor:
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(SecurityJavaConfig.class);
}
}
Or else we can declare it in web.xml of the application:
<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>
</filter-mapping>
We should name the filter ‘springSecurityFilterChain’ to match the default bean created by Spring Security in the container.
Note that the defined filter is not the actual class implementing the security logic. Instead, it is a DelegatingFilterProxy that delegate the filter’s methods to an internal bean. This is done so that the target bean can still benefit from the Spring context lifecycle and flexibility.
The URL pattern used to configure the Filter is /* even though the entire web service is mapped to /api/*. This gives the security configuration an option to secure other possible mappings as well if required.
3. Spring Security Java Configuration
We can do Security configurations entirely in a Java class by creating a config class that extends WebSecurityConfigurerAdapter and annotating it with @EnableWebSecurity:
@Configuration
@EnableWebSecurity
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
// ...
}
Now, let’s create users with different roles in SecurityJavaConfig that we will be using to authenticate our API endpoints:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password(encoder().encode("adminPass")).roles("ADMIN")
.and()
.withUser("user").password(encoder().encode("userPass")).roles("USER");
}
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
Next, let’s configure security for our API endpoints:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/api/foos").authenticated()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.successHandler(mySuccessHandler)
.failureHandler(myFailureHandler)
.and()
.logout();
}
3.1. The http Element
The http element is the starting element for security configuration and provides us with fluent and flexible methods to configure security.
In our implementation, we are creating secured mappings /api/foos and /api/admin/** using antMatchers.
The /api/foos pattern is accessible to any authenticated user. On the other hand, /api/admin/** will only be accessible to ADMIN role users.
3.2. The Entry Point
In a standard web application, the authentication process may automatically trigger when an un-authenticated client tries to access a secured resource. This process usually redirects to a login page so that the user can enter credentials.
However, for a REST Web Service,this behaviour doesn’t make much sense. We should be able to authenticate only by a request to the correct URI and if the user is not authenticated all requests should simply fail with a 401 UNAUTHORIZED status code.
Spring Security handles this automatic triggering of the authentication process with the concept of an Entry Point – this is a required part of the configuration, and can be injected via the authenticationEntryPoint method.
Keeping in mind that this functionality doesn’t make sense in the context of the REST Service, we define the new custom entry point to simply return 401 when triggered:
@Component
public final class RestAuthenticationEntryPoint
implements AuthenticationEntryPoint {
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Unauthorized");
}
}
A quick side note here is that the 401 is sent without the WWW-Authenticate header, as required by the HTTP Spec. We can, of course, set the value manually if we need to.
3.3. The Login Form for REST
There are multiple ways to do Authentication for a REST API. One of the defaults Spring Security provides is Form Login – which uses an authentication processing filter – org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.
The formLogin element will create this filter and also provides additional methods successHandler and failureHandler to set our custom authentication success and failure handlers respectively.
Note that for a standard web application, the @EnableWebSecurity annotation configures a lot of default configurations itself.
3.4. Authentication Should Return 200 Instead of 301
By default, form login will answer a successful authentication request with a 301 MOVED PERMANENTLY status code; this makes sense in the context of an actual login form which needs to redirect after login.
However, for a RESTful web service, the desired response for a successful authentication should be 200 OK.
We do this by injecting a custom authentication success handler in the form login filter, to replace the default one. The new handler implements the exact same login as the default org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler with one notable difference – it removes the redirect logic:
public class MySavedRequestAwareAuthenticationSuccessHandler
extends SimpleUrlAuthenticationSuccessHandler {
private RequestCache requestCache = new HttpSessionRequestCache();
@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws ServletException, IOException {
SavedRequest savedRequest
= requestCache.getRequest(request, response);
if (savedRequest == null) {
clearAuthenticationAttributes(request);
return;
}
String targetUrlParam = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl()
|| (targetUrlParam != null
&& StringUtils.hasText(request.getParameter(targetUrlParam)))) {
requestCache.removeRequest(request, response);
clearAuthenticationAttributes(request);
return;
}
clearAuthenticationAttributes(request);
}
public void setRequestCache(RequestCache requestCache) {
this.requestCache = requestCache;
}
}
3.5. Failed Authentication Should Return 401 Instead of 302
Similarly – we configured the authentication failure handler – the same way we did with the success handler.
Luckily – in this case, we don’t need to actually define a new class for this handler – the standard implementation – SimpleUrlAuthenticationFailureHandler – does just fine.
3.6. The Authentication Manager and Provider
The authentication process uses an in-memory provider to perform authentication. This is meant to simplify the configuration as a production implementation of these artifacts is outside the scope of this article.
We have created two users namely user having role USER and admin with role ADMIN.
3.7. Finally – Authentication Against the Running REST Service
The URL for login is /login – and a simple curl command performing login for the user of name user and password userPass would be:
curl -i -X POST -d username=user -d password=userPass
http://localhost:8080/spring-security-rest/login
This request will return the Cookie which we can use for any subsequent request against the REST Service.
We can use curl to authentication and store the cookie it receives in a file:
curl -i -X POST -d username=user -d password=userPass -c /opt/cookies.txt
http://localhost:8080/spring-security-rest/login
Then we can use the cookie from the file to do further authenticated requests:
curl -i --header "Accept:application/json" -X GET -b /opt/cookies.txt
http://localhost:8080/spring-security-rest/api/foos
Since the user can access the /api/foos/ endpoint, this authenticated request will correctly *result in a 200 OK:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Jul 2013 20:31:13 GMT
[{"id":0,"name":"JbidXc"}]
Similarly, for admin user we can use curl for authentication:
curl -i -X POST -d username=admin -d password=adminPass -c /opt/cookies.txt
http://localhost:8080/spring-security-rest/login
and then updated cookies for accessing admin endpoints /api/admin/*:
curl -i --header "Accept:application/json" -X GET -b /opt/cookies.txt
http://localhost:8080/spring-security-rest/api/admin/x
Since admin user can access endpoint /api/admin/* this will result in success response:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=ISO-8859-1
Content-Length: 5
Date: Mon, 15 Oct 2018 17:16:39 GMT
Hello
4. The XML Security Configuration
<http entry-point-ref="restAuthenticationEntryPoint">
<intercept-url pattern="/api/admin/**" access="ROLE_ADMIN"/>
<form-login
authentication-success-handler-ref="mySuccessHandler"
authentication-failure-handler-ref="myFailureHandler" />
<logout />
</http>
<beans:bean id="mySuccessHandler"
class="org.rest.security.MySavedRequestAwareAuthenticationSuccessHandler"/>
<beans:bean id="myFailureHandler" class=
"org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"/>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="admin" password="adminPass" authorities="ROLE_ADMIN"/>
<user name="user" password="userPass" authorities="ROLE_USER"/>
</user-service>
</authentication-provider>
</authentication-manager>
+ Most of the configuration is done using the security namespace. To enable this, we need to define the schema locations.
The namespace is designed so that it expresses the common use cases of Spring Security while still providing hooks for raw beans to accommodate more advanced scenarios.
5. Conclusion
In this tutorial, we covered the basic security configuration and implementation for a RESTful Service using Spring Security 5.
We learned how to do Security configuration for our REST API entirely via Java config and also looked at its web.xml configuration alternative.
Next, we discussed how to create users and roles for our secured application and also how to map these users to specific endpoints of our application.
Finally, we also looked into creating custom Authentication Entry Point and a custom Success Handler that gave our application a better flexibility in terms of controlling security.
The full implementation is available over on Github.