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}