Skip to content

Commit 32ecbcc

Browse files
rwinchDave Syer
authored andcommitted
SECOAUTH-175: Introduced OAuth2AuthenticationFailureHandler
1 parent 1db8c1f commit 32ecbcc

File tree

4 files changed

+163
-1
lines changed

4 files changed

+163
-1
lines changed

samples/oauth2/sparklr/src/test/java/org/springframework/security/oauth2/provider/TestNativeApplicationProvider.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.springframework.http.ResponseEntity;
1919
import org.springframework.security.crypto.codec.Base64;
2020
import org.springframework.security.oauth2.common.OAuth2AccessToken;
21+
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
2122
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
2223
import org.springframework.util.LinkedMultiValueMap;
2324
import org.springframework.util.MultiValueMap;
@@ -100,14 +101,18 @@ public void testHappyDayWithHeader() throws Exception {
100101
* tests a happy-day flow of the native application profile.
101102
*/
102103
@Test
104+
@SuppressWarnings({ "unchecked", "rawtypes" })
103105
public void testSecretRequired() throws Exception {
104106
MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
105107
formData.add("grant_type", "password");
106108
formData.add("client_id", "my-trusted-client-with-secret");
107109
formData.add("username", "marissa");
108110
formData.add("password", "koala");
109-
ResponseEntity<String> response = serverRunning.postForString("/sparklr2/oauth/token", formData);
111+
ResponseEntity<Map> response = serverRunning.postForMap("/sparklr2/oauth/token", formData);
110112
assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
113+
assertEquals(MediaType.APPLICATION_JSON,response.getHeaders().getContentType());
114+
OAuth2Exception oauthException = OAuth2Exception.valueOf(response.getBody());
115+
assertTrue("Should be an instance of InvalidClientException. Got "+oauthException,oauthException instanceof InvalidClientException);
111116
}
112117

113118
/**

spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/filter/ClientCredentialsTokenEndpointFilter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
2323
import org.springframework.security.core.Authentication;
2424
import org.springframework.security.core.AuthenticationException;
25+
import org.springframework.security.oauth2.web.authentication.OAuth2AuthenticationFailureHandler;
2526
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
2627
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
2728

@@ -38,6 +39,7 @@ public class ClientCredentialsTokenEndpointFilter extends AbstractAuthentication
3839

3940
protected ClientCredentialsTokenEndpointFilter() {
4041
super("/oauth/token");
42+
setAuthenticationFailureHandler(new OAuth2AuthenticationFailureHandler());
4143
}
4244

4345
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2011 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package org.springframework.security.oauth2.web.authentication;
14+
15+
import java.io.IOException;
16+
17+
import javax.servlet.ServletException;
18+
import javax.servlet.http.HttpServletRequest;
19+
import javax.servlet.http.HttpServletResponse;
20+
21+
import org.springframework.http.HttpStatus;
22+
import org.springframework.http.ResponseEntity;
23+
import org.springframework.security.core.AuthenticationException;
24+
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
25+
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
26+
import org.springframework.security.oauth2.provider.web.OAuth2ExceptionRenderer;
27+
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
28+
import org.springframework.util.Assert;
29+
import org.springframework.web.context.request.ServletWebRequest;
30+
31+
/**
32+
* Writes an {@link InvalidClientException} to the response using the {@link OAuth2ExceptionRenderer}.
33+
*
34+
* @author Rob Winch
35+
*/
36+
public final class OAuth2AuthenticationFailureHandler implements AuthenticationFailureHandler {
37+
private OAuth2ExceptionRenderer exceptionRenderer = new OAuth2ExceptionRenderer();
38+
39+
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
40+
AuthenticationException exception) throws IOException, ServletException {
41+
42+
InvalidClientException result = new InvalidClientException(exception.getMessage(), exception);
43+
// TODO: the status code should probably be result.getHttpErrorCode() but it returns 400. The spec seems to
44+
// indicate it should be 401. See SECOAUTH-176
45+
ResponseEntity<OAuth2Exception> responseEntity = new ResponseEntity<OAuth2Exception>(result,HttpStatus.UNAUTHORIZED);
46+
47+
try {
48+
exceptionRenderer.handleHttpEntityResponse(responseEntity , new ServletWebRequest(request,response));
49+
}
50+
catch (Exception e) {
51+
throw new ServletException("Failed to render "+responseEntity, e);
52+
}
53+
}
54+
55+
/**
56+
* Set the {@link OAuth2ExceptionRenderer} to be used. This allows for supporting custom Media Types.
57+
* @param exceptionRenderer
58+
*/
59+
public void setExceptionRenderer(OAuth2ExceptionRenderer exceptionRenderer) {
60+
Assert.notNull(exceptionRenderer,"exceptionRenderer cannot be null");
61+
this.exceptionRenderer = exceptionRenderer;
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2011 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package org.springframework.security.oauth2.web.authentication;
14+
15+
import static org.easymock.EasyMock.createMock;
16+
import static org.junit.Assert.assertEquals;
17+
import static org.junit.Assert.assertSame;
18+
import static org.junit.Assert.assertTrue;
19+
20+
import javax.servlet.http.HttpServletRequest;
21+
import javax.servlet.http.HttpServletResponse;
22+
23+
import org.junit.Before;
24+
import org.junit.Rule;
25+
import org.junit.Test;
26+
import org.junit.rules.ExpectedException;
27+
import org.springframework.http.HttpEntity;
28+
import org.springframework.http.HttpStatus;
29+
import org.springframework.http.ResponseEntity;
30+
import org.springframework.security.core.AuthenticationException;
31+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
32+
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
33+
import org.springframework.security.oauth2.provider.web.OAuth2ExceptionRenderer;
34+
import org.springframework.web.context.request.ServletWebRequest;
35+
36+
/**
37+
*
38+
* @author Rob Winch
39+
*/
40+
public class TestOAuth2AuthenticationFailureHandler {
41+
@Rule
42+
public ExpectedException thrown = ExpectedException.none();
43+
44+
private OAuth2ExceptionRendererStub renderer;
45+
private HttpServletRequest request;
46+
private HttpServletResponse response;
47+
private AuthenticationException originalException;
48+
49+
private OAuth2AuthenticationFailureHandler handler;
50+
51+
@Before
52+
public void setUp() {
53+
renderer = new OAuth2ExceptionRendererStub();
54+
request = createMock(HttpServletRequest.class);
55+
response = createMock(HttpServletResponse.class);
56+
originalException = new UsernameNotFoundException("not found");
57+
58+
handler = new OAuth2AuthenticationFailureHandler();
59+
handler.setExceptionRenderer(renderer);
60+
}
61+
62+
@Test
63+
public void onAuthenticationFailure() throws Exception {
64+
handler.onAuthenticationFailure(request, response, originalException);
65+
Object body = renderer.entity.getBody();
66+
assertTrue("The entity should be an InvalidClientException. Got "+body,body instanceof InvalidClientException);
67+
assertEquals(HttpStatus.UNAUTHORIZED,renderer.entity.getStatusCode());
68+
assertSame(request,renderer.webRequest.getNativeRequest());
69+
assertSame(response,renderer.webRequest.getNativeResponse());
70+
}
71+
72+
@Test
73+
public void setExceptionRendererNullExceptionRenderer() {
74+
thrown.expect(IllegalArgumentException.class);
75+
thrown.expectMessage("exceptionRenderer cannot be null");
76+
handler.setExceptionRenderer(null);
77+
}
78+
79+
/**
80+
* Rather than deal with EasyMock class extension just capture the arguments using this stub
81+
*/
82+
private static class OAuth2ExceptionRendererStub extends OAuth2ExceptionRenderer {
83+
private ResponseEntity<?> entity;
84+
private ServletWebRequest webRequest;
85+
@Override
86+
public void handleHttpEntityResponse(HttpEntity<?> responseEntity, ServletWebRequest webRequest)
87+
throws Exception {
88+
this.entity = (ResponseEntity<?>) responseEntity;
89+
this.webRequest = webRequest;
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)