Spring Security Integration

Spring 会话提供与 Spring Security 的集成。

Spring Session provides integration with Spring Security.

Spring Security Remember-me Support

Spring Session 提供与 @docs-url/spring-security/site/docs/{spring-security-core-version}/reference/html5/#servlet-rememberme[Spring Security 的 Remember-me 身份验证] 的集成。支持:

Spring Session provides integration with {docs-url}/spring-security/site/docs/{spring-security-core-version}/reference/html5/#servlet-rememberme[Spring Security’s Remember-me Authentication]. The support:

  • 更改会话过期时间长度。

  • Changes the session expiration length

  • 确保会话 cookie 在 `Integer.MAX_VALUE`到期。cookie 过期时间设置为最大可能值,因为 cookie 仅在创建会话时设置。如果将其设置为与会话过期时间相同的值,则用户使用会话时将续订会话,但不会更新 cookie 过期时间(导致过期时间固定)。

  • Ensures that the session cookie expires at Integer.MAX_VALUE. The cookie expiration is set to the largest possible value, because the cookie is set only when the session is created. If it were set to the same value as the session expiration, the session would get renewed when the user used it but the cookie expiration would not be updated (causing the expiration to be fixed).

要通过 Java 配置将 Spring 会话与 Spring Security 配置在一起,可以使用以下列表作为指南:

To configure Spring Session with Spring Security in Java Configuration, you can use the following listing as a guide:

/*
 * Copyright 2014-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.
 * 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 docs.security;

import java.util.concurrent.ConcurrentHashMap;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;

/**
 * @author rwinch
 */
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableSpringHttpSession
public class RememberMeSecurityConfiguration {

	// tag::http-rememberme[]
	@Bean
	SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ... additional configuration ...
			.rememberMe((rememberMe) -> rememberMe
				.rememberMeServices(rememberMeServices())
			);
		// end::http-rememberme[]

		return http
			.formLogin(Customizer.withDefaults())
			.authorizeHttpRequests((authorize) -> authorize
				.anyRequest().authenticated()
			).build();
	}

	// tag::rememberme-bean[]
	@Bean
	public SpringSessionRememberMeServices rememberMeServices() {
		SpringSessionRememberMeServices rememberMeServices =
				new SpringSessionRememberMeServices();
		// optionally customize
		rememberMeServices.setAlwaysRemember(true);
		return rememberMeServices;
	}
	// end::rememberme-bean[]

	@Bean
	public InMemoryUserDetailsManager userDetailsService() {
		return new InMemoryUserDetailsManager(
				User.withUsername("user").password("{noop}password").roles("USER").build());
	}

	@Bean
	MapSessionRepository sessionRepository() {
		return new MapSessionRepository(new ConcurrentHashMap<>());
	}

}
// end::class[]
	}

/*
 * Copyright 2014-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.
 * 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 docs.security;

import java.util.concurrent.ConcurrentHashMap;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;

/**
 * @author rwinch
 */
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableSpringHttpSession
public class RememberMeSecurityConfiguration {

	// tag::http-rememberme[]
	@Bean
	SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			// ... additional configuration ...
			.rememberMe((rememberMe) -> rememberMe
				.rememberMeServices(rememberMeServices())
			);
		// end::http-rememberme[]

		return http
			.formLogin(Customizer.withDefaults())
			.authorizeHttpRequests((authorize) -> authorize
				.anyRequest().authenticated()
			).build();
	}

	// tag::rememberme-bean[]
	@Bean
	public SpringSessionRememberMeServices rememberMeServices() {
		SpringSessionRememberMeServices rememberMeServices =
				new SpringSessionRememberMeServices();
		// optionally customize
		rememberMeServices.setAlwaysRemember(true);
		return rememberMeServices;
	}
	// end::rememberme-bean[]

	@Bean
	public InMemoryUserDetailsManager userDetailsService() {
		return new InMemoryUserDetailsManager(
				User.withUsername("user").password("{noop}password").roles("USER").build());
	}

	@Bean
	MapSessionRepository sessionRepository() {
		return new MapSessionRepository(new ConcurrentHashMap<>());
	}

}
// end::class[]

