Skip to content

Commit b882feb

Browse files
committed
session: require cookie session name
- remove the default jooby.sid cookie name, users must configured session store as well as cookie name - the default session store will generate an usage exception at runtime - fix #3639
1 parent 647520a commit b882feb

18 files changed

Lines changed: 150 additions & 177 deletions

File tree

docs/asciidoc/modules/redis.adoc

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,12 @@ import io.jooby.redis.RedisSessionStore;
140140
import io.lettuce.core.RedisClient;
141141
142142
{
143-
install(new RedisModule()); <1>
143+
install(new RedisModule()); <1>
144144
145-
setSessionStore(new RedisSessionStore(require(RedisClient.class))); <2>
145+
setSessionStore(new RedisSessionStore(Cookie.session("myappid"), require(RedisClient.class))); <2>
146146
147147
get("/", ctx -> {
148-
Session httpSession = ctx.session(); <3>
148+
Session httpSession = ctx.session(); <3>
149149
// HTTP session is backed by Redis
150150
});
151151
}
@@ -160,12 +160,12 @@ import io.jooby.redis.RedisSessionStore
160160
import io.lettuce.core.RedisClient
161161
162162
{
163-
install(RedisModule()) <1>
163+
install(RedisModule()) <1>
164164
165-
sessionStore = RedisSessionStore(require(RedisClient::class)) <2>
165+
sessionStore = RedisSessionStore(Cookie.session("myappid"), require(RedisClient::class)) <2>
166166
167167
get("/") {
168-
val httpSession = ctx.session() <3>
168+
val httpSession = ctx.session() <3>
169169
// HTTP session is backed by Redis
170170
}
171171
}
@@ -179,4 +179,3 @@ More Options:
179179

180180
- javadoc:redis.RedisSessionStore[setTimeout, java.time.Duration, artifact="jooby-redis"]: Set session timeout. Default is: `30 minutes`
181181
- javadoc:redis.RedisSessionStore[setNamespace, java.lang.String, artifact="jooby-redis"]: Set key prefix. Default is: `sessions`
182-
- javadoc:redis.RedisSessionStore[setToken, io.jooby.SessionToken, artifact="jooby-redis"]: Set session token. Default is a cookie token: `jooby.sid`

docs/asciidoc/session.adoc

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ objects. It's intended as a simple mechanism to store basic data (not an object
1313

1414
Jooby provides the following javadoc:SessionStore[]:
1515

16-
- In-Memory sessions - which you should combine with an a sticky sessions proxy if you plan to run multiple instances.
16+
- In-Memory sessionswhich you should combine with a sticky sessions proxy if you plan to run multiple instances.
1717
- Cookie sessions signed with a secret key
1818
- JSON Web Token sessions
1919
20+
Since 4.0.0 no session is configured by default. Attempt to access to a session at runtime results
21+
in exception.
22+
2023
=== In-Memory Session
2124

