Build a Spring-Boot REST service with Basic Authentication for several users
This post describes how to build a REST service with Spring-Boot that uses Basic-Authentication for several users and that uses the username of the authenticated user to do it’s work. Warning: A service using basic authentication should always use HTTPS as transport protocol, either by running behind a web server proxy or by setting up HTTPS by itself. I’ll cover the latter in a later post.
This might be a setup for a service, where for each user, data is stored in a database, so it not only is necessary to authenticate the user to use the service, but it is also necessary in the service to know which user is accessing the service.
The source code for this project can be found at GitHub . The coce relevant for this post is tagged post-20150728.
Setting up the project
I use IntelliJ IDEA (EAP Version 15 with Spring support) for the project and a maven project setup.
To create the initial project, I use Spring Initializr from within IDEA, but you can use https://start.spring.io as well to create the project. I named the project SecuRest, the initial dependencies are Core/Security and Web/Web:
](/migrated/2015/07/Bildschirmfoto-2015-07-28-um-13.13.57.png)
The created pom.xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sothawo</groupId>
<artifactId>securest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SecuRest</name>
<description>Demo project for Spring Boot with Basic Authentication and HTTPS</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The Service
The first version of the implemented service just has one method which echoes it’s call arguments back:
/**
* Copyright (c) 2015 sothawo
*
*
*/
package com.sothawo.securest;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Sample service.
*
* @author P.J. Meisch (pj.meisch@sothawo.com).
*/
@RestController
@RequestMapping("/service")
public class UserService {
@RequestMapping(value = "/echo/{in}", method = RequestMethod.GET)
public String echo(@PathVariable(value = "in") final String in) {
return "You said: " + in;
}
}
There is nothing yet to retrieve the name of the current user, we’ll do that later.
Running the application with the basic setup
This is all that is needed for the first basic application sceleton. When the application ist started, Spring-Boot sets up the security so that the whole application is secured and can only be accessed by a user user; the password is displayed on application startup in the log in a line like this (the password changes each time the application starts). This is the basic behaviour of Spring-Boot when spring-boot-starter-security is found on application startup.
Using default security password: e482e82e-1115-4fc4-86e4-bdcc432da039
When trying to access the application without passing in a username and password, the following result is returned:
curl localhost:8080/service/echo/hello
{"timestamp":1438088762118,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/service/echo/hello"}
With curl I can pass in the username and password like this:
curl user:e482e82e-1115-4fc4-86e4-bdcc432da039@localhost:8080/service/echo/hello
You said: hello
As an alternative I use IntelliJ’s REST Tool to create an authorization header for the user user and password e482e82e-1115-4fc4-86e4-bdcc432da039 and pass it along with the request:
curl -H "Authorization: Basic dXNlcjplNDgyZTgyZS0xMTE1LTRmYzQtODZlNC1iZGNjNDMyZGEwMzk=" localhost:8080/service/echo/hello
You said: hello
So now access to the service is granted. But, as I said, there is only one user named user allowed, and the password changes every time on application startup. Next, I’ll set up some users with passwords.
Setting up the Users
I am using the most basic form to setup different users, and that is by using an in-memory user store. To have that I add the following configuration class:
/**
* Copyright (c) 2015 sothawo
*
*
*/
package com.sothawo.securest;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Security configuration.
*
* @author P.J. Meisch (pj.meisch@sothawo.com).
*/
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user1").password("secret1").roles("USER")
.and()
.withUser("user2").password("secret2").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated();
http.httpBasic();
http.csrf().disable();
}
}
This configuration class configures two users with their passwords and roles. And although when starting the application, a password for the default user user is shown in the log, that user no longer can access the application, only the new configured two users can.
By using @EnableWebMvcSecurity
and not @EnableWebSecurity
the application has access to the current user in the following way:
/**
* Sample service.
*
* @author P.J. Meisch (pj.meisch@sothawo.com).
*/
@RestController
@RequestMapping("/service")
public class UserService {
@RequestMapping(value = "/echo/{in}", method = RequestMethod.GET)
public String echo(@PathVariable(value = "in") final String in, @AuthenticationPrincipal final UserDetails user) {
return "Hello " + user.getUsername() + ", you said: " + in;
}
}
After restarting the application the service gives the following result:
curl user1:secret1@localhost:8080/service/echo/hello
Hello user1, you said: hello
So our service is protected by username and passwords and has access to the name of the current user.
Notice: As written at the beginning of this post, it is necessary to use HTTPS transport. This is covered in a later post.