SpringSecurity 添加验证码的两种方式

发布时间 2023-05-30 14:06:44作者: 与f

一 验证码生产

<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>
@Configuration
public class KaptchaConfig {
    @Bean
    Producer kaptcha() {
        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;
    }
}
@RestController
public class LoginController {
    @Autowired
    Producer producer;
    @GetMapping("/vc.jpg")
    public void getVerifyCode(HttpServletResponse resp, HttpSession session) throws IOException {
        resp.setContentType("image/jpeg");
        String text = producer.createText();
        session.setAttribute("kaptcha", text);
        BufferedImage image = producer.createImage(text);
        try(ServletOutputStream out = resp.getOutputStream()) {
            ImageIO.write(image, "jpg", out);
        }
    }
}

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用户登陆</title>
</head>
<body>
<h2>登录页面</h2>
<!--${param.error}这个如果有值,就显示帐号或密码错误-->
<h4 th:if="${session.errorMsgs}" style="color: #c41f1f;">帐号或密码错误,请重新输入</h4>
<form action="/login/doLogin" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="uname" value="zhangsan"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="pwd" value="123456"></td>
        </tr>
        <td>验证码:</td>
        <td><input type="text" name="code"> <img src="/vc.jpg" style="height:33px;cursor:pointer;"
                                                 onclick="this.src=this.src">
            <span th:text="${session.errorMsg}" style="color: #FF0000;"></span>
        </td>
        <tr>
            <td colspan="2">
                <button type="submit">登录</button>
                <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
            </td>
        </tr>
    </table>
</form>
</body>
        

 

 

二 添加过滤器认证

 UsernamePasswordAuthenticationFilter收集用户名和密码,之前添加一个校验验证码的过滤器,通过配置加入到过滤器链。

 

@Component
public class ValidateCodeFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //得到请求地址
        String requestURI = request.getRequestURI();
        System.out.println("requestURL" + requestURI);

        //判断是否是登录请求
        if (requestURI.equals("/login/doLogin")) {
            //说明当前请求为登陆
            //1,得到登陆时用户输入的验证码
            String code1 = request.getSession().getAttribute("kaptcha").toString();
            String code = request.getParameter("kaptcha");
            System.out.println("用户输入的验证码:" + code);
            if (StringUtils.hasText(code)) {
                if (code.equalsIgnoreCase(code1)) {
                    //说明验证码正确  直接放行
                    request.getSession().removeAttribute("errorMSg");
                    filterChain.doFilter(request, response);
                    return;
                } else {
                    //说明验证码不正确,返回登陆页面
                    request.getSession().setAttribute("errorMsg", "验证码错误");
                    response.sendRedirect("/index/toLogin");
                    return;
                }
            } else {
                //用户没有输出验证码重定向到登陆页面
                request.getSession().setAttribute("errorMsg", "验证码不能为空");
                response.sendRedirect("/index/toLogin");
                return;
            }
        } else {
            //说明不是登陆 直接放行到下一个过滤器
            filterChain.doFilter(request, response);
            return;
        }

    }
}

 

 

 

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /*注入 登录成功处理器*/
    @Autowired
    private AppAuthenticationSuccessHandler appAuthenticationSuccessHandler;

    /*注入 登录 失败处理器*/
    @Autowired
    private AppAuthenticationFailureHandler appAuthenticationFailureHandler;

    /*注入  没有权限处理器*/
    @Autowired
    private AppAccessDeniedHandler appAccessDeniedHandler;

    /*注入 登出成功处理器*/
    @Autowired
    private AppLogoutSuccessHandler appLogoutSuccessHandler;

    @Autowired
    private AppUserDetailsService appUserDetailsService;

    //验证码拦截器注入
    @Autowired
    private ValidateCodeFilter validateCodeFilter;

    /*配置多用户*/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(appUserDetailsService);
    }

    /*http请求配置*/

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        super.configure(http);// 不使用父类的 方法, 需要 提供登录配置
        /*没权权限的处理*/
//        http.exceptionHandling().accessDeniedHandler(appAccessDeniedHandler);
        /*登录*/
        // 配置登录之前添加一个验证码的过滤器
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
        http.formLogin()
                .usernameParameter("uname")//页面表单账号的参数名  默认 为 username
                .passwordParameter("pwd")//页面表单密码的参数名   默认 为 password
                .loginPage("/index/toLogin")//定义登录页面的 请求 地址(转发到登录页面)
                .loginProcessingUrl("/login/doLogin")// 表单提交的 地址(不需要提供),登录验证.....
                .successForwardUrl("/index/toIndex")//登录成功 跳转的路径
                .failureForwardUrl("/index/toLogin")//登录失败 跳转的路径
//                .successHandler(appAuthenticationSuccessHandler)//登录成功处理器
//                .failureHandler(appAuthenticationFailureHandler)//登录失败处理器
                .permitAll();
        ;

        /*登出*/
        http.logout()
                .logoutUrl("/logout")//登出的 请求地址
                .logoutSuccessUrl("/index/toLogin")//登出成功后 访问的路径
//                .logoutSuccessHandler(appLogoutSuccessHandler)//登出成功处理器
                .permitAll()
        ;
          /*设置 资源所需要的 权限 (好比 门上锁)*/
        http.authorizeRequests()
