From 12958470b7087c8fa0e2ad10bedaf0ebeb0c3e70 Mon Sep 17 00:00:00 2001 From: esensar Date: Fri, 2 Jun 2017 00:12:09 +0200 Subject: [PATCH] Add auth filter to proxy --- .../eureka-service/bootstrap.properties | 5 ++ .../steleks-proxy/bootstrap.properties | 5 ++ settings.gradle | 2 +- steleks-proxy/build.gradle | 1 + .../ba/steles/SteleksProxyApplication.java | 22 ++++++ .../java/ba/steles/security/AuthResponse.java | 35 +++++++++ .../steles/security/AuthenticationFilter.java | 76 +++++++++++++++++++ .../ba/steles/security/RelayTokenFilter.java | 41 ++++++++++ .../ba/steles/security/SecurityConfig.java | 39 ++++++++++ .../service/DefaultServiceIdProvider.java | 41 ++++++++++ .../main/java/ba/steles/service/Service.java | 11 +++ .../ba/steles/service/ServiceIdProvider.java | 9 +++ .../EurekaServiceDiscoveryClient.java | 48 ++++++++++++ .../discovery/ServiceDiscoveryClient.java | 11 +++ .../controller/AuthenticationController.java | 5 ++ .../security/AuthenticationFilter.java | 2 + .../ba/steleks/security/SecurityConfig.java | 2 +- .../security/TokenAuthenticationService.java | 5 ++ ...tore.java => BasicInMemoryTokenStore.java} | 10 ++- 19 files changed, 364 insertions(+), 6 deletions(-) create mode 100644 out/production/eureka-service/bootstrap.properties create mode 100644 out/production/steleks-proxy/bootstrap.properties create mode 100644 steleks-proxy/src/main/java/ba/steles/security/AuthResponse.java create mode 100644 steleks-proxy/src/main/java/ba/steles/security/AuthenticationFilter.java create mode 100644 steleks-proxy/src/main/java/ba/steles/security/RelayTokenFilter.java create mode 100644 steleks-proxy/src/main/java/ba/steles/security/SecurityConfig.java create mode 100644 steleks-proxy/src/main/java/ba/steles/service/DefaultServiceIdProvider.java create mode 100644 steleks-proxy/src/main/java/ba/steles/service/Service.java create mode 100644 steleks-proxy/src/main/java/ba/steles/service/ServiceIdProvider.java create mode 100644 steleks-proxy/src/main/java/ba/steles/service/discovery/EurekaServiceDiscoveryClient.java create mode 100644 steleks-proxy/src/main/java/ba/steles/service/discovery/ServiceDiscoveryClient.java rename users/src/main/java/ba/steleks/security/token/{BasicTokenStore.java => BasicInMemoryTokenStore.java} (85%) diff --git a/out/production/eureka-service/bootstrap.properties b/out/production/eureka-service/bootstrap.properties new file mode 100644 index 0000000..ee58255 --- /dev/null +++ b/out/production/eureka-service/bootstrap.properties @@ -0,0 +1,5 @@ +spring.application.name=eureka-service +# N.B. this is the default: +spring.cloud.config.uri=${CONFIG_SERVER_URI:http://localhost:8888} +spring.cloud.config.username=${CONFIG_SERVER_USERNAME:root} +spring.cloud.config.password=${CONFIG_SERVER_PASSWORD:root} \ No newline at end of file diff --git a/out/production/steleks-proxy/bootstrap.properties b/out/production/steleks-proxy/bootstrap.properties new file mode 100644 index 0000000..78d4616 --- /dev/null +++ b/out/production/steleks-proxy/bootstrap.properties @@ -0,0 +1,5 @@ +spring.application.name=steleks-proxy +# N.B. this is the default: +spring.cloud.config.uri=${CONFIG_SERVER_URI:http://localhost:8888} +spring.cloud.config.username=${CONFIG_SERVER_USERNAME:root} +spring.cloud.config.password=${CONFIG_SERVER_PASSWORD:root} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 4f35e5f..d19d676 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include 'common', 'events', 'config_server', 'teams', 'users', 'eureka-service' \ No newline at end of file +include 'common', 'events', 'config_server', 'teams', 'users', 'eureka-service', 'steleks-proxy' \ No newline at end of file diff --git a/steleks-proxy/build.gradle b/steleks-proxy/build.gradle index b55cb45..327819e 100644 --- a/steleks-proxy/build.gradle +++ b/steleks-proxy/build.gradle @@ -30,6 +30,7 @@ dependencyManagement { dependencies { compile('org.springframework.cloud:spring-cloud-starter-config') compile('org.springframework.boot:spring-boot-starter-actuator') + compile('org.springframework.boot:spring-boot-starter-security') testCompile('org.springframework.boot:spring-boot-starter-test') compile('org.springframework.cloud:spring-cloud-starter-eureka') compile('org.springframework.cloud:spring-cloud-starter-zuul') diff --git a/steleks-proxy/src/main/java/ba/steles/SteleksProxyApplication.java b/steleks-proxy/src/main/java/ba/steles/SteleksProxyApplication.java index 51e841a..b8f1328 100644 --- a/steleks-proxy/src/main/java/ba/steles/SteleksProxyApplication.java +++ b/steleks-proxy/src/main/java/ba/steles/SteleksProxyApplication.java @@ -4,12 +4,34 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; +import org.springframework.context.annotation.Bean; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; @EnableZuulProxy @EnableEurekaClient @SpringBootApplication public class SteleksProxyApplication { + @Bean + public CorsFilter corsFilter() { + final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + final CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("OPTIONS"); + config.addAllowedMethod("HEAD"); + config.addAllowedMethod("GET"); + config.addAllowedMethod("PUT"); + config.addAllowedMethod("POST"); + config.addAllowedMethod("DELETE"); + config.addAllowedMethod("PATCH"); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + public static void main(String[] args) { SpringApplication.run(SteleksProxyApplication.class, args); } diff --git a/steleks-proxy/src/main/java/ba/steles/security/AuthResponse.java b/steleks-proxy/src/main/java/ba/steles/security/AuthResponse.java new file mode 100644 index 0000000..a626be4 --- /dev/null +++ b/steleks-proxy/src/main/java/ba/steles/security/AuthResponse.java @@ -0,0 +1,35 @@ +package ba.steles.security;/** + * Created by ensar on 01/06/17. + */ + +import java.util.Set; + +public class AuthResponse { + + private Long userId; + private Set userRoles; + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Set getUserRoles() { + return userRoles; + } + + public void setUserRoles(Set userRoles) { + this.userRoles = userRoles; + } + + @Override + public String toString() { + return "AuthResponse{" + + "userId=" + userId + + ", userRoles=" + userRoles + + '}'; + } +} diff --git a/steleks-proxy/src/main/java/ba/steles/security/AuthenticationFilter.java b/steleks-proxy/src/main/java/ba/steles/security/AuthenticationFilter.java new file mode 100644 index 0000000..cda6f6b --- /dev/null +++ b/steleks-proxy/src/main/java/ba/steles/security/AuthenticationFilter.java @@ -0,0 +1,76 @@ +package ba.steles.security;/** + * Created by ensar on 28/05/17. + */ + +import ba.steles.service.Service; +import ba.steles.service.discovery.ServiceDiscoveryClient; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.net.URI; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +public class AuthenticationFilter extends GenericFilterBean { + + private RestTemplate restTemplate; + + private ServiceDiscoveryClient discoveryClient; + + public AuthenticationFilter(RestTemplate restTemplate, ServiceDiscoveryClient discoveryClient) { + this.restTemplate = restTemplate; + this.discoveryClient = discoveryClient; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + + Authentication authentication; + + String token = ((HttpServletRequest)request).getHeader("Authorization"); + + if(token != null) { + try { + System.out.println("token: " + token); + String usersServiceBase = discoveryClient.getServiceUrl(Service.USERS); + AuthResponse usersResponse = restTemplate.getForObject(usersServiceBase + "/accesstoken/{token}", AuthResponse.class, token); + System.out.println("the response= " + usersResponse); + Set userRoleSet = usersResponse + .getUserRoles(); + if (userRoleSet == null) { + userRoleSet = new HashSet<>(); + userRoleSet.add("NO_ROLES"); + } + Set roleSet = + userRoleSet + .stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + authentication = new UsernamePasswordAuthenticationToken("a name", null, + roleSet); + } catch (Exception ex) { + ex.printStackTrace(); + authentication = null; + } + } else { + authentication = null; + } + + SecurityContextHolder.getContext().setAuthentication(authentication); + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/steleks-proxy/src/main/java/ba/steles/security/RelayTokenFilter.java b/steleks-proxy/src/main/java/ba/steles/security/RelayTokenFilter.java new file mode 100644 index 0000000..4376b10 --- /dev/null +++ b/steleks-proxy/src/main/java/ba/steles/security/RelayTokenFilter.java @@ -0,0 +1,41 @@ +package ba.steles.security;/** + * Created by ensar on 02/06/17. + */ + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import org.springframework.stereotype.Component; + +import java.util.Set; +import java.util.logging.Logger; + +@Component +public class RelayTokenFilter extends ZuulFilter { + + @Override + public Object run() { + RequestContext ctx = RequestContext.getCurrentContext(); + + // Alter ignored headers as per: https://gitter.im/spring-cloud/spring-cloud?at=56fea31f11ea211749c3ed22 + Set headers = (Set) ctx.get("ignoredHeaders"); + // We need our tokens relayed to resource servers + headers.remove("authorization"); + + return null; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public String filterType() { + return "pre"; + } + + @Override + public int filterOrder() { + return 10000; + } +} \ No newline at end of file diff --git a/steleks-proxy/src/main/java/ba/steles/security/SecurityConfig.java b/steleks-proxy/src/main/java/ba/steles/security/SecurityConfig.java new file mode 100644 index 0000000..18497b5 --- /dev/null +++ b/steleks-proxy/src/main/java/ba/steles/security/SecurityConfig.java @@ -0,0 +1,39 @@ +package ba.steles.security;/** + * Created by ensar on 16/05/17. + */ + +import ba.steles.service.discovery.ServiceDiscoveryClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.client.RestTemplate; + +@Configuration +@EnableWebSecurity +@ComponentScan("org.baeldung.security") +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private RestTemplateBuilder restTemplateBuilder; + + @Autowired + private ServiceDiscoveryClient discoveryClient; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable().authorizeRequests() + .antMatchers("/users/**", "/users", "/").permitAll() + .anyRequest().authenticated() + .and() + .addFilterBefore(new AuthenticationFilter(restTemplateBuilder.build(), discoveryClient), UsernamePasswordAuthenticationFilter.class); + } + +} \ No newline at end of file diff --git a/steleks-proxy/src/main/java/ba/steles/service/DefaultServiceIdProvider.java b/steleks-proxy/src/main/java/ba/steles/service/DefaultServiceIdProvider.java new file mode 100644 index 0000000..84cfa09 --- /dev/null +++ b/steleks-proxy/src/main/java/ba/steles/service/DefaultServiceIdProvider.java @@ -0,0 +1,41 @@ +package ba.steles.service;/** + * Created by ensar on 16/04/17. + */ + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import java.util.logging.Logger; + +@Primary +@Component +@RefreshScope +public class DefaultServiceIdProvider implements ServiceIdProvider { + private static final Logger logger = + Logger.getLogger(DefaultServiceIdProvider.class.getName()); + + public static final String NO_SERVICE = "NO_SERVICE"; + + @Value("${users.name}") + private String usersName; + @Value("${events.name}") + private String eventsName; + @Value("${teams.name}") + private String teamsName; + + @Override + public String getServiceId(Service service) { + switch (service) { + case USERS: + return usersName; + case TEAMS: + return teamsName; + case EVENTS: + return eventsName; + default: + return NO_SERVICE; + } + } +} diff --git a/steleks-proxy/src/main/java/ba/steles/service/Service.java b/steleks-proxy/src/main/java/ba/steles/service/Service.java new file mode 100644 index 0000000..496957d --- /dev/null +++ b/steleks-proxy/src/main/java/ba/steles/service/Service.java @@ -0,0 +1,11 @@ +package ba.steles.service; + +/** + * Created by ensar on 16/04/17. + */ + +public enum Service { + USERS, + EVENTS, + TEAMS; +} diff --git a/steleks-proxy/src/main/java/ba/steles/service/ServiceIdProvider.java b/steleks-proxy/src/main/java/ba/steles/service/ServiceIdProvider.java new file mode 100644 index 0000000..2c0b7dd --- /dev/null +++ b/steleks-proxy/src/main/java/ba/steles/service/ServiceIdProvider.java @@ -0,0 +1,9 @@ +package ba.steles.service; + +/** + * Created by ensar on 16/04/17. + */ + +public interface ServiceIdProvider { + String getServiceId(Service service); +} diff --git a/steleks-proxy/src/main/java/ba/steles/service/discovery/EurekaServiceDiscoveryClient.java b/steleks-proxy/src/main/java/ba/steles/service/discovery/EurekaServiceDiscoveryClient.java new file mode 100644 index 0000000..d3a039d --- /dev/null +++ b/steleks-proxy/src/main/java/ba/steles/service/discovery/EurekaServiceDiscoveryClient.java @@ -0,0 +1,48 @@ +package ba.steles.service.discovery; + +/** + * Created by ensar on 16/04/17. + */ + +import ba.steles.service.Service; +import ba.steles.service.ServiceIdProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.logging.Logger; + +@Primary +@Component +public class EurekaServiceDiscoveryClient implements ServiceDiscoveryClient { + private static final Logger logger = + Logger.getLogger(EurekaServiceDiscoveryClient.class.getName()); + + private DiscoveryClient discoveryClient; + private ServiceIdProvider serviceIdProvider; + + @Autowired + public EurekaServiceDiscoveryClient(DiscoveryClient discoveryClient, ServiceIdProvider serviceIdProvider) { + this.discoveryClient = discoveryClient; + this.serviceIdProvider = serviceIdProvider; + } + + @Override + public String getServiceUrl(Service service) { + String serviceId = serviceIdProvider.getServiceId(service); + List usersInstances = discoveryClient.getInstances(serviceId); + if(usersInstances == null || usersInstances.size() == 0) { + System.err.print(service.toString() + " service not found!"); + throw new RuntimeException(); + } + + ServiceInstance serviceInstance = usersInstances.get(0); + if(serviceInstance == null) { + throw new RuntimeException(); + } + return serviceInstance.getUri().toString(); + } +} diff --git a/steleks-proxy/src/main/java/ba/steles/service/discovery/ServiceDiscoveryClient.java b/steleks-proxy/src/main/java/ba/steles/service/discovery/ServiceDiscoveryClient.java new file mode 100644 index 0000000..64006f4 --- /dev/null +++ b/steleks-proxy/src/main/java/ba/steles/service/discovery/ServiceDiscoveryClient.java @@ -0,0 +1,11 @@ +package ba.steles.service.discovery; + +import ba.steles.service.Service; + +/** + * Created by ensar on 16/04/17. + */ +public interface ServiceDiscoveryClient { + + String getServiceUrl(Service service); +} diff --git a/users/src/main/java/ba/steleks/controller/AuthenticationController.java b/users/src/main/java/ba/steleks/controller/AuthenticationController.java index 44a144c..18ff246 100644 --- a/users/src/main/java/ba/steleks/controller/AuthenticationController.java +++ b/users/src/main/java/ba/steleks/controller/AuthenticationController.java @@ -71,11 +71,14 @@ public class AuthenticationController { @RequestMapping(path = "/accesstoken/{token}", method = RequestMethod.GET) public ResponseEntity validateToken(@PathVariable String token) { + System.out.println("Validating token: " + token); if (tokenStore.isValidToken(token)) { + System.out.println("Valid token: " + token); Long userId = tokenStore.getTokenInfo(token); User user = usersJpaRepository.findOne(userId); if(user != null) { + System.out.println("Found user with id: " + userId); Map response = new HashMap<>(); response.put("userId", String.valueOf(userId)); response.put("roles", @@ -85,11 +88,13 @@ public class AuthenticationController { .ok() .body(response); } else { + System.out.println("Found no user with id: " + userId); return ResponseEntity .status(HttpStatus.UNAUTHORIZED) .build(); } } else { + System.out.println("Invalid token: " + token); return ResponseEntity .status(HttpStatus.UNAUTHORIZED) .build(); diff --git a/users/src/main/java/ba/steleks/security/AuthenticationFilter.java b/users/src/main/java/ba/steleks/security/AuthenticationFilter.java index 854c5a2..b289465 100644 --- a/users/src/main/java/ba/steleks/security/AuthenticationFilter.java +++ b/users/src/main/java/ba/steleks/security/AuthenticationFilter.java @@ -29,6 +29,8 @@ public class AuthenticationFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { + System.out.println("Received a request which requires auth!"); + Authentication authentication = TokenAuthenticationService.getAuthentication( (HttpServletRequest) request, tokenStore, diff --git a/users/src/main/java/ba/steleks/security/SecurityConfig.java b/users/src/main/java/ba/steleks/security/SecurityConfig.java index 9dd9789..46c8121 100644 --- a/users/src/main/java/ba/steleks/security/SecurityConfig.java +++ b/users/src/main/java/ba/steleks/security/SecurityConfig.java @@ -42,7 +42,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() - .antMatchers("/accesstoken").permitAll() + .antMatchers("/accesstoken", "/accesstoken/**", "/").permitAll() .anyRequest().authenticated() .and() .addFilterBefore(new AuthenticationFilter(tokenStore, usersJpaRepository), CustomUrlUsernamePasswordAuthenticationFilter.class); diff --git a/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java b/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java index d457448..c4fbd00 100644 --- a/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java +++ b/users/src/main/java/ba/steleks/security/TokenAuthenticationService.java @@ -21,18 +21,23 @@ public class TokenAuthenticationService { TokenStore tokenStore, UsersJpaRepository usersJpaRepository) { String token = request.getHeader(HEADER_STRING); + System.out.println("token: " + token); + System.out.println("headers: " + request.getHeaderNames()); if (token != null && tokenStore.isValidToken(token)) { Long userId = tokenStore.getTokenInfo(token); User user = usersJpaRepository.findOne(userId); if(user != null) { + System.out.println("Found token... userId: " + userId); return new UsernamePasswordAuthenticationToken(user.getUsername(), null, UserRoleFactory.toGrantedAuthorities(user.getUserRoles())); } else { + System.out.println("Cant find token"); return null; } } + System.out.println("Cant find token"); return null; } } \ No newline at end of file diff --git a/users/src/main/java/ba/steleks/security/token/BasicTokenStore.java b/users/src/main/java/ba/steleks/security/token/BasicInMemoryTokenStore.java similarity index 85% rename from users/src/main/java/ba/steleks/security/token/BasicTokenStore.java rename to users/src/main/java/ba/steleks/security/token/BasicInMemoryTokenStore.java index 36674c8..18548ba 100644 --- a/users/src/main/java/ba/steleks/security/token/BasicTokenStore.java +++ b/users/src/main/java/ba/steleks/security/token/BasicInMemoryTokenStore.java @@ -14,18 +14,20 @@ import java.util.concurrent.TimeUnit; */ @Component -public class BasicTokenStore implements TokenStore { +public class BasicInMemoryTokenStore implements TokenStore { // Default one hour ttl public static final long DEFAULT_TTL = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS); - private KeyValueStore tokenStore; + private static KeyValueStore tokenStore; private long ttl = DEFAULT_TTL; @Autowired - public BasicTokenStore(KeyValueStore tokenStore) { - this.tokenStore = tokenStore; + public BasicInMemoryTokenStore(KeyValueStore tokenStore) { + if(BasicInMemoryTokenStore.tokenStore == null) { + BasicInMemoryTokenStore.tokenStore = tokenStore; + } } @Override