当前位置: 首页 > article >正文

spring security设置多个数据源和登录验证码

在 Spring Security 中集成多数据源和验证码登录需要结合自定义认证逻辑过滤器链扩展。以下是完整实现方案及代码示例:


一、多数据源认证配置

1. 定义两个数据源的 UserDetailsService
// 数据源1:数据库用户
@Service("db1UserDetailsService")
public class DB1UserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepositoryDB1 userRepoDB1;

    @Override
    public UserDetails loadUserByUsername(String username) {
        UserDB1 user = userRepoDB1.findByUsername(username);
        return new CustomUserDetails(user.getUsername(), user.getPassword(), "DB1_ROLE");
    }
}

// 数据源2:外部API用户
@Service("db2UserDetailsService")
public class DB2UserDetailsService implements UserDetailsService {

    @Autowired
    private ApiUserClient apiUserClient;

    @Override
    public UserDetails loadUserByUsername(String username) {
        UserApi user = apiUserClient.fetchUser(username);
        return new CustomUserDetails(user.getUsername(), user.getPassword(), "DB2_ROLE");
    }
}
2. 自定义 AuthenticationProvider 选择数据源
public class MultiSourceAuthProvider implements AuthenticationProvider {

    @Autowired
    @Qualifier("db1UserDetailsService")
    private UserDetailsService db1Service;

    @Autowired
    @Qualifier("db2UserDetailsService")
    private UserDetailsService db2Service;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {
        String username = auth.getName();
        
        // 根据用户名前缀选择数据源(如 "db1:user")
        UserDetails userDetails = null;
        if (username.startsWith("db1:")) {
            userDetails = db1Service.loadUserByUsername(username.substring(4));
        } else if (username.startsWith("db2:")) {
            userDetails = db2Service.loadUserByUsername(username.substring(4));
        } else {
            throw new BadCredentialsException("Invalid username prefix");
        }

        // 验证密码
        if (passwordEncoder.matches((String) auth.getCredentials(), userDetails.getPassword())) {
            return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        } else {
            throw new BadCredentialsException("Password mismatch");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
3. 注册 AuthenticationProvider
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(auth -> auth
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/home")
            );
        return http.build();
    }

    @Bean
    public AuthenticationProvider multiSourceAuthProvider() {
        return new MultiSourceAuthProvider();
    }

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

二、验证码集成

1. 生成验证码(使用 Kaptcha 库)
<!-- pom.xml 添加依赖 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>
@Configuration
public class CaptchaConfig {

    @Bean
    public Producer captchaProducer() {
        Properties props = new Properties();
        props.setProperty("kaptcha.textproducer.char.length", "4");
        props.setProperty("kaptcha.image.width", "120");
        DefaultKaptcha kaptcha = new DefaultKaptcha();
        kaptcha.setConfig(new Config(props));
        return kaptcha;
    }
}
2. 验证码接口与缓存
@RestController
public class CaptchaController {

    @Autowired
    private Producer captchaProducer;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping("/captcha")
    public void generateCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String code = captchaProducer.createText();
        String sessionId = request.getSession().getId();

        // 存储到 Redis(过期时间 2 分钟)
        redisTemplate.opsForValue().set("captcha:" + sessionId, code, 2, TimeUnit.MINUTES);

        response.setContentType("image/jpeg");
        BufferedImage image = captchaProducer.createImage(code);
        ImageIO.write(image, "jpg", response.getOutputStream());
    }
}
3. 自定义验证码过滤器
public class CaptchaFilter extends OncePerRequestFilter {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) 
            throws IOException, ServletException {

        if ("/login".equals(request.getRequestURI()) && "POST".equalsIgnoreCase(request.getMethod())) {
            String sessionId = request.getSession().getId();
            String captchaCode = redisTemplate.opsForValue().get("captcha:" + sessionId);
            String userInput = request.getParameter("captcha");

            if (userInput == null || !userInput.equalsIgnoreCase(captchaCode)) {
                throw new AuthenticationServiceException("验证码错误");
            }
        }
        chain.doFilter(request, response);
    }
}
4. 将过滤器插入到 Spring Security 链中
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(new CaptchaFilter(), UsernamePasswordAuthenticationFilter.class) // 插入验证码过滤器
            // 其他配置...
        return http.build();
    }
}

三、前端集成示例

<!-- login.html -->
<form action="/login" method="post">
    <input type="text" name="username" placeholder="用户名">
    <input type="password" name="password" placeholder="密码">
    <input type="text" name="captcha" placeholder="验证码">
    <img src="/captcha" onclick="this.src='/captcha?t='+new Date().getTime()">
    <button>登录</button>
</form>

四、关键配置总结

功能实现方式
多数据源选择自定义 AuthenticationProvider,根据用户名前缀选择 UserDetailsService
验证码生成使用 Kaptcha 生成图片,存储到 Redis
验证码校验自定义过滤器拦截 /login 请求,比对输入值与 Redis 缓存
密码加密统一使用 BCryptPasswordEncoder

五、扩展优化建议

  1. 动态数据源路由:通过注解或配置文件动态添加更多数据源。
  2. 验证码类型:支持短信验证码或邮件验证码。
  3. 限流防护:对验证码接口添加限流(如使用 Resilience4j)。
  4. 分布式 Session:使用 Spring Session + Redis 解决集群环境 Session 共享问题。

通过以上方案,可灵活扩展 Spring Security 的认证能力,满足复杂业务场景需求。


http://www.kler.cn/a/613695.html

相关文章:

  • 故障识别 | 基于改进螂优化算法(MSADBO)优化变分模态提取(VME)结合稀疏最大谐波噪声比解卷积(SMHD)进行故障诊断识别,matlab代码
  • MySQL Explain 分析 SQL 执行计划
  • 数据设计(范式、步骤)
  • 2025跳槽学习计划
  • 速卖通历史价格数据获取:API合规调用与爬虫方案风险对比
  • Maven版本统一管理
  • Cursor生成的UI太丑?如何减少UI拉扯?
  • WEB或移动端常用交互元素及组件 | Axure / 元件类型介绍(表单元件、菜单和表格 、流程元件、标记元件)
  • 一文详解k8s体系架构知识
  • 【Kafka】Kafka4.0在windows上启动
  • Android 蓝牙/Wi-Fi通信协议之:经典蓝牙(BT 2.1/3.0+)介绍
  • STM32 IIC通信
  • uWebSockets开发入门
  • ZW3D二次开发_非模板表单_创建
  • C#TCP通讯封装服务器工具类
  • Dify 0.15.3版本 本地部署指南
  • 【Spiffo】光速项目:LVGL v9框架下的MIPI简易相机_Part1
  • Unity中的MaterialPropertyBlock的作用和 Material 的区别
  • 【蓝桥杯】每日练习 Day14 递归
  • 项目复盘:websocket不受跨域限制的原理