Java tutorial
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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.verinice.rest.security; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; import org.springframework.security.authentication.dao.SaltSource; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.codec.Hex; import org.springframework.util.Assert; /** * An {@link AuthenticationProvider} implementation that retrieves user details * from a {@link UserDetailsService}. * * See: http://ryanjbaxter.com/2015/01/06/securing-rest-apis-with-spring-boot/ * * @author Ben Alex * @author Rob Winch * @author rmotza */ public class VeriniceAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { private static final Logger LOG = LoggerFactory.getLogger(VeriniceAuthenticationProvider.class); public VeriniceAuthenticationProvider(UserDetailsService userDetailsService, Environment environment) { super(); setUserDetailsService(userDetailsService); this.environement = environment; } private Environment environement; /** * The password used to perform * {@link PasswordEncoder#isPasswordValid(String, String, Object)} on when * the user is not found to avoid SEC-2056. This is necessary, because some * {@link PasswordEncoder} implementations will short circuit if the * password is not in a valid format. */ private SaltSource saltSource; private UserDetailsService userDetailsService; private boolean checkPassword(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) { String realm = environement.getProperty("veriniceserver.realm"); String a1 = authentication.getName() + ":" + realm + ":" + authentication.getCredentials(); String md5Hex = md5Hex(a1); return md5Hex.equals(userDetails.getPassword()); } private String md5Hex(String data) { MessageDigest digest; try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("No MD5 algorithm available!", e); } return new String(Hex.encode(digest.digest(data.getBytes()))); } @Override public Authentication authenticate(Authentication authentication) { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // Determine username String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.getUserCache().getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { logger.debug("User '" + username + "' not found"); if (hideUserNotFoundExceptions) { throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { throw notFound; } } catch (InternalAuthenticationServiceException e) { throw new BadCredentialsException(e.getMessage(), e); } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } try { getPreAuthenticationChecks().check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) { // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); getPreAuthenticationChecks().check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } else { throw exception; } } getPostAuthenticationChecks().check(user); if (!cacheWasUsed) { this.getUserCache().putUserInCache(user); } Object principalToReturn = user; if (isForcePrincipalAsString()) { principalToReturn = user.getUsername(); } return createSuccessAuthentication(principalToReturn, authentication, user); } @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { LOG.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } boolean rightPassword = checkPassword(userDetails, authentication); if (!rightPassword) { LOG.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } } @Override protected void doAfterPropertiesSet() throws Exception { Assert.notNull(this.userDetailsService, "A UserDetailsService must be set"); } @Override protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { loadedUser = this.getUserDetailsService().loadUserByUsername(username); } catch (UsernameNotFoundException notFound) { LOG.warn("user " + authentication + " not found"); throw notFound; } if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; } /** * The source of salts to use when decoding passwords. <code>null</code> is * a valid value, meaning the <code>DaoAuthenticationProvider</code> will * present <code>null</code> to the relevant <code>PasswordEncoder</code>. * <p> * Instead, it is recommended that you use an encoder which uses a random * salt and combines it with the password field. This is the default * approach taken in the * {@code org.springframework.security.crypto.password} package. * * @param saltSource * to use when attempting to decode passwords via the * <code>PasswordEncoder</code> */ public void setSaltSource(SaltSource saltSource) { this.saltSource = saltSource; } protected SaltSource getSaltSource() { return saltSource; } public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } protected UserDetailsService getUserDetailsService() { return userDetailsService; } }