基于 XML 的配置看起来如下所示:

An XML-based configuration would look something like the following:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:security="http://www.springframework.org/schema/security"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- tag::config[] -->
	<security:http>
		<!-- ... -->
		<security:form-login />
		<security:remember-me services-ref="rememberMeServices"/>
		<security:intercept-url pattern="/**" access="permitAll()"/>
	</security:http>

	<bean id="rememberMeServices"
		class="org.springframework.session.security.web.authentication.SpringSessionRememberMeServices"
		p:alwaysRemember="true"/>
	<!-- end::config[] -->


	<security:user-service>
		<security:user name="user" password="{noop}password" authorities="ROLE_USER"/>
	</security:user-service>

	<bean class="org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration"/>
	<bean id="springSessionRepository" class="org.springframework.session.MapSessionRepository">
		<constructor-arg>
			<bean class="java.util.concurrent.ConcurrentHashMap"/>
		</constructor-arg>
	</bean>
</beans>

Spring Security Concurrent Session Control

Spring 会话提供与 Spring Security 的集成,以支持其并发会话控制。这允许限制单个用户可以同时拥有的活动会话数,但与默认 Spring Security 支持不同,这在集群环境中也能起作用。这是通过提供Spring Security 的 SessionRegistry 接口的自定义实现来完成的。

Spring Session provides integration with Spring Security to support its concurrent session control. This allows limiting the number of active sessions that a single user can have concurrently, but, unlike the default Spring Security support, this also works in a clustered environment. This is done by providing a custom implementation of Spring Security’s SessionRegistry interface.

使用 Spring Security 的 Java 配置 DSL 时,您可以通过 SessionManagementConfigurer 配置自定义 SessionRegistry,如下面列表所示:

When using Spring Security’s Java config DSL, you can configure the custom SessionRegistry through the SessionManagementConfigurer, as the following listing shows:

/*
 * Copyright 2014-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.
 * 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 docs.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;

/**
 * @author Joris Kuipers
 */
// tag::class[]
@Configuration
public class SecurityConfiguration<S extends Session> {

	@Autowired
	private FindByIndexNameSessionRepository<S> sessionRepository;

	@Bean
	SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		return http
			// other config goes here...
			.sessionManagement((sessionManagement) -> sessionManagement
				.maximumSessions(2)
				.sessionRegistry(sessionRegistry())
			)
			.build();
	}

	@Bean
	public SpringSessionBackedSessionRegistry<S> sessionRegistry() {
		return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
	}

}
// end::class[]

这假设您还配置了 Spring 会话以提供 FindByIndexNameSessionRepository ,该会话返回 Session 实例。

This assumes that you have also configured Spring Session to provide a FindByIndexNameSessionRepository that returns Session instances.

使用 XML 配置时,它看起来如下所示:

When using XML configuration, it would look something like the following listing:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:security="http://www.springframework.org/schema/security"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
						   http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">

	<!-- tag::config[] -->
	<security:http>
		<!-- other config goes here... -->
		<security:session-management>
			<security:concurrency-control max-sessions="2" session-registry-ref="sessionRegistry"/>
		</security:session-management>
	</security:http>

	<bean id="sessionRegistry"
		  class="org.springframework.session.security.SpringSessionBackedSessionRegistry">
		<constructor-arg ref="sessionRepository"/>
	</bean>
	<!-- end::config[] -->

</beans>

这假设您的 Spring 会话 SessionRegistry bean 称为 sessionRegistry,这是所有 SpringHttpSessionConfiguration 子类的名称。

This assumes that your Spring Session SessionRegistry bean is called sessionRegistry, which is the name used by all SpringHttpSessionConfiguration subclasses.

Limitations

Spring 会话对 Spring Security SessionRegistry 接口的实现不支持 getAllPrincipals 方法,因为无法使用 Spring 会话检索此信息。Spring Security 永远不会调用此方法,因此只会影响访问 SessionRegistry 本身的应用程序。

Spring Session’s implementation of Spring Security’s SessionRegistry interface does not support the getAllPrincipals method, as this information cannot be retrieved by using Spring Session. This method is never called by Spring Security, so this affects only applications that access the SessionRegistry themselves.