Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSRF (Cross Site Request Forgery) Protection #1826

Merged
merged 108 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
3cf842a
add security-csrf module
sdelamo Oct 17, 2024
656913b
build: apply security module
sdelamo Oct 17, 2024
e42005a
add logback.xml to src/test/resources
sdelamo Oct 17, 2024
865b2c6
csrf configuration
sdelamo Oct 17, 2024
37cf4f8
CSRF Filter configuration
sdelamo Oct 17, 2024
6f0878e
add test
sdelamo Oct 17, 2024
97e3d6e
Http Header Csrf Token Resolver
sdelamo Oct 17, 2024
dece74b
CsrfTokenFilter
sdelamo Oct 17, 2024
91a81df
add disabled test
sdelamo Oct 17, 2024
6fbc3a5
start csrf token generation implementation
sdelamo Oct 17, 2024
bb94d27
add csrf module in testImplementation
sdelamo Oct 18, 2024
b140a3c
add getHttpSessionName in CSRF Configuration
sdelamo Oct 18, 2024
f994c7e
integration of csrf in session
sdelamo Oct 18, 2024
a448b19
csrf session test
sdelamo Oct 18, 2024
7ea61ff
CRSF more docs
sdelamo Oct 18, 2024
06b3ec8
double cookie pattern
sdelamo Oct 21, 2024
bb1131d
more implementation
sdelamo Oct 21, 2024
bcd2216
more docs
sdelamo Oct 21, 2024
d3b8aeb
fix checkstyle
sdelamo Oct 21, 2024
09bab4e
more docs
sdelamo Oct 21, 2024
9615cb9
fix test
sdelamo Oct 21, 2024
8f6cf8e
extract to a variable
sdelamo Oct 21, 2024
ad75096
add missing @Override
sdelamo Oct 21, 2024
5b50cfd
make field static
sdelamo Oct 21, 2024
adf3df7
provide the parametrized type for this generic
sdelamo Oct 21, 2024
75599a3
File does not end with a newline.
sdelamo Oct 21, 2024
a396d32
better implementation
sdelamo Oct 22, 2024
328b6ab
small improvements
sdelamo Oct 22, 2024
d6d723f
change test
sdelamo Oct 22, 2024
bd59e29
test
sdelamo Oct 22, 2024
8b646be
better test
sdelamo Oct 22, 2024
ebb0c25
add nullability annotation
sdelamo Oct 22, 2024
288b81e
remove extra space
sdelamo Oct 22, 2024
0433459
Add SessionPopulator api
sdelamo Oct 22, 2024
38f66ab
remove unused imports
sdelamo Oct 22, 2024
50e9c8e
remove extra dependencies
sdelamo Oct 22, 2024
3270995
remove logback config
sdelamo Oct 22, 2024
6e44aaa
add LoginCookieProvider api
sdelamo Oct 22, 2024
0e6bd44
better javadoc
sdelamo Oct 22, 2024
4280ae9
remove dependency
sdelamo Oct 22, 2024
0deafa6
remove method
sdelamo Oct 22, 2024
9b15e12
add @Configuration
sdelamo Oct 22, 2024
63a6c6b
Use HttpServerFilter
sdelamo Oct 22, 2024
66e8873
fix
yawkat Oct 22, 2024
e22554d
add @Requires
sdelamo Oct 23, 2024
00df7d6
ServerFilter/RequestFilter not HttpServerFilter
sdelamo Oct 23, 2024
5099873
simplify with .flatMap( …strream())
sdelamo Oct 23, 2024
77a1579
simplify with .flatMap( …strream())
sdelamo Oct 23, 2024
1effdbf
remove suppliers
sdelamo Oct 23, 2024
0fa4391
make it final
sdelamo Oct 23, 2024
950ddff
Update src/main/docs/guide/csrf/csrfMitigations/doubleSubmitCookiePat…
sdelamo Oct 24, 2024
629b8fe
remove start imports
sdelamo Oct 24, 2024
4506c47
Use Mono in private methods
sdelamo Oct 24, 2024
7d306be
javadoc: add missing @param
sdelamo Oct 24, 2024
b18f32f
remove NOTE about netty compatibility
sdelamo Oct 24, 2024
1f92c7c
fix links to CSRF APIs
sdelamo Oct 24, 2024
36f45cd
extract a package private method
sdelamo Oct 24, 2024
b233755
ensure separator is not present as substring
sdelamo Oct 24, 2024
07a755b
extract CsrfHmacTokenGenerator API
sdelamo Oct 24, 2024
54228f6
base64 encoded sessionid
sdelamo Oct 24, 2024
d8d5020
add length
sdelamo Oct 24, 2024
0b091ec
Update security-csrf/src/main/java/io/micronaut/security/csrf/validat…
sdelamo Oct 24, 2024
c12365c
Update src/main/docs/guide/toc.yml
sdelamo Oct 24, 2024
3db52fa
Update src/main/docs/guide/toc.yml
sdelamo Oct 24, 2024
9492086
Update security/src/main/java/io/micronaut/security/utils/HMacUtils.java
sdelamo Oct 24, 2024
0c38e0b
add import
sdelamo Oct 24, 2024
02b0589
add missing imports
sdelamo Oct 24, 2024
31fed71
increase coverage
sdelamo Oct 25, 2024
73266ba
core 4.7.1
sdelamo Oct 25, 2024
94beaf6
use FilterBodyParser API
sdelamo Oct 25, 2024
7f70803
Use CompletableFuture instead of Publisher
sdelamo Oct 25, 2024
18b8afe
javadoc: add missing @param
sdelamo Oct 25, 2024
13c9cfe
extra check of uri path with regex
sdelamo Oct 25, 2024
b6031ed
increase coverage
sdelamo Oct 25, 2024
dee17cf
extract request.getHeaders() into a local variable to avoid repeated …
sdelamo Oct 25, 2024
3730f59
split the method calls onto multiple lines to improve readability
sdelamo Oct 25, 2024
9634741
stored in a field toLowerCase() be to avoid repeated calls
sdelamo Oct 25, 2024
0e26b76
size the array according to the passed arguments
sdelamo Oct 25, 2024
76d404e
add nullability annotations to return type and arguments
sdelamo Oct 25, 2024
95bc79b
checkstyle: comman is not followed by a white space
sdelamo Oct 25, 2024
fdd4e43
add nullability annotation to the argument
sdelamo Oct 25, 2024
b83e55b
add nullability annotation to the second argument
sdelamo Oct 25, 2024
3dd3357
extra mono optional empty to constant
sdelamo Oct 25, 2024
6c7bd99
add javadoc
sdelamo Oct 25, 2024
a68d9c1
Use HttpResponse instead of MutableHttpResponse
sdelamo Oct 25, 2024
4cf98a2
wrap these debug statements in if statements
sdelamo Oct 25, 2024
533ab54
add nullability annotation here
sdelamo Oct 25, 2024
1d756e0
add nullability annotation to the argument
sdelamo Oct 25, 2024
8b81613
extract this property into a constant
sdelamo Oct 25, 2024
828b96b
make FieldCsrfTokenResolver
sdelamo Oct 25, 2024
f8161bd
remove jacoco config
sdelamo Oct 25, 2024
18ff890
Use static access with "io.micronaut.security.csrf.CsrfConfiguration"…
sdelamo Oct 25, 2024
4f2c118
add UnsupportedOperationException
sdelamo Oct 25, 2024
3f89d92
sonnar issues
sdelamo Oct 25, 2024
aba5267
don’t use reactor
sdelamo Oct 28, 2024
bb937ad
Use CollectionUtils::concat
sdelamo Oct 28, 2024
2fb98bf
FutureCsrfTokenResolverAdapter pck private & final
sdelamo Oct 28, 2024
c70e6e6
CsrfSessionPopulator pck private & final
sdelamo Oct 28, 2024
b6cd612
HttpSessionSessionIdResolver pck private & final
sdelamo Oct 28, 2024
816466c
RepositoryCsrfTokenValidator pck private & final
sdelamo Oct 28, 2024
37517d3
JsonWebTokenIdSessionIdResolver pkg private and final
sdelamo Oct 28, 2024
a89d437
DefaultSessionPopulator package private and final
sdelamo Oct 28, 2024
f2e2025
CompositeSessionIdResolver final and @Internal
sdelamo Oct 28, 2024
37a8bc0
CompositeCsrfTokenRepository final and internal
sdelamo Oct 28, 2024
7d4ecda
make possible to disable sessionidresolvers
sdelamo Oct 28, 2024
41cffe0
test HttpSessionSessionIdResolver
sdelamo Oct 28, 2024
3c66326
annotate it with @Internal
sdelamo Oct 28, 2024
aafcefb
ann interfaces with composite pattern with @Indexed
sdelamo Oct 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions security-csrf/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
plugins {
id("io.micronaut.build.internal.security-module")
}

