Adapt to Spring Security changes

Closes gh-32604
This commit is contained in:
Madhura Bhave
2022-10-07 15:23:31 -07:00
parent 2e74878ba4
commit ce3c933f77
58 changed files with 497 additions and 361 deletions

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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());
}

View File

@@ -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));
}
}

View File

@@ -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());