欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-04 20:34:22
spring security是一个很大的模块,本文中只涉及到了自定义参数的认证。spring security默认的验证参数只有username和password,一般来...

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验证流程就走完了

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