当前位置 博文首页 > 文章内容

    Spring Security添加验证码的两种方式小结

    作者:shunshunshun18 栏目:未分类 时间:2021-10-08 14:43:14

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    一、自定义认证逻辑

    生成验证码工具

    <dependency>
        <groupId>com.github.penggle</groupId>
        <artifactId>kaptcha</artifactId>
        <version>2.3.2</version>
    </dependency>
    

    添加Kaptcha配置

    @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;
        }
    }
    

    生成验证码文本,放入HttpSession中

    根据验证码文本生成图片 通过IO流写出到前端。

    @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);
            }
        }
        @RequestMapping("/index")
        public String index() {
            return "login success";
        }
        @RequestMapping("/hello")
        public String hello() {
            return "hello spring security";
        }
    }
    

    form表单

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
        <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="external nofollow"  rel="stylesheet" id="bootstrap-css">
        <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    </head>
    <style>
        #login .container #login-row #login-column #login-box {
            border: 1px solid #9C9C9C;
            background-color: #EAEAEA;
        }
    </style>
    <body>
    <div id="login">
        <div class="container">
            <div id="login-row" class="row justify-content-center align-items-center">
                <div id="login-column" class="col-md-6">
                    <div id="login-box" class="col-md-12">
                        <form id="login-form" class="form" action="/doLogin" method="post">
                            <h3 class="text-center text-info">登录</h3>
                            <div th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></div>
                            <div class="form-group">
                                <label for="username" class="text-info">用户名:</label><br>
                                <input type="text" name="uname" id="username" class="form-control">
                            </div>
                            <div class="form-group">
                                <label for="password" class="text-info">密码:</label><br>
                                <input type="text" name="passwd" id="password" class="form-control">
                            </div>
                            <div class="form-group">
                                <label for="kaptcha" class="text-info">验证码:</label><br>
                                <input type="text" name="kaptcha" id="kaptcha" class="form-control">
                                <img src="/vc.jpg" alt="">
                            </div>
                            <div class="form-group">
                                <input type="submit" name="submit" class="btn btn-info btn-md" value="登录">
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
    </body>
    

    验证码图片地址为我们在Controller中定义的验证码接口地址。

    身份认证是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:

    @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();
        }
    }
    
    1. 配置UserDetailsService提供的数据源
    2. 提供AuthenticationProvider实例并配置UserDetailsService
    3. 重写authenticationManagerBean方法提供一个自己的ProviderManager并自定义AuthenticationManager实例。

    二、自定义过滤器

    LoginFilter继承UsernamePasswordAuthenticationFilter 重写attemptAuthentication方法:

    public class LoginFilter extends UsernamePasswordAuthenticationFilter {
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            if (!request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException(
                        "Authentication method not supported: " + request.getMethod());
            }
            String kaptcha = request.getParameter("kaptcha");
            String sessionKaptcha = (String) request.getSession().getAttribute("kaptcha");
            if (!StringUtils.isEmpty(kaptcha) && !StringUtils.isEmpty(sessionKaptcha) && kaptcha.equalsIgnoreCase(sessionKaptcha)) {
                return super.attemptAuthentication(request, response);
            }
            throw new AuthenticationServiceException("验证码输入错误");
        }
    }
    

    在SecurityConfig中配置LoginFilter

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(AuthenticationManagerBuilder auth)
                throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("javaboy")
                    .password("{noop}123")
                    .roles("admin");
        }
    
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean()
                throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Bean
        LoginFilter loginFilter() throws Exception {
            LoginFilter loginFilter = new LoginFilter();
            loginFilter.setFilterProcessesUrl("/doLogin");
            loginFilter.setAuthenticationManager(authenticationManagerBean());
            loginFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/hello"));
            loginFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/mylogin.html"));
            return loginFilter;
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/vc.jpg").permitAll()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/mylogin.html")
                    .permitAll()
                    .and()
                    .csrf().disable();
            http.addFilterAt(loginFilter(),
                    UsernamePasswordAuthenticationFilter.class);
        }
    }
    

    显然第二种比较简单

    总结