Bael 4582: Custom Authentication using shared secret (#13801)

* Spring custom security filter add

* Integration Tests add

* Add Project into parent pom

* refactor code

* refactor code

* remove .env and refactor code

* refactor code

* exclude auto configuration

* Refactored code

* removed ssl certificate for security

* Set session management to statelees

* Refactor tests

* Refactor authentication logic

* Refactor authentication logic

* Refactor authentication logic

* Remove unused code

* Remove unused code

* Remove unused appconfig
This commit is contained in:
Saikat Chakraborty 2023-05-17 00:33:45 +05:30 committed by GitHub
parent 00ca91bd58
commit d37926eddf
12 changed files with 322 additions and 0 deletions

View File

@ -33,6 +33,7 @@
<module>spring-security-web-boot-2</module>
<module>spring-security-web-boot-3</module>
<module>spring-security-web-boot-4</module>
<module>spring-security-web-boot-5</module>
<module>spring-security-web-digest-auth</module>
<module>spring-security-web-login</module>
<module>spring-security-web-login-2</module>

View File

@ -0,0 +1,51 @@
<?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>
<artifactId>spring-security-web-boot-5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-web-boot-5</name>
<packaging>jar</packaging>
<description>Spring Security Custom Auth Application</description>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-2</relativePath>
</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-security</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>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,12 @@
package com.baeldung.customauth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}

View File

@ -0,0 +1,36 @@
package com.baeldung.customauth.authprovider;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
public class RequestHeaderAuthenticationProvider implements AuthenticationProvider {
@Value("${api.auth.secret}")
private String apiAuthSecret;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String authSecretKey = String.valueOf(authentication.getPrincipal());
if(StringUtils.isBlank(authSecretKey) || !authSecretKey.equals(apiAuthSecret)) {
throw new BadCredentialsException("Bad Request Header Credentials");
}
return new PreAuthenticatedAuthenticationToken(authentication.getPrincipal(), null, new ArrayList<>());
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(PreAuthenticatedAuthenticationToken.class);
}
}

View File

@ -0,0 +1,68 @@
package com.baeldung.customauth.configuration;
import com.baeldung.customauth.authprovider.RequestHeaderAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final RequestHeaderAuthenticationProvider requestHeaderAuthenticationProvider;
@Value("${api.auth.header.name}")
private String apiAuthHeaderName;
@Autowired
public SecurityConfig( RequestHeaderAuthenticationProvider requestHeaderAuthenticationProvider){
this.requestHeaderAuthenticationProvider = requestHeaderAuthenticationProvider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterAfter(requestHeaderAuthenticationFilter(), HeaderWriterFilter.class)
.authorizeHttpRequests()
.antMatchers(HttpMethod.GET,"/health").permitAll()
.antMatchers("/api/**").authenticated().and()
.exceptionHandling().authenticationEntryPoint((request, response, authException) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED));
return http.build();
}
@Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader(apiAuthHeaderName);
filter.setExceptionIfHeaderMissing(false);
filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/**"));
filter.setAuthenticationManager(authenticationManager());
return filter;
}
@Bean
protected AuthenticationManager authenticationManager() {
return new ProviderManager(Collections.singletonList(requestHeaderAuthenticationProvider));
}
}

View File

@ -0,0 +1,13 @@
package com.baeldung.customauth.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ApiController {
@GetMapping(path = "/api/hello")
public String hello(){
return "hello";
}
}

View File

@ -0,0 +1,13 @@
package com.baeldung.customauth.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HealthCheckController {
@GetMapping(path = "/health")
public String getHealthStatus(){
return "OK";
}
}

View File

@ -0,0 +1,5 @@
spring.application.name=spring-security-app
server.servlet.context-path=/app
server.port=8085
api.auth.header.name=x-auth-secret-key
logging.level.org.springframework.security=DEBUG

View File

@ -0,0 +1,16 @@
package com.baeldung.customauth;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = SpringSecurityApplication.class)
class SpringContextTest {
@Test
void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}

View File

@ -0,0 +1,68 @@
package com.baeldung.customauth.controller;
import com.baeldung.customauth.SpringSecurityApplication;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.net.URI;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(classes = SpringSecurityApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ExtendWith(SpringExtension.class)
class ApiControllerIntegrationTest {
private final TestRestTemplate restTemplate = new TestRestTemplate();
private static final String API_ENDPOINT = "http://localhost:8080/app/api/hello";
@Test
void givenAuthHeaderSecretIsValid_whenApiControllerCalled_thenReturnOk() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add("x-auth-secret-key", "test-secret");
ResponseEntity<String> response = restTemplate.exchange(new URI(API_ENDPOINT), HttpMethod.GET,
new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("hello", response.getBody());
}
@Test
void givenAAuthHeaderIsInvalid_whenApiControllerCalled_thenReturnUnAuthorised() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add("x-auth-secret-key", "invalid");
ResponseEntity<String> response = restTemplate.exchange(new URI(API_ENDPOINT), HttpMethod.GET,
new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
}
@Test
void givenAuthHeaderNameIsInValid_whenApiControllerCalled_thenReturnUnAuthorised() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add("x-auth-secret", "test-secret");
ResponseEntity<String> response = restTemplate.exchange(new URI(API_ENDPOINT), HttpMethod.GET,
new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
}
@Test
void givenAuthHeaderIsMissing_whenApiControllerCalled_thenReturnUnAuthorised() throws Exception {
HttpHeaders headers = new HttpHeaders();
ResponseEntity<String> response = restTemplate.exchange(new URI(API_ENDPOINT), HttpMethod.GET,
new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
}
}

View File

@ -0,0 +1,34 @@
package com.baeldung.customauth.controller;
import com.baeldung.customauth.SpringSecurityApplication;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.net.URI;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(classes = SpringSecurityApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ExtendWith(SpringExtension.class)
class HealthCheckControllerIntegrationTest {
private final TestRestTemplate restTemplate = new TestRestTemplate();
private static final String HEALTH_CHECK_ENDPOINT = "http://localhost:8080/app/health";
@Test
void givenApplicationIsRunning_whenHealthCheckControllerCalled_thenReturnOk() throws Exception {
HttpHeaders headers = new HttpHeaders();
ResponseEntity<String> response = restTemplate.exchange(new URI(HEALTH_CHECK_ENDPOINT), HttpMethod.GET,
new HttpEntity<>(headers), String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("OK", response.getBody());
}
}

View File

@ -0,0 +1,5 @@
spring.application.name=spring-security-app
server.servlet.context-path=/app
server.port=8080
api.auth.header.name=x-auth-secret-key
api.auth.secret=test-secret