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验证流程就走完了
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读