(八)、SpringBoot 图形验证码实现

灰太狼 2022-05-29 02:45 284阅读 0赞

可以前往第一篇博客查看目录结构 —> 这里

一、在core模块validate包下创建一个通用验证码实体类 -> ValidateCode (包含验证码、过期时间、判断是否过期的方法)

  1. public class ValidateCode {
  2. private String code;
  3. private LocalDateTime expireTime;
  4. public ValidateCode(String code, int expireTime) {
  5. this.code = code;
  6. this.expireTime = LocalDateTime.now().plusSeconds(expireTime);
  7. }
  8. public ValidateCode(String code, LocalDateTime expireTime) {
  9. this.code = code;
  10. this.expireTime = expireTime;
  11. }
  12. public String getCode() {
  13. return code;
  14. }
  15. public void setCode(String code) {
  16. this.code = code;
  17. }
  18. public LocalDateTime getExpireTime() {
  19. return expireTime;
  20. }
  21. public void setExpireTime(LocalDateTime expireTime) {
  22. this.expireTime = expireTime;
  23. }
  24. /**
  25. * 判断时间是否过期
  26. * @return
  27. */
  28. public boolean isExpried() {
  29. return LocalDateTime.now().isAfter(expireTime);
  30. }
  31. }

二、创建图形验证码实体类ImageCode 继承于 ValidateCode (新增image属性,保存验证图片)

  1. public class ImageCode extends ValidateCode{
  2. private BufferedImage image;
  3. public ImageCode(BufferedImage image, String code, int expireTime) {
  4. super(code, expireTime);
  5. this.image = image;
  6. }
  7. public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
  8. super(code, expireTime);
  9. this.image = image;
  10. }
  11. public BufferedImage getImage() {
  12. return image;
  13. }
  14. public void setImage(BufferedImage image) {
  15. this.image = image;
  16. }
  17. }

三、创建图形验证码java配置文件,自定义需要的属性

  1. public class ImageCodeProperties {
  2. private int width = 67;
  3. private int height = 23;
  4. private int length = 4;
  5. private int expireIn = 60;
  6. private String url;
  7. public int getWidth() {
  8. return width;
  9. }
  10. public void setWidth(int width) {
  11. this.width = width;
  12. }
  13. public int getHeight() {
  14. return height;
  15. }
  16. public void setHeight(int height) {
  17. this.height = height;
  18. }
  19. public int getLength() {
  20. return length;
  21. }
  22. public void setLength(int length) {
  23. this.length = length;
  24. }
  25. public int getExpireIn() {
  26. return expireIn;
  27. }
  28. public void setExpireIn(int expireIn) {
  29. this.expireIn = expireIn;
  30. }
  31. public String getUrl() {
  32. return url;
  33. }
  34. public void setUrl(String url) {
  35. this.url = url;
  36. }
  37. }

四、在ImageCodeProperties上封装多一层通用的ValidateCodeProperties

  1. public class ValidateCodeProperties {
  2. private ImageCodeProperties image = new ImageCodeProperties();
  3. public ImageCodeProperties getImage() {
  4. return image;
  5. }
  6. public void setImage(ImageCodeProperties image) {
  7. this.image = image;
  8. }
  9. }

五、在SecurityProperties中new 一个ValidateCodeProperties ,集中配置

  1. @ConfigurationProperties(prefix = "zeke.security")
  2. public class SecurityProperties {
  3. private BrowserProperties browser = new BrowserProperties();
  4. private ValidateCodeProperties code = new ValidateCodeProperties();
  5. public BrowserProperties getBrowser() {
  6. return browser;
  7. }
  8. public void setBrowser(BrowserProperties browser) {
  9. this.browser = browser;
  10. }
  11. public ValidateCodeProperties getCode() {
  12. return code;
  13. }
  14. public void setCode(ValidateCodeProperties code) {
  15. this.code = code;
  16. }
  17. }

六、需要一个验证码生成的工具,创建ValidateCodeGenerator验证码生成接口

  1. public interface ValidateCodeGenerator {
  2. ValidateCode generator(ServletWebRequest request);
  3. }

