Skip to content

Commit

Permalink
configure zuul proxy and filters , store tokens in cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
nivemaham committed Nov 13, 2017
1 parent 0ec8c2c commit 1ff9418
Show file tree
Hide file tree
Showing 15 changed files with 329 additions and 21 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-aop"
compile "org.springframework.boot:spring-boot-starter-data-jpa"
compile "org.springframework.boot:spring-boot-starter-security"
compile "org.springframework.cloud:spring-cloud-starter-zuul:${zuul_proxy_version}"
compile ("org.springframework.boot:spring-boot-starter-web") {
exclude module: 'spring-boot-starter-tomcat'
}
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ gatling_version=2.2.3
mapstruct_version=1.1.0.Final
undertow_version=1.4.10.Final
yarn_version=0.21.3
zuul_proxy_version=1.3.5.RELEASE

## below are some of the gradle performance improvement settings that can be used as required, these are not enabled by default

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;

Expand All @@ -23,6 +24,7 @@
import java.util.Collection;

@ComponentScan
@EnableZuulProxy
@EnableAutoConfiguration(exclude = {MetricFilterAutoConfiguration.class, MetricRepositoryAutoConfiguration.class})
@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class ,
ManagementPortalProperties.class})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
public class ManagementPortalProperties {
private final Mail mail = new Mail();

private final Frontend frontend = new Frontend();

public ManagementPortalProperties.Frontend getFrontend() {
return frontend;
}

public ManagementPortalProperties.Mail getMail() {
return mail;
}
Expand Down Expand Up @@ -36,4 +42,48 @@ public void setBaseUrl(String baseUrl) {
}
}

public static class Frontend {

private String clientId = "";

private String clientSecret = "";

private String clientScopes = "";

private Integer sessionTimeout = 24*60*60; // a day

public String getClientId() {
return clientId;
}

public void setClientId(String clientId) {
this.clientId = clientId;
}

public String getClientSecret() {
return clientSecret;
}

public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}

public Integer getSessionTimeout() {
return sessionTimeout;
}

public void setSessionTimeout(Integer sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}

public String getClientScopes() {
return clientScopes;
}

