在项目中使用spring security做用户登录的身份认证,配置完成后,spring security会帮我们验证用户身份。但是当我想给登录页面增加图片验证码还能时,spring security的自动验证机制让我无从下手。
(1). 在/login的控制器中接收并校验验证码,验证成功方可登录。事实是,spring security直接做了登录验证,我写的控制器根本没用。
(2). 根据某些博客的做法,尝试继承UsernamePasswordAuthenticationFilter和DaoAuthenticationProvider,最终也失败了。
最终我想到了使用失焦事件验证的办法:当文本框失去焦点的时,前端页面发送ajax请求给后台校验验证码,并在页面设置一个隐藏的input标签来存储校验结果。点击登录按钮时去读取这个input标签,如果失败,则提示验证码错误,拒绝发起登录请求。这样就没spring security什么事了。
1、生成图片验证码和校验验证码的controller:
import com.github.bingoohuang.patchca.custom.ConfigurableCaptchaService;
import com.github.bingoohuang.patchca.filter.FilterFactory;
import com.github.bingoohuang.patchca.filter.predefined.*;
import com.github.bingoohuang.patchca.utils.encoder.EncoderHelper;
import com.ufclub.entity.common.MsgResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@Controller
@RequestMapping("/yzm")
public class ValidateController {
protected final Logger logger = LoggerFactory.getLogger(getClass());
private static ConfigurableCaptchaService cs = CaptchaFactory.getInstance();
private static List<FilterFactory> factories;
static {
factories = new ArrayList<>();
factories.add(new CurvesRippleFilterFactory(cs.getColorFactory()));
factories.add(new MarbleRippleFilterFactory());
factories.add(new DoubleRippleFilterFactory());
factories.add(new WobbleRippleFilterFactory());
factories.add(new DiffuseRippleFilterFactory());
}
@RequestMapping("/getImage")
public void getImage(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
try {
cs.setFilterFactory(factories.get(new Random().nextInt(5)));
setResponseHeaders(response);
String token = EncoderHelper.getChallangeAndWriteImage(cs, "png",
response.getOutputStream());
session.setAttribute("TEST_YZM", token);
logger.info("当前的SessionID = " + session.getId() + ", 验证码 = " + token);
} catch (IOException e) {
e.printStackTrace();
}
}
@ResponseBody
@RequestMapping(value = "/verification")
public MsgResponse validate(HttpServletRequest request, String verifyCode) {
MsgResponse msgResponse = new MsgResponse();
if(verifyCode != null){
String yzm = (String) request.getSession().getAttribute("TEST_YZM");
if(verifyCode.equals(yzm))
msgResponse.setSuccess(true);
}
return msgResponse;
}
private void setResponseHeaders(HttpServletResponse response) {
response.setContentType("image/png");
response.setHeader("Cache-Control", "no-cache, no-store");
response.setHeader("Pragma", "no-cache");
long time = System.currentTimeMillis();
response.setDateHeader("Last-Modified", time);
response.setDateHeader("Date", time);
response.setDateHeader("Expires", time);
}
}
CaptchaFactory的代码:
import com.github.bingoohuang.patchca.color.RandomColorFactory;
import com.github.bingoohuang.patchca.custom.ConfigurableCaptchaService;
import com.github.bingoohuang.patchca.word.RandomWordFactory;
public class CaptchaFactory {
private static ConfigurableCaptchaService cs = new ConfigurableCaptchaService();
static {
cs.setColorFactory(new RandomColorFactory());
RandomWordFactory wf = new RandomWordFactory();
wf.setCharacters("1234567890");
wf.setMaxLength(4);
wf.setMinLength(4);
cs.setWordFactory(wf);
}
public static ConfigurableCaptchaService getInstance() {
return cs;
}
}
2、前端页面:
<form name="loginForm" id="loginForm" th:action="@{/login}" method="post">
<fieldset>
<label class="block clearfix">
<span class="block input-icon input-icon-right">
<input type="text" name="username" id="username" class="form-control" placeholder="用户名" />
<i class="icon-user"></i>
</span>
</label>
<label class="block clearfix">
<span class="block input-icon input-icon-right">
<input type="password" name="password" id="password" class="form-control" placeholder="密码" />
<i class="icon-lock"></i>
</span>
</label>
<div th:height="34px">
<input type="text" name="verifyCode" id="verifyCode" placeholder="请输入验证码" th:οnblur="'checkVerifyCode()'"/>
<input type="hidden" id="verifyStatus" />
<img src="/yzm/getImage" id="reloadImage" th:width="100px" th:height="28px"/>
<!--<a href="javaScript:;" id="changeImage" th:width="100px">换一张</a>-->
</div>
<div class="space-2"></div>
<div class="clearfix">
<div th:if="${param.error}">
<div class="btn btn-xs btn-danger">
<i class="icon-bolt bigger-110"></i>
用户名或密码不正确!
</div>
</div>
</div>
<div class="clearfix">
<label class="inline">
<input type="checkbox" id="remember-me" name="remember-me" class="ace" />
<span class="lbl"> 自动登录</span>
</label>
<input type="button" class="width-35 pull-right btn btn-sm btn-primary" value="登录" th:οnclick="'submitLoginForm()'"/>
</div>
<div class="space-4"></div>
</fieldset>
</form>
js代码:
function checkVerifyCode() {
var verifyCode = $("#verifyCode").val();
if(verifyCode == "")
return;
$.ajax({
type: "POST",
url: "/yzm/verification",
data: {
"verifyCode": verifyCode
},
dataType:"json",
success:function(data){
if(data.success){
$("#verifyStatus").val('true');
// layer.tips('验证码正确','#reloadImage', {tips: [2,'#D15B47']});
}else{
$("#verifyStatus").val('false');
layer.tips('验证码错误','#reloadImage', {tips: [2,'#D15B47']});
}
},
error:function (XMLHttpRequest, textStatus) {
layer.msg("[" + textStatus + "]" + "系统错误,请联系管理员", {time : 2500}, function(){});
}
});
}