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:
parent
00ca91bd58
commit
d37926eddf
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue