OIDCAuthenticationProvider.java

/*******************************************************************************
 * Copyright 2017 The MIT Internet Trust Consortium
 *
 * Portions copyright 2011-2013 The MITRE Corporation
 *
 * 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.mitre.openid.connect.client;

import java.util.Collection;

import org.mitre.openid.connect.model.OIDCAuthenticationToken;
import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken;
import org.mitre.openid.connect.model.UserInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.google.common.base.Strings;
import com.nimbusds.jwt.JWT;

/**
 * @author nemonik, Justin Richer
 *
 */
public class OIDCAuthenticationProvider implements AuthenticationProvider {

	private static Logger logger = LoggerFactory.getLogger(OIDCAuthenticationProvider.class);

	private UserInfoFetcher userInfoFetcher = new UserInfoFetcher();

	private OIDCAuthoritiesMapper authoritiesMapper = new NamedAdminAuthoritiesMapper();

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.security.authentication.AuthenticationProvider#
	 * authenticate(org.springframework.security.core.Authentication)
	 */
	@Override
	public Authentication authenticate(final Authentication authentication) throws AuthenticationException {

		if (!supports(authentication.getClass())) {
			return null;
		}

		if (authentication instanceof PendingOIDCAuthenticationToken) {

			PendingOIDCAuthenticationToken token = (PendingOIDCAuthenticationToken) authentication;

			// get the ID Token value out
			JWT idToken = token.getIdToken();

			// load the user info if we can
			UserInfo userInfo = userInfoFetcher.loadUserInfo(token);

			if (userInfo == null) {
				// user info not found -- could be an error, could be fine
			} else {
				// if we found userinfo, double check it
				if (!Strings.isNullOrEmpty(userInfo.getSub()) && !userInfo.getSub().equals(token.getSub())) {
					// the userinfo came back and the user_id fields don't match what was in the id_token
					throw new UsernameNotFoundException("user_id mismatch between id_token and user_info call: " + token.getSub() + " / " + userInfo.getSub());
				}
			}

			return createAuthenticationToken(token, authoritiesMapper.mapAuthorities(idToken, userInfo), userInfo);
		}

		return null;
	}

	/**
	 * Override this function to return a different kind of Authentication, processes the authorities differently,
	 * or do post-processing based on the UserInfo object.
	 *
	 * @param token
	 * @param authorities
	 * @param userInfo
	 * @return
	 */
	protected Authentication createAuthenticationToken(PendingOIDCAuthenticationToken token, Collection<? extends GrantedAuthority> authorities, UserInfo userInfo) {
		return new OIDCAuthenticationToken(token.getSub(),
				token.getIssuer(),
				userInfo, authorities,
				token.getIdToken(), token.getAccessTokenValue(), token.getRefreshTokenValue());
	}

	/**
	 * @param userInfoFetcher
	 */
	public void setUserInfoFetcher(UserInfoFetcher userInfoFetcher) {
		this.userInfoFetcher = userInfoFetcher;
	}

	/**
	 * @param authoritiesMapper
	 */
	public void setAuthoritiesMapper(OIDCAuthoritiesMapper authoritiesMapper) {
		this.authoritiesMapper = authoritiesMapper;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.springframework.security.authentication.AuthenticationProvider#supports
	 * (java.lang.Class)
	 */
	@Override
	public boolean supports(Class<?> authentication) {
		return PendingOIDCAuthenticationToken.class.isAssignableFrom(authentication);
	}
}