[Java]SpringSecurity5实现验证码登录

2021-04-07 29点热度 0人点赞 0条评论

前言

使用SpringSecurity默认的登录是没有验证码的,但是如果没有验证码,被暴力破解还是挺危险的,所以很有必要为SpringSecurity加一层验证码保护。

实现

可以通过给SpringSecurity加一个前置过滤器,来校验验证码来实现。

配置文件如下SecurityConfiguration.java:

主要看configure方法里的:http.addFilterBefore(new CaptchaAuthenticationFilter(loginProcessingUrl, "login?code=-2"), UsernamePasswordAuthenticationFilter.class)

其他的按需要复制

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private static final String REMEMBER_KEY = "xxx-web";

    @Autowired
    private MySecurityFilter mySecurityFilter;

    @Autowired
    private MyAuthenticationProvider myAuthenticationProvider;

    @Autowired
    private MyAccountDetailServiceImpl myAccountDetailService;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        MyJdbcTokenRepositoryImpl jdbcTokenRepository = new MyJdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        jdbcTokenRepository.setCreateTableOnStartup(false);
        return jdbcTokenRepository;
    }

    @Bean
    public PersistentTokenBasedRememberMeServices rememberMeServices() {
        return new PersistentTokenBasedRememberMeServices(REMEMBER_KEY, userDetailsService, persistentTokenRepository());
    }

    @Bean
    public MyLogoutHandler myLogoutHandler() {
        return new MyLogoutHandler("/login?code=1");
    }

    @Bean
    public AuthenticationSuccessHandler myAuthenticationSuccessHandler() {
        //也可以根据实际情况来决定首页跳转到哪里,写到MyAuthenticationSuccessHandler的逻辑里去,注入service参考MyLogoutHandler
        return new MyAuthenticationSuccessHandler();
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //防止用户名输入了中文,后面获取到了乱码
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        filter.setForceEncoding(true);
        http.addFilterBefore(filter, CsrfFilter.class);

        String loginProcessingUrl = "/j_spring_security_check";
        http.headers().frameOptions().disable();//可以嵌套到frame中去

        // 增加验证码校验的前置过滤器
        http
                .addFilterBefore(new CaptchaAuthenticationFilter(loginProcessingUrl, "login?code=-2"), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(mySecurityFilter, FilterSecurityInterceptor.class)
                .authorizeRequests()
                .antMatchers(loginProcessingUrl, "/main/**", "/", "/logout_index", "/login_index").permitAll()  //任何人都可以访问,需要把自定义的登录、登出的链接加到这里来,要不然会调到原生的 login 界面
                .anyRequest().authenticated() //其他所有资源都需要认证,登陆后访问
                .and().formLogin().loginPage("/login").failureHandler(new MyAuthFailHandler("/login?code=-1")).loginProcessingUrl(loginProcessingUrl).successHandler(myAuthenticationSuccessHandler()).authenticationDetailsSource(new MyWebAuthenticationDetailsSource())
//                .and().rememberMe().rememberMeServices(rememberMeServices())
                .and().rememberMe().key(REMEMBER_KEY).tokenRepository(persistentTokenRepository()).userDetailsService(myAccountDetailService)
                .and().exceptionHandling().accessDeniedPage("/login?code=-3")
                .and().logout().logoutUrl("/logout").logoutSuccessHandler(myLogoutHandler()).permitAll()
                .and().csrf().disable()
        ;
        //UserDetails实现类Account那边要 重写equals()和hashCode()
        http.sessionManagement().maximumSessions(5).expiredUrl("/login?code=-5").sessionRegistry(sessionRegistry());
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(myAuthenticationProvider);
    }

    @Override
    public void configure(WebSecurity web) {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/assets/**", "/dashboard/**");
        web.ignoring().antMatchers("/**/fav.ico", "/favicon.ico", "/robots.txt");
        web.ignoring().antMatchers("/**/captcha.jpg");
        web.ignoring().antMatchers("/login", "/authweb");
        web.ignoring().antMatchers("/test", "/404_error", "/generic_error", "/about");
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        SessionRegistry sessionRegistry = new SessionRegistryImpl();
        return sessionRegistry;
    }

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
}

CaptchaAuthenticationFilter.java 如下:

主要就是对比request里的captcha和session里生成的captcha是否一直,不一致的话,跑出SessionAuthenticationException

public class CaptchaAuthenticationFilter extends GenericFilterBean {

    //请求路径匹配
    private RequestMatcher requiresAuthenticationRequestMatcher;

    private String defaultFailureUrl;

    public CaptchaAuthenticationFilter(String filterUrl, String defaultFailureUrl) {
        this.requiresAuthenticationRequestMatcher = new AntPathRequestMatcher(filterUrl, "POST");
        this.defaultFailureUrl = defaultFailureUrl;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 不是需要过滤的路径,执行下一个过滤器
        if (!requiresAuthentication(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        HttpSession session = request.getSession();
        String requestCode = request.getParameter("captcha");
        String sessionCode = (String) session.getAttribute(Constants.SESSION_GENERATED_CAPTCHA_KEY);
        session.removeAttribute(Constants.SESSION_GENERATED_CAPTCHA_KEY);
        if (sessionCode == null || !sessionCode.equals(requestCode)) {
            String username = request.getParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY);
//            String password = request.getParameter("password");
            session.setAttribute(MyAuthFailHandler.LAST_USERNAME_KEY, username);
//            session.setAttribute(MyAuthFailHandler.LAST_AUTH_FAIL_MSG, "验证码错误");
            response.sendRedirect(defaultFailureUrl);
            return;
//            throw new SessionAuthenticationException("验证码错误");
        }
        filterChain.doFilter(request, response);
    }

    private boolean requiresAuthentication(HttpServletRequest request) {
        return requiresAuthenticationRequestMatcher.matches(request);
    }
}

前端登录的页面的表单里,增加类似captcha就可以了:

<input class="" name="captcha" placeholder="输入验证码" />

<%--生成验证码--%>
<img :src="captchasrc" @click="refreshCaptcha" class="img-responsive" style="min-width:120px;height: 38px;"/>

关于如何生成验证码,详见我之前的博客:[Java]Spring/SpringBoot实现验证码(Kaptcha的多种自定义使用)

验证码校验,也可以用在Spring Seciruty的自定义的登录,详见我之前的博客:[Java]SpringSecurity5实现自定义的登录和退出,包括 remember-me下次自动登录

 

 

admin

这个人很懒,什么都没留下

文章评论

*

code