spring security4 添加验证码的示例代码

spring security是一个很大的模块,本文中只涉及到了自定义参数的认证。spring security默认的验证参数只有username和password,一般来说都是不够用的。由于时间过太久,有些忘,可能有少许遗漏。好了,不废话。
spring以及spring security配置采用javaConfig,版本依次为4.2.5,4.0.4
总体思路:自定义EntryPoint,添加自定义参数扩展AuthenticationToken以及AuthenticationProvider进行验证。

首先定义EntryPoint:

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
  public MyAuthenticationEntryPoint(String loginFormUrl) {
    super(loginFormUrl);
  }
  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    super.commence(request, response, authException);
  }
}

接下来是token,validCode是验证码参数:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public class MyUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
  private String validCode;
  public MyUsernamePasswordAuthenticationToken(String principal, String credentials, String validCode) {
    super(principal, credentials);
    this.validCode = validCode;
  }
  public String getValidCode() {
    return validCode;
  }
  public void setValidCode(String validCode) {
    this.validCode = validCode;
  }
}

继续ProcessingFilter,

import com.core.shared.ValidateCodeHandle;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class MyValidCodeProcessingFilter extends AbstractAuthenticationProcessingFilter {
  private String usernameParam = "username";
  private String passwordParam = "password";
  private String validCodeParam = "validateCode";
  public MyValidCodeProcessingFilter() {
    super(new AntPathRequestMatcher("/user/login", "POST"));
  }

  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
    String username = request.getParameter(usernameParam);
    String password = request.getParameter(passwordParam);
    String validCode = request.getParameter(validCodeParam);
    valid(validCode, request.getSession());
    MyUsernamePasswordAuthenticationToken token = new MyUsernamePasswordAuthenticationToken(username, password, validCode);
    return this.getAuthenticationManager().authenticate(token);
  }

  public void valid(String validCode, HttpSession session) {
    if (validCode == null) {
      throw new ValidCodeErrorException("验证码为空!");
    }
    if (!ValidateCodeHandle.matchCode(session.getId(), validCode)) {
      throw new ValidCodeErrorException("验证码错误!");
    }
  }
}

分别定义三个参数,用于接收login表单过来的参数,构造方法给出了login的url以及需要post方式

接下来就是认证了,此处还没到认证用户名和密码的时候,只是认证了验证码

下面是ValidateCodeHandle一个工具类以及ValidCodeErrorException:

import java.util.concurrent.ConcurrentHashMap;
public class ValidateCodeHandle {
  private static ConcurrentHashMap validateCode = new ConcurrentHashMap<>();
  public static ConcurrentHashMap getCode() {
    return validateCode;
  }

  public static void save(String sessionId, String code) {
    validateCode.put(sessionId, code);
  }

  public static String getValidateCode(String sessionId) {
    Object obj = validateCode.get(sessionId);
    if (obj != null) {
      return String.valueOf(obj);
    }
    return null;
  }

  public static boolean matchCode(String sessionId, String inputCode) {
    String saveCode = getValidateCode(sessionId);
    if (saveCode.equals(inputCode)) {
      return true;
    }
    return false;
  }
}

这里需要继承AuthenticationException以表明它是security的认证失败,这样才会走后续的失败流程

import org.springframework.security.core.AuthenticationException;
public class ValidCodeErrorException extends AuthenticationException {

  public ValidCodeErrorException(String msg) {
    super(msg);
  }
  public ValidCodeErrorException(String msg, Throwable t) {
    super(msg, t);
  }
}

接下来是Provider:

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
public class MyAuthenticationProvider extends DaoAuthenticationProvider {
  @Override
  public boolean supports(Class<?> authentication) {
    return MyUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
  }

  @Override
  protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    Object salt = null;
    if (getSaltSource() != null) {
      salt = getSaltSource().getSalt(userDetails);
    }
    if (authentication.getCredentials() == null) {
      logger.debug("Authentication failed: no credentials provided");
      throw new BadCredentialsException("用户名或密码错误!");
    }
    String presentedPassword = authentication.getCredentials().toString();
    if (!this.getPasswordEncoder().isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
      logger.debug("Authentication failed: password does not match stored value");

      throw new BadCredentialsException("用户名或密码错误!");
    }

  }
}

其中supports方法指定使用自定义的token,additionalAuthenticationChecks方法和父类的逻辑一模一样,我只是更改了异常返回的信息。

接下来是处理认证成功和认证失败的handler

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FrontAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
  public FrontAuthenticationSuccessHandler(String defaultTargetUrl) {
    super(defaultTargetUrl);
  }

  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    super.onAuthenticationSuccess(request, response, authentication);
  }
}
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FrontAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
  public FrontAuthenticationFailureHandler(String defaultFailureUrl) {
    super(defaultFailureUrl);
  }

  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    super.onAuthenticationFailure(request, response, exception);
  }
}

