Skip to content

Commit a096558

Browse files
author
Dave Syer
committed
SECOAUTH-172: Move client credential checking to a filter
1 parent 544eebe commit a096558

File tree

15 files changed

+276
-107
lines changed

15 files changed

+276
-107
lines changed

samples/oauth2/sparklr/src/main/webapp/WEB-INF/spring-servlet.xml

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,25 @@
33
xmlns:oauth="http://www.springframework.org/schema/security/oauth2" xmlns:sec="http://www.springframework.org/schema/security"
44
xmlns:mvc="http://www.springframework.org/schema/mvc"
55
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
6-
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
6+
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
77
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
8-
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
8+
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
9+
10+
<http pattern="/oauth/token" create-session="never" authentication-manager-ref="clientAuthenticationManager"
11+
xmlns="http://www.springframework.org/schema/security">
12+
<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
13+
<anonymous enabled="false" />
14+
<http-basic />
15+
<!-- include this only if you need to authenticate clients via request parameters -->
16+
<custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" />
17+
</http>
918

1019
<http access-denied-page="/login.jsp" access-decision-manager-ref="accessDecisionManager" xmlns="http://www.springframework.org/schema/security">
1120
<intercept-url pattern="/photos" access="ROLE_USER,SCOPE_READ" />
1221
<intercept-url pattern="/photos/**" access="ROLE_USER,SCOPE_READ" />
1322
<intercept-url pattern="/trusted/**" access="ROLE_CLIENT,SCOPE_TRUST" />
1423
<intercept-url pattern="/user/**" access="ROLE_USER,SCOPE_TRUST" />
15-
<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_ANONYMOUSLY" />
24+
<!-- This needs to be anonymous so that the auth endpoint can handle oauth errors itself -->
1625
<intercept-url pattern="/oauth/authorize" access="IS_AUTHENTICATED_ANONYMOUSLY" />
1726
<intercept-url pattern="/oauth/**" access="ROLE_USER" />
1827
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY,DENY_OAUTH" />
@@ -24,6 +33,10 @@
2433
<custom-filter ref="resourceServerFilter" before="EXCEPTION_TRANSLATION_FILTER" />
2534
</http>
2635

36+
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.filter.ClientCredentialsTokenEndpointFilter">
37+
<property name="authenticationManager" ref="clientAuthenticationManager" />
38+
</bean>
39+
2740
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased" xmlns="http://www.springframework.org/schema/beans">
2841
<constructor-arg>
2942
<list>
@@ -34,6 +47,10 @@
3447
</constructor-arg>
3548
</bean>
3649

50+
<authentication-manager id="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
51+
<authentication-provider user-service-ref="clientDetailsUserService" />
52+
</authentication-manager>
53+
3754
<authentication-manager alias="authenticationManager" xmlns="http://www.springframework.org/schema/security">
3855
<authentication-provider>
3956
<user-service>
@@ -43,14 +60,18 @@
4360
</authentication-provider>
4461
</authentication-manager>
4562

63+
<bean id="clientDetailsUserService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
64+
<constructor-arg ref="clientDetails" />
65+
</bean>
66+
4667
<bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.RandomValueTokenServices">
4768
<property name="tokenStore">
4869
<bean class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />
4970
</property>
5071
<property name="supportRefreshToken" value="true" />
5172
</bean>
5273

53-
<oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices" >
74+
<oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices">
5475
<oauth:authorization-code />
5576
<oauth:implicit />
5677
<oauth:refresh-token />

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ public String getBaseUrl() {
174174
}
175175

