nl.talsmasoftware.context.springsecurity.SpringSecurityContextManager Maven / Gradle / Ivy
/*
* Copyright 2016-2019 Talsma ICT
*
* 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
*
* http://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 nl.talsmasoftware.context.springsecurity;
import nl.talsmasoftware.context.Context;
import nl.talsmasoftware.context.clearable.ClearableContextManager;
import nl.talsmasoftware.context.observer.ContextObservers;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A context manager that propagates spring-security {@link Authentication} objects
* into background threads using the {@code ContextAwareExecutorService}.
*
* Management of the authentication is fully delegated to the Spring {@link SecurityContextHolder},
* so no additional {@link java.lang.ThreadLocal} variables are used.
*
* @author Sjoerd Talsma
*/
public class SpringSecurityContextManager implements ClearableContextManager {
/**
* Creates a new Spring {@linkplain SecurityContext} and sets the {@linkplain Authentication value} in it.
*
* This new value is set in the {@linkplain SecurityContextHolder} and the current {@linkplain Authentication}
* is remembered, to be restored when the returned {@link Context} is closed.
*
* @param value The value to initialize a new context for.
* @return A context with the new Authentication, restoring the previous authentication when closed.
*/
public Context initializeNewContext(Authentication value) {
SecurityContext previous = SecurityContextHolder.getContext();
SecurityContext current = SecurityContextHolder.createEmptyContext();
current.setAuthentication(value);
SecurityContextHolder.setContext(current);
return new AuthenticationContext(current, previous, false);
}
/**
* @return A context object referring to the current {@code Authentication} in the spring security context holder.
* Closing the returned context does nothing.
*/
public Context getActiveContext() {
return new AuthenticationContext(SecurityContextHolder.getContext(), null, true);
}
/**
* Clears the Spring {@linkplain SecurityContext} by calling {@linkplain SecurityContextHolder#clearContext()}.
*/
public void clear() {
SecurityContextHolder.clearContext();
}
private static final class AuthenticationContext implements Context {
private volatile SecurityContext current;
private final SecurityContext previous;
private final AtomicBoolean closed;
private AuthenticationContext(SecurityContext current, SecurityContext previous, boolean alreadyClosed) {
this.current = current;
this.previous = previous;
this.closed = new AtomicBoolean(alreadyClosed);
ContextObservers.onActivate(SpringSecurityContextManager.class, auth(current), auth(previous));
}
public Authentication getValue() {
return auth(current);
}
public void close() {
if (closed.compareAndSet(false, true)) {
SecurityContextHolder.setContext(previous);
ContextObservers.onDeactivate(SpringSecurityContextManager.class, auth(current), auth(previous));
}
}
/**
* Null-safe {@code getAuthentication()} call
*
* @param securityContext optional security context to get authentication object from.
* @return the authentication object or null if security context itself was null.
*/
private static Authentication auth(SecurityContext securityContext) {
return securityContext == null ? null : securityContext.getAuthentication();
}
}
}