SI-141 Introduce Spring Security
diff --git a/gfsBackendService/src/main/asciidoc/img/architectureSIT.png b/gfsBackendService/src/main/asciidoc/img/architectureSIT.png new file mode 100644 index 0000000..48a1d8c --- /dev/null +++ b/gfsBackendService/src/main/asciidoc/img/architectureSIT.png Binary files differ
diff --git a/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/api/AuthNAuthApi.java b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/api/AuthNAuthApi.java new file mode 100644 index 0000000..21467a4 --- /dev/null +++ b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/api/AuthNAuthApi.java
@@ -0,0 +1,31 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ******************************************************************************* +*/ +package org.eclipse.openk.gridfailureinformation.api; + + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient(name = "${services.authNAuth.name}") +public interface AuthNAuthApi { + + @GetMapping(value= "/portal/rest/beservice/checkAuth") + feign.Response isTokenValid(@RequestHeader("Authorization") String token); + + @GetMapping( value="/portal/rest/beservice/logout") + feign.Response logout(@RequestHeader("Authorization") String token); + +}
diff --git a/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/CorsConfig.java b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/CorsConfig.java new file mode 100644 index 0000000..0294f69 --- /dev/null +++ b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/CorsConfig.java
@@ -0,0 +1,32 @@ +package org.eclipse.openk.gridfailureinformation.config; + +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Log4j2 +@Configuration +public class CorsConfig { + + @Value("${cors.corsEnabled}") + private boolean corsEnabled; + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + if (corsEnabled) { + log.info("Cors enabled"); + registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*") + .allowedHeaders("*"); + } else { + log.info("Cors disabled"); + } + } + }; + } +}
diff --git a/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/SecurityConfig.java b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/SecurityConfig.java index 46bca9e..bbb44e1 100644 --- a/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/SecurityConfig.java +++ b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/SecurityConfig.java
@@ -14,17 +14,59 @@ */ package org.eclipse.openk.gridfailureinformation.config; +import org.eclipse.openk.gridfailureinformation.config.auth.JwtAuthenticationEntryPoint; +import org.eclipse.openk.gridfailureinformation.config.auth.JwtAuthenticationTokenFilter; +import org.eclipse.openk.gridfailureinformation.config.auth.JwtTokenValidationFilter; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +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.www.BasicAuthenticationFilter; @Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity( + prePostEnabled = true, + securedEnabled = true, + jsr250Enabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; + + @Autowired + private JwtTokenValidationFilter jwtTokenValidationFilter; + + @Autowired + private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + @Override - protected void configure(HttpSecurity security ) throws Exception { - security.httpBasic().disable(); - security.csrf().disable(); + protected void configure(HttpSecurity http ) throws Exception { + http + .authorizeRequests() + .antMatchers("/**").permitAll() + .anyRequest().authenticated() + .and() + .cors() + .and() + .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) + .and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .csrf().disable() + .logout().disable() + .formLogin().disable() + .anonymous() + .and() + .addFilterAfter(jwtTokenValidationFilter, BasicAuthenticationFilter.class) + .anonymous() + .and() + .addFilterAfter(jwtAuthenticationTokenFilter, BasicAuthenticationFilter.class); } + }
diff --git a/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/auth/JwtAuthenticationEntryPoint.java b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/auth/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..762535e --- /dev/null +++ b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/auth/JwtAuthenticationEntryPoint.java
@@ -0,0 +1,34 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ******************************************************************************* +*/ +package org.eclipse.openk.gridfailureinformation.config.auth; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.Serializable; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + } +}
diff --git a/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/auth/JwtAuthenticationTokenFilter.java b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/auth/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..dfe086d --- /dev/null +++ b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/auth/JwtAuthenticationTokenFilter.java
@@ -0,0 +1,88 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ******************************************************************************* +*/ + +package org.eclipse.openk.gridfailureinformation.config.auth; + +import org.keycloak.RSATokenVerifier; +import org.keycloak.representations.AccessToken; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +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; +import java.util.ArrayList; +import java.util.List; + +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { + + @Value("${jwt.useStaticJwt}") + private boolean useStaticJwt; + + @Value("${jwt.tokenHeader}") + private String tokenHeader; + + @Value("${jwt.staticJwt}") + private String staticJwt; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { + String authenticationHeader = useStaticJwt ? staticJwt : request.getHeader(this.tokenHeader); + + try { + SecurityContext context= SecurityContextHolder.getContext(); + + if(authenticationHeader != null) { + + final String bearerTkn= authenticationHeader.replace("Bearer ", ""); + + createToken(context, bearerTkn); + + } + chain.doFilter(request, response); + } catch(AuthenticationException ex) { + throw new ServletException("Authentication exception."); + } + + } + + private void createToken(SecurityContext context, String bearerTkn) throws ServletException { + try { + AccessToken token = RSATokenVerifier.create(bearerTkn).getToken(); + + List<GrantedAuthority> authorities= new ArrayList<>(); + token.getRealmAccess().getRoles().stream() + .forEach( x -> authorities.add(new SimpleGrantedAuthority("ROLE_"+x.toUpperCase()))); + + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(token.getName(), null, authorities); + auth.setDetails(bearerTkn); + + context.setAuthentication(auth); + + } catch (Exception e) { + throw new ServletException("Invalid token."); + } + } +}
diff --git a/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/auth/JwtTokenValidationFilter.java b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/auth/JwtTokenValidationFilter.java new file mode 100644 index 0000000..8256fac --- /dev/null +++ b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/config/auth/JwtTokenValidationFilter.java
@@ -0,0 +1,63 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + ******************************************************************************* +*/ + +package org.eclipse.openk.gridfailureinformation.config.auth; + +import feign.Response; +import lombok.extern.log4j.Log4j2; +import org.eclipse.openk.gridfailureinformation.api.AuthNAuthApi; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +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 javax.servlet.http.HttpServletResponseWrapper; +import java.io.IOException; + +@Component +@Log4j2 +public class JwtTokenValidationFilter extends OncePerRequestFilter { + @Autowired + private AuthNAuthApi authNAuthApi; + + @Value("${jwt.useStaticJwt}") + private boolean useStaticJwt; + + @Value("${jwt.tokenHeader}") + private String tokenHeader; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + String authenticationHeader = useStaticJwt ? null : request.getHeader(this.tokenHeader); + + if(authenticationHeader != null) { + final String bearerTkn= authenticationHeader.replace("Bearer ", ""); + Response res = authNAuthApi.isTokenValid(bearerTkn); + if( res.status() != HttpStatus.OK.value() ) { + final HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse)response); + wrapper.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token expired or not valid"); + chain.doFilter(request, wrapper.getResponse()); + + return; + } + } + chain.doFilter(request, response); + } +}
diff --git a/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/model/JwtToken.java b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/model/JwtToken.java new file mode 100644 index 0000000..cd7e60b --- /dev/null +++ b/gfsBackendService/src/main/java/org/eclipse/openk/gridfailureinformation/model/JwtToken.java
@@ -0,0 +1,24 @@ +/** +****************************************************************************** +* Copyright © 2017-2018 PTA GmbH. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v1.0 +* which accompanies this distribution, and is available at +* +* http://www.eclipse.org/legal/epl-v10.html +* +****************************************************************************** +*/ +package org.eclipse.openk.gridfailureinformation.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class JwtToken { + + @JsonProperty("access_token") + private String accessToken; + + public String getAccessToken() { return accessToken; } + public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + +} \ No newline at end of file
diff --git a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/BranchControllerTest.java b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/BranchControllerTest.java index 9b4dbf8..34b1e09 100644 --- a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/BranchControllerTest.java +++ b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/BranchControllerTest.java
@@ -16,27 +16,27 @@ import org.eclipse.openk.gridfailureinformation.GridFailureInformationApplication; import org.eclipse.openk.gridfailureinformation.service.BranchService; -import org.eclipse.openk.gridfailureinformation.service.VersionService; import org.eclipse.openk.gridfailureinformation.support.MockDataHelper; import org.eclipse.openk.gridfailureinformation.viewmodel.BranchDto; -import org.eclipse.openk.gridfailureinformation.viewmodel.VersionDto; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import java.util.List; -import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest(classes = GridFailureInformationApplication.class) @AutoConfigureMockMvc +@ActiveProfiles("test") public class BranchControllerTest { @MockBean
diff --git a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureClassificationControllerTest.java b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureClassificationControllerTest.java index d3efe42..fdd71b2 100644 --- a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureClassificationControllerTest.java +++ b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureClassificationControllerTest.java
@@ -24,6 +24,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import java.util.List; @@ -35,6 +36,7 @@ @SpringBootTest(classes = GridFailureInformationApplication.class) @AutoConfigureMockMvc +@ActiveProfiles("test") public class FailureClassificationControllerTest { @MockBean
diff --git a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureInformationControllerTest.java b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureInformationControllerTest.java index 1ddbe29..ff7889d 100644 --- a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureInformationControllerTest.java +++ b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureInformationControllerTest.java
@@ -15,10 +15,8 @@ package org.eclipse.openk.gridfailureinformation.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import jdk.nashorn.internal.ir.annotations.Ignore; import org.eclipse.openk.gridfailureinformation.GridFailureInformationApplication; import org.eclipse.openk.gridfailureinformation.exceptions.NotFoundException; -import org.eclipse.openk.gridfailureinformation.model.TblFailureInformation; import org.eclipse.openk.gridfailureinformation.service.FailureInformationService; import org.eclipse.openk.gridfailureinformation.support.MockDataHelper; import org.eclipse.openk.gridfailureinformation.viewmodel.FailureInformationDto; @@ -30,24 +28,20 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; -import java.util.Date; import java.util.UUID; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest(classes = GridFailureInformationApplication.class) @AutoConfigureMockMvc +@ActiveProfiles("test") public class FailureInformationControllerTest { @MockBean
diff --git a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureTypeControllerTest.java b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureTypeControllerTest.java index 8d6fa2d..78ff37d 100644 --- a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureTypeControllerTest.java +++ b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/FailureTypeControllerTest.java
@@ -24,6 +24,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import java.util.List; @@ -35,6 +36,7 @@ @SpringBootTest(classes = GridFailureInformationApplication.class) @AutoConfigureMockMvc +@ActiveProfiles("test") public class FailureTypeControllerTest { @MockBean
diff --git a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/StatusControllerTest.java b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/StatusControllerTest.java index 05b8828..f21e371 100644 --- a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/StatusControllerTest.java +++ b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/StatusControllerTest.java
@@ -24,6 +24,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import java.util.List; @@ -35,6 +36,7 @@ @SpringBootTest(classes = GridFailureInformationApplication.class) @AutoConfigureMockMvc +@ActiveProfiles("test") public class StatusControllerTest { @MockBean
diff --git a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/VersionControllerTest.java b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/VersionControllerTest.java index 4000ed2..69fc38e 100644 --- a/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/VersionControllerTest.java +++ b/gfsBackendService/src/test/java/org/eclipse/openk/gridfailureinformation/controller/VersionControllerTest.java
@@ -27,6 +27,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.Matchers.is; @@ -36,6 +37,7 @@ @SpringBootTest(classes = GridFailureInformationApplication.class) @AutoConfigureMockMvc +@ActiveProfiles("test") public class VersionControllerTest { @MockBean