Merge pull request #7634 from mbhave/gh-7605

* pr/7634:
  Refine error message from Endpoint MVC security
  Send error with message from Endpoint MVC security
pull/7718/merge
Phillip Webb 8 years ago
commit e6097fb3e4

@ -17,11 +17,16 @@
package org.springframework.boot.actuate.endpoint.mvc; package org.springframework.boot.actuate.endpoint.mvc;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsUtils; import org.springframework.web.cors.CorsUtils;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
@ -34,10 +39,15 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
*/ */
public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter { public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter {
private static final Log logger = LogFactory
.getLog(MvcEndpointSecurityInterceptor.class);
private final boolean secure; private final boolean secure;
private final List<String> roles; private final List<String> roles;
private AtomicBoolean loggedUnauthorizedAttempt = new AtomicBoolean();
public MvcEndpointSecurityInterceptor(boolean secure, List<String> roles) { public MvcEndpointSecurityInterceptor(boolean secure, List<String> roles) {
this.secure = secure; this.secure = secure;
this.roles = roles; this.roles = roles;
@ -59,17 +69,30 @@ public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter {
return true; return true;
} }
} }
setFailureResponseStatus(request, response); sendFailureResponse(request, response);
return false; return false;
} }
private void setFailureResponseStatus(HttpServletRequest request, private void sendFailureResponse(HttpServletRequest request,
HttpServletResponse response) { HttpServletResponse response) throws Exception {
if (request.getUserPrincipal() != null) { if (request.getUserPrincipal() != null) {
response.setStatus(HttpStatus.FORBIDDEN.value()); String roles = StringUtils.collectionToDelimitedString(this.roles, " ");
response.sendError(HttpStatus.FORBIDDEN.value(),
"Access is denied. User must have one of the these roles: " + roles);
} }
else { else {
response.setStatus(HttpStatus.UNAUTHORIZED.value()); logUnauthorizedAttempt();
response.sendError(HttpStatus.UNAUTHORIZED.value(),
"Full authentication is required to access this resource.");
}
}
private void logUnauthorizedAttempt() {
if (this.loggedUnauthorizedAttempt.compareAndSet(false, true)
&& logger.isInfoEnabled()) {
logger.info("Full authentication is required to access "
+ "actuator endpoints. Consider adding Spring Security "
+ "or set 'management.security.enabled' to false.");
} }
} }

@ -16,19 +16,26 @@
package org.springframework.boot.actuate.endpoint.mvc; package org.springframework.boot.actuate.endpoint.mvc;
import java.security.Principal;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint; import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/** /**
* Tests for {@link MvcEndpointSecurityInterceptor}. * Tests for {@link MvcEndpointSecurityInterceptor}.
@ -37,6 +44,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class MvcEndpointSecurityInterceptorTests { public class MvcEndpointSecurityInterceptorTests {
@Rule
public OutputCapture output = new OutputCapture();
private MvcEndpointSecurityInterceptor securityInterceptor; private MvcEndpointSecurityInterceptor securityInterceptor;
private TestMvcEndpoint mvcEndpoint; private TestMvcEndpoint mvcEndpoint;
@ -47,7 +57,7 @@ public class MvcEndpointSecurityInterceptorTests {
private MockHttpServletRequest request; private MockHttpServletRequest request;
private MockHttpServletResponse response; private HttpServletResponse response;
private MockServletContext servletContext; private MockServletContext servletContext;
@ -62,7 +72,7 @@ public class MvcEndpointSecurityInterceptorTests {
this.handlerMethod = new HandlerMethod(this.mvcEndpoint, "invoke"); this.handlerMethod = new HandlerMethod(this.mvcEndpoint, "invoke");
this.servletContext = new MockServletContext(); this.servletContext = new MockServletContext();
this.request = new MockHttpServletRequest(this.servletContext); this.request = new MockHttpServletRequest(this.servletContext);
this.response = new MockHttpServletResponse(); this.response = mock(HttpServletResponse.class);
} }
@Test @Test
@ -87,11 +97,30 @@ public class MvcEndpointSecurityInterceptorTests {
} }
@Test @Test
public void sensitiveEndpointIfRoleIsNotPresentShouldNotAllowAccess() public void sensitiveEndpointIfNotAuthenticatedShouldNotAllowAccess()
throws Exception {
assertThat(this.securityInterceptor.preHandle(this.request, this.response,
this.handlerMethod)).isFalse();
verify(this.response).sendError(HttpStatus.UNAUTHORIZED.value(),
"Full authentication is required to access this resource.");
assertThat(this.securityInterceptor.preHandle(this.request, this.response,
this.handlerMethod)).isFalse();
assertThat(this.output.toString())
.containsOnlyOnce("Full authentication is required to access actuator "
+ "endpoints. Consider adding Spring Security or set "
+ "'management.security.enabled' to false");
}
@Test
public void sensitiveEndpointIfRoleIsNotCorrectShouldNotAllowAccess()
throws Exception { throws Exception {
Principal principal = mock(Principal.class);
this.request.setUserPrincipal(principal);
this.servletContext.declareRoles("HERO"); this.servletContext.declareRoles("HERO");
assertThat(this.securityInterceptor.preHandle(this.request, this.response, assertThat(this.securityInterceptor.preHandle(this.request, this.response,
this.handlerMethod)).isFalse(); this.handlerMethod)).isFalse();
verify(this.response).sendError(HttpStatus.FORBIDDEN.value(),
"Access is denied. User must have one of the these roles: SUPER_HERO");
} }
private static class TestEndpoint extends AbstractEndpoint<Object> { private static class TestEndpoint extends AbstractEndpoint<Object> {

Loading…
Cancel
Save