Spring Boot Security + REST + Basic Authentication

By Dhiraj Ray, 16 December,2016   | Last updated on: 11 March,2017

In the last post we tried securing our Spring MVC app using spring security Spring Boot Security Login Example.We protected our app against CSRF attack too. Today we will see how to secure REST Api using Basic Authentication with Spring security features.Here we will be using Spring boot to avoid basic configurations and complete java config.We will try to perform simple CRUD operation using Spring REST and user requires to provide username and password to access these resources.At the end, we will be testing our app using postman.

What is Basic Authentication

Basic authentification is a standard HTTP header with the user and password encoded in base64 : Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==.The userName and password is encoded in the format username:password. This is one of the simplest technique to protect the REST resources because it does not require cookies. session identifiers or any login pages.

In case of basic authentication, the username and password is only encoded with Base64, but not encrypted or hashed in any way. Hence, it can be compromised by any man in the middle. Hence, it is always recommended to authenticate rest API calls by this header over a ssl connection.

BasicAuthenticationFilter in Spring

BasicAuthenticationFilter in Spring is the class which is responsible for processing basic authentication credentials presented in HTTP Headers and putting the result into the SecurityContextHolder. The standard governing HTTP Basic Authentication is defined by RFC 1945, Section 11, and BasicAuthenticationFilter confirms with this RFC.

Environment Setup

1. JDK 8 2. Spring Boot 3. Intellij Idea/ eclipse 4. Maven

Maven Dependencies

spring-boot-starter-parent: provides useful Maven defaults. It also provides a dependency-management section so that you can omit version tags for existing dependencies.

spring-boot-starter-web: includes all the dependencies required to create a web app. This will avoid lining up different spring common project versions.

spring-boot-starter-tomcat: enable an embedded Apache Tomcat 7 instance, by default. We have overriden this by defining our version. This can be also marked as provided if you wish to deploy the war to any other standalone tomcat.

spring-boot-starter-security: take care of all the required dependencies related to spring security.

pom.xml
<properties> <tomcat.version>8.0.3</tomcat.version> <start-class>spring-boot-example.Application</start-class> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <!--<scope>provided</scope>--> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies>

Spring Bean Configuration

SpringBootServletInitializer enables process used in Servlet 3.0 using web.xml

@SpringBootApplication: This is a convenience annotation that is equivalent to declaring @Configuration

@EnableAutoConfiguration and @ComponentScan.

Application.java
package com.developerstack.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.web.SpringBootServletInitializer; import org.springframework.context.annotation.ComponentScan; import org.springframework.boot.autoconfigure.SpringBootApplication; @ComponentScan(basePackages = "com.developerstack") @SpringBootApplication public class Application extends SpringBootServletInitializer { private static Class applicationClass = Application.class; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

Now let us define our main configuration for spring security - SpringSecurityConfig.java.class is annotated with @EnableWebSecurity to enable Spring Security web security support. we have extended WebSecurityConfigurerAdapter to override spring features with our custom requirements.Here we want our every request to be authenticated using HTTP Basic authentication. If authentication fails, the configured AuthenticationEntryPoint will be used to retry the authentication process.

SpringSecurityConfig.java
package com.developerstack.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationEntryPoint authEntryPoint; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() .anyRequest().authenticated() .and().httpBasic() .authenticationEntryPoint(authEntryPoint); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("john123").password("password").roles("USER"); } }

Now let us define our authentication entry point.This class will be responsible to send response when the credentials are no longer authorized. If the authentication event was successful, or authentication was not attempted because the HTTP header did not contain a supported authentication request, the filter chain will continue as normal. The only time the filter chain will be interrupted is if authentication fails and the AuthenticationEntryPoint is called.

AuthenticationEntryPoint.java
package com.developerstack.config; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.stereotype.Component; @Component public class AuthenticationEntryPoint extends BasicAuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx) throws IOException, ServletException { response.addHeader("WWW-Authenticate", "Basic realm=" +getRealmName()); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); PrintWriter writer = response.getWriter(); writer.println("HTTP Status 401 - " + authEx.getMessage()); } @Override public void afterPropertiesSet() throws Exception { setRealmName("DeveloperStack"); super.afterPropertiesSet(); } }

Following is the controller class where we have exposed our APIs.

UserController.java
package com.developerstack.controller; import java.util.Arrays; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.developerstack.model.User; @Controller public class UserController { @RequestMapping(path="/user", method = RequestMethod.GET) public ResponseEntity> listUser(){ return new ResponseEntity>(getUsers(), HttpStatus.OK); } @RequestMapping(path="/user/{id}", method = RequestMethod.GET) public ResponseEntity listUser(@PathVariable(value = "id") String id){ return new ResponseEntity(getUsers().stream().filter(user -> user.getId().equals(id)).findFirst().orElse(null), HttpStatus.OK); } @RequestMapping(path="/user", method = RequestMethod.POST) public ResponseEntity listUser(@RequestBody User user){ return new ResponseEntity("18", HttpStatus.OK); }

Run Application

1. Run Application.java as a java application

2. Launch postman

3. Make a get request against http://localhost:8080/user/ with empty authoriztion and a 401 - unauthorised response can be noticed as below

spring-security-rest-authentication-postman-error

4. Now enter username/password as john123/password and make the get request again and you can see the following result.

spring-security-rest-postman-get-request

Conclusion

I hope this article served you that you were looking for. If you have anything that you want to add or share then please share it below in the comment section.

Download the source

References

Spring Security Docs

Spring Boot References

Basic Authentication Wiki

Suggest more topics in suggestion section or write your own article and share with your colleagues.

Is this page helpful to you? Please give us your feedback below. We would love to hear your thoughts on these articles, it will help us improve further our learning process.

Further Reading: