Introduce IndexResolver
This commit introduces IndexResolver as a strategy interface for resolving index values in FindByIndexNameSessionRepository implementations. Resolves: #557
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 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 org.springframework.session;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link IndexResolver} that resolves indexes using multiple @{link IndexResolver}
|
||||||
|
* delegates.
|
||||||
|
*
|
||||||
|
* @param <S> the type of Session being handled
|
||||||
|
* @author Vedran Pavic
|
||||||
|
* @since 2.2.0
|
||||||
|
*/
|
||||||
|
public class DelegatingIndexResolver<S extends Session> implements IndexResolver<S> {
|
||||||
|
|
||||||
|
private final List<IndexResolver<S>> delegates;
|
||||||
|
|
||||||
|
public DelegatingIndexResolver(List<IndexResolver<S>> delegates) {
|
||||||
|
this.delegates = Collections.unmodifiableList(delegates);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public DelegatingIndexResolver(IndexResolver<S>... delegates) {
|
||||||
|
this(Arrays.asList(delegates));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> resolveIndexesFor(S session) {
|
||||||
|
Map<String, String> indexes = new HashMap<>();
|
||||||
|
for (IndexResolver<S> delegate : this.delegates) {
|
||||||
|
indexes.putAll(delegate.resolveIndexesFor(session));
|
||||||
|
}
|
||||||
|
return indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 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 org.springframework.session;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy interface for resolving the {@link Session}'s indexes.
|
||||||
|
*
|
||||||
|
* @param <S> the type of Session being handled
|
||||||
|
* @author Rob Winch
|
||||||
|
* @author Vedran Pavic
|
||||||
|
* @since 2.2.0
|
||||||
|
* @see FindByIndexNameSessionRepository
|
||||||
|
*/
|
||||||
|
public interface IndexResolver<S extends Session> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve indexes for the session.
|
||||||
|
* @param session the session
|
||||||
|
* @return a map of resolved indexes, never {@code null}
|
||||||
|
*/
|
||||||
|
Map<String, String> resolveIndexesFor(S session);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 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 org.springframework.session;
|
||||||
|
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link IndexResolver} to resolve the principal name from session attribute named
|
||||||
|
* {@link FindByIndexNameSessionRepository#PRINCIPAL_NAME_INDEX_NAME} or Spring Security
|
||||||
|
* context stored in the session under {@code SPRING_SECURITY_CONTEXT} attribute.
|
||||||
|
*
|
||||||
|
* @param <S> the type of Session being handled
|
||||||
|
* @author Vedran Pavic
|
||||||
|
* @since 2.2.0
|
||||||
|
*/
|
||||||
|
public class PrincipalNameIndexResolver<S extends Session> extends SingleIndexResolver<S> {
|
||||||
|
|
||||||
|
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||||
|
|
||||||
|
private static final SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
|
||||||
|
public PrincipalNameIndexResolver() {
|
||||||
|
super(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveIndexValueFor(S session) {
|
||||||
|
String principalName = session.getAttribute(getIndexName());
|
||||||
|
if (principalName != null) {
|
||||||
|
return principalName;
|
||||||
|
}
|
||||||
|
Object authentication = session.getAttribute(SPRING_SECURITY_CONTEXT);
|
||||||
|
if (authentication != null) {
|
||||||
|
Expression expression = parser.parseExpression("authentication?.name");
|
||||||
|
return expression.getValue(authentication, String.class);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 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 org.springframework.session;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for {@link IndexResolver}s that resolve a single index.
|
||||||
|
*
|
||||||
|
* @param <S> the type of Session being handled
|
||||||
|
* @author Rob Winch
|
||||||
|
* @author Vedran Pavic
|
||||||
|
* @since 2.2.0
|
||||||
|
*/
|
||||||
|
public abstract class SingleIndexResolver<S extends Session> implements IndexResolver<S> {
|
||||||
|
|
||||||
|
private final String indexName;
|
||||||
|
|
||||||
|
protected SingleIndexResolver(String indexName) {
|
||||||
|
Assert.notNull(indexName, "Index name must not be null");
|
||||||
|
this.indexName = indexName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getIndexName() {
|
||||||
|
return this.indexName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String resolveIndexValueFor(S session);
|
||||||
|
|
||||||
|
public final Map<String, String> resolveIndexesFor(S session) {
|
||||||
|
String indexValue = resolveIndexValueFor(session);
|
||||||
|
return (indexValue != null) ? Collections.singletonMap(this.indexName, indexValue) : Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 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 org.springframework.session;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link DelegatingIndexResolver}.
|
||||||
|
*
|
||||||
|
* @author Vedran Pavic
|
||||||
|
*/
|
||||||
|
class DelegatingIndexResolverTests {
|
||||||
|
|
||||||
|
private DelegatingIndexResolver<MapSession> indexResolver;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
this.indexResolver = new DelegatingIndexResolver<>(new TestIndexResolver("one"), new TestIndexResolver("two"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolve() {
|
||||||
|
MapSession session = new MapSession();
|
||||||
|
session.setAttribute("one", "first");
|
||||||
|
session.setAttribute("two", "second");
|
||||||
|
Map<String, String> indexes = this.indexResolver.resolveIndexesFor(session);
|
||||||
|
assertThat(indexes).hasSize(2);
|
||||||
|
assertThat(indexes.get("one")).isEqualTo("first");
|
||||||
|
assertThat(indexes.get("two")).isEqualTo("second");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestIndexResolver implements IndexResolver<MapSession> {
|
||||||
|
|
||||||
|
private final String supportedIndex;
|
||||||
|
|
||||||
|
TestIndexResolver(String supportedIndex) {
|
||||||
|
this.supportedIndex = supportedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> resolveIndexesFor(MapSession session) {
|
||||||
|
return Collections.singletonMap(this.supportedIndex, session.getAttribute(this.supportedIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014-2019 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 org.springframework.session;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextImpl;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PrincipalNameIndexResolver}.
|
||||||
|
*
|
||||||
|
* @author Vedran Pavic
|
||||||
|
*/
|
||||||
|
class PrincipalNameIndexResolverTests {
|
||||||
|
|
||||||
|
private static final String PRINCIPAL_NAME = "principalName";
|
||||||
|
|
||||||
|
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||||
|
|
||||||
|
private PrincipalNameIndexResolver<Session> indexResolver;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
this.indexResolver = new PrincipalNameIndexResolver<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveFromPrincipalName() {
|
||||||
|
MapSession session = new MapSession();
|
||||||
|
session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, PRINCIPAL_NAME);
|
||||||
|
assertThat(this.indexResolver.resolveIndexValueFor(session)).isEqualTo(PRINCIPAL_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolveFromSpringSecurityContext() {
|
||||||
|
Authentication authentication = new UsernamePasswordAuthenticationToken(PRINCIPAL_NAME, "notused",
|
||||||
|
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||||
|
SecurityContext context = new SecurityContextImpl();
|
||||||
|
context.setAuthentication(authentication);
|
||||||
|
MapSession session = new MapSession();
|
||||||
|
session.setAttribute(SPRING_SECURITY_CONTEXT, context);
|
||||||
|
assertThat(this.indexResolver.resolveIndexValueFor(session)).isEqualTo(PRINCIPAL_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -36,10 +36,11 @@ import org.springframework.data.redis.core.BoundHashOperations;
|
|||||||
import org.springframework.data.redis.core.RedisOperations;
|
import org.springframework.data.redis.core.RedisOperations;
|
||||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.session.DelegatingIndexResolver;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
|
import org.springframework.session.IndexResolver;
|
||||||
import org.springframework.session.MapSession;
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.PrincipalNameIndexResolver;
|
||||||
import org.springframework.session.Session;
|
import org.springframework.session.Session;
|
||||||
import org.springframework.session.events.SessionCreatedEvent;
|
import org.springframework.session.events.SessionCreatedEvent;
|
||||||
import org.springframework.session.events.SessionDeletedEvent;
|
import org.springframework.session.events.SessionDeletedEvent;
|
||||||
@@ -252,8 +253,6 @@ public class RedisOperationsSessionRepository
|
|||||||
|
|
||||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||||
|
|
||||||
static PrincipalNameResolver PRINCIPAL_NAME_RESOLVER = new PrincipalNameResolver();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default Redis database used by Spring Session.
|
* The default Redis database used by Spring Session.
|
||||||
*/
|
*/
|
||||||
@@ -281,6 +280,8 @@ public class RedisOperationsSessionRepository
|
|||||||
|
|
||||||
private final RedisSessionExpirationPolicy expirationPolicy;
|
private final RedisSessionExpirationPolicy expirationPolicy;
|
||||||
|
|
||||||
|
private final IndexResolver<RedisSession> indexResolver;
|
||||||
|
|
||||||
private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
|
private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
|
||||||
@Override
|
@Override
|
||||||
public void publishEvent(ApplicationEvent event) {
|
public void publishEvent(ApplicationEvent event) {
|
||||||
@@ -311,6 +312,7 @@ public class RedisOperationsSessionRepository
|
|||||||
this.sessionRedisOperations = sessionRedisOperations;
|
this.sessionRedisOperations = sessionRedisOperations;
|
||||||
this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations, this::getExpirationsKey,
|
this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations, this::getExpirationsKey,
|
||||||
this::getSessionKey);
|
this::getSessionKey);
|
||||||
|
this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
|
||||||
configureSessionChannels();
|
configureSessionChannels();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,7 +535,8 @@ public class RedisOperationsSessionRepository
|
|||||||
|
|
||||||
private void cleanupPrincipalIndex(RedisSession session) {
|
private void cleanupPrincipalIndex(RedisSession session) {
|
||||||
String sessionId = session.getId();
|
String sessionId = session.getId();
|
||||||
String principal = PRINCIPAL_NAME_RESOLVER.resolvePrincipal(session);
|
Map<String, String> indexes = RedisOperationsSessionRepository.this.indexResolver.resolveIndexesFor(session);
|
||||||
|
String principal = indexes.get(PRINCIPAL_NAME_INDEX_NAME);
|
||||||
if (principal != null) {
|
if (principal != null) {
|
||||||
this.sessionRedisOperations.boundSetOps(getPrincipalKey(principal)).remove(sessionId);
|
this.sessionRedisOperations.boundSetOps(getPrincipalKey(principal)).remove(sessionId);
|
||||||
}
|
}
|
||||||
@@ -689,8 +692,9 @@ public class RedisOperationsSessionRepository
|
|||||||
RedisSession(MapSession cached) {
|
RedisSession(MapSession cached) {
|
||||||
Assert.notNull(cached, "MapSession cannot be null");
|
Assert.notNull(cached, "MapSession cannot be null");
|
||||||
this.cached = cached;
|
this.cached = cached;
|
||||||
this.originalPrincipalName = PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this);
|
|
||||||
this.originalSessionId = cached.getId();
|
this.originalSessionId = cached.getId();
|
||||||
|
Map<String, String> indexes = RedisOperationsSessionRepository.this.indexResolver.resolveIndexesFor(this);
|
||||||
|
this.originalPrincipalName = indexes.get(PRINCIPAL_NAME_INDEX_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNew(boolean isNew) {
|
public void setNew(boolean isNew) {
|
||||||
@@ -800,7 +804,9 @@ public class RedisOperationsSessionRepository
|
|||||||
RedisOperationsSessionRepository.this.sessionRedisOperations.boundSetOps(originalPrincipalRedisKey)
|
RedisOperationsSessionRepository.this.sessionRedisOperations.boundSetOps(originalPrincipalRedisKey)
|
||||||
.remove(sessionId);
|
.remove(sessionId);
|
||||||
}
|
}
|
||||||
String principal = PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this);
|
Map<String, String> indexes = RedisOperationsSessionRepository.this.indexResolver
|
||||||
|
.resolveIndexesFor(this);
|
||||||
|
String principal = indexes.get(PRINCIPAL_NAME_INDEX_NAME);
|
||||||
this.originalPrincipalName = principal;
|
this.originalPrincipalName = principal;
|
||||||
if (principal != null) {
|
if (principal != null) {
|
||||||
String principalRedisKey = getPrincipalKey(principal);
|
String principalRedisKey = getPrincipalKey(principal);
|
||||||
@@ -851,26 +857,4 @@ public class RedisOperationsSessionRepository
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Principal name resolver helper class.
|
|
||||||
*/
|
|
||||||
static class PrincipalNameResolver {
|
|
||||||
|
|
||||||
private SpelExpressionParser parser = new SpelExpressionParser();
|
|
||||||
|
|
||||||
public String resolvePrincipal(Session session) {
|
|
||||||
String principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);
|
|
||||||
if (principalName != null) {
|
|
||||||
return principalName;
|
|
||||||
}
|
|
||||||
Object authentication = session.getAttribute(SPRING_SECURITY_CONTEXT);
|
|
||||||
if (authentication != null) {
|
|
||||||
Expression expression = this.parser.parseExpression("authentication?.name");
|
|
||||||
return expression.getValue(authentication, String.class);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,15 +45,9 @@ import org.springframework.data.redis.core.BoundValueOperations;
|
|||||||
import org.springframework.data.redis.core.RedisOperations;
|
import org.springframework.data.redis.core.RedisOperations;
|
||||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextImpl;
|
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
import org.springframework.session.MapSession;
|
import org.springframework.session.MapSession;
|
||||||
import org.springframework.session.Session;
|
import org.springframework.session.Session;
|
||||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository.PrincipalNameResolver;
|
|
||||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession;
|
import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession;
|
||||||
import org.springframework.session.events.AbstractSessionEvent;
|
import org.springframework.session.events.AbstractSessionEvent;
|
||||||
|
|
||||||
@@ -597,32 +591,6 @@ class RedisOperationsSessionRepositoryTests {
|
|||||||
verifyZeroInteractions(this.boundHashOperations);
|
verifyZeroInteractions(this.boundHashOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void resolvePrincipalIndex() {
|
|
||||||
PrincipalNameResolver resolver = RedisOperationsSessionRepository.PRINCIPAL_NAME_RESOLVER;
|
|
||||||
String username = "username";
|
|
||||||
RedisSession session = this.redisRepository.createSession();
|
|
||||||
session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
|
|
||||||
|
|
||||||
assertThat(resolver.resolvePrincipal(session)).isEqualTo(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void resolveIndexOnSecurityContext() {
|
|
||||||
String principal = "resolveIndexOnSecurityContext";
|
|
||||||
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "notused",
|
|
||||||
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
|
||||||
SecurityContext context = new SecurityContextImpl();
|
|
||||||
context.setAuthentication(authentication);
|
|
||||||
|
|
||||||
PrincipalNameResolver resolver = RedisOperationsSessionRepository.PRINCIPAL_NAME_RESOLVER;
|
|
||||||
|
|
||||||
RedisSession session = this.redisRepository.createSession();
|
|
||||||
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
|
|
||||||
|
|
||||||
assertThat(resolver.resolvePrincipal(session)).isEqualTo(principal);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void flushModeOnSaveCreate() {
|
void flushModeOnSaveCreate() {
|
||||||
this.redisRepository.createSession();
|
this.redisRepository.createSession();
|
||||||
|
|||||||
@@ -40,10 +40,11 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.session.DelegatingIndexResolver;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
|
import org.springframework.session.IndexResolver;
|
||||||
import org.springframework.session.MapSession;
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.PrincipalNameIndexResolver;
|
||||||
import org.springframework.session.Session;
|
import org.springframework.session.Session;
|
||||||
import org.springframework.session.events.AbstractSessionEvent;
|
import org.springframework.session.events.AbstractSessionEvent;
|
||||||
import org.springframework.session.events.SessionCreatedEvent;
|
import org.springframework.session.events.SessionCreatedEvent;
|
||||||
@@ -129,10 +130,10 @@ public class HazelcastSessionRepository
|
|||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(HazelcastSessionRepository.class);
|
private static final Log logger = LogFactory.getLog(HazelcastSessionRepository.class);
|
||||||
|
|
||||||
private static final PrincipalNameResolver principalNameResolver = new PrincipalNameResolver();
|
|
||||||
|
|
||||||
private final HazelcastInstance hazelcastInstance;
|
private final HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
private final IndexResolver<HazelcastSession> indexResolver;
|
||||||
|
|
||||||
private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
|
private ApplicationEventPublisher eventPublisher = new ApplicationEventPublisher() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -162,6 +163,7 @@ public class HazelcastSessionRepository
|
|||||||
public HazelcastSessionRepository(HazelcastInstance hazelcastInstance) {
|
public HazelcastSessionRepository(HazelcastInstance hazelcastInstance) {
|
||||||
Assert.notNull(hazelcastInstance, "HazelcastInstance must not be null");
|
Assert.notNull(hazelcastInstance, "HazelcastInstance must not be null");
|
||||||
this.hazelcastInstance = hazelcastInstance;
|
this.hazelcastInstance = hazelcastInstance;
|
||||||
|
this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@@ -425,7 +427,8 @@ public class HazelcastSessionRepository
|
|||||||
this.delegate.setAttribute(attributeName, attributeValue);
|
this.delegate.setAttribute(attributeName, attributeValue);
|
||||||
this.delta.put(attributeName, attributeValue);
|
this.delta.put(attributeName, attributeValue);
|
||||||
if (SPRING_SECURITY_CONTEXT.equals(attributeName)) {
|
if (SPRING_SECURITY_CONTEXT.equals(attributeName)) {
|
||||||
String principal = (attributeValue != null) ? principalNameResolver.resolvePrincipal(this) : null;
|
Map<String, String> indexes = HazelcastSessionRepository.this.indexResolver.resolveIndexesFor(this);
|
||||||
|
String principal = (attributeValue != null) ? indexes.get(PRINCIPAL_NAME_INDEX_NAME) : null;
|
||||||
this.delegate.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal);
|
this.delegate.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||||
}
|
}
|
||||||
flushImmediateIfNecessary();
|
flushImmediateIfNecessary();
|
||||||
@@ -460,26 +463,4 @@ public class HazelcastSessionRepository
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the Spring Security principal name.
|
|
||||||
*/
|
|
||||||
static class PrincipalNameResolver {
|
|
||||||
|
|
||||||
private SpelExpressionParser parser = new SpelExpressionParser();
|
|
||||||
|
|
||||||
public String resolvePrincipal(Session session) {
|
|
||||||
String principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);
|
|
||||||
if (principalName != null) {
|
|
||||||
return principalName;
|
|
||||||
}
|
|
||||||
Object authentication = session.getAttribute(SPRING_SECURITY_CONTEXT);
|
|
||||||
if (authentication != null) {
|
|
||||||
Expression expression = this.parser.parseExpression("authentication?.name");
|
|
||||||
return expression.getValue(authentication, String.class);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,16 +40,17 @@ import org.springframework.core.convert.support.GenericConversionService;
|
|||||||
import org.springframework.core.serializer.support.DeserializingConverter;
|
import org.springframework.core.serializer.support.DeserializingConverter;
|
||||||
import org.springframework.core.serializer.support.SerializingConverter;
|
import org.springframework.core.serializer.support.SerializingConverter;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.expression.Expression;
|
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|
||||||
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
|
||||||
import org.springframework.jdbc.core.JdbcOperations;
|
import org.springframework.jdbc.core.JdbcOperations;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||||
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||||
import org.springframework.jdbc.support.lob.LobHandler;
|
import org.springframework.jdbc.support.lob.LobHandler;
|
||||||
|
import org.springframework.session.DelegatingIndexResolver;
|
||||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||||
|
import org.springframework.session.IndexResolver;
|
||||||
import org.springframework.session.MapSession;
|
import org.springframework.session.MapSession;
|
||||||
|
import org.springframework.session.PrincipalNameIndexResolver;
|
||||||
import org.springframework.session.Session;
|
import org.springframework.session.Session;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.TransactionDefinition;
|
import org.springframework.transaction.TransactionDefinition;
|
||||||
@@ -197,12 +198,12 @@ public class JdbcOperationsSessionRepository
|
|||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(JdbcOperationsSessionRepository.class);
|
private static final Log logger = LogFactory.getLog(JdbcOperationsSessionRepository.class);
|
||||||
|
|
||||||
private static final PrincipalNameResolver PRINCIPAL_NAME_RESOLVER = new PrincipalNameResolver();
|
|
||||||
|
|
||||||
private final JdbcOperations jdbcOperations;
|
private final JdbcOperations jdbcOperations;
|
||||||
|
|
||||||
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
|
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
|
||||||
|
|
||||||
|
private final IndexResolver<JdbcSession> indexResolver;
|
||||||
|
|
||||||
private TransactionOperations transactionOperations = new TransactionOperations() {
|
private TransactionOperations transactionOperations = new TransactionOperations() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -271,6 +272,7 @@ public class JdbcOperationsSessionRepository
|
|||||||
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) {
|
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) {
|
||||||
Assert.notNull(jdbcOperations, "JdbcOperations must not be null");
|
Assert.notNull(jdbcOperations, "JdbcOperations must not be null");
|
||||||
this.jdbcOperations = jdbcOperations;
|
this.jdbcOperations = jdbcOperations;
|
||||||
|
this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
|
||||||
this.conversionService = createDefaultConversionService();
|
this.conversionService = createDefaultConversionService();
|
||||||
prepareQueries();
|
prepareQueries();
|
||||||
}
|
}
|
||||||
@@ -406,6 +408,8 @@ public class JdbcOperationsSessionRepository
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||||
|
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
|
||||||
|
.resolveIndexesFor(session);
|
||||||
JdbcOperationsSessionRepository.this.jdbcOperations
|
JdbcOperationsSessionRepository.this.jdbcOperations
|
||||||
.update(JdbcOperationsSessionRepository.this.createSessionQuery, (ps) -> {
|
.update(JdbcOperationsSessionRepository.this.createSessionQuery, (ps) -> {
|
||||||
ps.setString(1, session.primaryKey);
|
ps.setString(1, session.primaryKey);
|
||||||
@@ -414,7 +418,7 @@ public class JdbcOperationsSessionRepository
|
|||||||
ps.setLong(4, session.getLastAccessedTime().toEpochMilli());
|
ps.setLong(4, session.getLastAccessedTime().toEpochMilli());
|
||||||
ps.setInt(5, (int) session.getMaxInactiveInterval().getSeconds());
|
ps.setInt(5, (int) session.getMaxInactiveInterval().getSeconds());
|
||||||
ps.setLong(6, session.getExpiryTime().toEpochMilli());
|
ps.setLong(6, session.getExpiryTime().toEpochMilli());
|
||||||
ps.setString(7, session.getPrincipalName());
|
ps.setString(7, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
|
||||||
});
|
});
|
||||||
Set<String> attributeNames = session.getAttributeNames();
|
Set<String> attributeNames = session.getAttributeNames();
|
||||||
if (!attributeNames.isEmpty()) {
|
if (!attributeNames.isEmpty()) {
|
||||||
@@ -430,13 +434,15 @@ public class JdbcOperationsSessionRepository
|
|||||||
@Override
|
@Override
|
||||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||||
if (session.isChanged()) {
|
if (session.isChanged()) {
|
||||||
|
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
|
||||||
|
.resolveIndexesFor(session);
|
||||||
JdbcOperationsSessionRepository.this.jdbcOperations
|
JdbcOperationsSessionRepository.this.jdbcOperations
|
||||||
.update(JdbcOperationsSessionRepository.this.updateSessionQuery, (ps) -> {
|
.update(JdbcOperationsSessionRepository.this.updateSessionQuery, (ps) -> {
|
||||||
ps.setString(1, session.getId());
|
ps.setString(1, session.getId());
|
||||||
ps.setLong(2, session.getLastAccessedTime().toEpochMilli());
|
ps.setLong(2, session.getLastAccessedTime().toEpochMilli());
|
||||||
ps.setInt(3, (int) session.getMaxInactiveInterval().getSeconds());
|
ps.setInt(3, (int) session.getMaxInactiveInterval().getSeconds());
|
||||||
ps.setLong(4, session.getExpiryTime().toEpochMilli());
|
ps.setLong(4, session.getExpiryTime().toEpochMilli());
|
||||||
ps.setString(5, session.getPrincipalName());
|
ps.setString(5, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
|
||||||
ps.setString(6, session.primaryKey);
|
ps.setString(6, session.primaryKey);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -742,10 +748,6 @@ public class JdbcOperationsSessionRepository
|
|||||||
this.delta.clear();
|
this.delta.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPrincipalName() {
|
|
||||||
return PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Instant getExpiryTime() {
|
Instant getExpiryTime() {
|
||||||
return getLastAccessedTime().plus(getMaxInactiveInterval());
|
return getLastAccessedTime().plus(getMaxInactiveInterval());
|
||||||
}
|
}
|
||||||
@@ -838,30 +840,6 @@ public class JdbcOperationsSessionRepository
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the Spring Security principal name.
|
|
||||||
*
|
|
||||||
* @author Vedran Pavic
|
|
||||||
*/
|
|
||||||
static class PrincipalNameResolver {
|
|
||||||
|
|
||||||
private SpelExpressionParser parser = new SpelExpressionParser();
|
|
||||||
|
|
||||||
public String resolvePrincipal(Session session) {
|
|
||||||
String principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);
|
|
||||||
if (principalName != null) {
|
|
||||||
return principalName;
|
|
||||||
}
|
|
||||||
Object authentication = session.getAttribute(SPRING_SECURITY_CONTEXT);
|
|
||||||
if (authentication != null) {
|
|
||||||
Expression expression = this.parser.parseExpression("authentication?.name");
|
|
||||||
return expression.getValue(authentication, String.class);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SessionResultSetExtractor implements ResultSetExtractor<List<JdbcSession>> {
|
private class SessionResultSetExtractor implements ResultSetExtractor<List<JdbcSession>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user