Spring Security配置多个数据源并添加登录验证码的实例代码
作者:liwenruo 发布时间:2022-11-19 13:49:26
1.配置多个数据源
多个数据源是指在同一个系统中,用户数据来自不同的表,在认证时,如果第一张表没有查找到用户,那就去第二张表中査询,依次类推。
看了前面的分析,要实现这个需求就很容易了,认证要经过AuthenticationProvider,每一 个 AuthenticationProvider 中都配置了一个 UserDetailsService,而不同的 UserDetailsService 则可以代表不同的数据源,所以我们只需要手动配置多个AuthenticationProvider,并为不同的 AuthenticationProvider 提供不同的 UserDetailsService 即可。
为了方便起见,这里通过 InMemoryUserDetailsManager 来提供 UserDetailsService 实例, 在实际开发中,只需要将UserDetailsService换成自定义的即可,具体配置如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Primary
UserDetailsService us1(){
return new InMemoryUserDetailsManager(User.builder().username("testuser1").password("{noop}123")
.roles("admin").build());
}
@Bean
UserDetailsService us2(){
return new InMemoryUserDetailsManager(User.builder().username("testuser2").password("{noop}123")
.roles("user").build());
}
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
DaoAuthenticationProvider dao1 = new DaoAuthenticationProvider();
dao1.setUserDetailsService(us1());
DaoAuthenticationProvider dao2 = new DaoAuthenticationProvider();
dao2.setUserDetailsService(us2());
ProviderManager manager = new ProviderManager(dao1, dao2);
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//省略
}
}
首先定义了两个UserDetailsService实例,不同实例中存储了不同的用户;然后重写 authenticationManagerBean 方法,在该方法中,定义了两个 DaoAuthenticationProvider 实例并分别设置了不同的UserDetailsService ;最后构建ProviderManager实例并传入两个 DaoAuthenticationProvider,当系统进行身份认证操作时,就会遍历ProviderManager中不同的 DaoAuthenticationProvider,进而调用到不同的数据源。
2. 添加登录验证码
登录验证码也是项目中一个常见的需求,但是Spring Security对此并未提供自动化配置方案,需要开发者自行定义,一般来说,有两种实现登录验证码的思路:
自定义过滤器。
自定义认证逻辑。
通过自定义过滤器来实现登录验证码,这种方案我们会在后面的过滤器中介绍, 这里先来看如何通过自定义认证逻辑实现添加登录验证码功能。
生成验证码,可以自定义一个生成工具类,也可以使用一些现成的开源库来实现,这里 采用开源库kaptcha,首先引入kaptcha依赖,代码如下:
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
然后对kaptcha进行配置:
package com.intehel.demo.config;
import com.google.code.kaptcha.Producer;
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 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;
}
}
配置一个Producer实例,主要配置一下生成的图片验证码的宽度、长度、生成字符、验证码的长度等信息,配置完成后,我们就可以在Controller中定义一个验证码接口了。
package com.intehel.demo.controller;
import com.google.code.kaptcha.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.IOException;
@RestController
public class LoginController {
@Autowired
Producer producer;
@RequestMapping("/vc.jpg")
public void getVerifyCode(HttpServletResponse resp, HttpSession session){
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);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个验证码接口中,我们主要做了两件事:
生成验证码文本,并将文本存入HttpSession中。
根据验证码文本生成验证码图片,并通过IO流写出到前端。
接下来修改登录表单,加入验证码,代码如下:
<!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="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>
<!--/*@thymesVar id="SPRING_SECURITY_LAST_EXCEPTION" type="com"*/-->
<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>
</html>
登录表单中增加一个验证码输入框,验证码的图片地址就是我们在Controller中定义的验证码接口地址。
接下来就是验证码的校验了。经过前面的介绍,读者已经了解到,身份认证实际上就是在AuthenticationProvider.authenticate方法中完成的。所以,验证码的校验,我们可以在该方法执行之前进行,需要配置如下类:
package com.intehel.demo.provider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
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 serssionKaptcha = (String) req.getSession().getAttribute("kaptcha");
if (kaptcha != null && serssionKaptcha != null && kaptcha.equalsIgnoreCase(serssionKaptcha)){
return super.authenticate(authentication);
}
throw new AuthenticationServiceException("验证码输入错误");
}
}
这里重写authenticate方法,在authenticate方法中,从RequestContextHolder中获取当前请求,进而获取到验证码参数和存储在HttpSession中的验证码文本进行比较,比较通过则继续执行父类的authenticate方法,比较不通过,就抛出异常。
你可能会想到通过重写 DaoAuthenticationProvider类的 additionalAuthenticationChecks 方法来完成验证码的校验,这个从技术上来说是没有问题的,但是这会让验证码失去存在的意义,因为当additionalAuthenticationChecks方法被调用时,数据库查询已经做了,仅仅剩下密码没有校验,此时,通过验证码来拦截恶意登录的功能就已经失效了。
最后,在 SecurityConfig 中配置 AuthenticationManager,代码如下:
package com.intehel.demo.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intehel.demo.Service.MyUserDetailsService;
import com.intehel.demo.handler.MyAuthenticationFailureHandler;
import com.intehel.demo.provider.KaptchaAuthenticationProvider;
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.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyUserDetailsService myUserDetailsService;
@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")
.failureHandler(new MyAuthenticationFailureHandler())
.usernameParameter("uname")
.passwordParameter("passwd")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new OrRequestMatcher(new AntPathRequestMatcher("/logout1","GET"),
new AntPathRequestMatcher("/logout2","POST")))
.invalidateHttpSession(true)
.clearAuthentication(true)
.defaultLogoutSuccessHandlerFor((req,resp,auth)->{
resp.setContentType("application/json;charset=UTF-8");
Map<String,Object> result = new HashMap<String,Object>();
result.put("status",200);
result.put("msg","使用logout1注销成功!");
ObjectMapper om = new ObjectMapper();
String s = om.writeValueAsString(result);
resp.getWriter().write(s);
},new AntPathRequestMatcher("/logout1","GET"))
.defaultLogoutSuccessHandlerFor((req,resp,auth)->{
resp.setContentType("application/json;charset=UTF-8");
Map<String,Object> result = new HashMap<String,Object>();
result.put("status",200);
result.put("msg","使用logout2注销成功!");
ObjectMapper om = new ObjectMapper();
String s = om.writeValueAsString(result);
resp.getWriter().write(s);
},new AntPathRequestMatcher("/logout1","GET"))
.and()
.csrf().disable();
}
@Bean
AuthenticationProvider kaptchaAuthenticationProvider(){
KaptchaAuthenticationProvider provider = new KaptchaAuthenticationProvider();
provider.setUserDetailsService(myUserDetailsService);
return provider;
}
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
ProviderManager manager = new ProviderManager(kaptchaAuthenticationProvider());
return manager;
}
}
这里配置分三步:首先配置UserDetailsService提供数据源;然后提供一个 AuthenticationProvider 实例并配置 UserDetailsService;最后重写 authenticationManagerBean 方 法,提供一个自己的PioviderManager并使用自定义的AuthenticationProvider实例。
另外需要注意,在configure(HttpSecurity)方法中给验证码接口配置放行,permitAll表示这个接口不需要登录就可以访问。
配置完成后,启动项目,浏览器中输入任意地址都会跳转到登录页面,如图3-5所示。
图 3-5
此时,输入用户名、密码以及验证码就可以成功登录。如果验证码输入错误,则登录页面会自动展示错误信息,如下:
来源:https://www.cnblogs.com/liwenruo/p/16550131.html
猜你喜欢
- 文件上传大部分通过web前端判断后尾名或者service后端判断后尾名,这种操作具有一定的风险,比如:我可以将一个jsp页面,修改后尾名改成
- 获得redis所有的key-value运行结果:redis配置文件需要序列化@Bean public RedisT
- 近日于LeetCode看题遇1114 按序打印,获悉一解法使用了Semaphore,顺势研究,记心得于此。此解视Semaphore为锁,以保
- 双向顺序队列ArrayDeque和双向链式队列LinkedList,JDK已经包含,在此略。ArrayDeque包括顺序栈和顺序队列,Lin
- Quartz简介Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的
- 项目介绍基于Layui的后台管理系统模板,扩展Layui原生UI样式,整合第三方开源组件,提供便捷快速的开发方式,延续LayuiAdmin的
- 应用场景有些时候项目中会用到很多路径,并且很可能多个路径在同一个根目录下,那为了方便配置的修改,达到只修改根目录即可达到一改全改的效果,此时
- 什么是mybatis,mybatis有什么特点,下面先给大家介绍下mybatis的概念及特点。jdbc开发优缺点:1)优点:简单易学,上手快
- C#中Invoke的用法()invoke和begininvoke 区别一直对invoke和begininvoke的使用和概念比较混乱,这两天
- 在8 里面Lambda是最火的主题,不仅仅是因为语法的改变,更重要的是带来了函数式编程的思想,我觉得优秀的程序员,有必要学习一下函数式编程的
- 数据结构是数据存储的方式,算法是数据计算的方式。所以在开发中,算法和数据结构息息相关。今天的讲义中会涉及部分数据结构的专业名词,如果各位铁粉
- 如下面代码以交灯为示例:public class Test { public static
- Android 资源 id详解我们平时获取资源是通过 findViewById 方法进行的,比如我们常在onCreate方法中使用这样的语句
- SpringBoot @NotBlank错误java 验证出现如下错误:javax.validation.UnexpectedTypeExc
- 前言在电商的应用中,最常见的就是在首页或完成某事件之后,弹出一堆的活动/广告。假如重叠弹出,很丑,给用户的体验也不好,所以一般都会依次依条件
- java修改JFrame默认字体修改默认字体的方法很简单。首先我们随便写一个按钮出来:import javax.swing.*; publi
- 本文实例为大家分享了Java实现图片拼接的具体代码,供大家参考,具体内容如下/** * 拼接图片(注:图片需长宽一致) * @param f
- 包含默认敏感词过滤和自定义敏感词过滤。导入依赖<dependency> <groupId>com.git
- 概述:App几乎都离不开与服务器的交互,本文主要讲解了flutter网络请求三种方式 flutter自带的HttpClient、 第三方库h
- 树的同构备忘!定义:给定两棵树r1、r2,如果r1可以通过若干次的左子树和右子树互换,使之与r2完全相同,这说明两者同构。举例树的构造树可以