|
@@ -30,6 +30,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
|
|
|
import org.springframework.context.MessageSource;
|
|
|
import org.springframework.context.MessageSourceAware;
|
|
|
import org.springframework.context.support.MessageSourceAccessor;
|
|
|
+import org.springframework.core.log.LogMessage;
|
|
|
import org.springframework.security.access.AccessDecisionManager;
|
|
|
import org.springframework.security.access.AccessDeniedException;
|
|
|
import org.springframework.security.access.ConfigAttribute;
|
|
@@ -47,6 +48,7 @@ import org.springframework.security.core.SpringSecurityMessageSource;
|
|
|
import org.springframework.security.core.context.SecurityContext;
|
|
|
import org.springframework.security.core.context.SecurityContextHolder;
|
|
|
import org.springframework.util.Assert;
|
|
|
+import org.springframework.util.CollectionUtils;
|
|
|
|
|
|
/**
|
|
|
* Abstract class that implements security interception for secure objects.
|
|
@@ -141,120 +143,91 @@ public abstract class AbstractSecurityInterceptor
|
|
|
() -> "RunAsManager does not support secure object class: " + getSecureObjectClass());
|
|
|
Assert.isTrue(this.accessDecisionManager.supports(getSecureObjectClass()),
|
|
|
() -> "AccessDecisionManager does not support secure object class: " + getSecureObjectClass());
|
|
|
-
|
|
|
if (this.afterInvocationManager != null) {
|
|
|
Assert.isTrue(this.afterInvocationManager.supports(getSecureObjectClass()),
|
|
|
() -> "AfterInvocationManager does not support secure object class: " + getSecureObjectClass());
|
|
|
}
|
|
|
-
|
|
|
if (this.validateConfigAttributes) {
|
|
|
Collection<ConfigAttribute> attributeDefs = this.obtainSecurityMetadataSource().getAllConfigAttributes();
|
|
|
-
|
|
|
if (attributeDefs == null) {
|
|
|
- this.logger.warn(
|
|
|
- "Could not validate configuration attributes as the SecurityMetadataSource did not return "
|
|
|
- + "any attributes from getAllConfigAttributes()");
|
|
|
+ this.logger.warn("Could not validate configuration attributes as the "
|
|
|
+ + "SecurityMetadataSource did not return any attributes from getAllConfigAttributes()");
|
|
|
return;
|
|
|
}
|
|
|
+ validateAttributeDefs(attributeDefs);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- Set<ConfigAttribute> unsupportedAttrs = new HashSet<>();
|
|
|
-
|
|
|
- for (ConfigAttribute attr : attributeDefs) {
|
|
|
- if (!this.runAsManager.supports(attr) && !this.accessDecisionManager.supports(attr)
|
|
|
- && ((this.afterInvocationManager == null) || !this.afterInvocationManager.supports(attr))) {
|
|
|
- unsupportedAttrs.add(attr);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (unsupportedAttrs.size() != 0) {
|
|
|
- throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttrs);
|
|
|
+ private void validateAttributeDefs(Collection<ConfigAttribute> attributeDefs) {
|
|
|
+ Set<ConfigAttribute> unsupportedAttrs = new HashSet<>();
|
|
|
+ for (ConfigAttribute attr : attributeDefs) {
|
|
|
+ if (!this.runAsManager.supports(attr) && !this.accessDecisionManager.supports(attr)
|
|
|
+ && ((this.afterInvocationManager == null) || !this.afterInvocationManager.supports(attr))) {
|
|
|
+ unsupportedAttrs.add(attr);
|
|
|
}
|
|
|
-
|
|
|
- this.logger.debug("Validated configuration attributes");
|
|
|
}
|
|
|
+ if (unsupportedAttrs.size() != 0) {
|
|
|
+ throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttrs);
|
|
|
+ }
|
|
|
+ this.logger.debug("Validated configuration attributes");
|
|
|
}
|
|
|
|
|
|
protected InterceptorStatusToken beforeInvocation(Object object) {
|
|
|
Assert.notNull(object, "Object was null");
|
|
|
- final boolean debug = this.logger.isDebugEnabled();
|
|
|
-
|
|
|
if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
|
|
|
throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName()
|
|
|
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
|
|
|
+ getSecureObjectClass());
|
|
|
}
|
|
|
-
|
|
|
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
|
|
|
-
|
|
|
- if (attributes == null || attributes.isEmpty()) {
|
|
|
- if (this.rejectPublicInvocations) {
|
|
|
- throw new IllegalArgumentException("Secure object invocation " + object
|
|
|
- + " was denied as public invocations are not allowed via this interceptor. "
|
|
|
- + "This indicates a configuration error because the "
|
|
|
- + "rejectPublicInvocations property is set to 'true'");
|
|
|
- }
|
|
|
-
|
|
|
- if (debug) {
|
|
|
- this.logger.debug("Public object - authentication not attempted");
|
|
|
- }
|
|
|
-
|
|
|
+ if (CollectionUtils.isEmpty(attributes)) {
|
|
|
+ Assert.isTrue(!this.rejectPublicInvocations,
|
|
|
+ () -> "Secure object invocation " + object
|
|
|
+ + " was denied as public invocations are not allowed via this interceptor. "
|
|
|
+ + "This indicates a configuration error because the "
|
|
|
+ + "rejectPublicInvocations property is set to 'true'");
|
|
|
+ this.logger.debug("Public object - authentication not attempted");
|
|
|
publishEvent(new PublicInvocationEvent(object));
|
|
|
-
|
|
|
return null; // no further work post-invocation
|
|
|
}
|
|
|
-
|
|
|
- if (debug) {
|
|
|
- this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
|
|
|
- }
|
|
|
-
|
|
|
+ this.logger.debug(LogMessage.format("Secure object: %s; Attributes: %s", object, attributes));
|
|
|
if (SecurityContextHolder.getContext().getAuthentication() == null) {
|
|
|
credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
|
|
|
"An Authentication object was not found in the SecurityContext"), object, attributes);
|
|
|
}
|
|
|
-
|
|
|
Authentication authenticated = authenticateIfRequired();
|
|
|
-
|
|
|
// Attempt authorization
|
|
|
- try {
|
|
|
- this.accessDecisionManager.decide(authenticated, object, attributes);
|
|
|
- }
|
|
|
- catch (AccessDeniedException accessDeniedException) {
|
|
|
- publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));
|
|
|
-
|
|
|
- throw accessDeniedException;
|
|
|
- }
|
|
|
-
|
|
|
- if (debug) {
|
|
|
- this.logger.debug("Authorization successful");
|
|
|
- }
|
|
|
-
|
|
|
+ attemptAuthorization(object, attributes, authenticated);
|
|
|
+ this.logger.debug("Authorization successful");
|
|
|
if (this.publishAuthorizationSuccess) {
|
|
|
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
|
|
|
}
|
|
|
|
|
|
// Attempt to run as a different user
|
|
|
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
|
|
|
-
|
|
|
- if (runAs == null) {
|
|
|
- if (debug) {
|
|
|
- this.logger.debug("RunAsManager did not change Authentication object");
|
|
|
- }
|
|
|
-
|
|
|
- // no further work post-invocation
|
|
|
- return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
|
|
|
- }
|
|
|
- else {
|
|
|
- if (debug) {
|
|
|
- this.logger.debug("Switching to RunAs Authentication: " + runAs);
|
|
|
- }
|
|
|
-
|
|
|
+ if (runAs != null) {
|
|
|
+ this.logger.debug(LogMessage.format("Switching to RunAs Authentication: %s", runAs));
|
|
|
SecurityContext origCtx = SecurityContextHolder.getContext();
|
|
|
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
|
|
|
SecurityContextHolder.getContext().setAuthentication(runAs);
|
|
|
-
|
|
|
// need to revert to token.Authenticated post-invocation
|
|
|
return new InterceptorStatusToken(origCtx, true, attributes, object);
|
|
|
}
|
|
|
+ this.logger.debug("RunAsManager did not change Authentication object");
|
|
|
+ // no further work post-invocation
|
|
|
+ return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,
|
|
|
+ Authentication authenticated) {
|
|
|
+ try {
|
|
|
+ this.accessDecisionManager.decide(authenticated, object, attributes);
|
|
|
+ }
|
|
|
+ catch (AccessDeniedException ex) {
|
|
|
+ publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));
|
|
|
+ throw ex;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -266,11 +239,8 @@ public abstract class AbstractSecurityInterceptor
|
|
|
*/
|
|
|
protected void finallyInvocation(InterceptorStatusToken token) {
|
|
|
if (token != null && token.isContextHolderRefreshRequired()) {
|
|
|
- if (this.logger.isDebugEnabled()) {
|
|
|
- this.logger.debug(
|
|
|
- "Reverting to original Authentication: " + token.getSecurityContext().getAuthentication());
|
|
|
- }
|
|
|
-
|
|
|
+ this.logger.debug(LogMessage.of(
|
|
|
+ () -> "Reverting to original Authentication: " + token.getSecurityContext().getAuthentication()));
|
|
|
SecurityContextHolder.setContext(token.getSecurityContext());
|
|
|
}
|
|
|
}
|
|
@@ -289,24 +259,19 @@ public abstract class AbstractSecurityInterceptor
|
|
|
// public object
|
|
|
return returnedObject;
|
|
|
}
|
|
|
-
|
|
|
finallyInvocation(token); // continue to clean in this method for passivity
|
|
|
-
|
|
|
if (this.afterInvocationManager != null) {
|
|
|
// Attempt after invocation handling
|
|
|
try {
|
|
|
returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(),
|
|
|
token.getSecureObject(), token.getAttributes(), returnedObject);
|
|
|
}
|
|
|
- catch (AccessDeniedException accessDeniedException) {
|
|
|
- AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(),
|
|
|
- token.getAttributes(), token.getSecurityContext().getAuthentication(), accessDeniedException);
|
|
|
- publishEvent(event);
|
|
|
-
|
|
|
- throw accessDeniedException;
|
|
|
+ catch (AccessDeniedException ex) {
|
|
|
+ publishEvent(new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(),
|
|
|
+ token.getSecurityContext().getAuthentication(), ex));
|
|
|
+ throw ex;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
return returnedObject;
|
|
|
}
|
|
|
|
|
@@ -318,25 +283,14 @@ public abstract class AbstractSecurityInterceptor
|
|
|
*/
|
|
|
private Authentication authenticateIfRequired() {
|
|
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
|
-
|
|
|
if (authentication.isAuthenticated() && !this.alwaysReauthenticate) {
|
|
|
- if (this.logger.isDebugEnabled()) {
|
|
|
- this.logger.debug("Previously Authenticated: " + authentication);
|
|
|
- }
|
|
|
-
|
|
|
+ this.logger.debug(LogMessage.format("Previously Authenticated: %s", authentication));
|
|
|
return authentication;
|
|
|
}
|
|
|
-
|
|
|
authentication = this.authenticationManager.authenticate(authentication);
|
|
|
-
|
|
|
- // We don't authenticated.setAuthentication(true), because each provider should do
|
|
|
- // that
|
|
|
- if (this.logger.isDebugEnabled()) {
|
|
|
- this.logger.debug("Successfully Authenticated: " + authentication);
|
|
|
- }
|
|
|
-
|
|
|
+ // Don't authenticated.setAuthentication(true) because each provider does that
|
|
|
+ this.logger.debug(LogMessage.format("Successfully Authenticated: %s", authentication));
|
|
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
|
-
|
|
|
return authentication;
|
|
|
}
|
|
|
|
|
@@ -351,11 +305,9 @@ public abstract class AbstractSecurityInterceptor
|
|
|
*/
|
|
|
private void credentialsNotFound(String reason, Object secureObject, Collection<ConfigAttribute> configAttribs) {
|
|
|
AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason);
|
|
|
-
|
|
|
AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject,
|
|
|
configAttribs, exception);
|
|
|
publishEvent(event);
|
|
|
-
|
|
|
throw exception;
|
|
|
}
|
|
|
|