SpringBoot Spring Security5 实现验证码登录

广告也精彩

关于 Spring Security 权限管理实现登录之前一篇文章

SpringBoot + Spring Security + Thymeleaf 实现权限管理登录 已经介绍了。

本文主要介绍在登录中加入验证码,关于验证码的使用,如下

SpringBoot中集成kaptcha验证码

先看效果图

SpringBoot Spring Security5 实现验证码登录

一、Spring Security 的核心且唯一配置文件

SecurityConfig.java

  1. package com.liuyanzhao.forum.config;
  2. import com.liuyanzhao.forum.entity.LoginRecord;
  3. import com.liuyanzhao.forum.entity.User;
  4. import com.liuyanzhao.forum.filter.KaptchaAuthenticationFilter;
  5. import com.liuyanzhao.forum.repository.LoginRecordRepository;
  6. import com.liuyanzhao.forum.service.impl.IpServiceImpl;
  7. import com.liuyanzhao.forum.service.impl.UserServiceImpl;
  8. import com.liuyanzhao.forum.util.IPUtil;
  9. import com.liuyanzhao.forum.vo.UserSessionVO;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. import org.springframework.beans.factory.annotation.Autowired;
  13. import org.springframework.context.annotation.Bean;
  14. import org.springframework.security.authentication.AuthenticationManager;
  15. import org.springframework.security.authentication.AuthenticationProvider;
  16. import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
  17. import org.springframework.security.config.BeanIds;
  18. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  19. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  20. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  21. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  22. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  23. import org.springframework.security.core.Authentication;
  24. import org.springframework.security.core.AuthenticationException;
  25. import org.springframework.security.core.context.SecurityContextHolder;
  26. import org.springframework.security.core.userdetails.UserDetails;
  27. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  28. import org.springframework.security.crypto.password.PasswordEncoder;
  29. import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  30. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  31. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  32. import javax.servlet.ServletException;
  33. import javax.servlet.http.HttpServletRequest;
  34. import javax.servlet.http.HttpServletResponse;
  35. import java.io.IOException;
  36. import java.io.PrintWriter;
  37. /**
  38.  * 安全配置类
  39.  *
  40.  * @author 言曌
  41.  * @date 2018/1/23 上午11:37
  42.  */
  43. @EnableWebSecurity
  44. @EnableGlobalMethodSecurity(prePostEnabled = true// 启用方法安全设置
  45. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  46.     private static final String KEY = "liuyanzhao.com";
  47.     @Autowired
  48.     private UserServiceImpl userService;//实现了UserDetailsService类
  49.     @Bean
  50.     public PasswordEncoder passwordEncoder() {
  51.         return new BCryptPasswordEncoder();// 使用 BCrypt 加密
  52.     }
  53.     @Bean
  54.     public AuthenticationProvider authenticationProvider() {
  55.         DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
  56.         authenticationProvider.setUserDetailsService(userService);
  57.         authenticationProvider.setPasswordEncoder(passwordEncoder()); // 设置密码加密方式
  58.         return authenticationProvider;
  59.     }
  60.     @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
  61.     @Override
  62.     public AuthenticationManager authenticationManagerBean() throws Exception {
  63.         return super.authenticationManagerBean();
  64.     }
  65.     /**
  66.      * 自定义配置
  67.      */
  68.     @Override
  69.     protected void configure(HttpSecurity http) throws Exception {
  70.         http.addFilterBefore(new KaptchaAuthenticationFilter("/login""/login?error"), UsernamePasswordAuthenticationFilter.class//在认证用户名之前认证验证码,如果验证码错误,将不执行用户名和密码的认证
  71.                 .authorizeRequests().antMatchers("/css/**""/js/**""/fonts/**""/index").permitAll() // 都可以访问
  72.                 .antMatchers("/h2-console/**").permitAll() // 都可以访问
  73.                 .antMatchers("/login").permitAll() // 都可以访问
  74.                 .antMatchers("/admin/**").hasRole("ADMIN"// 需要相应的角色才能访问
  75.                 .and()
  76.                 .formLogin()   //基于 Form 表单登录验证
  77.                 .loginPage("/login")
  78.                 .successHandler(new AuthenticationSuccessHandler() {
  79.                     //用户名和密码正确执行
  80.                     @Override
  81.                     public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
  82.                         Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  83.                         if (principal != null && principal instanceof UserDetails) {
  84.                             httpServletResponse.setContentType("application/json;charset=utf-8");
  85.                             PrintWriter out = httpServletResponse.getWriter();
  86.                             out.write("{\"status\":\"ok\",\"message\":\"登录成功\"}");
  87.                             out.flush();
  88.                             out.close();
  89.                         }
  90.                     }
  91.                 })
  92.                 .failureHandler(new AuthenticationFailureHandler() {
  93.                     //用户名密码错误执行
  94.                     @Override
  95.                     public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
  96.                         httpServletResponse.setContentType("application/json;charset=utf-8");
  97.                         PrintWriter out = httpServletResponse.getWriter();
  98.                         out.write("{\"status\":\"error\",\"message\":\"用户名或密码错误\"}");
  99.                         out.flush();
  100.                         out.close();
  101.                     }
  102.                 })
  103.                 .and().rememberMe().key(KEY) // 启用 remember me
  104.                 .and().exceptionHandling().accessDeniedPage("/403");  // 处理异常,拒绝访问就重定向到 403 页面
  105.         http.logout().logoutUrl("/logout").logoutSuccessUrl("/login");
  106.         http.csrf().ignoringAntMatchers("/h2-console/**"); // 禁用 H2 控制台的 CSRF 防护
  107.         http.csrf().ignoringAntMatchers("/ajax/**"); // 禁用 H2 控制台的 CSRF 防护
  108.         http.csrf().ignoringAntMatchers("/upload");
  109.         http.headers().frameOptions().sameOrigin(); // 允许来自同一来源的H2 控制台的请求
  110.     }
  111.     /**
  112.      * 认证信息管理
  113.      *
  114.      * @param auth
  115.      * @throws Exception
  116.      */
  117.     @Autowired
  118.     public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  119.         auth.userDetailsService(userService);
  120.         auth.authenticationProvider(authenticationProvider());
  121.     }
  122. }

 