176176
public String getUrl(String path) {
177+
if (path.startsWith("http")) {
178+
return path;
179+
}
177180
if (!path.startsWith("/")) {
178181
path = "/" + path;
179182
}

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class TestAuthorizationCodeProvider {
4242

4343
@Rule
4444
public ServerRunning serverRunning = ServerRunning.isRunning();
45-
45+
4646
/**
4747
* tests the basic authorization code provider
4848
*/
@@ -311,23 +311,19 @@ public void testClientIdProvidedInHeader() throws Exception {
311311
userAgent.setRedirectEnabled(false);
312312
URI uri = serverRunning.buildUri("/sparklr2/oauth/authorize").queryParam("response_type", "code")
313313
.queryParam("state", "mystateid")
314-
// .queryParam("client_id", "my-less-trusted-client")
315314
.queryParam("redirect_uri", "http://anywhere").build();
316315
WebRequestSettings settings = new WebRequestSettings(uri.toURL());
317316
settings.setAdditionalHeader("Authorization", String.format("Basic %s",
318317
new String(Base64.encode(String.format("%s:", "my-less-trusted-client").getBytes("UTF-8")), "UTF-8")));
319318

320-
String location = null;
321319
try {
322320
userAgent.getPage(settings);
323321
fail("should have been redirected to the login form.");
324322
}
325323
catch (FailingHttpStatusCodeException e) {
326-
assertEquals(302, e.getResponse().getStatusCode());
327-
location = e.getResponse().getResponseHeaderValue("Location");
324+
assertEquals(400, e.getResponse().getStatusCode());
325+
// The client id has to go in the query params, unless it is for authentication
328326
}
329-
330-
assertTrue("Wrong location: "+location, location.contains("login.jsp"));
331327

332328
}
333329

spring-security-oauth2/src/main/java/org/springframework/security/oauth2/config/AuthorizationServerBeanDefinitionParser.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P
4646
String tokenServicesRef, String serializerRef) {
4747

4848
String clientDetailsRef = element.getAttribute("client-details-service-ref");
49-
String passwordEncoderRef = element.getAttribute("password-encoder-ref");
5049
String tokenEndpointUrl = element.getAttribute("token-endpoint-url");
5150
String authorizationEndpointUrl = element.getAttribute("authorization-endpoint-url");
5251
String tokenGranterRef = element.getAttribute("token-granter-ref");
@@ -142,9 +141,6 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P
142141
parserContext.getRegistry().registerBeanDefinition("oauth2AuthorizationEndpoint",
143142
authorizationEndpointBean.getBeanDefinition());
144143
authorizationEndpointBean.addPropertyReference("tokenGranter", tokenGranterRef);
145-
if (StringUtils.hasText(passwordEncoderRef)) {
146-
authorizationCodeTokenGranterBean.addPropertyReference("passwordEncoder", passwordEncoderRef);
147-
}
148144

149145
if (tokenGranters!=null) {
150146
tokenGranters.add(authorizationCodeTokenGranterBean.getBeanDefinition());
@@ -159,9 +155,6 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P
159155
.rootBeanDefinition(RefreshTokenGranter.class);
160156
refreshTokenGranterBean.addConstructorArgReference(tokenServicesRef);
161157
refreshTokenGranterBean.addConstructorArgReference(clientDetailsRef);
162-
if (StringUtils.hasText(passwordEncoderRef)) {
163-
refreshTokenGranterBean.addPropertyReference("passwordEncoder", passwordEncoderRef);
164-
}
165158
tokenGranters.add(refreshTokenGranterBean.getBeanDefinition());
166159
}
167160
Element implicitElement = DomUtils.getChildElementByTagName(element, "implicit");
@@ -170,9 +163,6 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P
170163
.rootBeanDefinition(ImplicitTokenGranter.class);
171164
implicitGranterBean.addConstructorArgReference(tokenServicesRef);
172165
implicitGranterBean.addConstructorArgReference(clientDetailsRef);
173-
if (StringUtils.hasText(passwordEncoderRef)) {
174-
implicitGranterBean.addPropertyReference("passwordEncoder", passwordEncoderRef);
175-
}
176166
tokenGranters.add(implicitGranterBean.getBeanDefinition());
177167
}
178168
Element clientCredentialsElement = DomUtils.getChildElementByTagName(element, "client-credentials");
@@ -182,9 +172,6 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P
182172
.rootBeanDefinition(ClientCredentialsTokenGranter.class);
183173
clientCredentialsGranterBean.addConstructorArgReference(tokenServicesRef);
184174
clientCredentialsGranterBean.addConstructorArgReference(clientDetailsRef);
185-
if (StringUtils.hasText(passwordEncoderRef)) {
186-
clientCredentialsGranterBean.addPropertyReference("passwordEncoder", passwordEncoderRef);
187-
}
188175
tokenGranters.add(clientCredentialsGranterBean.getBeanDefinition());
189176
}
190177
Element clientPasswordElement = DomUtils.getChildElementByTagName(element, "password");
@@ -199,9 +186,6 @@ protected AbstractBeanDefinition parseEndpointAndReturnFilter(Element element, P
199186
clientPasswordTokenGranter.addConstructorArgReference(authenticationManagerRef);
200187
clientPasswordTokenGranter.addConstructorArgReference(tokenServicesRef);
201188
clientPasswordTokenGranter.addConstructorArgReference(clientDetailsRef);
202-
if (StringUtils.hasText(passwordEncoderRef)) {
203-
clientPasswordTokenGranter.addPropertyReference("passwordEncoder", passwordEncoderRef);
204-
}
205189
tokenGranters.add(clientPasswordTokenGranter.getBeanDefinition());
206190
}
207191
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import java.util.Map;
1616
import java.util.Set;
1717

18-
import org.springframework.security.authentication.encoding.PasswordEncoder;
1918
import org.springframework.security.oauth2.common.OAuth2AccessToken;
2019
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
2120

@@ -38,10 +37,6 @@ protected AbstractTokenGranter(AuthorizationServerTokenServices tokenServices,
3837
this.tokenServices = tokenServices;
3938
}
4039