最后就是最重要的security config 了:

import com.service.user.CustomerService;
import com.web.filter.SiteMeshFilter;
import com.web.mySecurity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.web.filter.CharacterEncodingFilter;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends AbstractSecurityWebApplicationInitializer {

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new StandardPasswordEncoder("MD5");
  }

  @Autowired
  private CustomerService customerService;

  @Configuration
  @Order(1)
  public static class FrontendWebSecurityConfigureAdapter extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyValidCodeProcessingFilter myValidCodeProcessingFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http.csrf().disable()
          .authorizeRequests()
          .antMatchers("/user/login", "/user/logout").permitAll()
          .anyRequest().authenticated()
          .and()
          .addFilterBefore(myValidCodeProcessingFilter, UsernamePasswordAuthenticationFilter.class)
          .formLogin()
          .loginPage("/user/login")
          .and()
          .logout()
          .logoutUrl("/user/logout")
          .logoutSuccessUrl("/user/login");
    }

  }

  @Bean(name = "frontAuthenticationProvider")
  public MyAuthenticationProvider frontAuthenticationProvider() {
    MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider();
    myAuthenticationProvider.setUserDetailsService(customerService);
    myAuthenticationProvider.setPasswordEncoder(passwordEncoder());
    return myAuthenticationProvider;
  }

  @Bean
  public AuthenticationManager authenticationManager() {
    List<AuthenticationProvider> list = new ArrayList<>();
    list.add(frontAuthenticationProvider());
    AuthenticationManager authenticationManager = new ProviderManager(list);
    return authenticationManager;
  }

  @Bean
  public MyValidCodeProcessingFilter myValidCodeProcessingFilter(AuthenticationManager authenticationManager) {
    MyValidCodeProcessingFilter filter = new MyValidCodeProcessingFilter();
    filter.setAuthenticationManager(authenticationManager);
    filter.setAuthenticationSuccessHandler(frontAuthenticationSuccessHandler());
    filter.setAuthenticationFailureHandler(frontAuthenticationFailureHandler());
    return filter;
  }

  @Bean
  public FrontAuthenticationFailureHandler frontAuthenticationFailureHandler() {
    return new FrontAuthenticationFailureHandler("/user/login");
  }

  @Bean
  public FrontAuthenticationSuccessHandler frontAuthenticationSuccessHandler() {
    return new FrontAuthenticationSuccessHandler("/front/test");
  }

  @Bean
  public MyAuthenticationEntryPoint myAuthenticationEntryPoint() {
    return new MyAuthenticationEntryPoint("/user/login");
  }
}

首先是一个加密类的bean,customerService是一个简单的查询用户

@Service("customerService")
public class CustomerServiceImpl implements CustomerService {

  @Autowired
  private UserDao userDao;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return userDao.findCustomerByUsername(username);
  }
}

下来就是FrontendWebSecurityConfigureAdapter了,重写了configure方法,先禁用csrf, 开启授权请求authorizeRequests(),其中”/user/login”, “/user/logout”放过权限验证, 其他请求需要进行登录认证, 然后是addFilterBefore(),把我自定义的myValidCodeProcessingFilter添加到security默认的UsernamePasswordAuthenticationFilter之前,也就是先进行我的自定义参数认证, 然后是formLogin(),配置登录url以及登出url,登录登出url都需要进行controller映射,也就是要自己写controller。
接下来就是AuthenticationProvider,AuthenticationManager,ProcessingFilter,AuthenticationFailureHandler,AuthenticationSuccessHandler,EntryPoint的bean显示声明。

下面是login.jsp

<body>
<div class="login_div">
  <form:form autocomplete="false" commandName="userDTO" method="post">
    <div>
      <span class="error_tips"><b>${SPRING_SECURITY_LAST_EXCEPTION.message}</b></span>
    </div>
    username:<form:input path="username" cssClass="form-control"/><br/>
    password:<form:password path="password" cssClass="form-control"/><br/>
    validateCode:<form:input path="validateCode" cssClass="form-control"/>
    <label>${validate_code}</label>
    <div class="checkbox">
      <label>
        <input type="checkbox" name="remember-me"/>记住我
      </label>
    </div>
    <input type="submit" class="btn btn-primary" value="submit"/>
  </form:form>
</div>
</body>

验证码验证失败的时候抛出的是ValidCodeErrorException,由于它继承AuthenticationException,security在验证的时候遇到AuthenticationException就会触发AuthenticationFailureHandler,上面的bean中声明了认证失败跳转到登录url,所以login.jsp里面有${SPRING_SECURITY_LAST_EXCEPTION.message}获取我认证时抛出异常信息,能友好的提示用户。

整个自定义security验证流程就走完了

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • Spring Security Oauth2.0 实现短信验证码登录示例
(0)