http.addFilterBefore(new KaptchaAuthenticationFilter("/login""/login?error"), UsernamePasswordAuthenticationFilter.class)

注意这里,addFilterBefore 表示在执行用户名和密码验证之前,先执行 KaptchaAuthenticationFilter 这个过滤器,这是一个我们自定义的过滤器,第一个参数表示登录页面路径,第二个是登录失败页面

下面给出 KaptchaAuthenticationFilter 的代码

 

二、验证码验证过滤器

KaptchaAuthenticationFilter.java

  1. package com.liuyanzhao.forum.filter;
  2. import com.google.code.kaptcha.Constants;
  3. import org.springframework.security.authentication.InsufficientAuthenticationException;
  4. import org.springframework.security.core.Authentication;
  5. import org.springframework.security.core.AuthenticationException;
  6. import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
  7. import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
  8. import javax.servlet.*;
  9. import javax.servlet.http.HttpServletRequest;
  10. import javax.servlet.http.HttpServletResponse;
  11. import java.io.IOException;
  12. /**
  13.  * @author 言曌
  14.  * @date 2018/5/4 下午12:36
  15.  */
  16. public class KaptchaAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  17.     private String servletPath;
  18.     public KaptchaAuthenticationFilter(String servletPath, String failureUrl) {
  19.         super(servletPath);
  20.         this.servletPath = servletPath;
  21.         setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(failureUrl));
  22.     }
  23.     @Override
  24.     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  25.         HttpServletRequest req = (HttpServletRequest) request;
  26.         HttpServletResponse res = (HttpServletResponse) response;
  27.         if ("POST".equalsIgnoreCase(req.getMethod()) && servletPath.equals(req.getServletPath())) {
  28.             String expect = (String) req.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
  29.             if (expect != null && !expect.equalsIgnoreCase(req.getParameter("kaptcha"))) {
  30.                 unsuccessfulAuthentication(req, res, new InsufficientAuthenticationException("输入的验证码不正确"));
  31.                 return;
  32.             }
  33.         }
  34.         chain.doFilter(request, response);
  35.     }
  36.     @Override
  37.     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
  38.         return null;
  39.     }
  40. }