public void setClientScopes(String clientScopes) {

this.clientScopes = clientScopes.replace(",", " ");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.radarcns.management.filters;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class CustomHttpServletRequest extends HttpServletRequestWrapper {
private final Map<String, String[]> additionalParams;
private final HttpServletRequest request;

public CustomHttpServletRequest(final HttpServletRequest request, final Map<String, String[]> additionalParams) {
super(request);
this.request = request;
this.additionalParams = additionalParams;
}

@Override
public Map<String, String[]> getParameterMap() {
final Map<String, String[]> map = request.getParameterMap();
final Map<String, String[]> param = new HashMap<String, String[]>();
param.putAll(map);
param.putAll(additionalParams);
return param;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.radarcns.management.filters;

import static org.radarcns.management.filters.OAuth2TokenRequestPreZuulFilter.REFRESH_TOKEN_COOKIE;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import javax.servlet.http.Cookie;

import org.apache.commons.io.IOUtils;
import org.radarcns.management.config.ManagementPortalProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;

/**
* A post-filter for all the request sent to Zuul proxy.
* This investigates the response body and stores the Refresh token in a Cookie and remove it from response body.
*/
@Component
public class OAuth2TokenRequestPostZuulFilter extends ZuulFilter {

@Autowired
private ManagementPortalProperties managementPortalProperties;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final ObjectMapper mapper = new ObjectMapper();

@Override
public Object run() {
final RequestContext ctx = RequestContext.getCurrentContext();
logger.debug("in zuul filter " + ctx.getRequest().getRequestURI());

final String requestURI = ctx.getRequest().getRequestURI();
final String requestMethod = ctx.getRequest().getMethod();

try {
final InputStream is = ctx.getResponseDataStream();
String responseBody = IOUtils.toString(is, "UTF-8");
if (responseBody.contains("refresh_token")) {
final Map<String, Object> responseMap = mapper
.readValue(responseBody, new TypeReference<Map<String, Object>>() {
});
final String refreshToken = responseMap.get("refresh_token").toString();
responseMap.remove("refresh_token");
responseBody = mapper.writeValueAsString(responseMap);

final Cookie cookie = new Cookie(REFRESH_TOKEN_COOKIE, refreshToken);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath(ctx.getRequest().getContextPath() + "oauthserver/oauth/token");
cookie.setMaxAge(this.managementPortalProperties.getFrontend().getSessionTimeout()); // 30 minites
ctx.getResponse().addCookie(cookie);
logger.info("refresh token = " + refreshToken);

}
if (requestURI.contains("oauth/token") && requestMethod.equals("DELETE")) {
final Cookie cookie = new Cookie(REFRESH_TOKEN_COOKIE, "");
cookie.setMaxAge(0);
cookie.setPath(ctx.getRequest().getContextPath() + "oauthserver/oauth/token");
ctx.getResponse().addCookie(cookie);
}
ctx.setResponseBody(responseBody);

} catch (final IOException e) {
logger.error("Error occured in zuul post filter", e);
}
return null;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public int filterOrder() {
return 10;
}

@Override
public String filterType() {
return "post";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.radarcns.management.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.radarcns.management.config.ManagementPortalProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.stereotype.Component;

/**
* A pre-filter for all request sent to Zuul proxy.
* This adds the client credentials as Basic authentication header and
* scope of the client configured in application.*.yml.
* This prevents exposing client credentials to front-end.
*/
@Component
public class OAuth2TokenRequestPreZuulFilter extends ZuulFilter {

static final String REFRESH_TOKEN_COOKIE = "rft";
@Autowired
private ManagementPortalProperties managementPortalProperties;

Logger logger = LoggerFactory.getLogger(OAuth2TokenRequestPreZuulFilter.class);
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.getRequest().getRequestURI().contains("/oauth/token")) {
byte[] encoded;
try {
// get this from properties, this will allow us to use ENV variables for docker
encoded = Base64.encode((managementPortalProperties.getFrontend().getClientId()+":"+managementPortalProperties.getFrontend().getClientSecret()).getBytes("UTF-8"));
ctx.addZuulRequestHeader("Authorization", "Basic " + new String(encoded));
final HttpServletRequest req = ctx.getRequest();
final Map<String, String[]> param = new HashMap<String, String[]>();
param.put("scope" , new String[] {managementPortalProperties.getFrontend().getClientScopes()});
final String refreshToken = extractRefreshToken(req);
if (refreshToken != null) {
param.put("refresh_token", new String[] { refreshToken });
param.put("grant_type", new String[] { "refresh_token" });
}
ctx.setRequest(new CustomHttpServletRequest(req, param));
} catch (UnsupportedEncodingException e) {
logger.error("Error occured in pre filter", e);
}
}
return null;
}

private String extractRefreshToken(HttpServletRequest req) {
final Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equalsIgnoreCase(REFRESH_TOKEN_COOKIE)) {
return cookies[i].getValue();
}
}
}
return null;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public int filterOrder() {
return -2;
}

@Override
public String filterType() {
return "pre";
}
}
5 changes: 5 additions & 0 deletions src/main/resources/config/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ managementportal:
mail: # specific ManagementPortal mail property, for standard properties see MailProperties
from: ManagementPortal@localhost
baseUrl: http://my-server-url-to-change # Modify according to your server's URL
frontend:
clientId: ManagementPortalapp
clientSecret: my-secret-token-to-change-in-production
clientScopes: "read,write"
sessionTimeout : 86400 # session for rft cookie

# ===================================================================
# JHipster specific properties
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/config/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ managementportal:
mail: # specific JHipster mail property, for standard properties see MailProperties
from: ManagementPortal@localhost
baseUrl: http://my-server-url-to-change-here # Modify according to your server's URL
frontend:
clientId: ManagementPortalapp
clientSecret:
clientScopes: read,write
sesssionTimeout : 86400

# ===================================================================
# JHipster specific properties
Expand Down
22 changes: 22 additions & 0 deletions src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@
# Full reference is available at:
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
# ===================================================================
zuul:
host:
max-total-connections: 1000
max-per-route-connections: 100
semaphore:
max-semaphores: 500
retryable: true
routes:
ManagementPortal:
path: /oauthserver/oauth/**
url: http://localhost:8080/oauth
ribbon:
eureka:
enabled: false

hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000

management:
security:
Expand Down
10 changes: 7 additions & 3 deletions src/main/webapp/app/blocks/interceptor/auth.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ import { Observable } from 'rxjs/Observable';
import { RequestOptionsArgs, Response } from '@angular/http';
import { LocalStorageService, SessionStorageService } from 'ng2-webstorage';
import { HttpInterceptor } from 'ng-jhipster';
import {Injector} from "@angular/core";
import {CookieService} from "angular2-cookie/core";
import {AUTH_TOKEN_COOKIE} from "../../shared/constants/common.constants";

export class AuthInterceptor extends HttpInterceptor {

constructor(
private localStorage: LocalStorageService,
private sessionStorage: SessionStorageService
private injector: Injector,
) {
super();
}

requestIntercept(options?: RequestOptionsArgs): RequestOptionsArgs {
const token = this.localStorage.retrieve('authenticationToken') || this.sessionStorage.retrieve('authenticationToken');
// retrieve token from cookie
const cookieService = this.injector.get(CookieService);
const token : any = cookieService.getObject(AUTH_TOKEN_COOKIE);
if (token && token.expires_at && token.expires_at > new Date().getTime()) {
options.headers.append('Authorization', 'Bearer ' + token.access_token);
}
Expand Down
Loading

0 comments on commit 1ff9418

Please sign in to comment.