SpringSecurity入门4---自定义登录认证实现图形验证码

代码地址
在上文中基于过滤器实现了图形验证码的操作,这次我们深入研究一下自定义登录认证,并基于自定义登录认证来完成图形验证码的验证操作。

Authentication

在SpringSecurity中将用户权限、其他系统和设备等包装成为了一个接口

public interface Authentication extends Principal, Serializable {
	// 权限列表
    Collection<? extends GrantedAuthority> getAuthorities();
	// 主体凭据,一般为密码
    Object getCredentials();
	// 主体携带的其他详细信息(等会验证码信息就会保存在这里)
    Object getDetails();
	// 主体,一般为用户名
    Object getPrincipal();
	// 是否验证成功
    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

在表单登录的过程中,使用的UsernamePasswordAuthenticationToken,查看该类的继承关系,可以看到其父类AbstractAuthenticationToken实现了Authentication接口

AuthenticationProvider

上面的Authentication 在各个AuthenticationProvider之间传输进行验证

public interface AuthenticationProvider {
	// 验证方法,验证完毕返回Authentication 
    Authentication authenticate(Authentication var1) throws AuthenticationException;
	// 是否支持当前的Authentication 类型
    boolean supports(Class<?> var1);
}

ProviderManager

AuthenticationProvider一般为很多个,由ProviderManager来进行管理并进行验证,其中保存了AuthenticationProvider的列表,在authenticate方法中会对对列表中的AuthenticationProvider进行迭代处理

private List<AuthenticationProvider> providers;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
	......
}

AbstractUserDetailsAuthenticationProvider

在该类中实现了基本的认证流程,不过我们这里并不是要继承它,而是继承之后的DaoAuthenticationProvider(相较之多了密码加密的过程),这里主要介绍一下各个方法的用途

	// 额外的认证,我们主要是实现这个方法来校验验证码
    protected abstract void additionalAuthenticationChecks(UserDetails var1, UsernamePasswordAuthenticationToken var2) throws AuthenticationException;
    // 检索用户
    protected abstract UserDetails retrieveUser(String var1, UsernamePasswordAuthenticationToken var2) throws AuthenticationException;
	// 验证用户信息
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
			......
	}

一般情况下继承并重写retrieveUseradditionalAuthenticationChecks就可以完成自定义认证的流程

获取验证码

上篇里面我们的验证码是从request中获取的,但是在additionalAuthenticationChecks方法中并没有HttpServletRequest的参数,在Authentication中有一个getDetails()的方法,我们可以将验证码信息放在Details中。所以要找到注入Authentication的时机,同时添加我们的验证码信息到Details中;在UsernamePasswordAuthenticationFilter中会调用ProviderManager,Authentication也是来源于这个过滤器,在这个过滤器中有一个setDetails的方法,可以看到入参就有HttpServletRequest 了

protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

但是这个方法使用的是authenticationDetailsSource.buildDetails,然而后进入方法,发现AuthenticationDetailsSource只是一个接口,所以我们需要自己实现一个AuthenticationDetailsSource,将我们的验证码放进去

实现

上面已经分析完毕,接下来就来实现吧。

我们需要一个自定义的Details,实现WebAuthenticationDetails

public class MyWebAuthenticationDetails extends WebAuthenticationDetails {
	// 在构造器就初始化验证码是否正确,用于在Provider判断
    private boolean isCodeRight;
    private String imageCode;

    public boolean isCodeRight() {
        return isCodeRight;
    }

    public MyWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        String requestCode = request.getParameter("captcha");
        HttpSession session = request.getSession();
        String vertificationCode = (String) session.getAttribute("captcha");
        // 不论校验成功还是失败,要保证session的验证码被删除
        session.removeAttribute("captcha");
        if(StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(vertificationCode)
                || !requestCode.equals(vertificationCode)){
            this.isCodeRight = false;
        }else {
            this.isCodeRight = true;
        }
    }
}

创建自己的AuthenticationDetailSource,用于setDetails调用并注入我们的MyWebAuthenticationDetails

@Component
public class MyAuthenticationDetailSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest request) {
        return new MyWebAuthenticationDetails(request);
    }
}

接下来就可以实现我们的自己的AuthenticationProvider了

@Component
public class MyAuthenticationProvider extends DaoAuthenticationProvider{
	// 由Spring注入UserDetailService和PasswordEncoder
    public MyAuthenticationProvider(@Qualifier("myUserDetailService") UserDetailsService userDetailsService,
                                    PasswordEncoder passwordEncoder) {
        this.setUserDetailsService(userDetailsService);
        this.setPasswordEncoder(passwordEncoder);
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // 获取details
        MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) authentication.getDetails();
        // 如果验证码错误抛出异常
        if(!details.isCodeRight()){
            throw new VerificationCodeException();
        }
        super.additionalAuthenticationChecks(userDetails, authentication);
    }
}

最后在配置文件中进行配置即可

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MySuccessHandler successHandler;

    @Autowired
    private MyFailureHandler failureHandler;

    @Autowired
    private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> myWebAuthenticationDetailsSource;

    @Autowired
    private AuthenticationProvider authenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/css/**", "/img/**", "/js/**", "/bootstrap/**","/captcha.jpg").permitAll()
                .antMatchers("/app/api/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage("/myLogin.html")
                .loginProcessingUrl("/login")
                .successHandler(successHandler)
                .failureHandler(failureHandler)
                // 注入自定义authenticationDetailsSource
                .authenticationDetailsSource(myWebAuthenticationDetailsSource)
                .permitAll()
                // 使登录页不受限
                .and()
                .csrf().disable();
                // 在验证用户名密码之前验证验证码信息
                //.addFilterBefore(new VerificationCodeFilter(),
                //UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    	// 添加自定义Provider
        auth.authenticationProvider(authenticationProvider);
    }

    @Bean
    // 配置验证码工具
    public Producer captcha(){
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width","150");
        properties.setProperty("kaptcha.image.height","50");
        // 字符集
        properties.setProperty("kaptcha.textproducer.char.string","0123456789");
        // 字符长度
        properties.setProperty("kaptcha.textproducer.char.length","4");
        Config config = new Config(properties);
        // 使用默认图形验证码
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

需要注意的是,之前的PasswordEncoder我在WebSecurityConfig中进行配置的,由于我们配置了自定义的Provider,使用构造器注入的PasswordEncoder,而WebSecurityConfig文件中又注入了Provider,我们需要将PasswordEncoder单独放在一个其他的配置文件中注入容器,否则会引起循环依赖

重启项目测试即可

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值