001/*******************************************************************************
002 * Copyright 2017 The MIT Internet Trust Consortium
003 *
004 * Portions copyright 2011-2013 The MITRE Corporation
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *******************************************************************************/
018package org.mitre.openid.connect.client;
019
020import java.util.Collection;
021
022import org.mitre.openid.connect.model.OIDCAuthenticationToken;
023import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken;
024import org.mitre.openid.connect.model.UserInfo;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027import org.springframework.security.authentication.AuthenticationProvider;
028import org.springframework.security.core.Authentication;
029import org.springframework.security.core.AuthenticationException;
030import org.springframework.security.core.GrantedAuthority;
031import org.springframework.security.core.userdetails.UsernameNotFoundException;
032
033import com.google.common.base.Strings;
034import com.nimbusds.jwt.JWT;
035
036/**
037 * @author nemonik, Justin Richer
038 *
039 */
040public class OIDCAuthenticationProvider implements AuthenticationProvider {
041
042        private static Logger logger = LoggerFactory.getLogger(OIDCAuthenticationProvider.class);
043
044        private UserInfoFetcher userInfoFetcher = new UserInfoFetcher();
045
046        private OIDCAuthoritiesMapper authoritiesMapper = new NamedAdminAuthoritiesMapper();
047
048        /*
049         * (non-Javadoc)
050         *
051         * @see org.springframework.security.authentication.AuthenticationProvider#
052         * authenticate(org.springframework.security.core.Authentication)
053         */
054        @Override
055        public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
056
057                if (!supports(authentication.getClass())) {
058                        return null;
059                }
060
061                if (authentication instanceof PendingOIDCAuthenticationToken) {
062
063                        PendingOIDCAuthenticationToken token = (PendingOIDCAuthenticationToken) authentication;
064
065                        // get the ID Token value out
066                        JWT idToken = token.getIdToken();
067
068                        // load the user info if we can
069                        UserInfo userInfo = userInfoFetcher.loadUserInfo(token);
070
071                        if (userInfo == null) {
072                                // user info not found -- could be an error, could be fine
073                        } else {
074                                // if we found userinfo, double check it
075                                if (!Strings.isNullOrEmpty(userInfo.getSub()) && !userInfo.getSub().equals(token.getSub())) {
076                                        // the userinfo came back and the user_id fields don't match what was in the id_token
077                                        throw new UsernameNotFoundException("user_id mismatch between id_token and user_info call: " + token.getSub() + " / " + userInfo.getSub());
078                                }
079                        }
080
081                        return createAuthenticationToken(token, authoritiesMapper.mapAuthorities(idToken, userInfo), userInfo);
082                }
083
084                return null;
085        }
086
087        /**
088         * Override this function to return a different kind of Authentication, processes the authorities differently,
089         * or do post-processing based on the UserInfo object.
090         *
091         * @param token
092         * @param authorities
093         * @param userInfo
094         * @return
095         */
096        protected Authentication createAuthenticationToken(PendingOIDCAuthenticationToken token, Collection<? extends GrantedAuthority> authorities, UserInfo userInfo) {
097                return new OIDCAuthenticationToken(token.getSub(),
098                                token.getIssuer(),
099                                userInfo, authorities,
100                                token.getIdToken(), token.getAccessTokenValue(), token.getRefreshTokenValue());
101        }
102
103        /**
104         * @param userInfoFetcher
105         */
106        public void setUserInfoFetcher(UserInfoFetcher userInfoFetcher) {
107                this.userInfoFetcher = userInfoFetcher;
108        }
109
110        /**
111         * @param authoritiesMapper
112         */
113        public void setAuthoritiesMapper(OIDCAuthoritiesMapper authoritiesMapper) {
114                this.authoritiesMapper = authoritiesMapper;
115        }
116
117        /*
118         * (non-Javadoc)
119         *
120         * @see
121         * org.springframework.security.authentication.AuthenticationProvider#supports
122         * (java.lang.Class)
123         */
124        @Override
125        public boolean supports(Class<?> authentication) {
126                return PendingOIDCAuthenticationToken.class.isAssignableFrom(authentication);
127        }
128}