Skip to content

Commit

Permalink
#203 Switched from self signed cookies to Json Web Token
Browse files Browse the repository at this point in the history
  • Loading branch information
svenkubiak committed Sep 20, 2016
1 parent dba26c7 commit 8ec3876
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 189 deletions.
24 changes: 24 additions & 0 deletions mangooio-core/src/main/java/io/mangoo/enums/Claims.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.mangoo.enums;

/**
*
* @author svenkubiak
*
*/
public enum Claims {
VERSION("version"),
DATA("data"),
AUHTNETICITYTOKEN("authenticityToken"),
AUTHENTICATEDUSER("authenticatedUser");

private final String value;

Claims (String value) {
this.value = value;
}

@Override
public String toString() {
return this.value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,22 @@ public class Authentication {
private boolean loggedOut;

public Authentication() {
//Empty constructor required for Google Guice
}

public Authentication(LocalDateTime expires, String authenticatedUser) {
public static Authentication build() {
return new Authentication();
}

public Authentication withExpires(LocalDateTime expires) {
Objects.requireNonNull(expires, "expires can not be null");

this.expires = expires;
return this;
}

public Authentication withAuthenticatedUser(String authenticatedUser) {
this.authenticatedUser = authenticatedUser;
return this;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Objects;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
Expand All @@ -19,19 +19,37 @@
public class Session {
private static final Logger LOG = LogManager.getLogger(Session.class);
private static final Set<String> BLACKLIST = Sets.newHashSet("|", ":", "&", " ");
private Map<String, String> values;
private Map<String, String> values = new HashMap<String, String>();
private String authenticityToken;
private boolean changed;
private LocalDateTime expires;

public Session(){
//Empty constructor required for Google Guice
}

public Session(Map<String, String> values, String authenticityToken, LocalDateTime expires) {
this.values = Optional.ofNullable(values).orElse(new HashMap<>());

public static Session build() {
return new Session();
}

public Session withContent(Map<String, String> values) {
Objects.requireNonNull(values, "values can not be null");

this.values = values;
return this;
}

public Session withAuthenticityToken(String authenticityToken) {
Objects.requireNonNull(authenticityToken, "authenticityToken can not be null");

this.authenticityToken = authenticityToken;
return this;
}

public Session withExpires(LocalDateTime expires) {
Objects.requireNonNull(expires, "expires can not be null");

this.expires = expires;
return this;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Splitter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.mangoo.configuration.Config;
import io.mangoo.core.Application;
import io.mangoo.routing.Attachment;
import io.mangoo.routing.bindings.Authentication;
import io.mangoo.routing.bindings.Flash;
import io.mangoo.routing.bindings.Session;
import io.mangoo.utils.DateUtils;
import io.mangoo.utils.RequestUtils;
import io.mangoo.utils.cookie.CookieParser;
import io.mangoo.utils.cookie.CookieUtils;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.Cookie;

/**
*
Expand Down Expand Up @@ -47,19 +49,23 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
* @param exchange The Undertow HttpServerExchange
*/
protected Session getSessionCookie(HttpServerExchange exchange) {
Session session;
Session session = null;

final CookieParser cookieParser = CookieParser
.create(exchange, CONFIG.getSessionCookieName(), CONFIG.getApplicationSecret(), CONFIG.isSessionCookieEncrypt());
CookieParser cookieParser = CookieParser.build()
.withContent(CookieUtils.getCookieValue(exchange, CONFIG.getSessionCookieName()))
.withSecret(CONFIG.getApplicationSecret())
.isEncrypted(CONFIG.isSessionCookieEncrypt());

if (cookieParser.hasValidSessionCookie()) {
session = new Session(cookieParser.getSessionValues(),
cookieParser.getAuthenticityToken(),
cookieParser.getExpiresDate());
session = Session.build()
.withContent(cookieParser.getSessionValues())
.withAuthenticityToken(cookieParser.getAuthenticityToken())
.withExpires(cookieParser.getExpiresDate());
} else {
session = new Session(new HashMap<>(),
RandomStringUtils.randomAlphanumeric(TOKEN_LENGTH),
LocalDateTime.now().plusSeconds(CONFIG.getSessionExpires()));
session = Session.build()
.withContent(new HashMap<>())
.withAuthenticityToken(RandomStringUtils.randomAlphanumeric(TOKEN_LENGTH))
.withExpires(LocalDateTime.now().plusSeconds(CONFIG.getSessionExpires()));
}

return session;
Expand All @@ -71,15 +77,21 @@ protected Session getSessionCookie(HttpServerExchange exchange) {
* @param exchange The Undertow HttpServerExchange
*/
protected Authentication getAuthenticationCookie(HttpServerExchange exchange) {
Authentication authentication;

final CookieParser cookieParser = CookieParser
.create(exchange,CONFIG.getAuthenticationCookieName(),CONFIG.getApplicationSecret(), CONFIG.isAuthenticationCookieEncrypt());
Authentication authentication = null;

final CookieParser cookieParser = CookieParser.build()
.withContent(CookieUtils.getCookieValue(exchange, CONFIG.getAuthenticationCookieName()))
.withSecret(CONFIG.getApplicationSecret())
.isEncrypted(CONFIG.isAuthenticationCookieEncrypt());

if (cookieParser.hasValidAuthenticationCookie()) {
authentication = new Authentication(cookieParser.getExpiresDate(), cookieParser.getAuthenticatedUser());
authentication = Authentication.build()
.withExpires(cookieParser.getExpiresDate())
.withAuthenticatedUser(cookieParser.getAuthenticatedUser());
} else {
authentication = new Authentication(LocalDateTime.now().plusSeconds(CONFIG.getAuthenticationExpires()), null);
authentication = Authentication.build()
.withExpires(LocalDateTime.now().plusSeconds(CONFIG.getAuthenticationExpires()))
.withAuthenticatedUser(null);
}

return authentication;
Expand All @@ -90,22 +102,25 @@ protected Authentication getAuthenticationCookie(HttpServerExchange exchange) {
*
* @param exchange The Undertow HttpServerExchange
*/
@SuppressWarnings("unchecked")
protected Flash getFlashCookie(HttpServerExchange exchange) {
Flash flash = null;
final Cookie cookie = exchange.getRequestCookies().get(CONFIG.getFlashCookieName());
if (cookie != null){
final String cookieValue = cookie.getValue();
if (StringUtils.isNotEmpty(cookieValue) && !("null").equals(cookieValue)) {
final Map<String, String> values = new HashMap<>();
for (final Map.Entry<String, String> entry : Splitter.on("&").withKeyValueSeparator(":").split(cookie.getValue()).entrySet()) {
values.put(entry.getKey(), entry.getValue());
}

final String cookieValue = CookieUtils.getCookieValue(exchange, CONFIG.getFlashCookieName());

if (StringUtils.isNotBlank(cookieValue)) {
Jws<Claims> jwsClaims = Jwts.parser()
.setSigningKey(CONFIG.getApplicationSecret())
.parseClaimsJws(cookieValue);

Claims claims = jwsClaims.getBody();
LocalDateTime expiration = DateUtils.dateToLocalDateTime(claims.getExpiration());
if (LocalDateTime.now().isBefore(expiration)) {
final Map<String, String> values = claims.get("data", Map.class);
flash = new Flash(values);
flash.setDiscard(true);
}
}

return flash == null ? new Flash() : flash;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package io.mangoo.routing.handlers;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.codec.digest.DigestUtils;

import com.google.common.base.Joiner;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.mangoo.configuration.Config;
import io.mangoo.core.Application;
import io.mangoo.enums.Default;
import io.mangoo.enums.Claims;
import io.mangoo.routing.Attachment;
import io.mangoo.routing.bindings.Authentication;
import io.mangoo.routing.bindings.Flash;
import io.mangoo.routing.bindings.Session;
import io.mangoo.utils.DateUtils;
import io.mangoo.utils.RequestUtils;
import io.mangoo.utils.cookie.CookieBuilder;
import io.undertow.server.HttpHandler;
Expand Down Expand Up @@ -46,29 +47,25 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
*/
protected void setSessionCookie(HttpServerExchange exchange, Session session) {
if (session != null && session.hasChanges()) {
final String data = Joiner.on(Default.SPLITTER.toString()).withKeyValueSeparator(Default.SEPERATOR.toString()).join(session.getValues());
final String version = CONFIG.getCookieVersion();
final String authenticityToken = session.getAuthenticityToken();
Map<String, Object> claims = new HashMap<>();
claims.put(Claims.AUHTNETICITYTOKEN.toString(), session.getAuthenticityToken());
claims.put(Claims.VERSION.toString(), CONFIG.getCookieVersion());
claims.put(Claims.DATA.toString(), session.getValues());

final LocalDateTime expires = session.getExpires();
final StringBuilder buffer = new StringBuilder()
.append(DigestUtils.sha512Hex(data + authenticityToken + expires + version + CONFIG.getApplicationSecret()))
.append(Default.DELIMITER.toString())
.append(authenticityToken)
.append(Default.DELIMITER.toString())
.append(expires)
.append(Default.DELIMITER.toString())
.append(version)
.append(Default.DATA_DELIMITER.toString())
.append(data);

String value = buffer.toString();
String jwt = Jwts.builder()
.setClaims(claims)
.setExpiration(DateUtils.localDateTimeToDate(expires))
.signWith(SignatureAlgorithm.HS512, CONFIG.getApplicationSecret())
.compact();

if (CONFIG.isSessionCookieEncrypt()) {
value = this.requestAttachment.getCrypto().encrypt(value);
jwt = this.requestAttachment.getCrypto().encrypt(jwt);
}

final Cookie cookie = CookieBuilder.create()
.name(CONFIG.getSessionCookieName())
.value(value)
.value(jwt)
.secure(CONFIG.isSessionCookieSecure())
.httpOnly(true)
.expires(expires)
Expand All @@ -95,27 +92,24 @@ protected void setAuthenticationCookie(HttpServerExchange exchange, Authenticati
cookie.setMaxAge(0);
cookie.setDiscard(true);
} else {
final String authenticatedUser = authentication.getAuthenticatedUser();
final LocalDateTime expires = authentication.isRemember() ? LocalDateTime.now().plusSeconds(CONFIG.getAuthenticationRememberExpires()) : authentication.getExpires();
final String version = CONFIG.getAuthCookieVersion();

final StringBuilder buffer = new StringBuilder()
.append(DigestUtils.sha512Hex(authenticatedUser + expires + version + CONFIG.getApplicationSecret()))
.append(Default.DELIMITER.toString())
.append(expires)
.append(Default.DELIMITER.toString())
.append(version)
.append(Default.DATA_DELIMITER.toString())
.append(authenticatedUser);

String value = buffer.toString();
Map<String, Object> claims = new HashMap<>();
claims.put(Claims.VERSION.toString(), CONFIG.getAuthCookieVersion());

final LocalDateTime expires = authentication.isRemember() ? LocalDateTime.now().plusHours(CONFIG.getAuthenticationRememberExpires()) : authentication.getExpires();
String jwt = Jwts.builder()
.setClaims(claims)
.setSubject(authentication.getAuthenticatedUser())
.setExpiration(DateUtils.localDateTimeToDate(expires))
.signWith(SignatureAlgorithm.HS512, CONFIG.getApplicationSecret())
.compact();

if (CONFIG.isAuthenticationCookieEncrypt()) {
value = this.requestAttachment.getCrypto().encrypt(value);
jwt = this.requestAttachment.getCrypto().encrypt(jwt);
}

cookie = CookieBuilder.create()
.name(cookieName)
.value(value)
.value(jwt)
.secure(CONFIG.isAuthenticationCookieSecure())
.httpOnly(true)
.expires(expires)
Expand All @@ -133,13 +127,22 @@ protected void setAuthenticationCookie(HttpServerExchange exchange, Authenticati
*/
protected void setFlashCookie(HttpServerExchange exchange, Flash flash) {
if (flash != null && !flash.isDiscard() && flash.hasContent()) {
final String values = Joiner.on("&").withKeyValueSeparator(":").join(flash.getValues());

Map<String, Object> claims = new HashMap<>();
claims.put(Claims.DATA.toString(), flash.getValues());

final LocalDateTime expires = LocalDateTime.now().plusSeconds(60);
String jwt = Jwts.builder()
.setClaims(claims)
.setExpiration(DateUtils.localDateTimeToDate(expires))
.signWith(SignatureAlgorithm.HS512, CONFIG.getApplicationSecret())
.compact();

final Cookie cookie = CookieBuilder.create()
.name(CONFIG.getFlashCookieName())
.value(values)
.value(jwt)
.secure(CONFIG.isFlashCookieSecure())
.httpOnly(true)
.expires(expires)
.build();

exchange.setResponseCookie(cookie);
Expand Down
Loading

0 comments on commit 8ec3876

Please sign in to comment.