2225
Default session store uses memory to save session data. This store:
@@ -28,6 +31,8 @@ Default session store uses memory to save session data. This store:
2831
[source,java,role="primary"]
2932
----
3033
{
34+
setSessionStore(SessionStore.memory(Cookie.session("myappid")));
35+
3136
get("/", ctx -> {
3237
Session session = ctx.session(); // <1>
3338
@@ -42,6 +47,8 @@ Default session store uses memory to save session data. This store:
4247
[source,kotlin,role="secondary"]
4348
----
4449
{
50+
setSessionStore(SessionStore.memory(Cookie.session("myappid")))
51+
4552
get("/") {
4653
val session = ctx.session() // <1>
4754
@@ -56,7 +63,7 @@ Default session store uses memory to save session data. This store:
5663
<2> Set a session attribute
5764
<3> Get a session attribute
5865

59-
Session token/ID is retrieved it from request cookie. Default session cookie is javadoc:SessionToken[SID, text=jooby.sid]. To customize cookie details:
66+
Session token/ID is retrieved it from request cookie. Default session cookie never expires, it is http only under the `/` path. To customize cookie details:
6067

6168
.In-Memory Session with Custom Cookie
6269
[source,java,role="primary"]
@@ -92,7 +99,7 @@ Session token/ID is retrieved it from request cookie. Default session cookie is
9299

93100
<1> Set an `in-memory` session store with a custom cookie named: `SESSION`
94101

95-
Alternative you can use a request header to retrieve a session token/ID:
102+
Alternatively, you can use a request header to retrieve a session token/ID:
96103

97104
.In-Memory Session with HTTP Header
98105
[source,java,role="primary"]
@@ -177,9 +184,9 @@ Data sign/unsign is done using javadoc:Cookie[sign, java.lang.String, java.lang.
177184
[source,java,role="primary"]
178185
----
179186
{
180-
String secret = "super secret key"; // <1>
187+
String secret = "super secret key"; // <1>
181188
182-
setSessionStore(SessionStore.signed(secret)); // <2>
189+
setSessionStore(SessionStore.signed(Cookie.session("myappid"), secret)); // <2>
183190
184191
get("/", ctx -> {
185192
Session session = ctx.session();
@@ -195,9 +202,9 @@ Data sign/unsign is done using javadoc:Cookie[sign, java.lang.String, java.lang.
195202
[source,kotlin,role="secondary"]
196203
----
197204
{
198-
val secret = "super secret key" // <1>
205+
val secret = "super secret key" // <1>
199206
200-
sessionStore = SessionStore.signed(secret) // <2>
207+
sessionStore = SessionStore.signed(Cookie.session("myappid"),secret) // <2>
201208
202209
get("/") {
203210
val session = ctx.session()
@@ -220,7 +227,7 @@ Like with `memory` session store you can use HTTP headers:
220227
{
221228
String secret = "super secret key"; // <1>
222229
223-
setSessionStore(SessionStore.signed(secret, SessionToken.header("TOKEN"))); // <2>
230+
setSessionStore(SessionStore.signed(SessionToken.header("TOKEN"), secret)); // <2>
224231
225232
get("/", ctx -> {
226233
Session session = ctx.session();
@@ -238,7 +245,7 @@ Like with `memory` session store you can use HTTP headers:
238245
{
239246
val secret = "super secret key" // <1>
240247
241-
sessionStore = SessionStore.signed(secret, SessionToken.header("TOKEN")) // <2>
248+
sessionStore = SessionStore.signed(SessionToken.header("TOKEN"), secret) // <2>
242249
243250
get("/") {
244251
val session = ctx.session()

jooby/src/main/java/io/jooby/Cookie.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,17 @@ public String toString() {
588588
return Optional.empty();
589589
}
590590

591+
/**
592+
* Creates a session cookie which never expires (maxAge: -1), is http only and path is <code>/
593+
* </code>.
594+
*
595+
* @param sid Session ID.
596+
* @return Session's cookie.
597+
*/
598+
public static Cookie session(@NonNull String sid) {
599+
return new Cookie(sid).setMaxAge(-1).setHttpOnly(true).setPath("/");
600+
}
601+
591602
private static <T> void value(
592603
Config conf, String name, BiFunction<Config, String, T> mapper, Consumer<T> consumer) {
593604
if (conf.hasPath(name)) {

jooby/src/main/java/io/jooby/SessionStore.java

Lines changed: 48 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,40 @@ public interface SessionStore {
2727
/** Default session timeout in minutes. */
2828
int DEFAULT_TIMEOUT = 30;
2929

30+
SessionStore UNSUPPORTED =
31+
new SessionStore() {
32+
33+
@NonNull @Override
34+
public Session newSession(@NonNull Context ctx) {
35+
throw Usage.noSession();
36+
}
37+
38+
@Nullable @Override
39+
public Session findSession(@NonNull Context ctx) {
40+
throw Usage.noSession();
41+
}
42+
43+
@Override
44+
public void deleteSession(@NonNull Context ctx, @NonNull Session session) {
45+
throw Usage.noSession();
46+
}
47+
48+
@Override
49+
public void touchSession(@NonNull Context ctx, @NonNull Session session) {
50+
throw Usage.noSession();
51+
}
52+
53+
@Override
54+
public void saveSession(@NonNull Context ctx, @NonNull Session session) {
55+
throw Usage.noSession();
56+
}
57+
58+
@Override
59+
public void renewSessionId(@NonNull Context ctx, @NonNull Session session) {
60+
throw Usage.noSession();
61+
}
62+
};
63+
3064
/**
3165
* Base class for in-memory session store.
3266
*
@@ -35,9 +69,9 @@ public interface SessionStore {
3569
*/
3670
abstract class InMemory implements SessionStore {
3771
protected static class Data {
38-
private Instant lastAccessedTime;
39-
private Instant creationTime;
40-
private Map hash;
72+
private final Instant lastAccessedTime;
73+
private final Instant creationTime;
74+
private final Map hash;
4175

4276
public Data(Instant creationTime, Instant lastAccessedTime, Map hash) {
4377
this.creationTime = creationTime;
@@ -64,12 +98,12 @@ protected InMemory(@NonNull SessionToken token) {
6498

6599
@Override
66100
public @NonNull Session newSession(@NonNull Context ctx) {
67-
String sessionId = token.newToken();
68-
Data data =
101+
var sessionId = token.newToken();
102+
var data =
69103
getOrCreate(
70104
sessionId, sid -> new Data(Instant.now(), Instant.now(), new ConcurrentHashMap()));
71105

72-
Session session = restore(ctx, sessionId, data);
106+
var session = restore(ctx, sessionId, data);
73107

74108
token.saveToken(ctx, sessionId);
75109
return session;
@@ -78,7 +112,7 @@ protected InMemory(@NonNull SessionToken token) {
78112
/**
79113
* Session token.
80114
*
81-
* @return Session token. Uses a cookie by default: {@link SessionToken#SID}.
115+
* @return Session token.
82116
*/
83117
public @NonNull SessionToken getToken() {
84118
return token;
@@ -95,7 +129,7 @@ protected InMemory(@NonNull SessionToken token) {
95129
return this;
96130
}
97131

98-
protected abstract @NonNull Data getOrCreate(
132+
protected abstract Data getOrCreate(
99133
@NonNull String sessionId, @NonNull Function<String, Data> factory);
100134

101135
protected abstract @Nullable Data getOrNull(@NonNull String sessionId);
@@ -105,7 +139,7 @@ protected InMemory(@NonNull SessionToken token) {
105139
protected abstract void put(@NonNull String sessionId, @NonNull Data data);
106140

107141
@Override
108-
public Session findSession(Context ctx) {
142+
public @Nullable Session findSession(@NonNull Context ctx) {
109143
String sessionId = token.findToken(ctx);
110144
if (sessionId == null) {
111145
return null;
@@ -219,48 +253,6 @@ private Session restore(Context ctx, String sessionId, Data data) {
219253
*/
220254
void renewSessionId(@NonNull Context ctx, @NonNull Session session);
221255

222-
/**
223-
* Creates a cookie based session and store data in memory. Session data is not keep after
224-
* restart.
225-
*
226-
* <p>It uses the default session cookie: {@link SessionToken#SID}.
227-
*
228-
* <p>- Session data is not keep after restart.
229-
*
230-
* @param timeout Timeout in seconds. Use <code>-1</code> for no timeout.
231-
* @return Session store.
232-
*/
233-
static @NonNull SessionStore memory(int timeout) {
234-
return memory(SessionToken.SID, Duration.ofSeconds(timeout));
235-
}
236-
237-
/**
238-
* Creates a cookie based session and store data in memory. Session data is not keep after
239-
* restart.
240-
*
241-
* <p>It uses the default session cookie: {@link SessionToken#SID}.
242-
*
243-
* <p>- Session expires after 30 minutes of inactivity. - Session data is not keep after restart.
244-
*
245-
* @return Session store.
246-
*/
247-
static @NonNull SessionStore memory() {
248-
return memory(SessionToken.SID);
249-
}
250-
251-
/**
252-
* Creates a cookie based session and store data in memory. Session data is not keep after
253-
* restart.
254-
*
255-
* <p>It uses the default session cookie: {@link SessionToken#SID}.
256-
*
257-
* @param timeout Expires session after amount of inactivity time.
258-
* @return Session store.
259-
*/
260-
static @NonNull SessionStore memory(@NonNull Duration timeout) {
261-
return memory(SessionToken.SID, timeout);
262-
}
263-
264256
/**
265257
* Creates a cookie based session and store data in memory.
266258
*
@@ -313,25 +305,12 @@ private Session restore(Context ctx, String sessionId, Data data) {
313305
*
314306
* <p>See {@link Cookie#sign(String, String)} and {@link Cookie#unsign(String, String)}.
315307
*
316-
* @param secret Secret token to signed data.
317-
* @return A browser session store.
318-
*/
319-
static @NonNull SessionStore signed(@NonNull String secret) {
320-
return signed(secret, SessionToken.SID);
321-
}
322-
323-
/**
324-
* Creates a session store that uses (un)signed data. Session data is signed it using <code>
325-
* HMAC_SHA256</code>.
326-
*
327-
* <p>See {@link Cookie#sign(String, String)} and {@link Cookie#unsign(String, String)}.
328-
*
329-
* @param secret Secret token to signed data.
330308
* @param cookie Cookie to use.
309+
* @param secret Secret token to signed data.
331310
* @return A browser session store.
332311
*/
333-
static @NonNull SessionStore signed(@NonNull String secret, @NonNull Cookie cookie) {
334-
return signed(secret, SessionToken.signedCookie(cookie));
312+
static @NonNull SessionStore signed(@NonNull Cookie cookie, @NonNull String secret) {
313+
return signed(SessionToken.signedCookie(cookie), secret);
335314
}
336315

337316
/**
@@ -340,11 +319,11 @@ private Session restore(Context ctx, String sessionId, Data data) {
340319
*
341320
* <p>See {@link Cookie#sign(String, String)} and {@link Cookie#unsign(String, String)}.
342321
*
343-
* @param secret Secret token to signed data.
344322
* @param token Session token to use.
323+
* @param secret Secret token to signed data.
345324
* @return A browser session store.
346325
*/
347-
static @NonNull SessionStore signed(@NonNull String secret, @NonNull SessionToken token) {
326+
static @NonNull SessionStore signed(@NonNull SessionToken token, @NonNull String secret) {
348327
SneakyThrows.Function<String, Map<String, String>> decoder =
349328
value -> {
350329
String unsign = Cookie.unsign(value, secret);

jooby/src/main/java/io/jooby/SessionToken.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,7 @@ public String findToken(@NonNull Context ctx) {
4646

4747
@Override
4848
public void saveToken(@NonNull Context ctx, @NonNull String token) {
49-
// FIXME: Review, bc we don;t need this
50-
// String existingId = findToken(ctx);
51-
// write cookie for new or expiring session
52-
// if (existingId == null || cookie.getMaxAge() > 0) {
5349
ctx.setResponseCookie(cookie.clone().setValue(token));
54-
// }
5550
}
5651

5752
@Override
@@ -64,7 +59,7 @@ public void deleteToken(@NonNull Context ctx, @NonNull String token) {
6459
* Looks for a session ID from request headers. This strategy:
6560
*
6661
* <p>- find a token from a request header. - on save, send the header back as response header. -
67-
* on session destroy. don't send response header back.
62+
* on session destruction. don't send response header back.
6863
*/
6964
class HeaderID implements SessionToken {
7065

@@ -130,12 +125,6 @@ public void deleteToken(@NonNull Context ctx, @NonNull String token) {
130125
}
131126
}
132127

133-
/**
134-
* Default cookie for cookie based session stores. Uses <code>jooby.sid</code> as name. It never
135-
* expires, use the root, only for HTTP.
136-
*/
137-
Cookie SID = new Cookie("jooby.sid").setMaxAge(-1).setHttpOnly(true).setPath("/");
138-
139128
/** Secure random for default session token generator. */
140129
SecureRandom RND = new SecureRandom();
141130

jooby/src/main/java/io/jooby/Usage.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,25 @@ public class Usage extends RuntimeException {
2323
* Creates a new Usage exception.
2424
*
2525
* @param message Message.
26-
* @param id Link to detailed section.
26+
* @param id Link to a detailed section.
2727
*/
2828
public Usage(@NonNull String message, @NonNull String id) {
29-
super(
29+
this(
3030
(message
3131
+ "\nFor more details, please visit: "
3232
+ System.getProperty("jooby.host", "https://jooby.io")
3333
+ "/usage#"
3434
+ id));
3535
}
3636

37+
protected Usage(@NonNull String message) {
38+
super(message);
39+
}
40+
41+
public static @NonNull Usage noSession() {
42+
return new Usage("No session available. See https://jooby.io/#session-in-memory-session");
43+
}
44+
3745
/**
3846
* Creates a mvc route missing exception.
3947
*

0 commit comments

Comments
 (0)