Skip to content

Commit 11030d2

Browse files
committed
Add client_credentials grant type support
1 parent c803eec commit 11030d2

17 files changed

+624
-296
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.server.authorization.authentication;
17+
18+
import org.springframework.security.authentication.AuthenticationProvider;
19+
import org.springframework.security.core.Authentication;
20+
import org.springframework.security.core.AuthenticationException;
21+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
22+
import org.springframework.security.oauth2.core.OAuth2Error;
23+
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
24+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
25+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
26+
import org.springframework.security.oauth2.server.authorization.token.TokenIssuer;
27+
28+
/**
29+
* An {@link AuthenticationProvider} that converts give {@link OAuth2ClientCredentialsAuthenticationToken} to a {@link OAuth2AccessTokenAuthenticationToken}
30+
* using the provided token generator.
31+
*
32+
* @author Alexey Nesterov
33+
*/
34+
public class OAuth2ClientCredentialsAuthenticationProvider implements AuthenticationProvider {
35+
36+
private final RegisteredClientRepository registeredClientsRepository;
37+
private final TokenIssuer<RegisteredClient> tokenIssuer;
38+
39+
public OAuth2ClientCredentialsAuthenticationProvider(RegisteredClientRepository registeredClientsRepository, TokenIssuer<RegisteredClient> tokenIssuer) {
40+
this.registeredClientsRepository = registeredClientsRepository;
41+
this.tokenIssuer = tokenIssuer;
42+
}
43+
44+
@Override
45+
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
46+
final OAuth2ClientCredentialsAuthenticationToken token = (OAuth2ClientCredentialsAuthenticationToken) authentication;
47+
final String clientId = token.getPrincipal().toString();
48+
final String clientSecret = token.getCredentials().toString();
49+
50+
RegisteredClient registeredClient = this.registeredClientsRepository.findByClientId(clientId);
51+
if (registeredClient == null) {
52+
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
53+
}
54+
55+
if (!clientSecret.equals(registeredClient.getClientSecret())) {
56+
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
57+
}
58+
59+
return new OAuth2AccessTokenAuthenticationToken(registeredClient, token, this.tokenIssuer.issue(registeredClient));
60+
}
61+
62+
@Override
63+
public boolean supports(Class<?> authentication) {
64+
return authentication == OAuth2ClientCredentialsAuthenticationToken.class;
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.oauth2.server.authorization.authentication;
18+
19+
import java.util.Collections;
20+
21+
import org.springframework.security.authentication.AbstractAuthenticationToken;
22+
import org.springframework.util.Assert;
23+
24+
public class OAuth2ClientCredentialsAuthenticationToken extends AbstractAuthenticationToken {
25+
26+
private final String clientId;
27+
private final String clientSecret;
28+
29+
public OAuth2ClientCredentialsAuthenticationToken(String clientId, String clientSecret) {
30+
super(Collections.emptyList());
31+
32+
Assert.hasText(clientId, "clientId cannot be empty");
33+
Assert.hasText(clientSecret, "clientId cannot be empty");
34+
35+
this.clientId = clientId;
36+
this.clientSecret = clientSecret;
37+
}
38+
39+
@Override
40+
public Object getCredentials() {
41+
return this.clientSecret;
42+
}
43+
44+
@Override
45+
public Object getPrincipal() {
46+
return this.clientId;
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.server.authorization.token;
17+
18+
import org.springframework.security.oauth2.core.OAuth2AccessToken;
19+
20+
/**
21+
* @author Alexey Nesterov
22+
*/
23+
public interface TokenIssuer<PRINCIPAL> {
24+
OAuth2AccessToken issue(PRINCIPAL principal);
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.oauth2.server.authorization.web;
18+
19+
import javax.servlet.http.HttpServletRequest;
20+
21+
import org.springframework.core.convert.converter.Converter;
22+
import org.springframework.security.core.Authentication;
23+
import org.springframework.security.core.context.SecurityContextHolder;
24+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
25+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
26+
import org.springframework.security.oauth2.core.OAuth2Error;
27+
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
28+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
29+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
30+
import org.springframework.util.MultiValueMap;
31+
import org.springframework.util.StringUtils;
32+
33+
class AuthorizationCodeAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
34+
35+
@Override
36+
public Authentication convert(HttpServletRequest request) {
37+
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
38+
39+
// grant_type (REQUIRED)
40+
String grantType = parameters.getFirst(OAuth2ParameterNames.GRANT_TYPE);
41+
if (!StringUtils.hasText(grantType) ||
42+
parameters.get(OAuth2ParameterNames.GRANT_TYPE).size() != 1) {
43+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.GRANT_TYPE);
44+
}
45+
if (!AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(grantType)) {
46+
throwError(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, OAuth2ParameterNames.GRANT_TYPE);
47+
}
48+
49+
// client_id (REQUIRED)
50+
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
51+
Authentication clientPrincipal = null;
52+
if (StringUtils.hasText(clientId)) {
53+
if (parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
54+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
55+
}
56+
} else {
57+
clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
58+
}
59+
60+
// code (REQUIRED)
61+
String code = parameters.getFirst(OAuth2ParameterNames.CODE);
62+
if (!StringUtils.hasText(code) ||
63+
parameters.get(OAuth2ParameterNames.CODE).size() != 1) {
64+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CODE);
65+
}
66+
67+
// redirect_uri (REQUIRED)
68+
// Required only if the "redirect_uri" parameter was included in the authorization request
69+
String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
70+
if (StringUtils.hasText(redirectUri) &&
71+
parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
72+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
73+
}
74+
75+
return clientPrincipal != null ?
76+
new OAuth2AuthorizationCodeAuthenticationToken(code, clientPrincipal, redirectUri) :
77+
new OAuth2AuthorizationCodeAuthenticationToken(code, clientId, redirectUri);
78+
}
79+
80+
private void throwError(String errorCode, String parameterName) {
81+
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName,
82+
"https://tools.ietf.org/html/rfc6749#section-5.2");
83+
throw new OAuth2AuthenticationException(error);
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.oauth2.server.authorization.web;
18+
19+
import javax.servlet.http.HttpServletRequest;
20+
21+
import org.springframework.core.convert.converter.Converter;
22+
import org.springframework.security.core.Authentication;
23+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
24+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
25+
import org.springframework.security.oauth2.core.OAuth2Error;
26+
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
27+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
28+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
29+
import org.springframework.util.MultiValueMap;
30+
import org.springframework.util.StringUtils;
31+
32+
class ClientCredentialsAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
33+
34+
@Override
35+
public Authentication convert(HttpServletRequest request) {
36+
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
37+
38+
// grant_type (REQUIRED)
39+
String grantType = parameters.getFirst(OAuth2ParameterNames.GRANT_TYPE);
40+
if (!StringUtils.hasText(grantType) ||
41+
parameters.get(OAuth2ParameterNames.GRANT_TYPE).size() != 1) {
42+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.GRANT_TYPE);
43+
}
44+
if (!AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(grantType)) {
45+
throwError(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, OAuth2ParameterNames.GRANT_TYPE);
46+
}
47+
48+
// client_id (REQUIRED)
49+
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
50+
if (!StringUtils.hasText(clientId)) {
51+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
52+
}
53+
54+
// client_secret (REQUIRED)
55+
String clientSecret = parameters.getFirst(OAuth2ParameterNames.CLIENT_SECRET);
56+
if (!StringUtils.hasText(clientSecret)) {
57+
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_SECRET);
58+
}
59+
60+
return new OAuth2ClientCredentialsAuthenticationToken(clientId, clientSecret);
61+
}
62+
63+
private void throwError(String errorCode, String parameterName) {
64+
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName,
65+
"https://tools.ietf.org/html/rfc6749#section-5.2");
66+
throw new OAuth2AuthenticationException(error);
67+
}
68+
}

0 commit comments

Comments
 (0)