SpringSecurity重写DaoAuthenticationProvider,自定义密码对比类

  1. 需求,默认的SpringSecurity密码校验,无法满足需求,需要自定义来进行密码比对

a、网上找了一些教程,没法一步到位,很多代码,连import都没有贴出来,烦死了。

b、这里说明一下,我是为了图方便,系统原有密码登录情况下,要加入验证码登录,由于验证码时4-5位纯数字,而密码是6位以上的数字+字母,因此不想Filter这些全部来搞,因此想了这个办法简洁一点,也很快能够完成功能。

c、主要涉及两个地方,1是新增MyAuthenticationProvider类,继承DaoAuthenticationProvider,2,public class SecurityConfig extends WebSecurityConfigurerAdapter配置类中修改protected void configure(AuthenticationManagerBuilder auth) throws Exception方法。

MyAuthenticationProvider.java代码如下:

import com.dhproject.common.constant.CacheConstants;
import com.dhproject.common.core.redis.RedisCache;
import com.dhproject.common.exception.ServiceException;
import com.dhproject.common.utils.SecurityUtils;
import com.dhproject.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;

public class MyAuthenticationProvider extends DaoAuthenticationProvider {

    //passwordEncoder密码比较器
    @Resource
    private PasswordEncoder passwordEncoder;

    //Redis
    @Autowired
    protected RedisCache redisCache;

    /**
     * 构造初始化
     * @param userDetailsService
     * @param passwordEncoder
     * @param redisCache
     */
    public MyAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder, RedisCache redisCache) {
        super();
        // 这个地方一定要对userDetailsService赋值,不然userDetailsService是null (这个坑有点深)
        setUserDetailsService(userDetailsService);
        //passwordEncoder由于父类是private,这里需要自定义初始化后才能使用
        this.passwordEncoder = passwordEncoder;
        //这个是Redis,根据实际情况初始化
        this.redisCache = redisCache;
    }

    /**
     * 重写该方法
     * @param userDetails
     * @param authentication
     * @throws AuthenticationException
     */
    @Override
    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"));
        } else {
            String mobile = authentication.getPrincipal().toString();
            String password = authentication.getCredentials().toString();
            //这里进行密码和验证码验证
            //如果是四位纯数字,那么认定是验证码登录
            if(isCodeLogin(password)){
                // 1. 检验Redis手机号的验证码
                String verifyKey = CacheConstants.SMS_CODE_KEY+mobile;
                String code = redisCache.getCacheObject(verifyKey);
                if (StringUtils.isEmpty(code)) {
                    throw new ServiceException("验证码已经过期或尚未发送,请重新发送验证码");
                }
                if (!password.equals(code)) {
                    throw new ServiceException("输入的验证码不正确,请重新输入");
                }
                // 2. 短信验证成功后要删除redis中的验证码
                redisCache.deleteObject(verifyKey);
            }else{
                if (!this.passwordEncoder.matches(password, 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"));
                }
            }
        }
    }
    /**
     * 时都是验证码登录
     * @param password
     * @return
     */
    public boolean isCodeLogin(String password) {
        return StringUtils.isNumeric(password) && (password.length()==4||password.length()==5);
    }
}

SecurityConfig.java代码如下:一定要注意看说明,不然肯定最后还会有问题

/**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        //定义了新的Provider方法,就不能使用默认的userDetailsService进行构造,否则抛出BadCredentialsException异常,代码会继续执行
        auth.authenticationProvider(new MyAuthenticationProvider(userDetailsService,bCryptPasswordEncoder(),redisCache));
        //默认的userDetailsService进行构造
        //auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

SecurityConfig.java类,全部文件,我也贴一下,方便各位参考:

import com.dhproject.common.core.redis.RedisCache;
import com.dhproject.framework.security.sms.MyAuthenticationProvider;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;
import com.dhproject.framework.config.properties.PermitAllUrlProperties;
import com.dhproject.framework.security.filter.JwtAuthenticationTokenFilter;
import com.dhproject.framework.security.handle.AuthenticationEntryPointImpl;
import com.dhproject.framework.security.handle.LogoutSuccessHandlerImpl;

/**
 * spring security配置
 *
 * @author
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    protected RedisCache redisCache;

    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 认证失败处理类
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 跨域过滤器
     */
    @Autowired
    private CorsFilter corsFilter;

    /**
     * 允许匿名访问的地址
     */
    @Autowired
    private PermitAllUrlProperties permitAllUrl;

    /**
     * 解决 无法直接注入 AuthenticationManager
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        // 注解标记允许匿名访问的url
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
        permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
        httpSecurity
            // CSRF禁用,因为不使用session
            .csrf().disable()
            // 禁用HTTP响应标头
            .headers().cacheControl().disable().and()
            // 认证失败处理类
            .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
            // 基于token,所以不需要session
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            // 过滤请求
            .authorizeRequests()
            // 对于登录login 注册register 验证码captchaImage 允许匿名访问
            .antMatchers("/login", "/register", "/captchaImage", "/container/public/sendSms").permitAll()
            // 静态资源,可匿名访问
            .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**", "/public/**").permitAll()
            .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
            // 除上面外的所有请求全部需要鉴权认证
            .anyRequest().authenticated()
            .and()
            .headers().frameOptions().disable();
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        //定义了新的Provider方法,就不能使用默认的userDetailsService进行构造,否则抛出BadCredentialsException异常,代码会继续执行
        auth.authenticationProvider(new MyAuthenticationProvider(userDetailsService,bCryptPasswordEncoder(),redisCache));
        //默认的userDetailsService进行构造
        //auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

2.可能存在问题问题

MyDaoAuthenticationProvider重写DaoAuthenticationProvider中additionalAuthenticationChecks方法后,自定义验证密码错误后,抛出BadCredentialsException异常,代码会继续执行,会执行DaoAuthenticationProvider中的additionalAuthenticationChecks发放,导致自定义抛出的BadCredentialsException的异常无用。

代码注释中已经说明

猜你喜欢

转载自blog.csdn.net/zlxls/article/details/129791257