关于 Spring Security 权限管理实现登录之前一篇文章
先看效果图
SecurityConfig.java
http.addFilterBefore(new KaptchaAuthenticationFilter("/login", "/login?error"), UsernamePasswordAuthenticationFilter.class)
注意这里,addFilterBefore 表示在执行用户名和密码验证之前,先执行 KaptchaAuthenticationFilter 这个过滤器,这是一个我们自定义的过滤器,第一个参数表示登录页面路径,第二个是登录失败页面
下面给出 KaptchaAuthenticationFilter 的代码
KaptchaAuthenticationFilter.java
第37行req.getParameter("kaptcha"),其中 kaptcha 是登录表单中验证码输入框的 name
我这里是在任何页面点击登录按钮,弹出模态框登录框。然后通过 ajax 验证
HTML
JS
layer.alert()这个不用管,是引入了 layui 的插件。
验证码的创建请参考 SpringBoot中集成kaptcha验证码
SpringBoot + Spring Security + Thymeleaf 实现权限管理登录 已经介绍了。
本文主要介绍在登录中加入验证码,关于验证码的使用,如下
先看效果图
一、Spring Security 的核心且唯一配置文件
SecurityConfig.java
- package com.liuyanzhao.forum.config;
- import com.liuyanzhao.forum.entity.LoginRecord;
- import com.liuyanzhao.forum.entity.User;
- import com.liuyanzhao.forum.filter.KaptchaAuthenticationFilter;
- import com.liuyanzhao.forum.repository.LoginRecordRepository;
- import com.liuyanzhao.forum.service.impl.IpServiceImpl;
- import com.liuyanzhao.forum.service.impl.UserServiceImpl;
- import com.liuyanzhao.forum.util.IPUtil;
- import com.liuyanzhao.forum.vo.UserSessionVO;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.authentication.AuthenticationProvider;
- import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
- import org.springframework.security.config.BeanIds;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import org.springframework.security.web.authentication.AuthenticationFailureHandler;
- import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.io.PrintWriter;
- /**
- * 安全配置类
- *
- * @author 言曌
- * @date 2018/1/23 上午11:37
- */
- @EnableWebSecurity
- @EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法安全设置
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- private static final String KEY = "liuyanzhao.com";
- @Autowired
- private UserServiceImpl userService;//实现了UserDetailsService类
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();// 使用 BCrypt 加密
- }
- @Bean
- public AuthenticationProvider authenticationProvider() {
- DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
- authenticationProvider.setUserDetailsService(userService);
- authenticationProvider.setPasswordEncoder(passwordEncoder()); // 设置密码加密方式
- return authenticationProvider;
- }
- @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception {
- return super.authenticationManagerBean();
- }
- /**
- * 自定义配置
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.addFilterBefore(new KaptchaAuthenticationFilter("/login", "/login?error"), UsernamePasswordAuthenticationFilter.class) //在认证用户名之前认证验证码,如果验证码错误,将不执行用户名和密码的认证
- .authorizeRequests().antMatchers("/css/**", "/js/**", "/fonts/**", "/index").permitAll() // 都可以访问
- .antMatchers("/h2-console/**").permitAll() // 都可以访问
- .antMatchers("/login").permitAll() // 都可以访问
- .antMatchers("/admin/**").hasRole("ADMIN") // 需要相应的角色才能访问
- .and()
- .formLogin() //基于 Form 表单登录验证
- .loginPage("/login")
- .successHandler(new AuthenticationSuccessHandler() {
- //用户名和密码正确执行
- @Override
- public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
- Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- if (principal != null && principal instanceof UserDetails) {
- httpServletResponse.setContentType("application/json;charset=utf-8");
- PrintWriter out = httpServletResponse.getWriter();
- out.write("{\"status\":\"ok\",\"message\":\"登录成功\"}");
- out.flush();
- out.close();
- }
- }
- })
- .failureHandler(new AuthenticationFailureHandler() {
- //用户名密码错误执行
- @Override
- public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
- httpServletResponse.setContentType("application/json;charset=utf-8");
- PrintWriter out = httpServletResponse.getWriter();
- out.write("{\"status\":\"error\",\"message\":\"用户名或密码错误\"}");
- out.flush();
- out.close();
- }
- })
- .and().rememberMe().key(KEY) // 启用 remember me
- .and().exceptionHandling().accessDeniedPage("/403"); // 处理异常,拒绝访问就重定向到 403 页面
- http.logout().logoutUrl("/logout").logoutSuccessUrl("/login");
- http.csrf().ignoringAntMatchers("/h2-console/**"); // 禁用 H2 控制台的 CSRF 防护
- http.csrf().ignoringAntMatchers("/ajax/**"); // 禁用 H2 控制台的 CSRF 防护
- http.csrf().ignoringAntMatchers("/upload");
- http.headers().frameOptions().sameOrigin(); // 允许来自同一来源的H2 控制台的请求
- }
- /**
- * 认证信息管理
- *
- * @param auth
- * @throws Exception
- */
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userService);
- auth.authenticationProvider(authenticationProvider());
- }
- }
http.addFilterBefore(new KaptchaAuthenticationFilter("/login", "/login?error"), UsernamePasswordAuthenticationFilter.class)
注意这里,addFilterBefore 表示在执行用户名和密码验证之前,先执行 KaptchaAuthenticationFilter 这个过滤器,这是一个我们自定义的过滤器,第一个参数表示登录页面路径,第二个是登录失败页面
下面给出 KaptchaAuthenticationFilter 的代码
二、验证码验证过滤器
KaptchaAuthenticationFilter.java
- package com.liuyanzhao.forum.filter;
- import com.google.code.kaptcha.Constants;
- import org.springframework.security.authentication.InsufficientAuthenticationException;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
- import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
- import javax.servlet.*;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- /**
- * @author 言曌
- * @date 2018/5/4 下午12:36
- */
- public class KaptchaAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
- private String servletPath;
- public KaptchaAuthenticationFilter(String servletPath, String failureUrl) {
- super(servletPath);
- this.servletPath = servletPath;
- setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(failureUrl));
- }
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
- HttpServletRequest req = (HttpServletRequest) request;
- HttpServletResponse res = (HttpServletResponse) response;
- if ("POST".equalsIgnoreCase(req.getMethod()) && servletPath.equals(req.getServletPath())) {
- String expect = (String) req.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
- if (expect != null && !expect.equalsIgnoreCase(req.getParameter("kaptcha"))) {
- unsuccessfulAuthentication(req, res, new InsufficientAuthenticationException("输入的验证码不正确"));
- return;
- }
- }
- chain.doFilter(request, response);
- }
- @Override
- public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
- return null;
- }
- }
第37行req.getParameter("kaptcha"),其中 kaptcha 是登录表单中验证码输入框的 name
三、视图层
我这里是在任何页面点击登录按钮,弹出模态框登录框。然后通过 ajax 验证
HTML
- <section class="box-login v5-input-txt" id="box-login">
- <form id="login_form" th:action="@{/login}" method="post" autocomplete="off">
- <ul>
- <li class="form-group">
- <input class="form-control" type="text" min="4" maxlength="20"
- name="username" placeholder="请输入用户名或邮箱" required>
- </li>
- <li class="form-group">
- <input class="form-control" type="password" minlength="6" maxlength="100"
- id="password" name="password" placeholder="请输入密码" required>
- </li>
- <li class="form-group">
- <input type="text" class="form-control pull-left margin-r-5" name="kaptcha"
- style="width: 50%;" placeholder="验证码">
- <img th:src="@{/getKaptchaImage}" width="80" height="34"
- class="captcha changeCaptcha pull-left margin-r-5" alt="验证码"/>
- <a href="javascript:void(0)" class="changeCaptcha">换一张</a>
- <div class="clear"></div>
- </li>
- <li class="form-group">
- <label>
- <input type="checkbox" name="remember-me" checked> 下次自动登录</a>
- </label>
- </li>
- </ul>
- </form>
- <p class="good-tips marginB10">
- <a class="fr" th:href="@{/forget}" target="_blank">忘记密码?</a>还没有账号?
- <a th:href="@{/register}" target="_blank" id="btnRegister">立即注册</a>
- </p>
- <div class="marginB10">
- <button id="login_btn" type="button" class="btn btn-micv5 btn-block globalLogin">
- 登录
- </button>
- <div id="login-form-tips" class="tips-error bg-danger" style="display: none;">错误提示
- </div>
- </div>
- <div class="threeLogin"><span>其他方式登录</span>
- <a class="nqq" href="javascript:;"></a>
- <a class="nwx" href="javascript:;"></a>
- </div>
- </section>
JS
- $(document).on("click", "#login_btn", function () {
- var randomAnim = Math.floor(Math.random() * 7);
- var _ctx = $("meta[name='ctx']").attr("content");
- var token = $("meta[name='_csrf']").attr("content");
- var header = $("meta[name='_csrf_header']").attr("content");
- $.ajax({
- url: _ctx + "/login",
- type: 'POST',
- data: $("#login_form").serialize(),
- beforeSend: function (request) {
- request.setRequestHeader(header, token);
- },
- success: function (data) {
- console.log(data);
- if (data.status == 'ok') {
- //登录成功
- window.location.reload();
- } else if (data.status == 'error') {
- layer.alert("用户名或密码错误!", {icon: 2, anim: randomAnim});
- } else {
- layer.alert("验证码错误", {icon: 2, anim: randomAnim});
- }
- },
- error: function () {
- layer.msg("出现错误,请尝试刷新页面!", {icon: 2, anim: 6});
- }
- });
- return false;
- });
layer.alert()这个不用管,是引入了 layui 的插件。
验证码的创建请参考 SpringBoot中集成kaptcha验证码
2018年06月19日 10:08:11
登录需要自己实现吗