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. MavenMaven 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.
<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
.
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 ClassapplicationClass = 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.
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.
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(); } }
Other Interesting Posts AES Encryption and Decryption in Java Spring Boot JWT Auth Spring Boot Security OAuth2 Example Spring Security Password Encoding using Bcrypt Encoder Spring 5 Features and Enhancements Spring Boot Security Redirect after Login with complete JavaConfig Spring Security Hibernate Example with complete JavaConfig Spring Boot Security Custom Form Login Example Websocket spring Boot Integration without STOMP Spring MVC Angularjs Integration with complete JavaConfig Spring Junit Integration with complete JavaConfig Spring Ehcache Cacheable Example with complete javaConfig Spring Boot Spring MVC Example Spring Boot Thymeleaf Example
Following is the controller class where we have exposed our APIs.
UserController.javapackage 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); }
Logout Implementation
After a succesdfull authentication, Spring updates the security context with an authentication object that contains credentials, roles, principal etc.So, while logging out we need to clear this context and spring provides SecurityContextLogoutHandler which performs a logout by modifying the SecurityContextHolder.Following is the implementation.
Following logout implementation is one way to logout programatically.You can also configure logout in SpringSecurityConfig.java
.You can use any one of them but not both.Once basic authentication is successfull, browser automatically sends basic auth header in every request following successfull authentication.Hence, if you are using this authentication mechanism in a browser based application, it is required to clear the Basic auth cache in the browser too after logout.
@RequestMapping(value="/logmeout", method = RequestMethod.POST) public String logoutPage (HttpServletRequest request, HttpServletResponse response) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null){ new SecurityContextLogoutHandler().logout(request, response, auth); } return "redirect:/login"; }
Handling Cross Origin Requests
To handle Cross Origin requests add following configuration in the bean definition.
@Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*") .allowedHeaders("*"); } }; }
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
4. Now enter username/password as john123/password and make the get request again and you can see the following result.
Spring Boot 2 Basic Authentication
There are certain changes required to run this app with spring boot 2. With spring boot 2, you need to Bcrypt the password.To make use of Bcrypt, first we need to define a Bean of BCryptPasswordEncoder as follow or else it throws error as PasswordEncoder mapped for the id "null"
@Bean public BCryptPasswordEncoder encoder() { return new BCryptPasswordEncoder(); }
Next, we require to Bcrypt our plain-text password in configureGlobal() method. You can use this online Bcrypt tool to generate Bcrypt password.Else, you will be getting this error Encoded password does not look like BCrypt
@Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("john123").password("$2a$04$AjFEmZeX7mN8zSn57PUEZeJgBeoKMvwteZMBiP57Jb4AGFsUORmLC").roles("USER"); }
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.