/** * Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.seedstack.seed.web.security.internal; import org.seedstack.seed.SeedException; import org.seedstack.seed.web.security.WebSecurityConfig; import org.seedstack.seed.web.spi.AntiXsrfService; import javax.inject.Inject; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.security.SecureRandom; class StatelessAntiXsrfService implements AntiXsrfService { private final static char[] CHARSET = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; private final WebSecurityConfig.XSRFConfig xsrfConfig; @Inject public StatelessAntiXsrfService(WebSecurityConfig webSecurityConfig) { xsrfConfig = webSecurityConfig.xsrf(); } @Override public void applyXsrfProtection(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { final String cookieName = xsrfConfig.getCookieName(); final String headerName = xsrfConfig.getHeaderName(); final HttpSession session = httpServletRequest.getSession(false); // Only apply XSRF protection when there is a session if (session != null) { // If session is new, generate a token and put it in a cookie if (session.isNew()) { Cookie cookie = new Cookie(cookieName, generateToken()); cookie.setHttpOnly(false); cookie.setPath("/"); cookie.setMaxAge(-1); httpServletResponse.addCookie(cookie); } // Else, check if the request and cookie tokens match else { String cookieToken = extractCookieToken(cookieName, httpServletRequest); String requestToken = httpServletRequest.getHeader(headerName); if (requestToken == null) { throw SeedException.createNew(WebSecurityErrorCode.MISSING_XSRF_HEADER); } if (cookieToken == null) { throw SeedException.createNew(WebSecurityErrorCode.MISSING_XSRF_COOKIE); } // Check for multiple headers (keep only the first one) int commaIndex = requestToken.indexOf(','); if (commaIndex != -1) { requestToken = requestToken.substring(0, commaIndex).trim(); } // Check if tokens match if (!cookieToken.equals(requestToken)) { throw SeedException.createNew(WebSecurityErrorCode.INVALID_XSRF_TOKEN); } } } } @Override public void cleanXsrfProtection(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { HttpSession session = httpServletRequest.getSession(false); // Delete an eventual existing token if no session if (session == null) { Cookie cookie = new Cookie(xsrfConfig.getCookieName(), "deleteMe"); cookie.setHttpOnly(false); cookie.setPath("/"); cookie.setMaxAge(0); httpServletResponse.addCookie(cookie); } } private String extractCookieToken(String cookieName, HttpServletRequest httpServletRequest) { for (Cookie cookie : httpServletRequest.getCookies()) { if (cookieName.equals(cookie.getName())) { return cookie.getValue(); } } return null; } private String generateToken() { final String tokenAlgorithm = xsrfConfig.getAlgorithm(); final int tokenLength = xsrfConfig.getLength(); try { SecureRandom secureRandom = SecureRandom.getInstance(tokenAlgorithm); StringBuilder sb = new StringBuilder(); for (int i = 1; i < tokenLength + 1; i++) { sb.append(CHARSET[secureRandom.nextInt(CHARSET.length)]); if ((i % 4) == 0 && i != 0 && i < tokenLength) { sb.append('-'); } } return sb.toString(); } catch (Exception e) { throw new RuntimeException(String.format("Unable to generate the random token - %s", e.getLocalizedMessage()), e); } } }