41-
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
42-
clientCredentialsChecker.setPasswordEncoder(passwordEncoder);
43-
}
44-
4540
public OAuth2AccessToken grant(String grantType, Map<String, String> parameters, String clientId,
4641
String clientSecret, Set<String> scopes) {
4742

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

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616
import java.util.List;
1717
import java.util.Set;
1818

19-
import org.springframework.security.authentication.encoding.PasswordEncoder;
20-
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
2119
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
2220
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
23-
import org.springframework.security.oauth2.common.exceptions.UnauthorizedClientException;
2421

2522
/**
2623
* @author Dave Syer
@@ -30,16 +27,10 @@ public class ClientCredentialsChecker {
3027

3128
private final ClientDetailsService clientDetailsService;
3229

33-
private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();
34-
3530
public ClientCredentialsChecker(ClientDetailsService clientDetailsService) {
3631
this.clientDetailsService = clientDetailsService;
3732
}
3833

39-
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
40-
this.passwordEncoder = passwordEncoder;
41-
}
42-
4334
public ClientToken validateCredentials(String grantType, String clientId, String clientSecret) {
4435
return this.validateCredentials(grantType, clientId, clientSecret, null);
4536
}
@@ -51,32 +42,11 @@ public ClientToken validateCredentials(String grantType, String clientId, String
5142
if (scopes != null) {
5243
validateScope(clientDetails, scopes);
5344
}
54-
validateClient(clientDetails, clientSecret);
55-
5645
return new ClientToken(clientId, new HashSet<String>(clientDetails.getResourceIds()), clientSecret, scopes,
5746
clientDetails.getAuthorities());
5847

5948
}
6049

61-
private void validateClient(ClientDetails clientDetails, String clientSecret) {
62-
if (clientDetails.isSecretRequired()) {
63-
String assertedSecret = clientSecret;
64-
if (assertedSecret == null) {
65-
throw new UnauthorizedClientException("Client secret is required but not provided.");
66-
}
67-
else {
68-
Object salt = null;
69-
if (clientDetails instanceof SaltedClientSecret) {
70-
salt = ((SaltedClientSecret) clientDetails).getSalt();
71-
}
72-
73-
if (!passwordEncoder.isPasswordValid(clientDetails.getClientSecret(), assertedSecret, salt)) {
74-
throw new UnauthorizedClientException("Invalid client secret.");
75-
}
76-
}
77-
}
78-
}
79-
8050
private void validateScope(ClientDetails clientDetails, Set<String> scopes) {
8151

8252
if (clientDetails.isScoped()) {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2006-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.provider.client;
14+
15+
import java.util.Collection;
16+
17+
import org.springframework.security.authentication.AbstractAuthenticationToken;
18+
import org.springframework.security.core.GrantedAuthority;
19+
20+
/**
21+
* @author Dave Syer
22+
*
23+
*/
24+
public class ClientAuthenticationToken extends AbstractAuthenticationToken {
25+
26+
private final String clientId;
27+
private final String clientSecret;
28+
29+
public ClientAuthenticationToken(String clientId, String clientSecret,
30+
Collection<? extends GrantedAuthority> authorities) {
31+
super(authorities);
32+
this.clientId = clientId;
33+
this.clientSecret = clientSecret;
34+
}
35+
36+
public ClientAuthenticationToken(String clientId, String clientSecret) {
37+
this(clientId, clientSecret, null);
38+
}
39+
40+
public Object getCredentials() {
41+
return clientSecret;
42+
}
43+
44+
public Object getPrincipal() {
45+
return clientId;
46+
}
47+
48+
public String getClientId() {
49+
return clientId;
50+
}
51+
52+
public String getClientSecret() {
53+
return clientSecret;
54+
}
55+
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2006-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.provider.client;
14+
15+
import org.springframework.security.core.userdetails.User;
16+
import org.springframework.security.core.userdetails.UserDetails;
17+
import org.springframework.security.core.userdetails.UserDetailsService;
18+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
19+
import org.springframework.security.oauth2.provider.ClientDetails;
20+
import org.springframework.security.oauth2.provider.ClientDetailsService;
21+
22+
/**
23+
* @author Dave Syer
24+
*
25+
*/
26+
public class ClientDetailsUserDetailsService implements UserDetailsService {
27+
28+
private final ClientDetailsService clientDetailsService;
29+
30+
public ClientDetailsUserDetailsService(ClientDetailsService clientDetailsService) {
31+
this.clientDetailsService = clientDetailsService;
32+
}
33+
34+
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
35+
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
36+
String clientSecret = clientDetails.getClientSecret() == null ? "" : clientDetails.getClientSecret();
37+
return new User(username, clientSecret, clientDetails.getAuthorities());
38+
}
39+
40+
}

spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/code/AuthorizationCodeTokenGranter.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.Map;
2020
import java.util.Set;
2121

22-
import org.springframework.security.authentication.encoding.PasswordEncoder;
2322
import org.springframework.security.core.Authentication;
2423
import org.springframework.security.oauth2.common.OAuth2AccessToken;
2524
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
@@ -47,10 +46,6 @@ public class AuthorizationCodeTokenGranter implements TokenGranter {
4746

4847
private final AuthorizationServerTokenServices tokenServices;
4948

50-
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
51-
clientCredentialsChecker.setPasswordEncoder(passwordEncoder);
52-
}
53-
5449
public AuthorizationCodeTokenGranter(AuthorizationServerTokenServices tokenServices,
5550
AuthorizationCodeServices authorizationCodeServices, ClientDetailsService clientDetailsService) {
5651
this.tokenServices = tokenServices;

0 commit comments

Comments
 (0)