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

打印 上一主题 下一主题

主题 924|帖子 924|积分 2772

1.配置多个数据源

  多个数据源是指在同一个系统中,用户数据来自不同的表,在认证时,如果第一张表没有查找到用户,那就去第二张表中査询,依次类推。
  看了前面的分析,要实现这个需求就很容易了,认证要经过AuthenticationProvider,每一 个 AuthenticationProvider 中都配置了一个 UserDetailsService,而不同的 UserDetailsService 则可以代表不同的数据源,所以我们只需要手动配置多个AuthenticationProvider,并为不同的 AuthenticationProvider 提供不同的 UserDetailsService 即可。
  为了方便起见,这里通过 InMemoryUserDetailsManager 来提供 UserDetailsService 实例, 在实际开发中,只需要将UserDetailsService换成自定义的即可,具体配置如下:
  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3.     @Bean
  4.     @Primary
  5.     UserDetailsService us1(){
  6.         return new InMemoryUserDetailsManager(User.builder().username("testuser1").password("{noop}123")
  7.                 .roles("admin").build());
  8.     }
  9.     @Bean
  10.     UserDetailsService us2(){
  11.         return new InMemoryUserDetailsManager(User.builder().username("testuser2").password("{noop}123")
  12.                 .roles("user").build());
  13.     }
  14.     @Override
  15.     public AuthenticationManager authenticationManagerBean() throws Exception {
  16.         DaoAuthenticationProvider dao1 = new DaoAuthenticationProvider();
  17.         dao1.setUserDetailsService(us1());
  18.         DaoAuthenticationProvider dao2 = new DaoAuthenticationProvider();
  19.         dao2.setUserDetailsService(us2());
  20.         ProviderManager manager = new ProviderManager(dao1, dao2);
  21.         return manager;
  22.     }
  23.     @Override
  24.     protected void configure(HttpSecurity http) throws Exception {
  25.         //省略
  26.     }
  27. }
复制代码
  首先定义了两个UserDetailsService实例,不同实例中存储了不同的用户;然后重写 authenticationManagerBean 方法,在该方法中,定义了两个 DaoAuthenticationProvider 实例并分别设置了不同的UserDetailsService ;最后构建ProviderManager实例并传入两个 DaoAuthenticationProvider,当系统进行身份认证操作时,就会遍历ProviderManager中不同的 DaoAuthenticationProvider,进而调用到不同的数据源。
2. 添加登录验证码

登录验证码也是项目中一个常见的需求,但是Spring Security对此并未提供自动化配置方案,需要开发者自行定义,一般来说,有两种实现登录验证码的思路:

  • 自定义过滤器。
  • 自定义认证逻辑。
通过自定义过滤器来实现登录验证码,这种方案我们会在后面的过滤器中介绍, 这里先来看如何通过自定义认证逻辑实现添加登录验证码功能。
生成验证码,可以自定义一个生成工具类,也可以使用一些现成的开源库来实现,这里 采用开源库kaptcha,首先引入kaptcha依赖,代码如下:
  1. <dependency>
  2.     <groupId>com.github.penggle</groupId>
  3.     <artifactId>kaptcha</artifactId>
  4.     <version>2.3.2</version>
  5. </dependency>
复制代码
然后对kaptcha进行配置:
查看代码
  1.  package com.intehel.demo.config;
  2. import com.google.code.kaptcha.Producer;
  3. import com.google.code.kaptcha.impl.DefaultKaptcha;
  4. import com.google.code.kaptcha.util.Config;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import java.util.Properties;
  8. @Configuration
  9. public class KaptchaConfig {
  10.     @Bean
  11.     Producer kaptcha(){
  12.         Properties properties = new Properties();
  13.         properties.setProperty("kaptcha.image.width", "150");
  14.         properties.setProperty("kaptcha.image.height", "50");
  15.         properties.setProperty("kaptcha.textproducer.char.string", "0123456789");
  16.         properties.setProperty("kaptcha.textproducer.char.length", "4");
  17.         Config config = new Config(properties);
  18.         DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
  19.         defaultKaptcha.setConfig(config);
  20.         return defaultKaptcha;
  21.     }
  22. }
复制代码
  配置一个Producer实例,主要配置一下生成的图片验证码的宽度、长度、生成字符、验证码的长度等信息,配置完成后,我们就可以在Controller中定义一个验证码接口了。
  
