Adapt to Spring Security changes
Closes gh-32604
This commit is contained in:
@@ -20,7 +20,6 @@ import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
@@ -55,9 +54,7 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.access.vote.RoleVoter;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
@@ -469,7 +466,7 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
||||
|
||||
private static final class ReactiveSecurityContext implements SecurityContext {
|
||||
|
||||
private final RoleVoter roleVoter = new RoleVoter();
|
||||
private static final String ROLE_PREFIX = "ROLE_";
|
||||
|
||||
private final Authentication authentication;
|
||||
|
||||
@@ -477,6 +474,10 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
||||
this.authentication = authentication;
|
||||
}
|
||||
|
||||
private Authentication getAuthentication() {
|
||||
return this.authentication;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return this.authentication;
|
||||
@@ -484,11 +485,9 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
||||
|
||||
@Override
|
||||
public boolean isUserInRole(String role) {
|
||||
if (!role.startsWith(this.roleVoter.getRolePrefix())) {
|
||||
role = this.roleVoter.getRolePrefix() + role;
|
||||
}
|
||||
return this.roleVoter.vote(this.authentication, null,
|
||||
Collections.singletonList(new SecurityConfig(role))) == AccessDecisionVoter.ACCESS_GRANTED;
|
||||
String authority = (!role.startsWith(ROLE_PREFIX)) ? ROLE_PREFIX + role : role;
|
||||
return AuthorityAuthorizationManager.hasAuthority(authority).check(this::getAuthentication, null)
|
||||
.isGranted();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,18 +21,21 @@ import org.springframework.boot.actuate.audit.listener.AuditApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.security.access.event.AbstractAuthorizationEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
|
||||
|
||||
/**
|
||||
* Abstract {@link ApplicationListener} to expose Spring Security
|
||||
* {@link AbstractAuthorizationEvent authorization events} as {@link AuditEvent}s.
|
||||
* {@link AuthorizationDeniedEvent authorization denied} and
|
||||
* {@link AuthorizationGrantedEvent authorization granted} events as {@link AuditEvent}s.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Vedran Pavic
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public abstract class AbstractAuthorizationAuditListener
|
||||
implements ApplicationListener<AbstractAuthorizationEvent>, ApplicationEventPublisherAware {
|
||||
implements ApplicationListener<AuthorizationEvent>, ApplicationEventPublisherAware {
|
||||
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.security;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
@@ -75,7 +76,7 @@ public class AuthenticationAuditListener extends AbstractAuthenticationAuditList
|
||||
}
|
||||
|
||||
private void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("type", event.getException().getClass().getName());
|
||||
data.put("message", event.getException().getMessage());
|
||||
if (event.getAuthentication().getDetails() != null) {
|
||||
@@ -85,7 +86,7 @@ public class AuthenticationAuditListener extends AbstractAuthenticationAuditList
|
||||
}
|
||||
|
||||
private void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
if (event.getAuthentication().getDetails() != null) {
|
||||
data.put("details", event.getAuthentication().getDetails());
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
package org.springframework.boot.actuate.security;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.security.access.event.AbstractAuthorizationEvent;
|
||||
import org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent;
|
||||
import org.springframework.security.access.event.AuthorizationFailureEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationEvent;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link AbstractAuthorizationAuditListener}.
|
||||
@@ -39,30 +40,38 @@ public class AuthorizationAuditListener extends AbstractAuthorizationAuditListen
|
||||
public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractAuthorizationEvent event) {
|
||||
if (event instanceof AuthenticationCredentialsNotFoundEvent credentialsNotFoundEvent) {
|
||||
onAuthenticationCredentialsNotFoundEvent(credentialsNotFoundEvent);
|
||||
}
|
||||
else if (event instanceof AuthorizationFailureEvent authorizationFailureEvent) {
|
||||
onAuthorizationFailureEvent(authorizationFailureEvent);
|
||||
public void onApplicationEvent(AuthorizationEvent event) {
|
||||
if (event instanceof AuthorizationDeniedEvent<?> authorizationDeniedEvent) {
|
||||
onAuthorizationDeniedEvent(authorizationDeniedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void onAuthenticationCredentialsNotFoundEvent(AuthenticationCredentialsNotFoundEvent event) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("type", event.getCredentialsNotFoundException().getClass().getName());
|
||||
data.put("message", event.getCredentialsNotFoundException().getMessage());
|
||||
publish(new AuditEvent("<unknown>", AuthenticationAuditListener.AUTHENTICATION_FAILURE, data));
|
||||
private void onAuthorizationDeniedEvent(AuthorizationDeniedEvent<?> event) {
|
||||
String name = getName(event.getAuthentication());
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
Object details = getDetails(event.getAuthentication());
|
||||
if (details != null) {
|
||||
data.put("details", details);
|
||||
}
|
||||
publish(new AuditEvent(name, AUTHORIZATION_FAILURE, data));
|
||||
}
|
||||
|
||||
private void onAuthorizationFailureEvent(AuthorizationFailureEvent event) {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("type", event.getAccessDeniedException().getClass().getName());
|
||||
data.put("message", event.getAccessDeniedException().getMessage());
|
||||
if (event.getAuthentication().getDetails() != null) {
|
||||
data.put("details", event.getAuthentication().getDetails());
|
||||
private String getName(Supplier<Authentication> authentication) {
|
||||
try {
|
||||
return authentication.get().getName();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
private Object getDetails(Supplier<Authentication> authentication) {
|
||||
try {
|
||||
return authentication.get().getDetails();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
publish(new AuditEvent(event.getAuthentication().getName(), AUTHORIZATION_FAILURE, data));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,21 +16,17 @@
|
||||
|
||||
package org.springframework.boot.actuate.security;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.boot.actuate.audit.listener.AuditApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.access.event.AbstractAuthorizationEvent;
|
||||
import org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent;
|
||||
import org.springframework.security.access.event.AuthorizationFailureEvent;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationEvent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
@@ -51,35 +47,48 @@ class AuthorizationAuditListenerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAuthenticationCredentialsNotFound() {
|
||||
AuditApplicationEvent event = handleAuthorizationEvent(
|
||||
new AuthenticationCredentialsNotFoundEvent(this, Collections.singletonList(new SecurityConfig("USER")),
|
||||
new AuthenticationCredentialsNotFoundException("Bad user")));
|
||||
assertThat(event.getAuditEvent().getType()).isEqualTo(AuthenticationAuditListener.AUTHENTICATION_FAILURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAuthorizationFailure() {
|
||||
AuditApplicationEvent event = handleAuthorizationEvent(new AuthorizationFailureEvent(this,
|
||||
Collections.singletonList(new SecurityConfig("USER")),
|
||||
new UsernamePasswordAuthenticationToken("user", "password"), new AccessDeniedException("Bad user")));
|
||||
assertThat(event.getAuditEvent().getType()).isEqualTo(AuthorizationAuditListener.AUTHORIZATION_FAILURE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDetailsAreIncludedInAuditEvent() {
|
||||
Object details = new Object();
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("user",
|
||||
void authorizationDeniedEvent() {
|
||||
AuthorizationDecision decision = new AuthorizationDecision(false);
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("spring",
|
||||
"password");
|
||||
authentication.setDetails(details);
|
||||
AuditApplicationEvent event = handleAuthorizationEvent(
|
||||
new AuthorizationFailureEvent(this, Collections.singletonList(new SecurityConfig("USER")),
|
||||
authentication, new AccessDeniedException("Bad user")));
|
||||
assertThat(event.getAuditEvent().getType()).isEqualTo(AuthorizationAuditListener.AUTHORIZATION_FAILURE);
|
||||
assertThat(event.getAuditEvent().getData()).containsEntry("details", details);
|
||||
authentication.setDetails("details");
|
||||
AuthorizationDeniedEvent<?> authorizationEvent = new AuthorizationDeniedEvent<>(() -> authentication, "",
|
||||
decision);
|
||||
AuditEvent auditEvent = handleAuthorizationEvent(authorizationEvent).getAuditEvent();
|
||||
assertThat(auditEvent.getPrincipal()).isEqualTo("spring");
|
||||
assertThat(auditEvent.getType()).isEqualTo(AuthorizationAuditListener.AUTHORIZATION_FAILURE);
|
||||
assertThat(auditEvent.getData()).containsEntry("details", "details");
|
||||
}
|
||||
|
||||
private AuditApplicationEvent handleAuthorizationEvent(AbstractAuthorizationEvent event) {
|
||||
@Test
|
||||
void authorizationDeniedEventWhenAuthenticationIsNotAvailable() {
|
||||
AuthorizationDecision decision = new AuthorizationDecision(false);
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("spring",
|
||||
"password");
|
||||
authentication.setDetails("details");
|
||||
AuthorizationDeniedEvent<?> authorizationEvent = new AuthorizationDeniedEvent<>(() -> {
|
||||
throw new RuntimeException("No authentication");
|
||||
}, "", decision);
|
||||
AuditEvent auditEvent = handleAuthorizationEvent(authorizationEvent).getAuditEvent();
|
||||
assertThat(auditEvent.getPrincipal()).isEqualTo("<unknown>");
|
||||
assertThat(auditEvent.getType()).isEqualTo(AuthorizationAuditListener.AUTHORIZATION_FAILURE);
|
||||
assertThat(auditEvent.getData()).doesNotContainKey("details");
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizationDeniedEventWhenAuthenticationDoesNotHaveDetails() {
|
||||
AuthorizationDecision decision = new AuthorizationDecision(false);
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("spring",
|
||||
"password");
|
||||
AuthorizationDeniedEvent<?> authorizationEvent = new AuthorizationDeniedEvent<>(() -> authentication, "",
|
||||
decision);
|
||||
AuditEvent auditEvent = handleAuthorizationEvent(authorizationEvent).getAuditEvent();
|
||||
assertThat(auditEvent.getPrincipal()).isEqualTo("spring");
|
||||
assertThat(auditEvent.getType()).isEqualTo(AuthorizationAuditListener.AUTHORIZATION_FAILURE);
|
||||
assertThat(auditEvent.getData()).doesNotContainKey("details");
|
||||
}
|
||||
|
||||
private AuditApplicationEvent handleAuthorizationEvent(AuthorizationEvent event) {
|
||||
ArgumentCaptor<AuditApplicationEvent> eventCaptor = ArgumentCaptor.forClass(AuditApplicationEvent.class);
|
||||
this.listener.onApplicationEvent(event);
|
||||
then(this.publisher).should().publishEvent(eventCaptor.capture());
|
||||
|
||||
Reference in New Issue
Block a user