springsecurity集成kaptcha功能

前端代码

本次采用简单的html静态页面作为演示,也可结合vue前后端分离开发,复制就可运行测试

项目目录

登录界面

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript">
        function refresh() {
            document.getElementById('captcha_img').src="/kaptcha?"+Math.random();
        }
    </script>
</head>
<body>
<form action="/login" method="post">
    账号:<input type="text" placeholder="请输入账号" name="username"><br>
    密码:<input type="password" placeholder="请输入密码" name="password"><br>
    验证码:  <input type="text" placeholder="请输入验证码" name="code">
    <div class="item-input">
        <img id="captcha_img" alt="点击更换" title="点击更换"
             onclick="refresh()" src="/kaptcha" />
    </div>
    记住我:<input type="checkbox" name="remember-me" value="true"><br>
    <input type="submit" value="提交"/>
</form>
</body>
</html>

登录成功

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
成功<a href="/logout">退出</a>
</body>
</html>

后端代码

pom

XML 复制代码
    <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--图形验证码-->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>
        <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
    </dependencies>

配置类

kaptcha配置类用于生成验证码格式

java 复制代码
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;



@Configuration
public class KaptchaConfiguration {
    @Bean
    public DefaultKaptcha getDefaultKaptcha() {
        com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
        Properties properties = new Properties();

        properties.put("kaptcha.textproducer.char.string", "0123456789");
        properties.put("kaptcha.border", "no");
        properties.put("kaptcha.textproducer.font.color", "black");
        properties.put("kaptcha.textproducer.char.space", "5");
        properties.put("kaptcha.textproducer.char.length","4");
        properties.put("kaptcha.image.height","34");
        properties.put("kaptcha.textproducer.font.size","30");
        properties.setProperty("kaptcha.image.width", "164");
        properties.setProperty("kaptcha.image.height", "64");
        properties.put("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise");

        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);

        return defaultKaptcha;
    }

}

springsecurity

java 复制代码
import com.sfy.kapcha.filter.CodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;


@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;
    @Autowired
    PersistentTokenRepository persistentTokenRepository;

    @Bean
    public PasswordEncoder getPw(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 基于内存存储的多用户
        auth.inMemoryAuthentication().withUser("admin").password(getPw().encode("123")).roles("root");
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 忽略静态请求
        web.ignoring().antMatchers("/img/**", "/js/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                //当发现是login时认为是登录,必须和表单提供的地址一致去执行UserDetailsServiceImpl
                .loginProcessingUrl("/login")
                //自定义登录界面
                .loginPage("/login.html")
                .successForwardUrl("/toMain")
                .permitAll()
                .and()
                .addFilterBefore(new CodeFilter(), UsernamePasswordAuthenticationFilter.class);

        //认证授权
        http.authorizeRequests()
                .antMatchers("/kaptcha").permitAll()
                //登录放行不需要认证
                .antMatchers("/login.html").permitAll()
                //所有请求都被拦截类似于mvc必须登录后访问
                .anyRequest().authenticated();
        //关闭csrf防护
        http.csrf().disable();

        //退出登录
        http.logout()
                .logoutSuccessUrl("/login.html");

        //记住我
        http.rememberMe().tokenValiditySeconds(60);
    }

    @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PersistentTokenRepository getPersistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //第一次启动时建表,第二次使用时注释掉
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
}

controller

kaptcha

java 复制代码
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;

/**
 * @Author: sfy
 * @Date: 2024/1/18 11:13
 */

@RestController
public class KapchaController {
    @Autowired
    DefaultKaptcha defaultKaptcha;

    @GetMapping("/kaptcha")
    public void getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpSession session = request.getSession();
        response.setDateHeader("Expires", 0);
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        response.setHeader("Pragma", "no-cache");
        response.setContentType("image/jpeg");
        // 创建验证码
        String capText = defaultKaptcha.createText();
        // 验证码放入session
        session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);
        BufferedImage bi = defaultKaptcha.createImage(capText);
        ServletOutputStream out = response.getOutputStream();
        ImageIO.write(bi, "jpg", out);
        try {
            out.flush();
        } finally {
            out.close();
        }
    }

}

login进行简单的页面重定向(要用Controller)

java 复制代码
@Controller
public class LoginController {

    @RequestMapping("/toMain")
    public String toMain(){
        return "redirect:main.html";
    }

}

filter

用于检测图像验证码的正确性,只有当验证码正确时,过滤器链才会走到springsecurity的检测

java 复制代码
public class CodeFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {

        String uri = req.getServletPath();
        if (uri.equals("/login") && req.getMethod().equalsIgnoreCase("post")) {
            // 服务端生成的验证码数据
            String sessionCode = req.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY).toString();
            System.out.println("正确的验证码: " + sessionCode);
            // 用户输入的验证码数据
            String formCode = req.getParameter("code").trim();
            System.out.println("用户输入的验证码: " + formCode);
            if (StringUtils.isEmpty(formCode)) {
                throw new RuntimeException("验证码不能为空");
            }
            if (sessionCode.equals(formCode)) {
                System.out.println("验证通过");
            } else {
                throw new AuthenticationServiceException("验证码输入不正确");
            }
        }
        chain.doFilter(req, res);
    }

}

数据库

XML 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/security?serverTimezone=GMT%2B8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
  main:
    allow-circular-references: true #开始支持spring循环依赖

当第一次执行项目时,会在库中生成表数据

相关推荐
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
Yaml43 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~3 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7894 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java4 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
睡觉谁叫~~~5 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
程序媛小果5 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot