SpringBoot集成Spring Security(2)——密码校验

一、前言

        在上一篇《SpringBoot集成Spring Security(1)——登录认证》中已经做了Spring Security的基本入门,可以登录和做角色校验,这其中有一点比较好奇的就是密码校对这块。

二、简要分析

        下面通过源码简单的来了解下Spring Security的密码校对这块,在上一篇博客中代码示例里的SecurityConfig里面,我们自己配置了一个密码编码器,然后在检验过程中就会获取改密码编码器,拿到数据库中该用户的密码和你前端传进来的密码,调用matches方法进行校验。那具体是在哪调用的?

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(loginUserDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(charSequence.toString());
            }
        };
    }

密码校验的实现是通过UsernamePasswordAuthenticationFilter这个过滤器捕获到登录请求,获取到前端传来的用户名、密码,代码如下:

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    private String usernameParameter = "username";
    private String passwordParameter = "password";
    private boolean postOnly = true;

    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            // 省略....
            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

	    @Nullable
    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(this.passwordParameter);
    }

    @Nullable
    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(this.usernameParameter);
    }
    
	// 省略....
}

        看到UsernamePasswordAuthenticationFilter.attemptAuthentication方法里面的这一句return this.getAuthenticationManager().authenticate(authRequest);,getAuthenticationManager()获取ProviderManager对象,然后调用其authenticate方法:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //省略....
        while(var8.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                    result = provider.authenticate(authentication); // 这里
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (AuthenticationException var14) {
                    lastException = var14;
                }
            }
        }
        //省略....

    }

provider.authenticate(authentication)这里会调用到DaoAuthenticationProvider父类AbstractUserDetailsAuthenticationProvider的authenticate方法:

这里的provider是DaoAuthenticationProvider对象

public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
    //省略....
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
       	//省略....
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                // 这里会调用到我们自己实现的LoginUserDetailsService的loadUserByUsername方法,通过用户名查询数据库中用户数据
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
               //省略....
            }
				//省略....
        }

        try {
            this.preAuthenticationChecks.check(user);
            // 校验密码
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            
        }
		//省略....        
    }
}

然后在this.additionalAuthenticationChecks(),这里的additionalAuthenticationChecks()是在DaoAuthenticationProvider中实现的:

    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
        	// 获取页面填写的密码
            String presentedPassword = authentication.getCredentials().toString();
            // passwordEncoder当前的密码编码器,调用其matches方法比较,这里就是我们最开始自己配置的那个密码编码器对象了
            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                this.logger.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }

三、自定义密码解码器

        上面是对源码的一个跟踪,主要目的也是解答下心中对于密码在哪校验的疑惑,通过debug的过程发现,security5中如果说我们不去指定密码解析器,那么security默认生成DelegatingPasswordEncoder这个解析器对象,那么在其matches方法中校验时,因为无法获取到security自带的密码编码器,也没有自己指定,最终会抛出异常提示"There is no PasswordEncoder mapped for the id “null”。

PS:自带哪些密码编码器可以去PasswordEncoderFactories中查看,官网也可查看详细介绍。

那自定义密码编码器其实就很简单了,最上面那个其实就是一种实现方式,那最后在贴一个MD5加密的。

// MD5工具类,这个就是在网上找的一个
public class MD5Util {

    private static final String SALT = "test";
    public static String encode(String password) {
        password = password + SALT;
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        char[] charArray = password.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++)
            byteArray[i] = (byte) charArray[i];
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

}

然后在SecurityConfig中:

	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(loginUserDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return MD5Util.encode((String)charSequence);
            }

			// charSequence:前端传过来的
			// s:数据库存储的加密后的密码
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(MD5Util.encode((String)charSequence));
            }
        };
    }

如果是说直接使用Security自带的,例如BCryptPasswordEncoder:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(loginUserDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    @Bean
    BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security 是一个基于 Spring 的安全框架,提供了一组完整的安全性功能,包括身份验证、授权、防止 CSRF 攻击等等。Spring Boot 基于 Spring,自然也集成Spring Security。下面是 Spring Boot 集成 Spring Security 的步骤: 1. 在 pom.xml 中添加 Spring Security 的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2. 编写一个继承自 WebSecurityConfigurerAdapter 的配置类,并使用 @EnableWebSecurity 注解开启 Spring Security: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"); } } ``` 上面的代码中,我们配置Spring Security 的基本功能:对所有请求进行身份验证,允许访问首页和登录页面,使用 inMemoryAuthentication 来指定用户和密码。 3. 在 application.properties 或 application.yml 中配置登录页面的 URL: ```properties spring.security.login.form=/login ``` 4. 编写一个登录页面,例如一个 Thymeleaf 模板: ```html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <form th:action="@{/login}" method="post"> <div><label>Username: <input type="text" name="username"/></label></div> <div><label>Password: <input type="password" name="password"/></label></div> <div><input type="submit" value="Log in"/></div> </form> </body> </html> ``` 这样就完成了 Spring Boot 集成 Spring Security配置。当用户访问受保护的页面时,会被重定向到登录页面进行身份验证。如果用户输入的用户名和密码正确,就会跳转到原来请求的页面。如果不正确,就会显示错误信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值