This is the third post of my Spring Boot Blog post series. In the very first post, I talked about my experience with creating RESTFul Services using Spring Boot. Then I have expanded the sample to
integrate with Swagger documentation. In this post, I am going to expand above sample with security aspect.
What is API Security
API Security is a wide area with many different definitions, meanings, and solutions. The main key terms in API security are Authorization, Authentication, Encryption, Federation, and Delegation. However, I am not going to talk about each of them here.
What is Authentication
Authentication is used to reliably determine the identity of an end user and give access to the resources based on the correctly identified user.
What is Basic Authentication
Basic Authentication is the simplest way to enforce access controling to resources. Here, the HTTP user agent provides the username and the password when making a request. The string containing the username and password separated by a colon is Base64 encoded before sending to the backend when authentication is required.
How to Invoke Basic Auth Protected API
Option 1: Send Authorization header. This value is base64 encoded username:password Ex: “Authorization: Basic Y2hhbmRhbmE6Y2hhbmRhbmE=”
1 | curl -X GET http://localhost:8080/admin/hello/chandana -H 'authorization: Basic Y2hhbmRhbmE6Y2hhbmRhbmE=' |
Option 2: Using URL:
1 | curl -X GET -u username:password http://localhost:8080/admin/hello/chandana |
OK, we talked about basic stuff. So let’s move to see how to secure a REST API using Spring Security. You can download the initial sample code from my GitHub repo(Swagger Spring Boot Project source code)
To enhance our previous sample with basic auth security, first I am going to add “spring-boot-starter-security” and “spring-boot-starter-tomcat” dependencies into the pom file.
03 | < groupId >org.springframework.boot</ groupId > |
04 | < artifactId >spring-boot-starter-security</ artifactId > |
07 | < groupId >javax.servlet</ groupId > |
08 | < artifactId >javax.servlet-api</ artifactId > |
09 | < version >3.1.0</ version > |
Next step is that our configuration class is annotated with @EnableWebSecurity annotation and configuration class is extended from the WebSecurityConfigurerAdapter. The EnableWebSecurity annotation will enable Spring-Security web security support.
4 | public class ApplicationConfig extends WebSecurityConfigurerAdapter { |
Overridden configure(HttpSecurity) method is used to define which URL paths should be secured and which should not be. In my example “/” and “/api” paths are not required any authentication and any other paths(ex: “admin”) should be authenticated with basic auth.
2 | protected void configure(HttpSecurity http) throws Exception { |
4 | http.authorizeRequests().antMatchers( "/" , "/api/**" ).permitAll() |
5 | .anyRequest().authenticated(); |
6 | http.httpBasic().authenticationEntryPoint(basicAuthenticationPoint); |
In the configureGlobal(AuthenticationManagerBuilder) method, I have created an in-memory user store with a user called ‘chandana’. There I have added username, password, and userole for the in-memory user.
2 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { |
3 | auth.inMemoryAuthentication().withUser( "chandana" ).password( "chandana" ).roles( "USER" ); |
In Addition to that, you can see that I have added autowired BasicAuthenticationPoint, into my config class. Purpose of the BasicAuthenticationEntryPoint class is to set the “WWW-Authenticate” header to the response. So, web browsers will display a dialog to enter usename and password based on basic authentication mechanism(WWW-Authenticate header)
Then you can run the sample using “mvn spring-boot:run”. When you are accessing “localhost:8080/api/hello/chandana” basic authentication is not required to invoke the api. However, if you try to access the “localhost:8080/admin/hello/chandana” it will be required to provide basic auth credentials to access the resource.
AppConfig class:
01 | package com.chandana.helloworld.config; |
02 | import org.springframework.beans.factory.annotation.Autowired; |
03 | import org.springframework.context.annotation.Bean; |
04 | import org.springframework.context.annotation.Configuration; |
05 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; |
06 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
07 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
08 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
09 | import springfox.documentation.builders.ApiInfoBuilder; |
10 | import springfox.documentation.builders.PathSelectors; |
11 | import springfox.documentation.builders.RequestHandlerSelectors; |
12 | import springfox.documentation.service.ApiInfo; |
13 | import springfox.documentation.service.Contact; |
14 | import springfox.documentation.spi.DocumentationType; |
15 | import springfox.documentation.spring.web.plugins.Docket; |
16 | import springfox.documentation.swagger2.annotations.EnableSwagger2; |
20 | public class ApplicationConfig extends WebSecurityConfigurerAdapter { |
22 | private BasicAuthenticationPoint basicAuthenticationPoint; |
25 | return new Docket(DocumentationType.SWAGGER_2) |
26 | .apiInfo(getApiInfo()) |
28 | .apis(RequestHandlerSelectors.basePackage( "com.chandana.helloworld.controllers" )) |
29 | .paths(PathSelectors.any()) |
33 | protected void configure(HttpSecurity http) throws Exception { |
34 | http.csrf().disable(); |
35 | http.authorizeRequests().antMatchers( "/" , "/api/**" ).permitAll() |
36 | .anyRequest().authenticated(); |
37 | http.httpBasic().authenticationEntryPoint(basicAuthenticationPoint); |
39 | private ApiInfo getApiInfo() { |
41 | return new ApiInfoBuilder() |
42 | .title( "Example Api Title" ) |
43 | .description( "Example Api Definition" ) |
45 | .license( "Apache 2.0" ) |
46 | .licenseUrl( "http://www.apache.org/licenses/LICENSE-2.0" ) |
51 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { |
52 | auth.inMemoryAuthentication().withUser( "chandana" ).password( "chandana" ).roles( "USER" ); |
BasicAuthenticationEntryPoint class:
01 | package com.chandana.helloworld.config; |
02 | import org.springframework.security.core.AuthenticationException; |
03 | import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; |
04 | import org.springframework.stereotype.Component; |
05 | import java.io.IOException; |
06 | import java.io.PrintWriter; |
07 | import javax.servlet.ServletException; |
08 | import javax.servlet.http.HttpServletRequest; |
09 | import javax.servlet.http.HttpServletResponse; |
11 | public class BasicAuthenticationPoint extends BasicAuthenticationEntryPoint { |
13 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx) |
14 | throws IOException, ServletException { |
15 | response.addHeader( "WWW-Authenticate" , "Basic realm=" +getRealmName()); |
16 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); |
17 | PrintWriter writer = response.getWriter(); |
18 | writer.println( "HTTP Status 401 - " + authEx.getMessage()); |
21 | public void afterPropertiesSet() throws Exception { |
22 | setRealmName( "Chandana" ); |
23 | super .afterPropertiesSet(); |
Comments
Post a Comment