001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.server.session; 018 019import java.time.Duration; 020import java.util.Collections; 021import java.util.List; 022import java.util.function.Consumer; 023import java.util.stream.Collectors; 024 025import org.springframework.http.HttpCookie; 026import org.springframework.http.ResponseCookie; 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029import org.springframework.util.MultiValueMap; 030import org.springframework.web.server.ServerWebExchange; 031 032/** 033 * Cookie-based {@link WebSessionIdResolver}. 034 * 035 * @author Rossen Stoyanchev 036 * @author Brian Clozel 037 * @since 5.0 038 */ 039public class CookieWebSessionIdResolver implements WebSessionIdResolver { 040 041 private String cookieName = "SESSION"; 042 043 private Duration cookieMaxAge = Duration.ofSeconds(-1); 044 045 @Nullable 046 private Consumer<ResponseCookie.ResponseCookieBuilder> cookieInitializer = null; 047 048 049 /** 050 * Set the name of the cookie to use for the session id. 051 * <p>By default set to "SESSION". 052 * @param cookieName the cookie name 053 */ 054 public void setCookieName(String cookieName) { 055 Assert.hasText(cookieName, "'cookieName' must not be empty"); 056 this.cookieName = cookieName; 057 } 058 059 /** 060 * Return the configured cookie name. 061 */ 062 public String getCookieName() { 063 return this.cookieName; 064 } 065 066 /** 067 * Set the value for the "Max-Age" attribute of the cookie that holds the 068 * session id. For the range of values see {@link ResponseCookie#getMaxAge()}. 069 * <p>By default set to -1. 070 * @param maxAge the maxAge duration value 071 */ 072 public void setCookieMaxAge(Duration maxAge) { 073 this.cookieMaxAge = maxAge; 074 } 075 076 /** 077 * Return the configured "Max-Age" attribute value for the session cookie. 078 */ 079 public Duration getCookieMaxAge() { 080 return this.cookieMaxAge; 081 } 082 083 /** 084 * Add a {@link Consumer} for a {@code ResponseCookieBuilder} that will be invoked 085 * for each cookie being built, just before the call to {@code build()}. 086 * @param initializer consumer for a cookie builder 087 * @since 5.1 088 */ 089 public void addCookieInitializer(Consumer<ResponseCookie.ResponseCookieBuilder> initializer) { 090 this.cookieInitializer = this.cookieInitializer != null ? 091 this.cookieInitializer.andThen(initializer) : initializer; 092 } 093 094 095 @Override 096 public List<String> resolveSessionIds(ServerWebExchange exchange) { 097 MultiValueMap<String, HttpCookie> cookieMap = exchange.getRequest().getCookies(); 098 List<HttpCookie> cookies = cookieMap.get(getCookieName()); 099 if (cookies == null) { 100 return Collections.emptyList(); 101 } 102 return cookies.stream().map(HttpCookie::getValue).collect(Collectors.toList()); 103 } 104 105 @Override 106 public void setSessionId(ServerWebExchange exchange, String id) { 107 Assert.notNull(id, "'id' is required"); 108 ResponseCookie cookie = initSessionCookie(exchange, id, getCookieMaxAge()); 109 exchange.getResponse().getCookies().set(this.cookieName, cookie); 110 } 111 112 @Override 113 public void expireSession(ServerWebExchange exchange) { 114 ResponseCookie cookie = initSessionCookie(exchange, "", Duration.ZERO); 115 exchange.getResponse().getCookies().set(this.cookieName, cookie); 116 } 117 118 private ResponseCookie initSessionCookie( 119 ServerWebExchange exchange, String id, Duration maxAge) { 120 121 ResponseCookie.ResponseCookieBuilder cookieBuilder = ResponseCookie.from(this.cookieName, id) 122 .path(exchange.getRequest().getPath().contextPath().value() + "/") 123 .maxAge(maxAge) 124 .httpOnly(true) 125 .secure("https".equalsIgnoreCase(exchange.getRequest().getURI().getScheme())) 126 .sameSite("Lax"); 127 128 if (this.cookieInitializer != null) { 129 this.cookieInitializer.accept(cookieBuilder); 130 } 131 132 return cookieBuilder.build(); 133 } 134 135}