七、创建ImageCodeGenerator实现ValidateCodeGenerator接口(实现内容不必深究,百度一堆一堆的,大公司也有自己的内部生成方式)

其中的图片长、宽,验证码长度等,都从SecurtiyProperties中获取(可以在application.properties中配置),例:

  1. zeke.security.code.image.length = 4
  2. zeke.security.code.image.width = 100
  3. public class ImageCodeGenerator implements ValidateCodeGenerator {
  4. @Autowired
  5. private SecurityProperties securityProperties;
  6. @Override
  7. public ImageCode generator(ServletWebRequest request) {
  8. int width = ServletRequestUtils.getIntParameter(request.getRequest(),"width",securityProperties.getCode().getImage().getWidth());
  9. int height = ServletRequestUtils.getIntParameter(request.getRequest(),"height",securityProperties.getCode().getImage().getHeight());
  10. BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
  11. Graphics g = image.getGraphics();
  12. Random random = new Random();
  13. g.setColor(getRandColor(200, 250));
  14. g.fillRect(0, 0, width, height);
  15. g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
  16. g.setColor(getRandColor(160, 200));
  17. for (int i = 0; i < 155; i++){
  18. int x = random.nextInt(width);
  19. int y = random.nextInt(height);
  20. int x1 = random.nextInt(12);
  21. int y1 = random.nextInt(12);
  22. g.drawLine(x, y, x+x1, y+y1);
  23. }
  24. String sRand = "";
  25. for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++){
  26. String rand = String.valueOf(random.nextInt(10));
  27. sRand += rand;
  28. g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
  29. g.drawString(rand, 13 * i + 6, 16);
  30. }
  31. g.dispose();
  32. return new ImageCode(image, sRand, securityProperties.getCode().getImage().getExpireIn());
  33. }
  34. private Color getRandColor(int fc, int bc){
  35. Random random = new Random();
  36. if (fc > 255){
  37. fc = 255;
  38. }
  39. if (bc > 255){
  40. bc = 255;
  41. }
  42. int r = fc + random.nextInt(bc - fc);
  43. int g = fc + random.nextInt(bc - fc);
  44. int b = fc + random.nextInt(bc - fc);
  45. return new Color(r, g, b);
  46. }
  47. public SecurityProperties getSecurityProperties() {
  48. return securityProperties;
  49. }
  50. public void setSecurityProperties(SecurityProperties securityProperties) {
  51. this.securityProperties = securityProperties;
  52. }
  53. }

八、创建ValidateCodeBeanConfig,把ImageCodeGenerator注入到Spring容器中

  1. @Configuration
  2. public class ValidateCodeBeanConfig {
  3. @Autowired
  4. private SecurityProperties securityProperties;
  5. /**
  6. * 如果要更换图形验证码的实现,可以到DemoImageCodeGenerator中实现(加上@Component("imageCodeGenerator")注解即可)
  7. * @return
  8. */
  9. @Bean
  10. @ConditionalOnMissingBean(name = "imageCodeGenerator")
  11. public ValidateCodeGenerator imageCodeGenerator(){
  12. ImageCodeGenerator imageCodeGenerator = new ImageCodeGenerator();
  13. imageCodeGenerator.setSecurityProperties(securityProperties);
  14. return imageCodeGenerator;
  15. }
  16. }

九、创建图形验证码接口 ValidateCodeController

  1. @RestController
  2. public class ValidateCodeController {
  3. public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
  4. private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
  5. @Autowired
  6. private ValidateCodeGenerator ImageCodeGenerator;
  7. /**
  8. * 图形验证码生成、保存、发送
  9. * @param request
  10. * @param response
  11. * @throws IOException
  12. */
  13. @GetMapping("/code/image")
  14. public void createImageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
  15. ImageCode imageCode = (ImageCode) ImageCodeGenerator.generator(new ServletWebRequest(request));
  16. sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
  17. ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
  18. }
  19. }

十、自定义一个简单的验证码异常

  1. public class ValidateCodeException extends AuthenticationException {
  2. public ValidateCodeException(String explanation) {
  3. super(explanation);
  4. }
  5. }

十一、创建一个验证码过滤器,对指定URL进行过滤,验证码错误抛出异常,验证码正确则移除session中保存的验证码

