ソースを参照

首页登陆添加公共登陆密码验证功能,重写 Spring Security密码验证方法

user5 2 年 前
コミット
b3f5c2d4c5

+ 1 - 1
jeeplus-platform/jeeplus-admin/src/main/java/com/jeeplus/security/config/WebSecurityConfig.java

@@ -5,11 +5,11 @@ import com.jeeplus.security.exception.JwtAuthenticationEntryPoint;
 import com.jeeplus.security.jwt.JWTConfigurer;
 import com.jeeplus.security.jwt.TokenProvider;
 import com.jeeplus.security.service.CustomUserDetailsService;
+import com.jeeplus.security.util.DaoAuthenticationProvider;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;

+ 183 - 0
jeeplus-platform/jeeplus-admin/src/main/java/com/jeeplus/security/util/DaoAuthenticationProvider.java

@@ -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;
+    }
+
+}