diff --git a/fda-container-service/pom.xml b/fda-container-service/pom.xml index 0088aac01ecc9de3d0e6aa1d18cf3443b0e0ea32..90a7a0142ed4f0baf1fc757d1e4cda3a7f5a11b4 100644 --- a/fda-container-service/pom.xml +++ b/fda-container-service/pom.xml @@ -51,6 +51,15 @@ <artifactId>spring-cloud-starter-bootstrap</artifactId> <version>${spring-cloud.version}</version> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-test</artifactId> + <scope>test</scope> + </dependency> <!-- Data Source --> <dependency> <groupId>org.springframework.boot</groupId> diff --git a/fda-container-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-container-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java index 2ece75e3cdda899a18f28e272501bb9d4acbee61..6bcb362bcd7ed5d338c250782c68145597fce5df 100644 --- a/fda-container-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java +++ b/fda-container-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java @@ -2,7 +2,6 @@ package at.tuwien.auth; import at.tuwien.gateway.AuthenticationServiceGateway; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; diff --git a/fda-database-service/pom.xml b/fda-database-service/pom.xml index b2345c6e17deacdfb61d1d9a28c9e9a4f091dd6c..4c8b57eddb32eb9d2734fe2c7fb13aafd7bc071a 100644 --- a/fda-database-service/pom.xml +++ b/fda-database-service/pom.xml @@ -46,6 +46,15 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-test</artifactId> + <scope>test</scope> + </dependency> <!-- elasticsearch --> <dependency> <groupId>org.springframework.data</groupId> diff --git a/fda-database-service/rest-service/src/main/resources/application-docker.yml b/fda-database-service/rest-service/src/main/resources/application-docker.yml index 007f62c3aab7ace2fe81c9b7208198c36aba7ee4..6a012e40342e60e426747745f5c3a7ab059a9f9d 100644 --- a/fda-database-service/rest-service/src/main/resources/application-docker.yml +++ b/fda-database-service/rest-service/src/main/resources/application-docker.yml @@ -30,4 +30,5 @@ eureka: client.serviceUrl.defaultZone: http://fda-discovery-service:9090/eureka/ fda: elastic.endpoint: fda-search-service:9200 - ready.path: /ready \ No newline at end of file + ready.path: /ready + auth.endpoint: http://fda-authentication-service:9097 \ No newline at end of file diff --git a/fda-database-service/rest-service/src/main/resources/application.yml b/fda-database-service/rest-service/src/main/resources/application.yml index 99e9373d500e273b3fbb9c371c9513496ea1f1da..970d401a591c58dd876f241380c533ad4fc7278d 100644 --- a/fda-database-service/rest-service/src/main/resources/application.yml +++ b/fda-database-service/rest-service/src/main/resources/application.yml @@ -30,4 +30,5 @@ eureka: client.serviceUrl.defaultZone: http://localhost:9090/eureka/ fda: elastic.endpoint: localhost:9200 - ready.path: ./ready \ No newline at end of file + ready.path: ./ready + auth.endpoint: http://localhost:9097 \ No newline at end of file diff --git a/fda-database-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-database-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..6bcb362bcd7ed5d338c250782c68145597fce5df --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java @@ -0,0 +1,56 @@ +package at.tuwien.auth; + +import at.tuwien.gateway.AuthenticationServiceGateway; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +public class AuthTokenFilter extends OncePerRequestFilter { + + private final AuthenticationServiceGateway authenticationServiceGateway; + + public AuthTokenFilter(AuthenticationServiceGateway authenticationServiceGateway) { + this.authenticationServiceGateway = authenticationServiceGateway; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String jwt = parseJwt(request); + log.debug("parsed jwt {}", jwt); + if (jwt != null) { + final UserDetails userDetails = authenticationServiceGateway.validate(jwt); + final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(request, response); + } + + /** + * Parses the token from the HTTP header of the request + * + * @param request The request. + * @return The token. + */ + private String parseJwt(HttpServletRequest request) { + String headerAuth = request.getHeader("Authorization"); + if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { + return headerAuth.substring(7, headerAuth.length()); + } + return null; + } +} \ No newline at end of file diff --git a/fda-database-service/services/src/main/java/at/tuwien/config/GatewayConfig.java b/fda-database-service/services/src/main/java/at/tuwien/config/GatewayConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..252235a1eb97fcee790d7cf270935f06924f007f --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/config/GatewayConfig.java @@ -0,0 +1,22 @@ +package at.tuwien.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; + +@Configuration +public class GatewayConfig { + + @Value("${fda.auth.endpoint}") + private String authEndpoint; + + @Bean + public RestTemplate restTemplate() { + final RestTemplate restTemplate = new RestTemplate(); + restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(authEndpoint)); + return restTemplate; + } + +} diff --git a/fda-database-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-database-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..84aa080940b6685d609e1f09dd72d1b23a500851 --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java @@ -0,0 +1,82 @@ +package at.tuwien.config; + +import at.tuwien.auth.AuthTokenFilter; +import at.tuwien.gateway.AuthenticationServiceGateway; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +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.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import javax.servlet.http.HttpServletResponse; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final AuthenticationServiceGateway authenticationServiceGateway; + + @Autowired + public WebSecurityConfig(AuthenticationServiceGateway authenticationServiceGateway) { + this.authenticationServiceGateway = authenticationServiceGateway; + } + + @Bean + public AuthTokenFilter authTokenFilter() { + return new AuthTokenFilter(authenticationServiceGateway); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + /* enable CORS and disable CSRF */ + http = http.cors().and().csrf().disable(); + /* set session management to stateless */ + http = http + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and(); + /* set unauthorized requests exception handler */ + http = http + .exceptionHandling() + .authenticationEntryPoint( + (request, response, ex) -> { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, + ex.getMessage() + ); + } + ).and(); + /* set permissions on endpoints */ + http.authorizeRequests() + /* our public endpoints */ + .antMatchers(HttpMethod.GET, "/api/container/**").permitAll() + .antMatchers(HttpMethod.GET, "/api/image/**").permitAll() + /* our private endpoints */ + .anyRequest().authenticated(); + /* add JWT token filter */ + http.addFilterBefore(authTokenFilter(), + UsernamePasswordAuthenticationFilter.class + ); + } + + @Bean + public CorsFilter corsFilter() { + final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + final CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + +} diff --git a/fda-database-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java b/fda-database-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java new file mode 100644 index 0000000000000000000000000000000000000000..fc47ce246da851244cf1630b15f493ded20249d8 --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/gateway/AuthenticationServiceGateway.java @@ -0,0 +1,14 @@ +package at.tuwien.gateway; + +import org.springframework.security.core.userdetails.UserDetails; + +public interface AuthenticationServiceGateway { + + /** + * Validates a token + * + * @param token The token + * @return User details on success + */ + UserDetails validate(String token); +} diff --git a/fda-database-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java b/fda-database-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..30507c4b6e662a7e772e85c609fdb5fbc563a99f --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/gateway/impl/AuthenticationServiceGatewayImpl.java @@ -0,0 +1,36 @@ +package at.tuwien.gateway.impl; + +import at.tuwien.api.user.UserDto; +import at.tuwien.gateway.AuthenticationServiceGateway; +import at.tuwien.mapper.UserMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +public class AuthenticationServiceGatewayImpl implements AuthenticationServiceGateway { + + private final UserMapper userMapper; + private final RestTemplate restTemplate; + + @Autowired + public AuthenticationServiceGatewayImpl(UserMapper userMapper, RestTemplate restTemplate) { + this.userMapper = userMapper; + this.restTemplate = restTemplate; + } + + @Override + public UserDetails validate(String token) { + final HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer " + token); + final ResponseEntity<UserDto> response = restTemplate.exchange("/api/auth", HttpMethod.PUT, + new HttpEntity<>("", headers), UserDto.class); + return userMapper.userDtoToUserDetailsDto(response.getBody()); + } + +} diff --git a/fda-database-service/services/src/main/java/at/tuwien/mapper/UserMapper.java b/fda-database-service/services/src/main/java/at/tuwien/mapper/UserMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..f14430e9675c7a152c778df18fc892e3423afdc6 --- /dev/null +++ b/fda-database-service/services/src/main/java/at/tuwien/mapper/UserMapper.java @@ -0,0 +1,18 @@ +package at.tuwien.mapper; + +import at.tuwien.api.user.GrantedAuthorityDto; +import at.tuwien.api.user.UserDetailsDto; +import at.tuwien.api.user.UserDto; +import org.mapstruct.Mapper; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +@Mapper(componentModel = "spring") +public interface UserMapper { + + UserDetailsDto userDtoToUserDetailsDto(UserDto data); + + default GrantedAuthority grantedAuthorityDtoToGrantedAuthority(GrantedAuthorityDto data) { + return new SimpleGrantedAuthority(data.getAuthority()); + } +}