//                .mvcMatchers("/index/toLogin", "/index.html","/code/img").permitAll()//不需要认证就可以访问
                .antMatchers("/code/img")  // 放行验证码的路径
                .permitAll()
                .anyRequest().authenticated()//所有请求都需要登录认证 才能进行

        ;
        /*禁用csrf跨域请求攻击   如果不禁用  自定义的登录页面无法登录*/
        http.csrf().disable();
    }

    /*资源服务匹配放行:静态资源*/
    @Override
    public void configure(WebSecurity web) throws Exception {
//        super.configure(web);
        web.ignoring().antMatchers("/css/**");
    }

    /*强制要求配置 密码加密器*/
    @Bean// 将对象 交给 spring容器 管理
    public PasswordEncoder passwordEncoder() {
//        return NoOpPasswordEncoder.getInstance();//不加密
        return new BCryptPasswordEncoder();
    }

}

 

 

 

 

 

 

三 自定义认证

 

UsernamePasswordAuthenticationFilter收集用户名和密码后,要往ProviderManager--DaoAuthenticationProvider--InMemoryUserDetailsManager/UserDetailsService传递。

我们可以继承原有DaoAuthenticationProvider类重写其方法,让在调用UserDetailsService之前,校验验证码。

 

 身份认证是 AuthenticationProvider 的 authenticate 方法完成,因此验证码可以在此之前完成:

public class KaptchaAuthenticationProvider extends DaoAuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String kaptcha = req.getParameter("kaptcha");
        String sessionKaptcha = (String) req.getSession().getAttribute("kaptcha");
        if (kaptcha != null && sessionKaptcha != null && kaptcha.equalsIgnoreCase(sessionKaptcha)) {
            return super.authenticate(authentication);
        }
        throw new AuthenticationServiceException("验证码输入错误");
    }
}

配置 AuthenticationManager:(旧方式继承WebSecurityConfigurerAdapter

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    AuthenticationProvider kaptchaAuthenticationProvider() {
        InMemoryUserDetailsManager users = new InMemoryUserDetailsManager(User.builder()
                .username("xiepanapn").password("{noop}123").roles("admin").build());
        KaptchaAuthenticationProvider provider = new KaptchaAuthenticationProvider();
        provider.setUserDetailsService(users);
        return provider;
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        ProviderManager manager = new ProviderManager(kaptchaAuthenticationProvider());
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/vc.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/mylogin.html")
                .loginProcessingUrl("/doLogin")
                .defaultSuccessUrl("/index.html")
                .failureForwardUrl("/mylogin.html")
                .usernameParameter("uname")
                .passwordParameter("passwd")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

新方式过滤器链

@Configuration
@EnableWebSecurity    // 添加 security 过滤器
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)    // 启用方法级别的权限认证
public class SecurityWebConfig {

    @Autowired(required=true)
    public UserDetailsService userDetailsServiceImpl;

    @Bean
    AuthenticationProvider kaptchaAuthenticationProvider() {
        //内存认证
        //InMemoryUserDetailsManager users = new InMemoryUserDetailsManager(User.builder().username("xiepanapn").password("{noop}123").roles("admin").build());
        //provider.setUserDetailsService(users);

        //userDetailsService认证
        KaptchaAuthenticationProvider provider = new KaptchaAuthenticationProvider();
        provider.setUserDetailsService(userDetailsServiceImpl);
        return provider;
    }

    /**
     * 获取AuthenticationManager(认证管理器),登录时认证使用
     * @param authenticationConfiguration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        //return authenticationConfiguration.getAuthenticationManager(); //方式一
        ProviderManager manager = new ProviderManager(kaptchaAuthenticationProvider());  //方式二,(验证码)
        return manager;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 注解标记允许匿名访问的url
        //ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
        //permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());

        http
            // 基于 web,需要 csrf (其实不用配置,默认支持)
            .csrf().and()
            // 基于 web,需要 session  (其实不用配置,默认支持)
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and()
            // 下面开始设置权限过滤请求
            .authorizeRequests(authorize -> authorize
                    // 请求放开
                    .antMatchers("/index","/index1","/index2","/index3").permitAll()
                    // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                    .antMatchers("/login", "/register", "/captchaImage").permitAll()
                    // 静态资源,可匿名访问
                    .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                    .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
                    // 其他地址的访问均需验证权限
                    .anyRequest().authenticated()
            );
            // 认证用户时用户信息加载配置,注入springAuthUserService  (其实不用配置,默认实现接口即可调用)
            //.userDetailsService(userDetailsServiceImpl);

        //请求过滤规则

        //指定登录表单的规则
        http.formLogin()
            //这个路径必须和登录表单的提交路径一致。
            .loginProcessingUrl("/login")
            //设置自定义登录界面
            .loginPage("/login.html")
            //登录成功后转发的路径
            .successForwardUrl("/main")
            //登录失败跳转地址
            .failureForwardUrl("/fail")
            .permitAll();

        //登出配置
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/").clearAuthentication(true);

        //记住我功能

        //没有权限时跳转的路径
        http.exceptionHandling().accessDeniedPage("/403.html");
        return http.build();
    }
    @Bean
    public PasswordEncoder passwordEncoder(){
        return  new BCryptPasswordEncoder();
    }
    /**
     * 配置跨源访问(CORS)
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
    
}

 

 

 

  1. 配置 UserDetailsService 提供的数据源

  2. 提供 AuthenticationProvider 实例,并配置 UserDetailsService

  3. 重写 authenticationManagerBean 方法提供一个自己的 ProviderManager 并自定义 AuthenticationManager 实例。

 

 

 

 

 

转: https://xie.infoq.cn/article/a5614c477aa97be61ae2a0ee6

https://blog.csdn.net/qq_51307593/article/details/127561058

https://www.cnblogs.com/dalianpai/p/14744704.html