第37行req.getParameter("kaptcha"),其中 kaptcha 是登录表单中验证码输入框的 name

 

三、视图层

我这里是在任何页面点击登录按钮,弹出模态框登录框。然后通过 ajax 验证

HTML

  1. <section class="box-login v5-input-txt" id="box-login">
  2.                   <form id="login_form" th:action="@{/login}" method="post" autocomplete="off">
  3.                       <ul>
  4.                           <li class="form-group">
  5.                               <input class="form-control" type="text" min="4" maxlength="20"
  6.                                      name="username" placeholder="请输入用户名或邮箱" required>
  7.                           </li>
  8.                           <li class="form-group">
  9.                               <input class="form-control" type="password" minlength="6" maxlength="100"
  10.                                      id="password" name="password" placeholder="请输入密码" required>
  11.                           </li>
  12.                           <li class="form-group">
  13.                               <input type="text" class="form-control pull-left margin-r-5" name="kaptcha"
  14.                                      style="width: 50%;" placeholder="验证码">
  15.                               <img th:src="@{/getKaptchaImage}" width="80" height="34"
  16.                                    class="captcha changeCaptcha pull-left margin-r-5" alt="验证码"/>
  17.                               <a href="javascript:void(0)" class="changeCaptcha">换一张</a>
  18.                               <div class="clear"></div>
  19.                           </li>
  20.                           <li class="form-group">
  21.                               <label>
  22.                                   <input type="checkbox" name="remember-me" checked> 下次自动登录</a>
  23.                               </label>
  24.                           </li>
  25.                       </ul>
  26.                   </form>
  27.                   <p class="good-tips marginB10">
  28.                       <a class="fr" th:href="@{/forget}" target="_blank">忘记密码?</a>还没有账号?
  29.                       <a th:href="@{/register}" target="_blank" id="btnRegister">立即注册</a>
  30.                   </p>
  31.                   <div class="marginB10">
  32.                       <button id="login_btn" type="button" class="btn btn-micv5 btn-block globalLogin">
  33.                           登录
  34.                       </button>
  35.                       <div id="login-form-tips" class="tips-error bg-danger" style="display: none;">错误提示
  36.                       </div>
  37.                   </div>
  38.                   <div class="threeLogin"><span>其他方式登录</span>
  39.                       <a class="nqq" href="javascript:;"></a>
  40.                       <a class="nwx" href="javascript:;"></a>
  41.                   </div>
  42.               </section>

 

JS

  1. $(document).on("click""#login_btn"function () {
  2.        var randomAnim = Math.floor(Math.random() * 7);
  3.        var _ctx = $("meta[name='ctx']").attr("content");
  4.        var token = $("meta[name='_csrf']").attr("content");
  5.        var header = $("meta[name='_csrf_header']").attr("content");
  6.        $.ajax({
  7.            url: _ctx + "/login",
  8.            type: 'POST',
  9.            data: $("#login_form").serialize(),
  10.            beforeSend: function (request) {
  11.                request.setRequestHeader(header, token);
  12.            },
  13.            success: function (data) {
  14.                console.log(data);
  15.                if (data.status == 'ok') {
  16.                    //登录成功
  17.                    window.location.reload();
  18.                } else if (data.status == 'error') {
  19.                    layer.alert("用户名或密码错误!", {icon: 2, anim: randomAnim});
  20.                } else {
  21.                    layer.alert("验证码错误", {icon: 2, anim: randomAnim});
  22.                }
  23.            },
  24.            error: function () {
  25.                layer.msg("出现错误,请尝试刷新页面!", {icon: 2, anim: 6});
  26.            }
  27.        });
  28.        return false;
  29.    });

layer.alert()这个不用管,是引入了 layui 的插件。

 

验证码的创建请参考 SpringBoot中集成kaptcha验证码

 

  • 微信
  • 赶快加我聊天吧
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
言曌

发表评论

:?::razz::sad::evil::!::smile::oops::grin::eek::shock::???::cool::lol::mad::twisted::roll::wink::idea::arrow::neutral::cry::mrgreen: