/* * Copyright 2015 Smartling, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.keycloak.adapters.springsecurity.support; import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakSecurityContext; import org.keycloak.RSATokenVerifier; import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.OidcKeycloakAccount; import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.springsecurity.account.KeycloakRole; import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount; import org.keycloak.common.VerificationException; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.IDToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.util.Assert; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; /** * Provides common utility methods for working with Spring Security and Keycloak * * @author <a href="mailto:[email protected]">Scott Rossillo</a> */ public final class KeycloakSpringAdapterUtils { /** * Creates a {@link OidcKeycloakAccount} from the given {@link KeycloakDeployment} and {@link RefreshableKeycloakSecurityContext}. * * @param deployment the <code>KeycloakDeployment</code> requesting an account (required) * @param context the current <code>RefreshableKeycloakSecurityContext</code> (required) * * @return a <code>KeycloakAccount</code> for the given <code>deployment</code> and <code>context</code> */ public static OidcKeycloakAccount createAccount(KeycloakDeployment deployment, RefreshableKeycloakSecurityContext context) { Assert.notNull(context); Set<String> roles = AdapterUtils.getRolesFromSecurityContext(context); KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = AdapterUtils.createPrincipal(deployment, context); return new SimpleKeycloakAccount(principal, roles, context); } /** * Creates a {@link GrantedAuthority} collection from the given {@link RefreshableKeycloakSecurityContext}. * * @param context the current <code>RefreshableKeycloakSecurityContext</code> (required) * * @return a {@link GrantedAuthority} collection if any; an empty list otherwise */ public static Collection<?extends GrantedAuthority> createGrantedAuthorities(RefreshableKeycloakSecurityContext context) { return createGrantedAuthorities(context, null); } /** * Creates a {@link GrantedAuthority} collection from the given {@link KeycloakSecurityContext}. * * @param context the current <code>RefreshableKeycloakSecurityContext</code> (required) * @param mapper an optional {@link GrantedAuthoritiesMapper} to convert the * authorities loaded the given <code>context</code> which will be used in the * {@code Authentication} object * * @return a {@link GrantedAuthority} collection if any; an empty list otherwise */ public static Collection<? extends GrantedAuthority> createGrantedAuthorities(RefreshableKeycloakSecurityContext context, GrantedAuthoritiesMapper mapper) { Assert.notNull(context, "RefreshableKeycloakSecurityContext cannot be null"); List<KeycloakRole> grantedAuthorities = new ArrayList<>(); for (String role : AdapterUtils.getRolesFromSecurityContext(context)) { grantedAuthorities.add(new KeycloakRole(role)); } return mapper != null ? mapper.mapAuthorities(grantedAuthorities) : Collections.unmodifiableList(grantedAuthorities); } /** * Creates a new {@link RefreshableKeycloakSecurityContext} from the given {@link KeycloakDeployment} and {@link AccessTokenResponse}. * * @param deployment the <code>KeycloakDeployment</code> for which to create a <code>RefreshableKeycloakSecurityContext</code> (required) * @param accessTokenResponse the <code>AccessTokenResponse</code> from which to create a RefreshableKeycloakSecurityContext (required) * * @return a <code>RefreshableKeycloakSecurityContext</code> created from the given <code>accessTokenResponse</code> * @throws VerificationException if the given <code>AccessTokenResponse</code> contains an invalid {@link IDToken} */ public static RefreshableKeycloakSecurityContext createKeycloakSecurityContext(KeycloakDeployment deployment, AccessTokenResponse accessTokenResponse) throws VerificationException { String tokenString = accessTokenResponse.getToken(); String idTokenString = accessTokenResponse.getIdToken(); AccessToken accessToken = RSATokenVerifier .verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl()); IDToken idToken; try { JWSInput input = new JWSInput(idTokenString); idToken = input.readJsonContent(IDToken.class); } catch (JWSInputException e) { throw new VerificationException("Unable to verify ID token", e); } // FIXME: does it make sense to pass null for the token store? return new RefreshableKeycloakSecurityContext(deployment, null, tokenString, accessToken, idTokenString, idToken, accessTokenResponse.getRefreshToken()); } private KeycloakSpringAdapterUtils() { } }