MitreID code compiles against latest Spring libraries
parent
711a2e7eab
commit
12bfab4f55
|
@ -50,8 +50,6 @@ import org.mitre.oauth2.model.convert.JWTStringConverter;
|
|||
import org.mitre.openid.connect.model.ApprovedSite;
|
||||
import org.mitre.uma.model.Permission;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Deserializer;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Serializer;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer;
|
||||
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
|
||||
|
@ -74,8 +72,6 @@ import com.nimbusds.jwt.JWT;
|
|||
@NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_RESOURCE_SET, query = "select a from OAuth2AccessTokenEntity a join a.permissions p where p.resourceSet.id = :" + OAuth2AccessTokenEntity.PARAM_RESOURCE_SET_ID),
|
||||
@NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_NAME, query = "select r from OAuth2AccessTokenEntity r where r.authenticationHolder.userAuth.name = :" + OAuth2AccessTokenEntity.PARAM_NAME)
|
||||
})
|
||||
@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class)
|
||||
@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class)
|
||||
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class)
|
||||
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class)
|
||||
public class OAuth2AccessTokenEntity implements OAuth2AccessToken {
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*******************************************************************************/
|
||||
package org.mitre.oauth2.service.impl;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
|
@ -91,7 +90,7 @@ public class UriEncodedClientUserDetailsService implements UserDetailsService {
|
|||
} else {
|
||||
throw new UsernameNotFoundException("Client not found: " + clientId);
|
||||
}
|
||||
} catch (UnsupportedEncodingException | InvalidClientException e) {
|
||||
} catch (InvalidClientException e) {
|
||||
throw new UsernameNotFoundException("Client not found: " + clientId);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
*******************************************************************************/
|
||||
package org.mitre.openid.connect.web;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
@ -218,10 +217,6 @@ public class DynamicClientRegistrationEndpoint {
|
|||
m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201
|
||||
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Unsupported encoding", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return HttpCodeView.VIEWNAME;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Couldn't save client", e);
|
||||
|
||||
|
@ -260,23 +255,16 @@ public class DynamicClientRegistrationEndpoint {
|
|||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
|
||||
OAuth2AccessTokenEntity token = rotateRegistrationTokenIfNecessary(auth, client);
|
||||
RegisteredClient registered =
|
||||
new RegisteredClient(client, token.getValue(), config.getIssuer() + "register/"
|
||||
+ UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));
|
||||
|
||||
try {
|
||||
OAuth2AccessTokenEntity token = rotateRegistrationTokenIfNecessary(auth, client);
|
||||
RegisteredClient registered =
|
||||
new RegisteredClient(client, token.getValue(), config.getIssuer() + "register/"
|
||||
+ UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));
|
||||
// send it all out to the view
|
||||
m.addAttribute("client", registered);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
|
||||
|
||||
// send it all out to the view
|
||||
m.addAttribute("client", registered);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
|
||||
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Unsupported encoding", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
|
||||
} else {
|
||||
// client mismatch
|
||||
|
@ -380,10 +368,6 @@ public class DynamicClientRegistrationEndpoint {
|
|||
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
|
||||
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Unsupported encoding", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return HttpCodeView.VIEWNAME;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Couldn't save client", e);
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*******************************************************************************/
|
||||
package org.mitre.openid.connect.web;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
|
@ -59,412 +58,431 @@ import com.google.gson.JsonSyntaxException;
|
|||
@RequestMapping(value = ProtectedResourceRegistrationEndpoint.URL)
|
||||
public class ProtectedResourceRegistrationEndpoint {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static final String URL = "resource";
|
||||
|
||||
@Autowired
|
||||
private ClientDetailsEntityService clientService;
|
||||
|
||||
@Autowired
|
||||
private OAuth2TokenEntityService tokenService;
|
||||
|
||||
@Autowired
|
||||
private SystemScopeService scopeService;
|
||||
|
||||
@Autowired
|
||||
private ConfigurationPropertiesBean config;
|
||||
|
||||
@Autowired
|
||||
private OIDCTokenService connectTokenService;
|
||||
|
||||
/**
|
||||
* Logger for this class
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProtectedResourceRegistrationEndpoint.class);
|
||||
|
||||
/**
|
||||
* Create a new Client, issue a client ID, and create a registration access token.
|
||||
* @param jsonString
|
||||
* @param m
|
||||
* @param p
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String registerNewProtectedResource(@RequestBody String jsonString, Model m) {
|
||||
|
||||
ClientDetailsEntity newClient = null;
|
||||
try {
|
||||
newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
|
||||
} catch (JsonSyntaxException e) {
|
||||
// bad parse
|
||||
// didn't parse, this is a bad request
|
||||
logger.error("registerNewProtectedResource failed; submitted JSON is malformed");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
|
||||
if (newClient != null) {
|
||||
// it parsed!
|
||||
|
||||
//
|
||||
// Now do some post-processing consistency checks on it
|
||||
//
|
||||
|
||||
// clear out any spurious id/secret (clients don't get to pick)
|
||||
newClient.setClientId(null);
|
||||
newClient.setClientSecret(null);
|
||||
|
||||
// do validation on the fields
|
||||
try {
|
||||
newClient = validateScopes(newClient);
|
||||
newClient = validateAuth(newClient);
|
||||
} catch (ValidationException ve) {
|
||||
// validation failed, return an error
|
||||
m.addAttribute(JsonErrorView.ERROR, ve.getError());
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription());
|
||||
m.addAttribute(HttpCodeView.CODE, ve.getStatus());
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
|
||||
|
||||
// no grant types are allowed
|
||||
newClient.setGrantTypes(new HashSet<String>());
|
||||
newClient.setResponseTypes(new HashSet<String>());
|
||||
newClient.setRedirectUris(new HashSet<String>());
|
||||
|
||||
// don't issue tokens to this client
|
||||
newClient.setAccessTokenValiditySeconds(0);
|
||||
newClient.setIdTokenValiditySeconds(0);
|
||||
newClient.setRefreshTokenValiditySeconds(0);
|
||||
|
||||
// clear out unused fields
|
||||
newClient.setDefaultACRvalues(new HashSet<String>());
|
||||
newClient.setDefaultMaxAge(null);
|
||||
newClient.setIdTokenEncryptedResponseAlg(null);
|
||||
newClient.setIdTokenEncryptedResponseEnc(null);
|
||||
newClient.setIdTokenSignedResponseAlg(null);
|
||||
newClient.setInitiateLoginUri(null);
|
||||
newClient.setPostLogoutRedirectUris(null);
|
||||
newClient.setRequestObjectSigningAlg(null);
|
||||
newClient.setRequireAuthTime(null);
|
||||
newClient.setReuseRefreshToken(false);
|
||||
newClient.setSectorIdentifierUri(null);
|
||||
newClient.setSubjectType(null);
|
||||
newClient.setUserInfoEncryptedResponseAlg(null);
|
||||
newClient.setUserInfoEncryptedResponseEnc(null);
|
||||
newClient.setUserInfoSignedResponseAlg(null);
|
||||
|
||||
// this client has been dynamically registered (obviously)
|
||||
newClient.setDynamicallyRegistered(true);
|
||||
|
||||
// this client has access to the introspection endpoint
|
||||
newClient.setAllowIntrospection(true);
|
||||
|
||||
// now save it
|
||||
try {
|
||||
ClientDetailsEntity savedClient = clientService.saveNewClient(newClient);
|
||||
|
||||
// generate the registration access token
|
||||
OAuth2AccessTokenEntity token = connectTokenService.createResourceAccessToken(savedClient);
|
||||
tokenService.saveAccessToken(token);
|
||||
|
||||
// send it all out to the view
|
||||
|
||||
RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
|
||||
m.addAttribute("client", registered);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201
|
||||
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Unsupported encoding", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return HttpCodeView.VIEWNAME;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Couldn't save client", e);
|
||||
|
||||
m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client due to invalid or inconsistent metadata.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
} else {
|
||||
// didn't parse, this is a bad request
|
||||
logger.error("registerNewClient failed; submitted JSON is malformed");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ClientDetailsEntity validateScopes(ClientDetailsEntity newClient) throws ValidationException {
|
||||
// scopes that the client is asking for
|
||||
Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope());
|
||||
|
||||
// the scopes that the client can have must be a subset of the dynamically allowed scopes
|
||||
Set<SystemScope> allowedScopes = scopeService.removeRestrictedAndReservedScopes(requestedScopes);
|
||||
|
||||
// if the client didn't ask for any, give them the defaults
|
||||
if (allowedScopes == null || allowedScopes.isEmpty()) {
|
||||
allowedScopes = scopeService.getDefaults();
|
||||
}
|
||||
|
||||
newClient.setScope(scopeService.toStrings(allowedScopes));
|
||||
|
||||
return newClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the meta information for a client.
|
||||
* @param clientId
|
||||
* @param m
|
||||
* @param auth
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String readResourceConfiguration(@PathVariable("id") String clientId, Model m, OAuth2Authentication auth) {
|
||||
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
|
||||
|
||||
|
||||
|
||||
try {
|
||||
// possibly update the token
|
||||
OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client);
|
||||
|
||||
RegisteredClient registered = new RegisteredClient(client, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));
|
||||
|
||||
// send it all out to the view
|
||||
m.addAttribute("client", registered);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
|
||||
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Unsupported encoding", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
} else {
|
||||
// client mismatch
|
||||
logger.error("readResourceConfiguration failed, client ID mismatch: "
|
||||
+ clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metainformation for a given client.
|
||||
* @param clientId
|
||||
* @param jsonString
|
||||
* @param m
|
||||
* @param auth
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String updateProtectedResource(@PathVariable("id") String clientId, @RequestBody String jsonString, Model m, OAuth2Authentication auth) {
|
||||
|
||||
|
||||
ClientDetailsEntity newClient = null;
|
||||
try {
|
||||
newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
|
||||
} catch (JsonSyntaxException e) {
|
||||
// bad parse
|
||||
// didn't parse, this is a bad request
|
||||
logger.error("updateProtectedResource failed; submitted JSON is malformed");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
|
||||
ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (newClient != null && oldClient != null // we have an existing client and the new one parsed
|
||||
&& oldClient.getClientId().equals(auth.getOAuth2Request().getClientId()) // the client passed in the URI matches the one in the auth
|
||||
&& oldClient.getClientId().equals(newClient.getClientId()) // the client passed in the body matches the one in the URI
|
||||
) {
|
||||
|
||||
// a client can't ask to update its own client secret to any particular value
|
||||
newClient.setClientSecret(oldClient.getClientSecret());
|
||||
|
||||
newClient.setCreatedAt(oldClient.getCreatedAt());
|
||||
|
||||
// no grant types are allowed
|
||||
newClient.setGrantTypes(new HashSet<String>());
|
||||
newClient.setResponseTypes(new HashSet<String>());
|
||||
newClient.setRedirectUris(new HashSet<String>());
|
||||
|
||||
// don't issue tokens to this client
|
||||
newClient.setAccessTokenValiditySeconds(0);
|
||||
newClient.setIdTokenValiditySeconds(0);
|
||||
newClient.setRefreshTokenValiditySeconds(0);
|
||||
|
||||
// clear out unused fields
|
||||
newClient.setDefaultACRvalues(new HashSet<String>());
|
||||
newClient.setDefaultMaxAge(null);
|
||||
newClient.setIdTokenEncryptedResponseAlg(null);
|
||||
newClient.setIdTokenEncryptedResponseEnc(null);
|
||||
newClient.setIdTokenSignedResponseAlg(null);
|
||||
newClient.setInitiateLoginUri(null);
|
||||
newClient.setPostLogoutRedirectUris(null);
|
||||
newClient.setRequestObjectSigningAlg(null);
|
||||
newClient.setRequireAuthTime(null);
|
||||
newClient.setReuseRefreshToken(false);
|
||||
newClient.setSectorIdentifierUri(null);
|
||||
newClient.setSubjectType(null);
|
||||
newClient.setUserInfoEncryptedResponseAlg(null);
|
||||
newClient.setUserInfoEncryptedResponseEnc(null);
|
||||
newClient.setUserInfoSignedResponseAlg(null);
|
||||
|
||||
// this client has been dynamically registered (obviously)
|
||||
newClient.setDynamicallyRegistered(true);
|
||||
|
||||
// this client has access to the introspection endpoint
|
||||
newClient.setAllowIntrospection(true);
|
||||
|
||||
// do validation on the fields
|
||||
try {
|
||||
newClient = validateScopes(newClient);
|
||||
newClient = validateAuth(newClient);
|
||||
} catch (ValidationException ve) {
|
||||
// validation failed, return an error
|
||||
m.addAttribute(JsonErrorView.ERROR, ve.getError());
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription());
|
||||
m.addAttribute(HttpCodeView.CODE, ve.getStatus());
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// save the client
|
||||
ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient);
|
||||
|
||||
// possibly update the token
|
||||
OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, savedClient);
|
||||
|
||||
RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
|
||||
|
||||
// send it all out to the view
|
||||
m.addAttribute("client", registered);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
|
||||
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Unsupported encoding", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return HttpCodeView.VIEWNAME;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Couldn't save client", e);
|
||||
|
||||
m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client due to invalid or inconsistent metadata.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
} else {
|
||||
// client mismatch
|
||||
logger.error("updateProtectedResource" +
|
||||
" failed, client ID mismatch: "
|
||||
+ clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the indicated client from the system.
|
||||
* @param clientId
|
||||
* @param m
|
||||
* @param auth
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String deleteResource(@PathVariable("id") String clientId, Model m, OAuth2Authentication auth) {
|
||||
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
|
||||
|
||||
clientService.deleteClient(client);
|
||||
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); // http 204
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
} else {
|
||||
// client mismatch
|
||||
logger.error("readClientConfiguration failed, client ID mismatch: "
|
||||
+ clientId + " and " + auth.getOAuth2Request().getClientId() + " do not match.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
}
|
||||
|
||||
private ClientDetailsEntity validateAuth(ClientDetailsEntity newClient) throws ValidationException {
|
||||
if (newClient.getTokenEndpointAuthMethod() == null) {
|
||||
newClient.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC);
|
||||
}
|
||||
|
||||
if (newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_BASIC ||
|
||||
newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_JWT ||
|
||||
newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_POST) {
|
||||
|
||||
if (Strings.isNullOrEmpty(newClient.getClientSecret())) {
|
||||
// no secret yet, we need to generate a secret
|
||||
newClient = clientService.generateClientSecret(newClient);
|
||||
}
|
||||
} else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.PRIVATE_KEY) {
|
||||
if (Strings.isNullOrEmpty(newClient.getJwksUri()) && newClient.getJwks() == null) {
|
||||
throw new ValidationException("invalid_client_metadata", "JWK Set URI required when using private key authentication", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
newClient.setClientSecret(null);
|
||||
} else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.NONE) {
|
||||
newClient.setClientSecret(null);
|
||||
} else {
|
||||
throw new ValidationException("invalid_client_metadata", "Unknown authentication method", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return newClient;
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenEntity fetchValidRegistrationToken(OAuth2Authentication auth, ClientDetailsEntity client) {
|
||||
|
||||
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
|
||||
OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());
|
||||
|
||||
if (config.getRegTokenLifeTime() != null) {
|
||||
|
||||
try {
|
||||
// Re-issue the token if it has been issued before [currentTime - validity]
|
||||
Date validToDate = new Date(System.currentTimeMillis() - config.getRegTokenLifeTime() * 1000);
|
||||
if(token.getJwt().getJWTClaimsSet().getIssueTime().before(validToDate)) {
|
||||
logger.info("Rotating the registration access token for " + client.getClientId());
|
||||
tokenService.revokeAccessToken(token);
|
||||
OAuth2AccessTokenEntity newToken = connectTokenService.createResourceAccessToken(client);
|
||||
tokenService.saveAccessToken(newToken);
|
||||
return newToken;
|
||||
} else {
|
||||
// it's not expired, keep going
|
||||
return token;
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
logger.error("Couldn't parse a known-valid token?", e);
|
||||
return token;
|
||||
}
|
||||
} else {
|
||||
// tokens don't expire, just return it
|
||||
return token;
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static final String URL = "resource";
|
||||
|
||||
@Autowired
|
||||
private ClientDetailsEntityService clientService;
|
||||
|
||||
@Autowired
|
||||
private OAuth2TokenEntityService tokenService;
|
||||
|
||||
@Autowired
|
||||
private SystemScopeService scopeService;
|
||||
|
||||
@Autowired
|
||||
private ConfigurationPropertiesBean config;
|
||||
|
||||
@Autowired
|
||||
private OIDCTokenService connectTokenService;
|
||||
|
||||
/**
|
||||
* Logger for this class
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(ProtectedResourceRegistrationEndpoint.class);
|
||||
|
||||
/**
|
||||
* Create a new Client, issue a client ID, and create a registration access token.
|
||||
*
|
||||
* @param jsonString
|
||||
* @param m
|
||||
* @param p
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String registerNewProtectedResource(@RequestBody String jsonString, Model m) {
|
||||
|
||||
ClientDetailsEntity newClient = null;
|
||||
try {
|
||||
newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
|
||||
} catch (JsonSyntaxException e) {
|
||||
// bad parse
|
||||
// didn't parse, this is a bad request
|
||||
logger.error("registerNewProtectedResource failed; submitted JSON is malformed");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
|
||||
if (newClient != null) {
|
||||
// it parsed!
|
||||
|
||||
//
|
||||
// Now do some post-processing consistency checks on it
|
||||
//
|
||||
|
||||
// clear out any spurious id/secret (clients don't get to pick)
|
||||
newClient.setClientId(null);
|
||||
newClient.setClientSecret(null);
|
||||
|
||||
// do validation on the fields
|
||||
try {
|
||||
newClient = validateScopes(newClient);
|
||||
newClient = validateAuth(newClient);
|
||||
} catch (ValidationException ve) {
|
||||
// validation failed, return an error
|
||||
m.addAttribute(JsonErrorView.ERROR, ve.getError());
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription());
|
||||
m.addAttribute(HttpCodeView.CODE, ve.getStatus());
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
|
||||
|
||||
// no grant types are allowed
|
||||
newClient.setGrantTypes(new HashSet<String>());
|
||||
newClient.setResponseTypes(new HashSet<String>());
|
||||
newClient.setRedirectUris(new HashSet<String>());
|
||||
|
||||
// don't issue tokens to this client
|
||||
newClient.setAccessTokenValiditySeconds(0);
|
||||
newClient.setIdTokenValiditySeconds(0);
|
||||
newClient.setRefreshTokenValiditySeconds(0);
|
||||
|
||||
// clear out unused fields
|
||||
newClient.setDefaultACRvalues(new HashSet<String>());
|
||||
newClient.setDefaultMaxAge(null);
|
||||
newClient.setIdTokenEncryptedResponseAlg(null);
|
||||
newClient.setIdTokenEncryptedResponseEnc(null);
|
||||
newClient.setIdTokenSignedResponseAlg(null);
|
||||
newClient.setInitiateLoginUri(null);
|
||||
newClient.setPostLogoutRedirectUris(null);
|
||||
newClient.setRequestObjectSigningAlg(null);
|
||||
newClient.setRequireAuthTime(null);
|
||||
newClient.setReuseRefreshToken(false);
|
||||
newClient.setSectorIdentifierUri(null);
|
||||
newClient.setSubjectType(null);
|
||||
newClient.setUserInfoEncryptedResponseAlg(null);
|
||||
newClient.setUserInfoEncryptedResponseEnc(null);
|
||||
newClient.setUserInfoSignedResponseAlg(null);
|
||||
|
||||
// this client has been dynamically registered (obviously)
|
||||
newClient.setDynamicallyRegistered(true);
|
||||
|
||||
// this client has access to the introspection endpoint
|
||||
newClient.setAllowIntrospection(true);
|
||||
|
||||
// now save it
|
||||
try {
|
||||
ClientDetailsEntity savedClient = clientService.saveNewClient(newClient);
|
||||
|
||||
// generate the registration access token
|
||||
OAuth2AccessTokenEntity token = connectTokenService.createResourceAccessToken(savedClient);
|
||||
tokenService.saveAccessToken(token);
|
||||
|
||||
// send it all out to the view
|
||||
|
||||
RegisteredClient registered =
|
||||
new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "resource/"
|
||||
+ UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
|
||||
m.addAttribute("client", registered);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201
|
||||
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Couldn't save client", e);
|
||||
|
||||
m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE,
|
||||
"Unable to save client due to invalid or inconsistent metadata.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
} else {
|
||||
// didn't parse, this is a bad request
|
||||
logger.error("registerNewClient failed; submitted JSON is malformed");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ClientDetailsEntity validateScopes(ClientDetailsEntity newClient)
|
||||
throws ValidationException {
|
||||
// scopes that the client is asking for
|
||||
Set<SystemScope> requestedScopes = scopeService.fromStrings(newClient.getScope());
|
||||
|
||||
// the scopes that the client can have must be a subset of the dynamically allowed scopes
|
||||
Set<SystemScope> allowedScopes =
|
||||
scopeService.removeRestrictedAndReservedScopes(requestedScopes);
|
||||
|
||||
// if the client didn't ask for any, give them the defaults
|
||||
if (allowedScopes == null || allowedScopes.isEmpty()) {
|
||||
allowedScopes = scopeService.getDefaults();
|
||||
}
|
||||
|
||||
newClient.setScope(scopeService.toStrings(allowedScopes));
|
||||
|
||||
return newClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the meta information for a client.
|
||||
*
|
||||
* @param clientId
|
||||
* @param m
|
||||
* @param auth
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
|
||||
+ SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String readResourceConfiguration(@PathVariable("id") String clientId, Model m,
|
||||
OAuth2Authentication auth) {
|
||||
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
|
||||
|
||||
// possibly update the token
|
||||
OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client);
|
||||
|
||||
RegisteredClient registered =
|
||||
new RegisteredClient(client, token.getValue(), config.getIssuer() + "resource/"
|
||||
+ UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));
|
||||
|
||||
// send it all out to the view
|
||||
m.addAttribute("client", registered);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
|
||||
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
|
||||
} else {
|
||||
// client mismatch
|
||||
logger.error("readResourceConfiguration failed, client ID mismatch: " + clientId + " and "
|
||||
+ auth.getOAuth2Request().getClientId() + " do not match.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the metainformation for a given client.
|
||||
*
|
||||
* @param clientId
|
||||
* @param jsonString
|
||||
* @param m
|
||||
* @param auth
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
|
||||
+ SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.PUT,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String updateProtectedResource(@PathVariable("id") String clientId,
|
||||
@RequestBody String jsonString, Model m, OAuth2Authentication auth) {
|
||||
|
||||
|
||||
ClientDetailsEntity newClient = null;
|
||||
try {
|
||||
newClient = ClientDetailsEntityJsonProcessor.parse(jsonString);
|
||||
} catch (JsonSyntaxException e) {
|
||||
// bad parse
|
||||
// didn't parse, this is a bad request
|
||||
logger.error("updateProtectedResource failed; submitted JSON is malformed");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
|
||||
ClientDetailsEntity oldClient = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (newClient != null && oldClient != null // we have an existing client and the new one parsed
|
||||
&& oldClient.getClientId().equals(auth.getOAuth2Request().getClientId()) // the client
|
||||
// passed in the
|
||||
// URI matches the
|
||||
// one in the auth
|
||||
&& oldClient.getClientId().equals(newClient.getClientId()) // the client passed in the body
|
||||
// matches the one in the URI
|
||||
) {
|
||||
|
||||
// a client can't ask to update its own client secret to any particular value
|
||||
newClient.setClientSecret(oldClient.getClientSecret());
|
||||
|
||||
newClient.setCreatedAt(oldClient.getCreatedAt());
|
||||
|
||||
// no grant types are allowed
|
||||
newClient.setGrantTypes(new HashSet<String>());
|
||||
newClient.setResponseTypes(new HashSet<String>());
|
||||
newClient.setRedirectUris(new HashSet<String>());
|
||||
|
||||
// don't issue tokens to this client
|
||||
newClient.setAccessTokenValiditySeconds(0);
|
||||
newClient.setIdTokenValiditySeconds(0);
|
||||
newClient.setRefreshTokenValiditySeconds(0);
|
||||
|
||||
// clear out unused fields
|
||||
newClient.setDefaultACRvalues(new HashSet<String>());
|
||||
newClient.setDefaultMaxAge(null);
|
||||
newClient.setIdTokenEncryptedResponseAlg(null);
|
||||
newClient.setIdTokenEncryptedResponseEnc(null);
|
||||
newClient.setIdTokenSignedResponseAlg(null);
|
||||
newClient.setInitiateLoginUri(null);
|
||||
newClient.setPostLogoutRedirectUris(null);
|
||||
newClient.setRequestObjectSigningAlg(null);
|
||||
newClient.setRequireAuthTime(null);
|
||||
newClient.setReuseRefreshToken(false);
|
||||
newClient.setSectorIdentifierUri(null);
|
||||
newClient.setSubjectType(null);
|
||||
newClient.setUserInfoEncryptedResponseAlg(null);
|
||||
newClient.setUserInfoEncryptedResponseEnc(null);
|
||||
newClient.setUserInfoSignedResponseAlg(null);
|
||||
|
||||
// this client has been dynamically registered (obviously)
|
||||
newClient.setDynamicallyRegistered(true);
|
||||
|
||||
// this client has access to the introspection endpoint
|
||||
newClient.setAllowIntrospection(true);
|
||||
|
||||
// do validation on the fields
|
||||
try {
|
||||
newClient = validateScopes(newClient);
|
||||
newClient = validateAuth(newClient);
|
||||
} catch (ValidationException ve) {
|
||||
// validation failed, return an error
|
||||
m.addAttribute(JsonErrorView.ERROR, ve.getError());
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, ve.getErrorDescription());
|
||||
m.addAttribute(HttpCodeView.CODE, ve.getStatus());
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// save the client
|
||||
ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient);
|
||||
|
||||
// possibly update the token
|
||||
OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, savedClient);
|
||||
|
||||
RegisteredClient registered =
|
||||
new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "resource/"
|
||||
+ UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
|
||||
|
||||
// send it all out to the view
|
||||
m.addAttribute("client", registered);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200
|
||||
|
||||
return ClientInformationResponseView.VIEWNAME;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Couldn't save client", e);
|
||||
|
||||
m.addAttribute(JsonErrorView.ERROR, "invalid_client_metadata");
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE,
|
||||
"Unable to save client due to invalid or inconsistent metadata.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); // http 400
|
||||
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
} else {
|
||||
// client mismatch
|
||||
logger.error("updateProtectedResource" + " failed, client ID mismatch: " + clientId + " and "
|
||||
+ auth.getOAuth2Request().getClientId() + " do not match.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the indicated client from the system.
|
||||
*
|
||||
* @param clientId
|
||||
* @param m
|
||||
* @param auth
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('"
|
||||
+ SystemScopeService.RESOURCE_TOKEN_SCOPE + "')")
|
||||
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String deleteResource(@PathVariable("id") String clientId, Model m,
|
||||
OAuth2Authentication auth) {
|
||||
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
|
||||
|
||||
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
|
||||
|
||||
clientService.deleteClient(client);
|
||||
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); // http 204
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
} else {
|
||||
// client mismatch
|
||||
logger.error("readClientConfiguration failed, client ID mismatch: " + clientId + " and "
|
||||
+ auth.getOAuth2Request().getClientId() + " do not match.");
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); // http 403
|
||||
|
||||
return HttpCodeView.VIEWNAME;
|
||||
}
|
||||
}
|
||||
|
||||
private ClientDetailsEntity validateAuth(ClientDetailsEntity newClient)
|
||||
throws ValidationException {
|
||||
if (newClient.getTokenEndpointAuthMethod() == null) {
|
||||
newClient.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC);
|
||||
}
|
||||
|
||||
if (newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_BASIC
|
||||
|| newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_JWT
|
||||
|| newClient.getTokenEndpointAuthMethod() == AuthMethod.SECRET_POST) {
|
||||
|
||||
if (Strings.isNullOrEmpty(newClient.getClientSecret())) {
|
||||
// no secret yet, we need to generate a secret
|
||||
newClient = clientService.generateClientSecret(newClient);
|
||||
}
|
||||
} else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.PRIVATE_KEY) {
|
||||
if (Strings.isNullOrEmpty(newClient.getJwksUri()) && newClient.getJwks() == null) {
|
||||
throw new ValidationException("invalid_client_metadata",
|
||||
"JWK Set URI required when using private key authentication", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
newClient.setClientSecret(null);
|
||||
} else if (newClient.getTokenEndpointAuthMethod() == AuthMethod.NONE) {
|
||||
newClient.setClientSecret(null);
|
||||
} else {
|
||||
throw new ValidationException("invalid_client_metadata", "Unknown authentication method",
|
||||
HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return newClient;
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenEntity fetchValidRegistrationToken(OAuth2Authentication auth,
|
||||
ClientDetailsEntity client) {
|
||||
|
||||
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
|
||||
OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());
|
||||
|
||||
if (config.getRegTokenLifeTime() != null) {
|
||||
|
||||
try {
|
||||
// Re-issue the token if it has been issued before [currentTime - validity]
|
||||
Date validToDate =
|
||||
new Date(System.currentTimeMillis() - config.getRegTokenLifeTime() * 1000);
|
||||
if (token.getJwt().getJWTClaimsSet().getIssueTime().before(validToDate)) {
|
||||
logger.info("Rotating the registration access token for " + client.getClientId());
|
||||
tokenService.revokeAccessToken(token);
|
||||
OAuth2AccessTokenEntity newToken = connectTokenService.createResourceAccessToken(client);
|
||||
tokenService.saveAccessToken(newToken);
|
||||
return newToken;
|
||||
} else {
|
||||
// it's not expired, keep going
|
||||
return token;
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
logger.error("Couldn't parse a known-valid token?", e);
|
||||
return token;
|
||||
}
|
||||
} else {
|
||||
// tokens don't expire, just return it
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
11
pom.xml
11
pom.xml
|
@ -385,35 +385,36 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>4.3.7.RELEASE</version>
|
||||
<version>5.3.12</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.0.pr2</version>
|
||||
<version>2.12.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>2.9.0.pr2</version>
|
||||
<version>2.12.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-bom</artifactId>
|
||||
<version>4.2.4.RELEASE</version>
|
||||
<version>5.5.3</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth</groupId>
|
||||
<artifactId>spring-security-oauth2</artifactId>
|
||||
<version>2.1.0.RELEASE</version>
|
||||
<version>2.5.1.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Servlet -->
|
||||
|
|
Loading…
Reference in New Issue