dependencies {
api(projects.micronautSecurity)
compileOnly(mn.micronaut.http.server)
compileOnly(projects.micronautSecuritySession)
implementation(mnReactor.micronaut.reactor)
testAnnotationProcessor(mn.micronaut.inject.java)
testImplementation(mnTest.micronaut.test.junit5)
testRuntimeOnly(libs.junit.jupiter.engine)
testRuntimeOnly(mnLogging.logback.classic)
testImplementation(mn.micronaut.http.server.netty)
testImplementation(mn.micronaut.http.client)
testAnnotationProcessor(mnSerde.micronaut.serde.processor)
testImplementation(mnSerde.micronaut.serde.jackson)
testImplementation(projects.testSuiteUtilsSecurity)
testImplementation(projects.micronautSecurityJwt)
testImplementation(projects.micronautSecuritySession)
}

tasks.withType<Test> {
useJUnitPlatform()
}

micronautBuild {
binaryCompatibility.enabled = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.security.csrf;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.util.Toggleable;
import io.micronaut.http.cookie.CookieConfiguration;

/**
* CSRF Configuration.
* @author Sergio del Amo
* @since 4.11.0
*/
public interface CsrfConfiguration extends CookieConfiguration, Toggleable {
/**
*
* @return Random value's size in bytes. The random value used is used to build a CSRF Token.
*/
int getRandomValueSize();

/**
*
* @return The Secret Key that is used to calculate an HMAC as part of a CSRF token generation.
*/
String getSecretKey();
sdelamo marked this conversation as resolved.
Show resolved Hide resolved

/**
* HTTP Header name to look for the CSRF token. It is recommended to use a custom request header. By using a custom HTTP Header name, it will not be possible to send them cross-origin without a permissive CORS implementation.
* @return HTTP Header name to look for the CSRF token.
*/
@NonNull
String getHeaderName();

/**
*
* @return Key to look for the CSRF token in an HTTP Session.
*/
@NonNull
String getHttpSessionName();

/**
*
* @return Field name in a form url encoded submission to look for the CSRF token.
*/
@NonNull
String getFieldName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.security.csrf;

import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.cookie.SameSite;
import io.micronaut.security.config.SecurityConfigurationProperties;
import io.micronaut.security.token.generator.AccessTokenConfigurationProperties;

import java.time.Duration;
import java.time.temporal.TemporalAmount;
import java.util.Optional;

@Internal
@ConfigurationProperties(CsrfConfigurationProperties.PREFIX)
final class CsrfConfigurationProperties implements CsrfConfiguration {
public static final String PREFIX = SecurityConfigurationProperties.PREFIX + ".csrf";

/**
* The default HTTP Header name.
*/
@SuppressWarnings("WeakerAccess")
public static final String DEFAULT_HTTP_HEADER_NAME = "X-CSRF-TOKEN";

/**
* The default fieldName.
*/
@SuppressWarnings("WeakerAccess")
public static final String DEFAULT_FIELD_NAME = "csrfToken";

/**
* The default cookie name..
* @see <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#using-cookies-with-host-prefixes-to-identify-origins">Using Cookies with Host Prefixes to Identify Origins</a>
*/
@SuppressWarnings("WeakerAccess")
public static final String DEFAULT_COOKIE_NAME = "__Host-csrfToken";

/**
* The default HTTP Session name.
*/
@SuppressWarnings("WeakerAccess")
public static final String DEFAULT_HTTP_SESSION_NAME = "csrfToken";

/**
* The default Same Site Configuration.
*/
@SuppressWarnings("WeakerAccess")
public static final SameSite DEFAULT_SAME_SITE = SameSite.Strict;

public static final int DEFAULT_RANDOM_VALUE_SIZE = 16;

public static final boolean DEFAULT_ENABLED = true;

private static final boolean DEFAULT_HTTPONLY = true;
private static final String DEFAULT_COOKIEPATH = "/";
private static final Boolean DEFAULT_SECURE = true;
private static final Duration DEFAULT_MAX_AGE = Duration.ofSeconds(AccessTokenConfigurationProperties.DEFAULT_EXPIRATION);

private boolean enabled = DEFAULT_ENABLED;
private String headerName = DEFAULT_HTTP_HEADER_NAME;
private String fieldName = DEFAULT_FIELD_NAME;
private int randomValueSize = DEFAULT_RANDOM_VALUE_SIZE;
private String httpSessionName = DEFAULT_HTTP_SESSION_NAME;
private String cookieDomain;
private Boolean cookieSecure = DEFAULT_SECURE;
private String cookiePath = DEFAULT_COOKIEPATH;
private Boolean cookieHttpOnly = DEFAULT_HTTPONLY;
private Duration cookieMaxAge = DEFAULT_MAX_AGE;
private String cookieName = DEFAULT_COOKIE_NAME;
private SameSite sameSite = DEFAULT_SAME_SITE;
private String signatureKey;

@Override
public String getSecretKey() {
return signatureKey;
}

/**
* The Secret Key that is used to calculate an HMAC as part of a CSRF token generation. Default Value `null`.
* @param signatureKey The Secret Key that is used to calculate an HMAC as part of a CSRF token generation.
*/
public void setSignatureKey(String signatureKey) {
this.signatureKey = signatureKey;
}

@Override
public String getHttpSessionName() {
return httpSessionName;
}

/**
* Key to look for the CSRF token in an HTTP Session. Default Value: {@value #DEFAULT_HTTP_SESSION_NAME}.
* @param httpSessionName Key to look for the CSRF token in an HTTP Session.
*/
public void setHttpSessionName(String httpSessionName) {
this.httpSessionName = httpSessionName;
}

@Override
public int getRandomValueSize() {
return randomValueSize;
}

/**
* Random value's size in bytes. The random value used is used to build a CSRF Token. Default Value: {@value #DEFAULT_RANDOM_VALUE_SIZE}.
* @param randomValueSize Random CSRF Token size in bytes.
*/
public void setRandomValueSize(int randomValueSize) {
this.randomValueSize = randomValueSize;
}

@Override
@NonNull
public String getHeaderName() {
return headerName;
}

/**
* HTTP Header name to look for the CSRF token. Default Value: {@value #DEFAULT_HTTP_HEADER_NAME}.
* @param headerName HTTP Header name to look for the CSRF token.
*/
public void setHeaderName(@NonNull String headerName) {
this.headerName = headerName;
}

@Override
public String getFieldName() {
return fieldName;
}

/**
* Field name in a form url encoded submission to look for the CSRF token. Default Value: {@value #DEFAULT_FIELD_NAME}.
* @param fieldName Field name in a form url encoded submission to look for the CSRF token.
*/
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}

@Override
public boolean isEnabled() {
return enabled;
}

/**
* Whether the CSRF integration is enabled. Default value {@value #DEFAULT_ENABLED}.
* @param enabled Whether the CSRF integration is enabled
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

@Override
public Optional<String> getCookieDomain() {
return Optional.ofNullable(cookieDomain);
}

/**
* Sets the domain name of this Cookie. Default value (null).
*
* @param cookieDomain the domain name of this Cookie
*/
public void setCookieDomain(@Nullable String cookieDomain) {
this.cookieDomain = cookieDomain;
}

@Override
public Optional<Boolean> isCookieSecure() {
return Optional.ofNullable(cookieSecure);
}

/**
* Sets whether the cookie is secured. Defaults to the secure status of the request.
*
* @param cookieSecure True if the cookie is secure
*/
public void setCookieSecure(Boolean cookieSecure) {
this.cookieSecure = cookieSecure;
}

@NonNull
@Override
public String getCookieName() {
return this.cookieName;
}

/**
* Cookie Name.
*
* @param cookieName Cookie name
*/
public void setCookieName(@NonNull String cookieName) {
this.cookieName = cookieName;
}

@Override
public Optional<String> getCookiePath() {
return Optional.ofNullable(cookiePath);
}

/**
* Sets the path of the cookie. Default value ({@value #DEFAULT_COOKIEPATH}).
*
* @param cookiePath The path of the cookie.
*/
public void setCookiePath(@Nullable String cookiePath) {
this.cookiePath = cookiePath;
}

@Override
public Optional<Boolean> isCookieHttpOnly() {
return Optional.ofNullable(cookieHttpOnly);
}

/**
* Whether the Cookie can only be accessed via HTTP. Default value ({@value #DEFAULT_HTTPONLY}).
*
* @param cookieHttpOnly Whether the Cookie can only be accessed via HTTP
*/
public void setCookieHttpOnly(Boolean cookieHttpOnly) {
this.cookieHttpOnly = cookieHttpOnly;
}

@Override
public Optional<TemporalAmount> getCookieMaxAge() {
return Optional.ofNullable(cookieMaxAge);
}

/**
* Sets the maximum age of the cookie. Default value ({@value AccessTokenConfigurationProperties#DEFAULT_EXPIRATION} seconds).
*
* @param cookieMaxAge The maximum age of the cookie
*/
public void setCookieMaxAge(Duration cookieMaxAge) {
this.cookieMaxAge = cookieMaxAge;
}

@Override
public Optional<SameSite> getCookieSameSite() {
return Optional.of(this.sameSite);
}

/**
* Cookie Same Site Configuration. It defaults to Strict.
* @param sameSite Same Site Configuration
*/
public void setCookieSameSite(SameSite sameSite) {
this.sameSite = sameSite;
}
}
Loading
Loading