查看代码
  1.  package com.intehel.demo.controller;
  2. import com.google.code.kaptcha.Producer;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import javax.imageio.ImageIO;
  7. import javax.servlet.ServletOutputStream;
  8. import javax.servlet.http.HttpServletResponse;
  9. import javax.servlet.http.HttpSession;
  10. import java.awt.image.BufferedImage;
  11. import java.io.IOException;
  12. @RestController
  13. public class LoginController {
  14.     @Autowired
  15.     Producer producer;
  16.     @RequestMapping("https://www.cnblogs.com/vc.jpg")
  17.     public void getVerifyCode(HttpServletResponse resp, HttpSession session){
  18.         resp.setContentType("image/jpeg");
  19.         String text = producer.createText();
  20.         session.setAttribute("kaptcha",text);
  21.         BufferedImage image = producer.createImage(text);
  22.         try(ServletOutputStream out = resp.getOutputStream()) {
  23.             ImageIO.write(image,"jpg",out);
  24.         } catch (IOException e) {
  25.             e.printStackTrace();
  26.         }
  27.     }
  28. }
复制代码
在这个验证码接口中,我们主要做了两件事:

  • 生成验证码文本,并将文本存入HttpSession中。
  • 根据验证码文本生成验证码图片,并通过IO流写出到前端。
接下来修改登录表单,加入验证码,代码如下:
  
查看代码
  1.  <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>登录</title>
  6.     <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
  7.    
  8.    
  9. </head>
  10. <body>
  11.    
  12.         
  13.             
  14.                
  15.                     <form id="login-form"  action="/doLogin" method="post">
  16.                         <h3 >登录</h3>
  17.                         
  18.                         
  19.                         
  20.                             <label for="username" >用户名:</label><br>
  21.                             <input type="text" name="uname" id="username" >
  22.                         
  23.                         
  24.                             <label for="password" >密码:</label><br>
  25.                             <input type="text" name="passwd" id="password" >
  26.                         
  27.                         
  28.                             <label for="kaptcha" >验证码:</label><br>
  29.                             <input type="text" name="kaptcha" id="kaptcha" >
  30.                             <img src="https://www.cnblogs.com/vc.jpg" alt="">
  31.                         
  32.                         
  33.                             <input type="submit" name="submit"  value="登录">
  34.                         
  35.                     </form>
  36.                
  37.             
  38.         
  39.    
  40. </body>
  41. </html>
复制代码
  登录表单中增加一个验证码输入框,验证码的图片地址就是我们在Controller中定义的验证码接口地址。
  接下来就是验证码的校验了。经过前面的介绍,读者已经了解到,身份认证实际上就是在AuthenticationProvider.authenticate方法中完成的。所以,验证码的校验,我们可以在该方法执行之前进行,需要配置如下类:
  
查看代码
  1.  package com.intehel.demo.provider;
  2. import org.springframework.security.authentication.AuthenticationServiceException;
  3. import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
  4. import org.springframework.security.core.Authentication;
  5. import org.springframework.security.core.AuthenticationException;
  6. import org.springframework.web.context.request.RequestContextHolder;
  7. import org.springframework.web.context.request.ServletRequestAttributes;
  8. import javax.servlet.http.HttpServletRequest;
  9. public class KaptchaAuthenticationProvider extends DaoAuthenticationProvider{
  10.     @Override
  11.     public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  12.         HttpServletRequest req = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
  13.         String kaptcha = req.getParameter("kaptcha");
  14.         String serssionKaptcha = (String) req.getSession().getAttribute("kaptcha");
  15.         if (kaptcha != null && serssionKaptcha != null && kaptcha.equalsIgnoreCase(serssionKaptcha)){
  16.             return super.authenticate(authentication);
  17.         }
  18.         throw new AuthenticationServiceException("验证码输入错误");
  19.     }
  20. }
复制代码
  这里重写authenticate方法,在authenticate方法中,从RequestContextHolder中获取当前请求,进而获取到验证码参数和存储在HttpSession中的验证码文本进行比较,比较通过则继续执行父类的authenticate方法,比较不通过,就抛出异常。
  你可能会想到通过重写 DaoAuthenticationProvider类的 additionalAuthenticationChecks 方法来完成验证码的校验,这个从技术上来说是没有问题的,但是这会让验证码失去存在的意义,因为当additionalAuthenticationChecks方法被调用时,数据库查询已经做了,仅仅剩下密码没有校验,此时,通过验证码来拦截恶意登录的功能就已经失效了。
  最后,在 SecurityConfig 中配置 AuthenticationManager,代码如下:
  