相关推荐

  • Spring Security Oauth2.0 实现短信验证码登录示例

    本文介绍了Spring Security Oauth2.0 实现短信验证码登录示例,分享给大家,具体如下: 定义手机号登录令牌 /** * @author lengleng * @date 2018/1/9 * 手机号登录令牌 */ public class MobileAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecur

  • spring security4 添加验证码的示例代码

    spring security是一个很大的模块,本文中只涉及到了自定义参数的认证.spring security默认的验证参数只有username和password,一般来说都是不够用的.由于时间过太久,有些忘,可能有少许遗漏.好了,不废话. spring以及spring security配置采用javaConfig,版本依次为4.2.5,4.0.4 总体思路:自定义EntryPoint,添加自定义参数扩展AuthenticationToken以及AuthenticationProvider进行

  • Java实现短信验证码的示例代码

    目录 项目需求 需求来由 代码实现 发送验证码方法 注册方法 忘记密码 前端代码 编码中遇到的问题 如何改进 短信验证码相信大家都不陌生吗,但是短信验证码怎么生成的你真的了解吗,本文揭示本人项目中对短信验证码的. 项目需求 用户注册/忘记密码添加短信验证码 需求来由 登录注册页面需要确保用户同一个手机号只关联一个账号确保非人为操作,避免系统用户信息紊乱增加系统安全性 代码实现 同事提供了WebService接口,很好,之前没调过,又增加了困难. 这边用的阿里云的短信服务,废话少说上图,呸,上代码

  • spring boot整合Swagger2的示例代码

    Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化RESTful风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同样的速度来更新.文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步.Swagger 让部署管理和使用功能强大的API从未如此简单. 1.代码示例 1).在pom.xml文件中引入Swagger2 <dependency> <groupId>io.springfox</groupId> <artifa

  • Spring Boot集成Kafka的示例代码

    本文介绍了Spring Boot集成Kafka的示例代码,分享给大家,也给自己留个笔记 系统环境 使用远程服务器上搭建的kafka服务 Ubuntu 16.04 LTS kafka_2.12-0.11.0.0.tgz zookeeper-3.5.2-alpha.tar.gz 集成过程 1.创建spring boot工程,添加相关依赖: <?xml version="1.0" encoding="UTF-8"?> <project xmlns=&qu

  • IDEA 中创建Spring Data Jpa 项目的示例代码

    一.IDEA 创建工程 使用IDEA 创建工程的过程,使用文字做简单描述. 选择工程类别[Spring Initializr]. 设置工程的元数据[Metadata],根据自己的情况填写即可. 设置工程的依赖:在[Web]中选择"Spring Web";在[SQL]中选中"Spring Data JPA"."Spring Data JDBC"."MySQL Driver"."JDBC API".选中的可能有

  • java生成图片验证码的示例代码

    给大家分享一款java生成验证码的源码,可设置随机字符串,去掉了几个容易混淆的字符,还可以设置验证码位数,比如4位,6位.当然也可以根据前台验证码的位置大小,设置验证码图片的大小.下边是源码分享,直接看吧,很简单! 创建servlet类 import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServ

  • 基于C#实现图片滑动验证码的示例代码

    目录 图片准备 合成目标 实现 1.创建项目 2.Nuget添加ImageSharp 3.vscode打开 4.引入图片 5.生成out_bg.jpg 6.生成out_slider.png 全部代码 最后 图片准备 hole.png和slider.png为png是因为图片带有透明度. 合成目标 最终为前端生成两张图片: out_slider.png高度为344与背景图等高. 也可以打开滑动验证Demo页面,F12来观察图片. 实现 本机环境为.net 6.0.300-preview.22204.

  • vue生成随机验证码的示例代码

    本文介绍了vue生成随机验证码的示例代码,分享给大家,具体如下: 样式自调,最终效果如图: 实现效果: 点击右边input框会自动切换,如果输入的值与字不同,则清空换一串随机数 HTML <input type="text" placeholder="请输入验证码" class="yanzhengma_input" @blur="checkLpicma" v-model="picLyanzhengma"

  • selenium+java破解极验滑动验证码的示例代码

    摘要 分析验证码素材图片混淆原理,并采用selenium模拟人拖动滑块过程,进而破解验证码. 人工验证的过程 1.打开威锋网注册页面 2.移动鼠标至小滑块,一张完整的图片会出现(如下图1) 3.点击鼠标左键,图片中间会出现一个缺块(如下图2) 4.移动小滑块正上方图案至缺块处 5.验证通过 selenium模拟验证的过程 加载威锋网注册页面 下载图片1和缺块图片2 根据两张图片的差异计算平移的距离x 模拟鼠标点击事件,点击小滑块向右移动x 验证通过 详细分析 1.打开chrome浏览器控制台,会

  • Java实现动态获取图片验证码的示例代码

    本文介绍了Java实现动态获取图片验证码的示例代码,分享给大家,具体如下: import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingEx

随机推荐