Skip to content

Commit

Permalink
Polish Remember-Me SHA-256 Algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
marcusdacoregio committed Jan 4, 2022
1 parent 2d64401 commit 20e8724
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 137 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -92,7 +92,7 @@ public final class RememberMeConfigurer<H extends HttpSecurityBuilder<H>>

private String key;

private RememberMeHashingAlgorithm hashingAlgorithm = RememberMeHashingAlgorithm.UNSET;
private RememberMeHashingAlgorithm hashingAlgorithm = RememberMeHashingAlgorithm.MD5;

private RememberMeServices rememberMeServices;

Expand Down Expand Up @@ -199,9 +199,10 @@ public RememberMeConfigurer<H> key(String key) {
* {@link #rememberMeServices(RememberMeServices)} are used.
* @param hashingAlgorithm the algorithm used when creating new cookies
* @return the {@link RememberMeConfigurer} for further customization
* @since 5.5
* @since 5.7
*/
public RememberMeConfigurer<H> hashingAlgorithm(RememberMeHashingAlgorithm hashingAlgorithm) {
Assert.notNull(hashingAlgorithm, "hashingAlgorithm cannot be null");
this.hashingAlgorithm = hashingAlgorithm;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -757,10 +757,6 @@ remember-me.attlist &=
## The period (in seconds) for which the remember-me cookie should be valid.
attribute token-validity-seconds {xsd:string}?

remember-me.attlist &=
## The algorithm of cookie which store the token for remember-me authentication.
attribute hashing-algorithm {"UNSET" | "MD5" | "SHA256"}?

remember-me.attlist &=
## Reference to an AuthenticationSuccessHandler bean which should be used to handle a successful remember-me authentication.
attribute authentication-success-handler-ref {xsd:token}?
Expand All @@ -781,7 +777,6 @@ remember-me-data-source-ref =
## DataSource bean for the database that contains the token repository schema.
data-source-ref


anonymous =
## Adds support for automatically granting all anonymous web requests a particular principal identity and a corresponding granted authority.
element anonymous {anonymous.attlist}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2262,19 +2262,6 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="hashing-algorithm">
<xs:annotation>
<xs:documentation>The algorithm of cookie which store the token for remember-me authentication.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="UNSET"/>
<xs:enumeration value="MD5"/>
<xs:enumeration value="SHA256"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="authentication-success-handler-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to an AuthenticationSuccessHandler bean which should be used to handle a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,9 @@ remember-me.attlist &=
remember-me.attlist &=
## The name of cookie which store the token for remember-me authentication. Defaults to 'remember-me'.
attribute remember-me-cookie {xsd:token}?
remember-me.attlist &=
## The algorithm of cookie which store the token for remember-me authentication.
attribute hashing-algorithm {"MD5" | "SHA256"}?

token-repository-ref =
## Reference to a PersistentTokenRepository bean for use with the persistent token remember-me implementation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2353,6 +2353,18 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="hashing-algorithm">
<xs:annotation>
<xs:documentation>The algorithm of cookie which store the token for remember-me authentication.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="MD5"/>
<xs:enumeration value="SHA256"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="token-repository-ref">
<xs:attribute name="token-repository-ref" use="required" type="xs:token">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@

package org.springframework.security.config.annotation.web.configurers;

import java.util.Base64;
import java.util.Collections;

import javax.servlet.http.Cookie;
Expand All @@ -30,6 +31,7 @@
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
Expand All @@ -42,6 +44,7 @@
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.RememberMeHashingAlgorithm;
Expand Down Expand Up @@ -101,7 +104,8 @@ public void postWhenNoUserDetailsServiceThenException() {
@Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnRememberMeAuthenticationFilter() {
this.spring.register(ObjectPostProcessorConfig.class).autowire();
verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(RememberMeAuthenticationFilter.class));
verify(this.spring.getContext().getBean(ObjectPostProcessor.class))
.postProcess(any(RememberMeAuthenticationFilter.class));
}

@Test
Expand Down Expand Up @@ -132,7 +136,7 @@ public void loginWhenRememberMeTrueThenRespondsWithRememberMeCookie() throws Exc
}

@Test
public void loginWithSha256HashingAlgorithmThenRespondsWithSha256RememberMeCookie() throws Exception {
public void loginWhenSha256HashingAlgorithmThenRespondsWithSha256RememberMeCookie() throws Exception {
this.spring.register(RememberMeWithSha256Config.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder request = post("/login")
Expand All @@ -144,8 +148,26 @@ public void loginWithSha256HashingAlgorithmThenRespondsWithSha256RememberMeCooki
MvcResult result = this.mvc.perform(request).andReturn();
Cookie rememberMe = result.getResponse().getCookie("remember-me");
assertThat(rememberMe).isNotNull();
assertThat(new String(Base64.decodeBase64(rememberMe.getValue())))
.contains(RememberMeHashingAlgorithm.SHA256.getIdentifier());
assertThat(new String(Base64.getDecoder().decode(rememberMe.getValue())))
.contains(RememberMeHashingAlgorithm.SHA256.name());
}

@Test
public void loginWhenSha256HashingAlgorithmConfiguredInLambdaThenRespondsWithSha256RememberMeCookie()
throws Exception {
this.spring.register(RememberMeWithSha256InLambdaConfig.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder request = post("/login")
.with(csrf())
.param("username", "user")
.param("password", "password")
.param("remember-me", "true");
// @formatter:on
MvcResult result = this.mvc.perform(request).andReturn();
Cookie rememberMe = result.getResponse().getCookie("remember-me");
assertThat(rememberMe).isNotNull();
assertThat(new String(Base64.getDecoder().decode(rememberMe.getValue())))
.contains(RememberMeHashingAlgorithm.SHA256.name());
}

@Test
Expand Down Expand Up @@ -319,14 +341,14 @@ protected void configure(AuthenticationManagerBuilder auth) {
@EnableWebSecurity
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {

static ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);

@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.rememberMe()
.userDetailsService(new AuthenticationManagerBuilder(objectPostProcessor).getDefaultUserDetailsService());
.userDetailsService(new AuthenticationManagerBuilder(this.objectPostProcessor).getDefaultUserDetailsService());
// @formatter:on
}

Expand All @@ -339,8 +361,8 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}

@Bean
static ObjectPostProcessor<Object> objectPostProcessor() {
return objectPostProcessor;
ObjectPostProcessor<Object> objectPostProcessor() {
return this.objectPostProcessor;
}

}
Expand Down Expand Up @@ -416,10 +438,10 @@ void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
}

@EnableWebSecurity
static class RememberMeWithSha256Config extends WebSecurityConfigurerAdapter {
static class RememberMeWithSha256Config {

@Override
protected void configure(HttpSecurity http) throws Exception {
@Bean
SecurityFilterChain app(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
// @formatter:off
http
.authorizeRequests()
Expand All @@ -428,17 +450,41 @@ protected void configure(HttpSecurity http) throws Exception {
.formLogin()
.and()
.rememberMe()
.hashingAlgorithm(RememberMeHashingAlgorithm.SHA256);
.hashingAlgorithm(RememberMeHashingAlgorithm.SHA256)
.userDetailsService(userDetailsService);
// @formatter:on
return http.build();
}

@Autowired
void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
}

}

@EnableWebSecurity
static class RememberMeWithSha256InLambdaConfig {

@Bean
SecurityFilterChain app(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
// @formatter:off
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user());
http
.authorizeRequests((requests) -> requests
.anyRequest().hasRole("USER")
)
.formLogin(Customizer.withDefaults())
.rememberMe((rememberMe) -> rememberMe
.hashingAlgorithm(RememberMeHashingAlgorithm.SHA256)
.userDetailsService(userDetailsService)
);
// @formatter:on
return http.build();
}

@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@

package org.springframework.security.config.http;

import java.util.Base64;
import java.util.Collections;

import javax.servlet.http.Cookie;
Expand Down Expand Up @@ -59,15 +60,15 @@
* @author Rob Winch
* @author Oliver Becker
*/
@ExtendWith(SpringTestContextExtension.class)
public class RememberMeConfigTests {

private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/RememberMeConfigTests";

@Autowired
MockMvc mvc;

@Rule
public final SpringTestRule spring = new SpringTestRule();
public final SpringTestContext spring = new SpringTestContext(this);

@Test
public void requestWithRememberMeWhenUsingCustomTokenRepositoryThenAutomaticallyReauthenticates() throws Exception {
Expand Down Expand Up @@ -165,8 +166,8 @@ public void configureWithHashingAlgorithm() throws Exception {
MvcResult result = rememberAuthentication("user", "password").andReturn();
Cookie cookie = rememberMeCookie(result);
assertThat(cookie).isNotNull();
assertThat(new String(Base64.decodeBase64(cookie.getValue())))
.contains(RememberMeHashingAlgorithm.SHA256.getIdentifier());
assertThat(new String(Base64.getDecoder().decode(cookie.getValue())))
.contains(RememberMeHashingAlgorithm.SHA256.name());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ username: As identifiable to the UserDetailsService
password: That matches the one in the retrieved UserDetails
expirationTime: The date and time when the remember-me token expires, expressed in milliseconds
key: A private key to prevent modification of the remember-me token
algorithmName The name of the algorithm used to create the hash
----

As such the remember-me token is valid only for the period specified, and provided that the username, password and key does not change.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,37 +16,21 @@

package org.springframework.security.web.authentication.rememberme;

import java.util.Arrays;
import java.util.Optional;

/**
* Hashing algorithms supported by {@link TokenBasedRememberMeServices}
*
* @since 5.5
* @since 5.7
*/
public enum RememberMeHashingAlgorithm {

UNSET("", ""), MD5("MD5", "MD5"), SHA256("SHA256", "SHA-256");

private final String identifier;
MD5("MD5"), SHA256("SHA-256");

private final String digestAlgorithm;

RememberMeHashingAlgorithm(String identifier, String digestAlgorithm) {
this.identifier = identifier;
RememberMeHashingAlgorithm(String digestAlgorithm) {
this.digestAlgorithm = digestAlgorithm;
}

/**
* The identifier to use in cookies created by {@link TokenBasedRememberMeServices} to
* signify this algorithm is being used.
*
* If empty, then no algorithm will be specified in the resulting cookie.
*/
public String getIdentifier() {
return this.identifier;
}

/**
* The name of the algorithm to use
*
Expand All @@ -57,8 +41,13 @@ public String getDigestAlgorithm() {
return this.digestAlgorithm;
}

static Optional<RememberMeHashingAlgorithm> findByIdentifier(String identifier) {
return Arrays.stream(values()).filter((algorithm) -> algorithm.getIdentifier().equals(identifier)).findAny();
public static RememberMeHashingAlgorithm from(String name) {
for (RememberMeHashingAlgorithm algorithm : values()) {
if (algorithm.name().equals(name)) {
return algorithm;
}
}
return null;
}

}
Loading

0 comments on commit 20e8724

Please sign in to comment.