查看代码
  1.  package com.intehel.demo.config;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import com.intehel.demo.Service.MyUserDetailsService;
  4. import com.intehel.demo.handler.MyAuthenticationFailureHandler;
  5. import com.intehel.demo.provider.KaptchaAuthenticationProvider;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import org.springframework.security.authentication.AuthenticationManager;
  10. import org.springframework.security.authentication.AuthenticationProvider;
  11. import org.springframework.security.authentication.ProviderManager;
  12. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  13. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  14. import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
  15. import org.springframework.security.web.util.matcher.OrRequestMatcher;
  16. import java.util.HashMap;
  17. import java.util.Map;
  18. @Configuration
  19. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  20.     @Autowired
  21.     MyUserDetailsService myUserDetailsService;
  22.     @Override
  23.     protected void configure(HttpSecurity http) throws Exception {
  24.         http.authorizeRequests()
  25.                 .antMatchers("https://www.cnblogs.com/vc.jpg").permitAll()
  26.                 .anyRequest().authenticated()
  27.                 .and()
  28.                 .formLogin()
  29.                 .loginPage("/mylogin.html")
  30.                 .loginProcessingUrl("/doLogin")
  31.                 .defaultSuccessUrl("/index.html")
  32.                 .failureHandler(new MyAuthenticationFailureHandler())
  33.                 .usernameParameter("uname")
  34.                 .passwordParameter("passwd")
  35.                 .permitAll()
  36.                 .and()
  37.                 .logout()
  38.                 .logoutRequestMatcher(new OrRequestMatcher(new AntPathRequestMatcher("/logout1","GET"),
  39.                         new AntPathRequestMatcher("/logout2","POST")))
  40.                 .invalidateHttpSession(true)
  41.                 .clearAuthentication(true)
  42.                 .defaultLogoutSuccessHandlerFor((req,resp,auth)->{
  43.                     resp.setContentType("application/json;charset=UTF-8");
  44.                     Map<String,Object> result = new HashMap<String,Object>();
  45.                     result.put("status",200);
  46.                     result.put("msg","使用logout1注销成功!");
  47.                     ObjectMapper om = new ObjectMapper();
  48.                     String s = om.writeValueAsString(result);
  49.                     resp.getWriter().write(s);
  50.                 },new AntPathRequestMatcher("/logout1","GET"))
  51.                 .defaultLogoutSuccessHandlerFor((req,resp,auth)->{
  52.                     resp.setContentType("application/json;charset=UTF-8");
  53.                     Map<String,Object> result = new HashMap<String,Object>();
  54.                     result.put("status",200);
  55.                     result.put("msg","使用logout2注销成功!");
  56.                     ObjectMapper om = new ObjectMapper();
  57.                     String s = om.writeValueAsString(result);
  58.                     resp.getWriter().write(s);
  59.                 },new AntPathRequestMatcher("/logout1","GET"))
  60.                 .and()
  61.                 .csrf().disable();
  62.     }
  63.     @Bean
  64.     AuthenticationProvider kaptchaAuthenticationProvider(){
  65.         KaptchaAuthenticationProvider provider = new KaptchaAuthenticationProvider();
  66.         provider.setUserDetailsService(myUserDetailsService);
  67.         return provider;
  68.     }
  69.     @Override
  70.     public AuthenticationManager authenticationManagerBean() throws Exception {
  71.         ProviderManager manager = new ProviderManager(kaptchaAuthenticationProvider());
  72.         return manager;
  73.     }
  74. }
复制代码
  
  这里配置分三步:首先配置UserDetailsService提供数据源;然后提供一个 AuthenticationProvider 实例并配置 UserDetailsService;最后重写 authenticationManagerBean 方 法提供一个自己的PioviderManager并使用自定义的AuthenticationProvider实例。
  另外需要注意,在configure(HttpSecurity)方法中给验证码接口配置放行,permitAll表示这个接口不需要登录就可以访问。
  配置完成后,启动项目,浏览器中输入任意地址都会跳转到登录页面,如图3-5所示。
  
图 3-5
此时,输入用户名、密码以及验证码就可以成功登录。如果验证码输入错误,则登录页面会自动展示错误信息,如下:
  



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用户云卷云舒

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表