Spring Security配置多个数据源并添加登录验证码(7)(spring security 多种认证)

  本篇文章为你整理了Spring Security配置多个数据源并添加登录验证码(7)(spring security 多种认证)的详细内容,包含有spring security多个认证管理器 spring security 多种认证 spring security登录接口 spring security限制多个用户登录 Spring Security配置多个数据源并添加登录验证码(7),希望能帮助你了解 Spring Security配置多个数据源并添加登录验证码(7)。

  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

   div id="login-row"

   div id="login-column"

   div id="login-box"

   form id="login-form" action="/doLogin" method="post"

   h3 登录 /h3

   !--/*@thymesVar id="SPRING_SECURITY_LAST_EXCEPTION" type="com"*/--

   div th:text="${SPRING_SECURITY_LAST_EXCEPTION}" /div

   div

   label for="username" 用户名: /label br

   input type="text" name="uname" id="username"

   /div

   div

   label for="password" 密码: /label br

   input type="text" name="passwd" id="password"

   /div

   div

   label for="kaptcha" 验证码: /label br

   input type="text" name="kaptcha" id="kaptcha"

   img src="/vc.jpg" alt=""

   /div

   div

   input type="submit" name="submit" 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

  此时,输入用户名、密码以及验证码就可以成功登录。如果验证码输入错误,则登录页面会自动展示错误信息,如下:

  以上就是Spring Security配置多个数据源并添加登录验证码(7)(spring security 多种认证)的详细内容,想要了解更多 Spring Security配置多个数据源并添加登录验证码(7)的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: