private void validateClient(ClientDetails client, boolean create) { final Set<String> VALID_GRANTS = new HashSet<String>( Arrays.asList( "implicit", "password", "client_credentials", "authorization_code", "refresh_token")); for (String grant : client.getAuthorizedGrantTypes()) { if (!VALID_GRANTS.contains(grant)) { throw new InvalidClientDetailsException( grant + " is not an allowed grant type. Must be one of: " + VALID_GRANTS.toString()); } } if (create) { // Only check for missing secret if client is being created. if (client.getAuthorizedGrantTypes().size() == 1 && client.getAuthorizedGrantTypes().contains("implicit")) { if (StringUtils.hasText(client.getClientSecret())) { throw new InvalidClientDetailsException( "implicit grant does not require a client_secret"); } } else { if (!StringUtils.hasText(client.getClientSecret())) { throw new InvalidClientDetailsException( "client_secret is required for non-implicit grant types"); } } } }
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { System.out.println("loading client"); ClientDetails client = jdbcClientService.loadClientByClientId(clientId); System.out.println(client.getClientId() + " " + client.getClientSecret()); return client; }
/** * Is a refresh token supported for this client (or the global setting if {@link * #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set. * * @param authorizationRequest the current authorization request * @return boolean to indicate if refresh token is supported */ protected boolean isSupportRefreshToken(OAuth2Request authorizationRequest) { if (clientDetailsService != null) { ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); return client.getAuthorizedGrantTypes().contains("refresh_token"); } return this.supportRefreshToken; }
private HttpHeaders getHeaders(ClientDetails config) { HttpHeaders headers = new HttpHeaders(); String token = new String( Base64.encode((config.getClientId() + ":" + config.getClientSecret()).getBytes())); headers.set("Authorization", "Basic " + token); return headers; }
@Override public AuthorizationRequest createAuthorizationRequest(Map<String, String> parameters) { String clientId = parameters.get("client_id"); if (clientId == null) { throw new InvalidClientException("A client id must be provided"); } ClientDetails client = clientDetailsService.loadClientByClientId(clientId); String requestNonce = parameters.get("nonce"); // Only process if the user is authenticated. If the user is not authenticated yet, this // code will be called a second time once the user is redirected from the login page back // to the auth endpoint. Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (requestNonce != null && principal != null && principal instanceof User) { // Check request nonce for reuse Collection<Nonce> clientNonces = nonceService.getByClientId(client.getClientId()); for (Nonce nonce : clientNonces) { String nonceVal = nonce.getValue(); if (nonceVal.equals(requestNonce)) { throw new NonceReuseException(client.getClientId(), nonce); } } // Store nonce Nonce nonce = new Nonce(); nonce.setClientId(client.getClientId()); nonce.setValue(requestNonce); DateTime now = new DateTime(new Date()); nonce.setUseDate(now.toDate()); DateTime expDate = now.plus(nonceStorageDuration); Date expirationJdkDate = expDate.toDate(); nonce.setExpireDate(expirationJdkDate); nonceService.save(nonce); } Set<String> scopes = OAuth2Utils.parseParameterList(parameters.get("scope")); if ((scopes == null || scopes.isEmpty())) { // TODO: do we want to allow default scoping at all? // If no scopes are specified in the incoming data, it is possible to default to the client's // registered scopes, but minus the "openid" scope. OpenID Connect requests MUST have the // "openid" scope. Set<String> clientScopes = client.getScope(); if (clientScopes.contains("openid")) { clientScopes.remove("openid"); } scopes = clientScopes; } DefaultAuthorizationRequest request = new DefaultAuthorizationRequest( parameters, Collections.<String, String>emptyMap(), clientId, scopes); request.addClientDetails(client); return request; }
@RequestMapping(value = "/oauth/clients", method = RequestMethod.GET) public ResponseEntity<Map<String, ClientDetails>> listClientDetails() throws Exception { List<ClientDetails> details = clientRegistrationService.listClientDetails(); Map<String, ClientDetails> map = new LinkedHashMap<String, ClientDetails>(); for (ClientDetails client : details) { map.put(client.getClientId(), removeSecret(client)); } return new ResponseEntity<Map<String, ClientDetails>>(map, HttpStatus.OK); }
private Object[] getFields(ClientDetails clientDetails) { Object[] fieldsForUpdate = getFieldsForUpdate(clientDetails); Object[] fields = new Object[fieldsForUpdate.length + 1]; System.arraycopy(fieldsForUpdate, 0, fields, 1, fieldsForUpdate.length); fields[0] = clientDetails.getClientSecret() != null ? passwordEncoder.encode(clientDetails.getClientSecret()) : null; return fields; }
/** * The refresh token validity period in seconds * * @param authorizationRequest the current authorization request * @return the refresh token validity period in seconds */ protected int getRefreshTokenValiditySeconds(OAuth2Request authorizationRequest) { if (clientDetailsService != null) { ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); Integer validity = client.getRefreshTokenValiditySeconds(); if (validity != null) { return validity; } } return refreshTokenValiditySeconds; }
private String getAutoApproveScopes(ClientDetails clientDetails) { if (clientDetails.isAutoApprove("true")) { return "true"; // all scopes autoapproved } Set<String> scopes = new HashSet<String>(); for (String scope : clientDetails.getScope()) { if (clientDetails.isAutoApprove(scope)) { scopes.add(scope); } } return StringUtils.collectionToCommaDelimitedString(scopes); }
@Override public void validateParameters(Map<String, String> parameters, ClientDetails clientDetails) { if (parameters.containsKey("scope")) { if (clientDetails.isScoped()) { Set<String> validScope = clientDetails.getScope(); for (String scope : OAuth2Utils.parseParameterList(parameters.get("scope"))) { if (!validScope.contains(scope)) { throw new InvalidScopeException("Invalid scope: " + scope, validScope); } } } } }
@Test public void testEnvironmentalOverrides() { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); EnvironmentTestUtils.addEnvironment( this.context, "security.oauth2.client.clientId:myclientid", "security.oauth2.client.clientSecret:mysecret"); this.context.register( AuthorizationAndResourceServerConfiguration.class, MinimalSecureWebApplication.class); this.context.refresh(); ClientDetails config = this.context.getBean(ClientDetails.class); assertThat(config.getClientId(), equalTo("myclientid")); assertThat(config.getClientSecret(), equalTo("mysecret")); verifyAuthentication(config); }
@Test public void verification_link() throws Exception { ScimUser joel = setUpScimUser(); MockHttpServletRequestBuilder get = setUpVerificationLinkRequest(joel, scimCreateToken); MvcResult result = getMockMvc().perform(get).andExpect(status().isOk()).andReturn(); VerificationResponse verificationResponse = JsonUtils.readValue(result.getResponse().getContentAsString(), VerificationResponse.class); assertThat( verificationResponse.getVerifyLink().toString(), startsWith("http://localhost/verify_user")); String query = verificationResponse.getVerifyLink().getQuery(); String code = getQueryStringParam(query, "code"); assertThat(code, is(notNullValue())); ExpiringCode expiringCode = codeStore.retrieveCode(code); assertThat(expiringCode.getExpiresAt().getTime(), is(greaterThan(System.currentTimeMillis()))); assertThat(expiringCode.getIntent(), is(REGISTRATION.name())); Map<String, String> data = JsonUtils.readValue(expiringCode.getData(), new TypeReference<Map<String, String>>() {}); assertThat(data.get(InvitationConstants.USER_ID), is(notNullValue())); assertThat(data.get(CLIENT_ID), is(clientDetails.getClientId())); assertThat(data.get(REDIRECT_URI), is(HTTP_REDIRECT_EXAMPLE_COM)); }
public void addClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException { try { jdbcTemplate.update(insertClientDetailsSql, getFields(clientDetails)); } catch (DuplicateKeyException e) { throw new ClientAlreadyExistsException( "Client already exists: " + clientDetails.getClientId(), e); } }
/** * Apply UAA rules to validate the requested scope. For client credentials grants the valid scopes * are actually in the authorities of the client. * * @see * org.springframework.security.oauth2.provider.endpoint.ParametersValidator#validateParameters(java.util.Map, * org.springframework.security.oauth2.provider.ClientDetails) */ @Override public void validateParameters(Map<String, String> parameters, ClientDetails clientDetails) { if (parameters.containsKey("scope")) { Set<String> validScope = clientDetails.getScope(); if ("client_credentials".equals(parameters.get("grant_type"))) { validScope = AuthorityUtils.authorityListToSet(clientDetails.getAuthorities()); } for (String scope : OAuth2Utils.parseParameterList(parameters.get("scope"))) { if (!validScope.contains(scope)) { throw new InvalidScopeException( "Invalid scope: " + scope + ". Did you know that you can get default scopes by simply sending no value?", validScope); } } } }
public String resolveRedirect(String requestedRedirect, ClientDetails client) throws OAuth2Exception { Set<String> redirectUris = client.getRegisteredRedirectUri(); if (redirectUris != null && !redirectUris.isEmpty()) { return obtainMatchingRedirect(redirectUris, requestedRedirect); } else if (StringUtils.hasText(requestedRedirect)) { return requestedRedirect; } else { throw new OAuth2Exception("A redirect_uri must be supplied."); } }
/** * Add or remove scopes derived from the current authenticated user's authorities (if any) * * @param scopes the initial set of scopes from the client registration * @param clientDetails * @param collection the users authorities * @return modified scopes adapted according to the rules specified */ private Set<String> checkUserScopes( Set<String> scopes, Collection<? extends GrantedAuthority> authorities, ClientDetails clientDetails) { Set<String> result = new LinkedHashSet<String>(scopes); Set<String> allowed = new LinkedHashSet<String>(AuthorityUtils.authorityListToSet(authorities)); // Add in all default scopes allowed.addAll(defaultScopes); // Find intersection of user authorities, default scopes and client // scopes: for (Iterator<String> iter = allowed.iterator(); iter.hasNext(); ) { String scope = iter.next(); if (!clientDetails.getScope().contains(scope)) { iter.remove(); } } // Weed out disallowed scopes: for (Iterator<String> iter = result.iterator(); iter.hasNext(); ) { String scope = iter.next(); if (!allowed.contains(scope)) { iter.remove(); } } // Check that a token with empty scope is not going to be granted if (result.isEmpty() && !clientDetails.getScope().isEmpty()) { throw new InvalidScopeException( "Invalid scope (empty) - this user is not allowed any of the requested scopes: " + scopes + " (either you requested a scope that was not allowed or client '" + clientDetails.getClientId() + "' is not allowed to act on behalf of this user)", allowed); } return result; }
public String resolveRedirect(String requestedRedirect, ClientDetails client) throws OAuth2Exception { Set<String> authorizedGrantTypes = client.getAuthorizedGrantTypes(); if (authorizedGrantTypes.isEmpty()) { throw new InvalidGrantException("A client must have at least one authorized grant type."); } if (!containsRedirectGrantType(authorizedGrantTypes)) { throw new InvalidGrantException( "A redirect_uri can only be used by implicit or authorization_code grant types."); } Set<String> redirectUris = client.getRegisteredRedirectUri(); if (redirectUris != null && !redirectUris.isEmpty()) { return obtainMatchingRedirect(redirectUris, requestedRedirect); } else if (StringUtils.hasText(requestedRedirect)) { return requestedRedirect; } else { throw new RedirectMismatchException("A redirect_uri must be supplied."); } }
private ClientDetails removeSecret(ClientDetails client) { BaseClientDetails details = new BaseClientDetails(); details.setClientId(client.getClientId()); details.setScope(client.getScope()); details.setResourceIds(client.getResourceIds()); details.setAuthorizedGrantTypes(client.getAuthorizedGrantTypes()); details.setRegisteredRedirectUri(client.getRegisteredRedirectUri()); details.setAuthorities(client.getAuthorities()); details.setAccessTokenValiditySeconds(client.getAccessTokenValiditySeconds()); return details; }
private Set<String> getResourceIds(ClientDetails clientDetails, Set<String> scopes) { Set<String> resourceIds = new LinkedHashSet<String>(); for (String scope : scopes) { if (scopeToResource.containsKey(scope)) { resourceIds.add(scopeToResource.get(scope)); } else if (scope.contains(scopeSeparator) && !scope.endsWith(scopeSeparator) && !scope.equals("uaa.none")) { String id = scope.substring(0, scope.lastIndexOf(scopeSeparator)); resourceIds.add(id); } } return resourceIds.isEmpty() ? clientDetails.getResourceIds() : resourceIds; }
@Test public void testDefaultConfiguration() { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); this.context.register( AuthorizationAndResourceServerConfiguration.class, MinimalSecureWebApplication.class); this.context.refresh(); this.context.getBean(AUTHORIZATION_SERVER_CONFIG); this.context.getBean(RESOURCE_SERVER_CONFIG); this.context.getBean(OAuth2MethodSecurityConfiguration.class); ClientDetails config = this.context.getBean(BaseClientDetails.class); AuthorizationEndpoint endpoint = this.context.getBean(AuthorizationEndpoint.class); UserApprovalHandler handler = (UserApprovalHandler) ReflectionTestUtils.getField(endpoint, "userApprovalHandler"); ClientDetailsService clientDetailsService = this.context.getBean(ClientDetailsService.class); ClientDetails clientDetails = clientDetailsService.loadClientByClientId(config.getClientId()); assertThat(AopUtils.isJdkDynamicProxy(clientDetailsService), equalTo(true)); assertThat( AopUtils.getTargetClass(clientDetailsService).getName(), equalTo(ClientDetailsService.class.getName())); assertThat(handler instanceof ApprovalStoreUserApprovalHandler, equalTo(true)); assertThat(clientDetails, equalTo(config)); verifyAuthentication(config); }
@Override public UserDetails loadUserByUsername(String clientId) throws UsernameNotFoundException, DataAccessException { ClientDetails client = clientDetailsService.loadClientByClientId(clientId); String password = client.getClientSecret(); boolean enabled = true; boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); GrantedAuthority roleClient = new SimpleGrantedAuthority("ROLE_CLIENT"); authorities.add(roleClient); return new User( clientId, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); }
private void checkPasswordChangeIsAllowed(ClientDetails clientDetails, String oldSecret) { if (!securityContextAccessor.isClient()) { // Trusted client (not acting on behalf of user) throw new IllegalStateException("Only a client can change client secret"); } String clientId = clientDetails.getClientId(); // Call is by client String currentClientId = securityContextAccessor.getClientId(); if (securityContextAccessor.isAdmin()) { // even an admin needs to provide the old value to change password if (clientId.equals(currentClientId) && !StringUtils.hasText(oldSecret)) { throw new IllegalStateException("Previous secret is required even for admin"); } } else { if (!clientId.equals(currentClientId)) { logger.warn( "Client with id " + currentClientId + " attempting to change password for client " + clientId); // TODO: This should be audited when we have non-authentication events in the log throw new IllegalStateException( "Bad request. Not permitted to change another client's secret"); } // Client is changing their own secret, old password is required if (!StringUtils.hasText(oldSecret)) { throw new IllegalStateException("Previous secret is required"); } } }
public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException { int count = jdbcTemplate.update(updateClientDetailsSql, getFieldsForUpdate(clientDetails)); if (count != 1) { throw new NoSuchClientException("No client found with id = " + clientDetails.getClientId()); } }
@Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { final boolean debug = logger.isDebugEnabled(); final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; try { Authentication credentials = extractCredentials(request); if (credentials != null) { if (debug) { logger.debug("Authentication credentials found for '" + credentials.getName() + "'"); } Authentication authResult = authenticationManager.authenticate(credentials); if (debug) { logger.debug("Authentication success: " + authResult.getName()); } Authentication requestingPrincipal = SecurityContextHolder.getContext().getAuthentication(); if (requestingPrincipal == null) { throw new BadCredentialsException( "No client authentication found. Remember to put a filter upstream of the LoginAuthenticationFilter."); } String clientId = request.getParameter("client_id"); if (null == clientId) { logger.error("No client_id in the request"); throw new BadCredentialsException("No client_id in the request"); } // Check that the client exists ClientDetails authenticatingClient = clientDetailsService.loadClientByClientId(clientId); if (authenticatingClient == null) { throw new BadCredentialsException("No client " + clientId + " found"); } DefaultAuthorizationRequest authorizationRequest = new DefaultAuthorizationRequest( getSingleValueMap(request), null, authenticatingClient.getClientId(), getScope(request)); if (requestingPrincipal.isAuthenticated()) { // Ensure the OAuth2Authentication is authenticated authorizationRequest.setApproved(true); } SecurityContextHolder.getContext() .setAuthentication(new OAuth2Authentication(authorizationRequest, authResult)); onSuccessfulAuthentication(request, response, authResult); } } catch (AuthenticationException failed) { SecurityContextHolder.clearContext(); if (debug) { logger.debug("Authentication request for failed: " + failed); } onUnsuccessfulAuthentication(request, response, failed); authenticationEntryPoint.commence(request, response, failed); return; } chain.doFilter(request, response); }
private Object[] getFieldsForUpdate(ClientDetails clientDetails) { String json = null; try { json = mapper.write(clientDetails.getAdditionalInformation()); } catch (Exception e) { logger.warn("Could not serialize additional information: " + clientDetails, e); } return new Object[] { clientDetails.getResourceIds() != null ? StringUtils.collectionToCommaDelimitedString(clientDetails.getResourceIds()) : null, clientDetails.getScope() != null ? StringUtils.collectionToCommaDelimitedString(clientDetails.getScope()) : null, clientDetails.getAuthorizedGrantTypes() != null ? StringUtils.collectionToCommaDelimitedString(clientDetails.getAuthorizedGrantTypes()) : null, clientDetails.getRegisteredRedirectUri() != null ? StringUtils.collectionToCommaDelimitedString(clientDetails.getRegisteredRedirectUri()) : null, clientDetails.getAuthorities() != null ? StringUtils.collectionToCommaDelimitedString(clientDetails.getAuthorities()) : null, clientDetails.getAccessTokenValiditySeconds(), clientDetails.getRefreshTokenValiditySeconds(), json, getAutoApproveScopes(clientDetails), clientDetails.getClientId() }; }