上一节实现了自定义数据库的验证,这里使用过滤器实现图形验证码。图形验证码也是登录时,常用的项目之一。
这里使用验证码(CAPTCHA),验证码就是为了防止恶意用户暴力重试而设置的,防止恶意用户使用程序发起无限重试,使系统遭到破坏
这里使用kaptcha组件来获取验证码
添加依赖
<dependency>
<groupId>com.github.pengglegroupId>
<artifactId>kaptchaartifactId>
<version>2.3.2version>
dependency>
配置kaptcha实例
/**
* 配置kaptcha实例
* @return
*/
@Bean
public Producer captcha(){
//配置图形验证码的基本参数
Properties properties = new Properties();
//图片宽度
properties.setProperty("kaptcha.image.width","150");
//图片长度
properties.setProperty("kaptcha.image.height","50");
//字符集
properties.setProperty("kaptcha.textproducer.char.string","0123456789");
//字符长度
properties.setProperty("kaptcha.textproducer.char.length","4");
Config config = new Config(properties);
//使用默认的图形验证码实现,当然也可以自定义实现
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
获取图形验证码的Controller
其中验证码的文本被放在session中,使用时会从session中取出验证码用于后续的校验
package com.example.controller;
import com.google.code.kaptcha.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
@Controller
public class CaptchaController {
@Autowired
private Producer captchaProducer;
@GetMapping("/captcha.jpg")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
//设置内容类型
response.setContentType("image/jpeg");
//创建验证码文本
String text = captchaProducer.createText();
//将验证码文本设置到session
request.getSession().setAttribute("captcha",text);
//创建验证码图片
BufferedImage bi = captchaProducer.createImage(text);
//获取响应输出流
ServletOutputStream out = response.getOutputStream();
//将图片验证码数据写到响应输出流
ImageIO.write(bi,"jpg",out);
//推送并关闭响应输出流
try{
out.flush();
} finally {
out.close();
}
}
}
自定义异常
package com.example.Exception;
import org.springframework.security.core.AuthenticationException;
public class VerificationCodeException extends AuthenticationException {
public VerificationCodeException() {
super("图形验证码校验失败");
}
}
自定义失败处理
package com.example.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Service;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Service
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
Map<String, Object> data = new HashMap<>();
data.put("exception", e.getMessage());
httpServletResponse.getOutputStream().println(objectMapper.writeValueAsString(data));
}
}
验证码校验过滤器
这里使用的是OncePerRequestFilter 过滤器,他可以确保一次请求只会通过一次该过滤器(Filter实际上并不能保证这一点)
package com.example.Filter;
import com.example.Exception.VerificationCodeException;
import com.example.service.MyAuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
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 VerificationCodeFilter extends OncePerRequestFilter {
private AuthenticationFailureHandler authenticationFailureHandler = new MyAuthenticationFailureHandler();
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//非登录请求不校验验证码
if(!"/auth/form".equals(httpServletRequest.getRequestURI())){
filterChain.doFilter(httpServletRequest,httpServletResponse);
}else{
try{
verificationCode(httpServletRequest);
filterChain.doFilter(httpServletRequest,httpServletResponse);
} catch (VerificationCodeException e){
authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
}
}
}
private void verificationCode(HttpServletRequest httpServletRequest) {
String requestCode = httpServletRequest.getParameter("captcha");
HttpSession session = httpServletRequest.getSession();
String savedCode = (String) session.getAttribute("captcha");
if(!StringUtils.isEmpty(savedCode)){
//无论成功失败,均需清除验证码
session.removeAttribute("captcha");
}
//校验不通过,抛出异常
if(StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(savedCode)){
throw new VerificationCodeException();
}
}
}
权限修改
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/api/**").hasRole("ADMIN")
.antMatchers("/user/api/**").hasRole("USER")
.antMatchers("/app/api/**","/captcha.jpg").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.formLogin()
.loginPage("/myLogin.html")
.loginProcessingUrl("/auth/form").permitAll()
.failureHandler(new MyAuthenticationFailureHandler());
//将过滤器加载UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);
}
自定义登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆</title>
</head>
<body>
<div class="login">
<h1>LOGIN FORM</h1>
<form action="/auth/form" method="post">
<input type="text" name="username" placeholder="username"><br>
<input type="password" name="password" placeholder="password"><br>
<div>
<input type="text" name="captcha" placeholder="captcha">
<img src="/captcha.jpg" alt="captcha" height="50px" width="150px" style="margin-left: 20px">
</div>
<input type="submit" value="Login">
</form>
</div>
</body>
</html>
其他如数据库验证还是使用之前的内容
打开localhost:8080/user/api/hello,跳转至登录页面,如下,忽略style
输入user账户信息,登录
再跳转至http://localhost:8080/admin/api/hello
显示权限不足
以上是图形验证码添加的实现