|
@@ -0,0 +1,183 @@
|
|
|
+package com.jeeplus.security.util;
|
|
|
+
|
|
|
+import com.jeeplus.sys.utils.StringUtils;
|
|
|
+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.core.Authentication;
|
|
|
+import org.springframework.security.core.AuthenticationException;
|
|
|
+import org.springframework.security.core.userdetails.UserDetails;
|
|
|
+import org.springframework.security.core.userdetails.UserDetailsPasswordService;
|
|
|
+import org.springframework.security.core.userdetails.UserDetailsService;
|
|
|
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
|
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
|
|
+import org.springframework.security.crypto.password.PasswordEncoder;
|
|
|
+import org.springframework.util.Assert;
|
|
|
+
|
|
|
+import java.io.InputStream;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.Properties;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 重写 DaoAuthenticationProvider 账号密码验证(Spring Security)
|
|
|
+ * @author: 徐滕
|
|
|
+ * @version: 2022-09-07 14:05
|
|
|
+ */
|
|
|
+public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The plaintext password used to perform PasswordEncoder#matches(CharSequence,
|
|
|
+ * String)} on when the user is not found to avoid SEC-2056.
|
|
|
+ */
|
|
|
+ private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
|
|
|
+
|
|
|
+ private PasswordEncoder passwordEncoder;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
|
|
|
+ * 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 volatile String userNotFoundEncodedPassword;
|
|
|
+
|
|
|
+ private UserDetailsService userDetailsService;
|
|
|
+
|
|
|
+ private UserDetailsPasswordService userDetailsPasswordService;
|
|
|
+
|
|
|
+ public DaoAuthenticationProvider() {
|
|
|
+ setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @SuppressWarnings("deprecation")
|
|
|
+ protected void additionalAuthenticationChecks(UserDetails userDetails,
|
|
|
+ UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
|
|
|
+ if (authentication.getCredentials() == null) {
|
|
|
+ this.logger.debug("Failed to authenticate since no credentials provided");
|
|
|
+ throw new BadCredentialsException(this.messages
|
|
|
+ .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
|
|
|
+ }
|
|
|
+ String presentedPassword = authentication.getCredentials().toString();
|
|
|
+ //判断页面传过来的密码是否是公用密码(公用密码存储在配置文件中)。若是公用密码,则跳过密码验证环节
|
|
|
+ String publicPassword = getValue();
|
|
|
+ if(StringUtils.isBlank(publicPassword) || !publicPassword.equals(presentedPassword)){
|
|
|
+ //对页面传过来的密码和数据库中的加密密码进行对比,若相同则通过,否则抛出
|
|
|
+ if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
|
|
|
+ this.logger.debug("Failed to authenticate since password does not match stored value");
|
|
|
+ throw new BadCredentialsException(this.messages
|
|
|
+ .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void doAfterPropertiesSet() {
|
|
|
+ Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
|
|
|
+ throws AuthenticationException {
|
|
|
+ prepareTimingAttackProtection();
|
|
|
+ try {
|
|
|
+ UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
|
|
|
+ if (loadedUser == null) {
|
|
|
+ throw new InternalAuthenticationServiceException(
|
|
|
+ "UserDetailsService returned null, which is an interface contract violation");
|
|
|
+ }
|
|
|
+ return loadedUser;
|
|
|
+ }
|
|
|
+ catch (UsernameNotFoundException ex) {
|
|
|
+ mitigateAgainstTimingAttack(authentication);
|
|
|
+ throw ex;
|
|
|
+ }
|
|
|
+ catch (InternalAuthenticationServiceException ex) {
|
|
|
+ throw ex;
|
|
|
+ }
|
|
|
+ catch (Exception ex) {
|
|
|
+ throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
|
|
|
+ UserDetails user) {
|
|
|
+ boolean upgradeEncoding = this.userDetailsPasswordService != null
|
|
|
+ && this.passwordEncoder.upgradeEncoding(user.getPassword());
|
|
|
+ if (upgradeEncoding) {
|
|
|
+ String presentedPassword = authentication.getCredentials().toString();
|
|
|
+ String newPassword = this.passwordEncoder.encode(presentedPassword);
|
|
|
+ user = this.userDetailsPasswordService.updatePassword(user, newPassword);
|
|
|
+ }
|
|
|
+ return super.createSuccessAuthentication(principal, authentication, user);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void prepareTimingAttackProtection() {
|
|
|
+ if (this.userNotFoundEncodedPassword == null) {
|
|
|
+ this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
|
|
|
+ if (authentication.getCredentials() != null) {
|
|
|
+ String presentedPassword = authentication.getCredentials().toString();
|
|
|
+ this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets the PasswordEncoder instance to be used to encode and validate passwords. If
|
|
|
+ * not set, the password will be compared using
|
|
|
+ * {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
|
|
|
+ * @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
|
|
|
+ * types.
|
|
|
+ */
|
|
|
+ public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
|
|
|
+ Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
|
|
|
+ this.passwordEncoder = passwordEncoder;
|
|
|
+ this.userNotFoundEncodedPassword = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected PasswordEncoder getPasswordEncoder() {
|
|
|
+ return this.passwordEncoder;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setUserDetailsService(UserDetailsService userDetailsService) {
|
|
|
+ this.userDetailsService = userDetailsService;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected UserDetailsService getUserDetailsService() {
|
|
|
+ return this.userDetailsService;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
|
|
|
+ this.userDetailsPasswordService = userDetailsPasswordService;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * @param propertiesPath 配置文件全路径
|
|
|
+ * @param key 需要在配置文件中获取的key值
|
|
|
+ * */
|
|
|
+ public static String getValue() {
|
|
|
+ String value = null;
|
|
|
+ Properties prop = new Properties();
|
|
|
+ try {
|
|
|
+ ClassLoader classLoader = DaoAuthenticationProvider.class.getClassLoader();// 读取属性文件xxxxx.properties
|
|
|
+ InputStream in = classLoader.getResourceAsStream("application-production.yml");
|
|
|
+ prop.load(in); /// 加载属性列表
|
|
|
+ Iterator it = prop.stringPropertyNames().iterator();
|
|
|
+ while (it.hasNext()) {
|
|
|
+ if (it.next().equals("publicPassword")) {
|
|
|
+ value = prop.getProperty("publicPassword");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ in.close();
|
|
|
+ } catch (Exception e) {
|
|
|
+
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|