Spring Cloud – Securing Services
Spring Cloud Security is an extremely versatile and strong framework for access control and authentication. To secure the Spring-based applications, this is a good mechanism. A Java application’s authentication and authorization are the main goals of the Spring Security framework. Our application can be authenticated across the board by using Spring Cloud Gateway and Spring Session to log users into a single service. This indicates that it will be simple for us to divide our application into appropriate domains and protect each one as needed.
Step-by-Step Implementation Spring Cloud – Securing Services
Below are the steps to implement Spring Cloud – Securing Services
Step 1: Integrate The UAA with Spring Cloud Gateway
The OAuth2 configuration included in the application.yml file of our gateway is the first item to take care of while configuring this functionality.
security:
oauth2:
client:
registration:
gateway:
provider: uaa
client-id: gateway
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri-template: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid,profile,email,resource.read
provider:
uaa:
authorization-uri: http://localhost:8090/uaa/oauth/authorize
token-uri: http://uaa:8099/uaa/oauth/token
user-info-uri: http://uaa:8099/uaa/userinfo
user-name-attribute: sub
jwk-set-uri: http://uaa:8099/uaa/token_keys
Step 2: Add the Maven Dependency
First, let’s make sure that every system module has the spring-boot-starter-security dependency:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Step 3: Create a SecurityConfig class
To replicate our discovery service, let’s construct a SecurityConfig class and this class will contain methods to configure authentication and authorization for the application.
Java
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.server.SecurityWebFilterChain; @EnableWebFluxSecurity @Configuration public class SecurityConfig { /** * Configure a MapReactiveUserDetailsService with two users: "user" and "admin". * * @param passwordEncoder the password encoder * @return a MapReactiveUserDetailsService with two users */ @Bean public MapReactiveUserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { UserDetails user = User.withUsername( "user" ) .password(passwordEncoder.encode( "password" )) .roles( "USER" ) .build(); UserDetails adminUser = User.withUsername( "admin" ) .password(passwordEncoder.encode( "admin" )) .roles( "ADMIN" ) .build(); return new MapReactiveUserDetailsService(user, adminUser); } /** * Configure a SecurityWebFilterChain with URL authorization. * * @param http the ServerHttpSecurity object * @return a SecurityWebFilterChain object with URL authorization */ @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.authorizeExchange() .pathMatchers( "/api/employees" ).permitAll() .pathMatchers( "/api/employees/*" ).authenticated() .and().formLogin() .and().logout(); return http.build(); } } |
Step 4: Create an Employee class
To represent employee data, create an employee class.
Employee.java:
Java
public class Employee { private Long id; private String firstName; private String lastName; private String email; public Long getId() { return id; } public void setId(Long id) { this .id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this .firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this .lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this .email = email; } } |
Step 5: Configure Authentication and Authorization
We need to configure authentication and authorization in our application. We can use the SecurityConfig
class to define our security configuration.
Java
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * Configuration class for security settings. */ @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * Configures HTTP security. * * @param http the HttpSecurity object to configure * @throws Exception if an error occurs during configuration */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers(HttpMethod.GET, "/api/public/**" ).permitAll() .antMatchers(HttpMethod.POST, "/api/public/**" ).permitAll() .antMatchers(HttpMethod.GET, "/api/private/**" ).hasRole( "USER" ) .antMatchers(HttpMethod.POST, "/api/private/**" ).hasRole( "ADMIN" ) .and().httpBasic(); } /** * Creates a PasswordEncoder bean for password encoding. * * @return a BCryptPasswordEncoder bean */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } |
Step 6: Create a Controller Class to Handle Employee Requests
To handle employee requests, create a controller class.
Java
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * RestController for handling employee-related requests. */ @RestController @RequestMapping ( "/api" ) public class EmployeeController { @Autowired private EmployeeService employeeService; /** * Handles GET requests to "/api/public" for public data. * * @return the public data */ @GetMapping ( "/public" ) public String getPublicData() { return employeeService.getPublicData(); } /** * Handles GET requests to "/api/private/user" for user data. * * @return the user data */ @GetMapping ( "/private/user" ) public String getUserData() { return employeeService.getUserData(); } /** * Handles POST requests to "/api/private/admin" for admin data. * * @return the admin data */ @PostMapping ( "/private/admin" ) public String getAdminData() { return employeeService.getAdminData(); } } |
Step 7: Add properties to the application.properties file
Add the following properties to the rating service’s src/main/resources application.properties file:
spring.cloud.config.username=configUser
spring.cloud.config.password=configPassword
eureka.client.serviceUrl.defaultZone=http://discUser:discPassword@localhost:8082/eureka/
Step 8: Create a test class
In gateway project, let’s first construct a test class and a test method:
Java
public class GatewayApplicationTest { @Test public void testAccess() { // Add test logic to verify access to the gateway application // For example, use tools like RestTemplate or WebClient to make HTTP requests // and assert the expected behavior. } } |
Step 9: Access unprotected /api/employees Endpoint
Let’s add this code snippet to our test method so that we can configure our test and confirm that we can access our unprotected /api/employees resource:
Java
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.web.client.TestRestTemplate; // Create a new instance of TestRestTemplate to test HTTP requests TestRestTemplate testRestTemplate = new TestRestTemplate(); // Define the base URL of the API being tested String testUrl = "http://localhost:8080" ; // Send a GET request to the "/api/employees" endpoint ResponseEntity<String> response = testRestTemplate.getForEntity(testUrl + "/api/employees" , String. class ); // Verify that the HTTP status code of the response is OK (200) Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); // Verify that the response body is not null Assert.assertNotNull(response.getBody()); |
Step 10: Access the admin protected resource
Our ability to access the admin-protected resource and log in as the administrator will be verified by the following test:
Admin-Protected /api/employees Endpoint:
Java
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; form.add( "username" , "admin" ); // Add username form.add( "password" , "admin" ); // Add password // Send a POST request to the "/login" endpoint with the form data ResponseEntity<String> response = testRestTemplate.postForEntity(testUrl + "/login" , form, String. class ); // Verify that the HTTP status code of the response is OK (200) Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); // Verify that the response body is not null Assert.assertNotNull(response.getBody()); |
By following the above steps, we can secure various services of our Spring Cloud application.
Contact Us