该url也可以在application.properties指定(用逗号分割):

  1. zeke.security.code.image.url = /user/*,/user

ValidateCodeFilter:

  1. public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean{
  2. @Autowired
  3. private AuthenticationFailureHandler authenticationFailureHandler;
  4. private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
  5. /**
  6. * 存放所有需要拦截的URL
  7. */
  8. private Set<String> urls = new HashSet<>();
  9. private SecurityProperties securityProperties;
  10. private AntPathMatcher pathMatcher = new AntPathMatcher();
  11. @Override
  12. public void afterPropertiesSet() throws ServletException {
  13. super.afterPropertiesSet();
  14. String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(),",");
  15. for (String configUrl : configUrls) {
  16. urls.add(configUrl);
  17. }
  18. urls.add("/authentication/form");
  19. }
  20. @Override
  21. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  22. boolean action = false;
  23. for (String url : urls) {
  24. if (pathMatcher.match(url,request.getRequestURI())){
  25. action = true;
  26. }
  27. }
  28. if (action){
  29. try {
  30. validate(new ServletWebRequest(request));
  31. }
  32. catch (ValidateCodeException e) {
  33. authenticationFailureHandler.onAuthenticationFailure(request,response,e);
  34. return;
  35. }
  36. }
  37. filterChain.doFilter(request,response);
  38. }
  39. /**
  40. * 校验提交验证码的合法性
  41. * @param request
  42. * @throws ServletRequestBindingException
  43. */
  44. private void validate(ServletWebRequest request) throws ServletRequestBindingException {
  45. ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,ValidateCodeController.SESSION_KEY);
  46. String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
  47. if (StringUtils.isBlank(codeInRequest)){
  48. throw new ValidateCodeException("验证码的值不能为空");
  49. }
  50. if (codeInSession == null){
  51. throw new ValidateCodeException("验证码不存在");
  52. }
  53. if (codeInSession.isExpried()){
  54. sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
  55. throw new ValidateCodeException("验证码已过期");
  56. }
  57. if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)){
  58. throw new ValidateCodeException("验证码不匹配");
  59. }
  60. sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
  61. }
  62. public AuthenticationFailureHandler getAuthenticationFailureHandler() {
  63. return authenticationFailureHandler;
  64. }
  65. public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
  66. this.authenticationFailureHandler = authenticationFailureHandler;
  67. }
  68. public SessionStrategy getSessionStrategy() {
  69. return sessionStrategy;
  70. }
  71. public void setSessionStrategy(SessionStrategy sessionStrategy) {
  72. this.sessionStrategy = sessionStrategy;
  73. }
  74. public Set<String> getUrls() {
  75. return urls;
  76. }
  77. public void setUrls(Set<String> urls) {
  78. this.urls = urls;
  79. }
  80. public SecurityProperties getSecurityProperties() {
  81. return securityProperties;
  82. }
  83. public void setSecurityProperties(SecurityProperties securityProperties) {
  84. this.securityProperties = securityProperties;
  85. }

十二、更改zeke-login.html中的表单,增加图形验证码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Login Page</title>
  6. </head>
  7. <body>
  8. <form action="/authentication/form" method="post">
  9. <table>
  10. <tr>
  11. <td>用户名:</td>
  12. <td><input type="text" name="username"/></td>
  13. </tr>
  14. <tr>
  15. <td>密码:</td>
  16. <td><input type="password" name="password"/></td>
  17. </tr>
  18. <tr>
  19. <td>图形验证码: </td>
  20. <td>
  21. <input type="text" name="imageCode"/>
  22. <img src="/code/image"/>
  23. </td>
  24. </tr>
  25. <tr>
  26. <td colspan="2"><button type="submit">登录</button></td>
  27. </tr>
  28. </table>
  29. </form>
  30. </body>
  31. </html>

十三、在BrowserSecurityConfig中把 /code/image加入放行url ,不然页面会拦截/code/image的请求;

  1. 配置ValidateCodeFilter

![Image 1][]

十四、启动项目访问localhost/zeke-login.html 测试

[Image 1]:

发表评论

表情:
评论列表 (有 0 条评论,284人围观)

还没有评论,来说两句吧...

相关阅读