generated from pagopa/template-payments-java-repository
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: P4ADEV-1437 add spring security config (#7)
- Loading branch information
Showing
13 changed files
with
507 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
src/main/java/it/gov/pagopa/payhub/pdnd/dto/auth/UserInfoDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package it.gov.pagopa.payhub.pdnd.dto.auth; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.ToString; | ||
|
||
@Data | ||
@Builder | ||
@AllArgsConstructor | ||
@RequiredArgsConstructor | ||
public class UserInfoDTO { | ||
|
||
private String userId; | ||
private String mappedExternalUserId; | ||
@ToString.Exclude | ||
private String fiscalCode; | ||
@ToString.Exclude | ||
private String familyName; | ||
@ToString.Exclude | ||
private String name; | ||
@ToString.Exclude | ||
private String email; | ||
private String issuer; | ||
private String organizationAccess; | ||
@Builder.Default | ||
private List<UserOrganizationRolesDTO> organizations = new ArrayList<>(); | ||
|
||
} | ||
|
25 changes: 25 additions & 0 deletions
25
src/main/java/it/gov/pagopa/payhub/pdnd/dto/auth/UserOrganizationRolesDTO.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package it.gov.pagopa.payhub.pdnd.dto.auth; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.ToString; | ||
|
||
@Data | ||
@Builder | ||
@AllArgsConstructor | ||
@RequiredArgsConstructor | ||
public class UserOrganizationRolesDTO { | ||
|
||
private String operatorId; | ||
private String organizationIpaCode; | ||
@ToString.Exclude | ||
private String email; | ||
@Builder.Default | ||
private List<String> roles = new ArrayList<>(); | ||
|
||
} | ||
|
7 changes: 7 additions & 0 deletions
7
src/main/java/it/gov/pagopa/payhub/pdnd/exception/custom/InvalidAccessTokenException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package it.gov.pagopa.payhub.pdnd.exception.custom; | ||
|
||
public class InvalidAccessTokenException extends RuntimeException { | ||
public InvalidAccessTokenException(String message) { | ||
super(message); | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
src/main/java/it/gov/pagopa/payhub/pdnd/security/JwtAuthenticationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package it.gov.pagopa.payhub.pdnd.security; | ||
|
||
import it.gov.pagopa.payhub.pdnd.dto.auth.UserInfoDTO; | ||
import it.gov.pagopa.payhub.pdnd.exception.custom.InvalidAccessTokenException; | ||
import it.gov.pagopa.payhub.pdnd.service.auth.AuthorizationService; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.util.Collection; | ||
import lombok.NonNull; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.StringUtils; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
@Component | ||
@Slf4j | ||
public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
|
||
private final AuthorizationService authorizationService; | ||
|
||
public JwtAuthenticationFilter(AuthorizationService authorizationService) { | ||
this.authorizationService = authorizationService; | ||
} | ||
|
||
@Override | ||
protected void doFilterInternal(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response, | ||
@NonNull FilterChain filterChain) throws ServletException, IOException { | ||
try { | ||
String authorization = request.getHeader(HttpHeaders.AUTHORIZATION); | ||
if (StringUtils.hasText(authorization)) { | ||
String token = authorization.replace("Bearer ", ""); | ||
UserInfoDTO userInfo = authorizationService.validateToken(token); | ||
Collection<? extends GrantedAuthority> authorities = null; | ||
if (userInfo.getOrganizationAccess() != null) { | ||
authorities = userInfo.getOrganizations().stream() | ||
.filter(o -> userInfo.getOrganizationAccess().equals(o.getOrganizationIpaCode())) | ||
.flatMap(r -> r.getRoles().stream()) | ||
.map(SimpleGrantedAuthority::new) | ||
.toList(); | ||
} | ||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userInfo, null, authorities); | ||
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
SecurityContextHolder.getContext().setAuthentication(authToken); | ||
} | ||
} catch (InvalidAccessTokenException e){ | ||
log.info("An invalid accessToken has been provided: " + e.getMessage()); | ||
} catch (Exception e){ | ||
log.error("Something gone wrong while validate accessToken", e); | ||
} | ||
filterChain.doFilter(request, response); | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/main/java/it/gov/pagopa/payhub/pdnd/security/WebSecurityConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package it.gov.pagopa.payhub.pdnd.security; | ||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
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.configurers.AbstractHttpConfigurer; | ||
import org.springframework.security.config.http.SessionCreationPolicy; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
||
@Configuration | ||
@EnableWebSecurity | ||
public class WebSecurityConfig { | ||
|
||
private final JwtAuthenticationFilter jwtAuthenticationFilter; | ||
|
||
public WebSecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { | ||
this.jwtAuthenticationFilter = jwtAuthenticationFilter; | ||
} | ||
|
||
@Bean | ||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
http | ||
.csrf(AbstractHttpConfigurer::disable) | ||
.authorizeHttpRequests(requests -> requests | ||
// Swagger endpoints | ||
.requestMatchers( | ||
"/swagger-ui.html", | ||
"/swagger-ui/**", | ||
"/v3/api-docs/**" | ||
).permitAll() | ||
|
||
// Actuator endpoints | ||
.requestMatchers( | ||
"/actuator", | ||
"/actuator/**" | ||
).permitAll() | ||
.anyRequest().authenticated() | ||
) | ||
.sessionManagement(session -> session | ||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) | ||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); | ||
return http.build(); | ||
} | ||
|
||
} |
50 changes: 50 additions & 0 deletions
50
src/main/java/it/gov/pagopa/payhub/pdnd/service/auth/AuthorizationService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package it.gov.pagopa.payhub.pdnd.service.auth; | ||
|
||
import it.gov.pagopa.payhub.pdnd.dto.auth.UserInfoDTO; | ||
import it.gov.pagopa.payhub.pdnd.exception.custom.InvalidAccessTokenException; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.boot.web.client.RestTemplateBuilder; | ||
import org.springframework.http.HttpEntity; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.web.client.HttpStatusCodeException; | ||
import org.springframework.web.client.RestTemplate; | ||
import org.springframework.web.util.DefaultUriBuilderFactory; | ||
|
||
@Service | ||
@Slf4j | ||
public class AuthorizationService { | ||
|
||
private final RestTemplate restTemplate; | ||
|
||
public AuthorizationService(@Value("${app.auth.base-url}") String authServerBaseUrl, | ||
RestTemplateBuilder restTemplateBuilder) { | ||
DefaultUriBuilderFactory ubf = new DefaultUriBuilderFactory(authServerBaseUrl); | ||
ubf.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY); | ||
this.restTemplate = restTemplateBuilder | ||
.uriTemplateHandler(ubf) | ||
.build(); | ||
} | ||
|
||
public UserInfoDTO validateToken(String accessToken){ | ||
log.info("Requesting validate token"); | ||
try{ | ||
HttpHeaders headers = new HttpHeaders(); | ||
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); | ||
return restTemplate.exchange("/auth/userinfo", HttpMethod.GET, new HttpEntity<>(headers), UserInfoDTO.class).getBody(); | ||
} catch (HttpStatusCodeException ex){ | ||
String errorMessage; | ||
if(HttpStatus.UNAUTHORIZED.equals(ex.getStatusCode())){ | ||
errorMessage="Bad Access Token provided"; | ||
log.info(errorMessage); | ||
} else { | ||
errorMessage="Something gone wrong while validate token"; | ||
log.error(errorMessage, ex); | ||
} | ||
throw new InvalidAccessTokenException(errorMessage); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
src/test/java/it/gov/pagopa/payhub/pdnd/security/JwtAuthenticationFilterTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package it.gov.pagopa.payhub.pdnd.security; | ||
|
||
import it.gov.pagopa.payhub.pdnd.dto.auth.UserInfoDTO; | ||
import it.gov.pagopa.payhub.pdnd.dto.auth.UserOrganizationRolesDTO; | ||
import it.gov.pagopa.payhub.pdnd.exception.custom.InvalidAccessTokenException; | ||
import it.gov.pagopa.payhub.pdnd.service.auth.AuthorizationService; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import java.io.IOException; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.InjectMocks; | ||
import org.mockito.Mock; | ||
import org.mockito.Mockito; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.mock.web.MockHttpServletRequest; | ||
import org.springframework.mock.web.MockHttpServletResponse; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
class JwtAuthenticationFilterTest { | ||
|
||
@Mock | ||
private FilterChain filterChainMock; | ||
|
||
@Mock | ||
private AuthorizationService authorizationService; | ||
|
||
@InjectMocks | ||
private JwtAuthenticationFilter jwtAuthenticationFilter; | ||
|
||
@Test | ||
void givenValidTokenWhenDoFilterInternalThenOk() throws ServletException, IOException { | ||
// Given | ||
String accessToken = "ACCESSTOKEN"; | ||
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/path"); | ||
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); | ||
|
||
MockHttpServletResponse response = new MockHttpServletResponse(); | ||
|
||
UserInfoDTO userInfo = UserInfoDTO.builder() | ||
.mappedExternalUserId("MAPPEDEXTERNALUSERID") | ||
.fiscalCode("FISCALCODE") | ||
.familyName("FAMILYNAME") | ||
.name("NAME") | ||
.issuer("ISSUER") | ||
.organizationAccess("ORG") | ||
.organizations(List.of(UserOrganizationRolesDTO.builder() | ||
.organizationIpaCode("ORG") | ||
.roles(List.of("ROLE")) | ||
.build())) | ||
.build(); | ||
|
||
Collection<? extends GrantedAuthority> authorities = null; | ||
if (userInfo.getOrganizationAccess() != null) { | ||
authorities = userInfo.getOrganizations().stream() | ||
.filter(o -> userInfo.getOrganizationAccess().equals(o.getOrganizationIpaCode())) | ||
.flatMap(r -> r.getRoles().stream()) | ||
.map(SimpleGrantedAuthority::new) | ||
.toList(); | ||
} | ||
|
||
Mockito.when(authorizationService.validateToken(accessToken)).thenReturn(userInfo); | ||
|
||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userInfo, null, authorities); | ||
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
|
||
// When | ||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChainMock); | ||
|
||
// Then | ||
Mockito.verify(filterChainMock).doFilter(request, response); | ||
Assertions.assertEquals( | ||
authToken, | ||
SecurityContextHolder.getContext().getAuthentication() | ||
); | ||
} | ||
|
||
@Test | ||
void givenInvalidTokenWhenDoFilterInternalThenInvalidAccessTokenException() throws ServletException, IOException { | ||
// Given | ||
String accessToken = "INVALIDACCESSTOKEN"; | ||
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/path"); | ||
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); | ||
|
||
MockHttpServletResponse response = new MockHttpServletResponse(); | ||
|
||
Mockito.doThrow(new InvalidAccessTokenException("An invalid accessToken has been provided")).when(authorizationService).validateToken(accessToken); | ||
|
||
// When | ||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChainMock); | ||
|
||
// Then | ||
Mockito.verify(filterChainMock).doFilter(request, response); | ||
} | ||
|
||
@Test | ||
void givenInvalidTokenWhenDoFilterInternalThenRuntimeException() throws ServletException, IOException { | ||
// Given | ||
String accessToken = "EXCEPTION"; | ||
MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/path"); | ||
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); | ||
|
||
MockHttpServletResponse response = new MockHttpServletResponse(); | ||
|
||
Mockito.doThrow(new RuntimeException("Something gone wrong while validate accessToken")).when(authorizationService).validateToken(accessToken); | ||
|
||
// When | ||
jwtAuthenticationFilter.doFilterInternal(request, response, filterChainMock); | ||
|
||
// Then | ||
Mockito.verify(filterChainMock).doFilter(request, response); | ||
} | ||
} |
Oops, something went wrong.