当前位置: 移动技术网 > IT编程>开发语言>Java > spring security4 添加验证码的示例代码

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

2019年07月19日  | 移动技术网IT编程  | 我要评论

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

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网