zoukankan      html  css  js  c++  java
  • springsecurity生成图形验证码并校验

    根据随机数生成图片
    将随机数存到session中
    在将生成的图片写到接口的响应
    

    1. 创建项目

    使用idea中的spring工具引入springbootspringsecruity创建项目最终的pom.xml如下

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>demo-spring-secruity</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>demo-spring-secruity</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.9</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.10</version>
            </dependency>
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>2.6</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.social</groupId>
                <artifactId>spring-social-web</artifactId>
                <version>1.1.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    创建一个基本访问路由

    package com.example.demospringsecruity.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * 简单测试控制器
     * @author john
     * @date 2020/1/6 - 9:47
     */
    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello() {
            return "hello spring secruity";
        }
    }
    

    创建一个简单的响应对象

    package com.example.demospringsecruity.support;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.ToString;
    
    /**
     *  一个简单的响应对象
     * @author john
     * @date 2020/1/6 - 16:23
     */
    @Data
    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    public class SimpleResponse {
        private Object content;
    }
    

    实现图形验证码的校验,需要在用户和密码认证过滤器前增加一个验证码过滤器处理判断逻辑

    2. 创建验证码类

    package com.example.demospringsecruity.utils;
    
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.ToString;
    
    import java.awt.image.BufferedImage;
    import java.time.LocalDateTime;
    
    /**
     * 验证码对象
     *
     * @author john
     * @date 2020/1/7 - 9:54
     */
    @Data
    @ToString
    @NoArgsConstructor
    public class ImageCode {
        //验证码
        private String code;
        //过期时间
        private LocalDateTime expireTime;
        //图片
        private BufferedImage image;
    
        public ImageCode(String code, int expireTime, BufferedImage image) {
            this.code = code;
            this.expireTime = LocalDateTime.now().plusSeconds(expireTime);
            this.image = image;
        }
    
        public boolean isExpried() {
            return LocalDateTime.now().isAfter(expireTime);
        }
    }
    

    3. 编写验证码的过滤器

    package com.example.demospringsecruity.filter;
    
    import com.example.demospringsecruity.controller.ValidateCodeController;
    import com.example.demospringsecruity.exception.ValidateCodeException;
    import com.example.demospringsecruity.handler.MyAuthenticationFailureHandler;
    import com.example.demospringsecruity.utils.ImageCode;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.social.connect.web.HttpSessionSessionStrategy;
    import org.springframework.social.connect.web.SessionStrategy;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.ServletRequestBindingException;
    import org.springframework.web.bind.ServletRequestUtils;
    import org.springframework.web.context.request.ServletWebRequest;
    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 java.io.IOException;
    
    /**
     * @author john
     * @date 2020/1/7 - 10:41
     */
    @Slf4j
    @Component
    public class ValidateCodeFilter extends OncePerRequestFilter {
    
    
        MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    
        public MyAuthenticationFailureHandler getMyAuthenticationFailureHandler() {
            return myAuthenticationFailureHandler;
        }
    
        public void setMyAuthenticationFailureHandler(MyAuthenticationFailureHandler myAuthenticationFailureHandler) {
            this.myAuthenticationFailureHandler = myAuthenticationFailureHandler;
        }
    
        /**
         * 操作session的工具类
         */
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
            //只有当处理登录功能时才执行
            if (StringUtils.equals("/auth/form", request.getRequestURI()) && StringUtils.equalsIgnoreCase(request.getMethod(), "post")) {
                // 最好做try catch 处理异常
                try {
                    validate(new ServletWebRequest(request));
                } catch (AuthenticationException e) {
                    myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
                    return;
                }
            }
            filterChain.doFilter(request, response);
        }
    
        //执行验证码逻辑
        private void validate(ServletWebRequest request) throws AuthenticationException, ServletRequestBindingException {
            //取出session中的验证码对象
            ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
            //取出表单提交中的验证码信息
            String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
    
            if (StringUtils.isBlank(codeInRequest)) {
                throw new ValidateCodeException("code in request is null");
            }
    
            if (codeInSession == null) {
                throw new ValidateCodeException("code in session is null");
            }
    
            if (codeInSession.isExpried()) {
                sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
                throw new ValidateCodeException("code has expreied");
            }
    
            if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
                throw new ValidateCodeException("code is not right");
            }
    
            //验证通过,清除session中的验证码信息
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
        }
    }
    

    4. 定义一个自定义登录错误处理

    package com.example.demospringsecruity.handler;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 自定义登录失败处理
     *
     * @author john
     * @date 2020/1/6 - 18:43
     */
    @Component
    @Slf4j
    public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
        @Autowired
        private ObjectMapper objectMapper;
    
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
            log.info("登录失败");
            //这里处理登录失败后就会输出错误信息
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset-UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(e.getMessage()));
        }
    }
    
    

    5. 自定义验证码异常

    package com.example.demospringsecruity.exception;
    
    import org.springframework.security.core.AuthenticationException;
    
    /**
     *  自定义异常
     * @author john
     * @date 2020/1/7 - 11:25
     */
    public class ValidateCodeException extends AuthenticationException {
        public ValidateCodeException(String msg) {
            super(msg);
        }
    }
    

    6. 定义验证码图片输出控制器

    package com.example.demospringsecruity.controller;
    
    import com.example.demospringsecruity.utils.ImageCode;
    import org.springframework.social.connect.web.HttpSessionSessionStrategy;
    import org.springframework.social.connect.web.SessionStrategy;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.context.request.ServletWebRequest;
    
    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.util.Random;
    
    /**
     * 验证码校验控制器
     *
     * @author john
     * @date 2020/1/7 - 9:57
     */
    @RestController
    public class ValidateCodeController {
        public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
    
        private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    
        @GetMapping("/code/image")
        public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
            ImageCode imageCode = createImageCode(request);
            //将验证码存入session
            sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
            //输出图片
            ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
        }
    
        //创建验证码对象
        private ImageCode createImageCode(HttpServletRequest request) {
            int width = 67;
            int height = 23;
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    
            Graphics g = image.getGraphics();
    
            Random random = new Random();
    
            g.setColor(getRandColor(200, 250));
            g.fillRect(0, 0, width, height);
            g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
            g.setColor(getRandColor(160, 200));
            for (int i = 0; i < 155; i++) {
                int x = random.nextInt(width);
                int y = random.nextInt(height);
                int xl = random.nextInt(12);
                int yl = random.nextInt(12);
                g.drawLine(x, y, x + xl, y + yl);
            }
    
            String sRand = "";
            for (int i = 0; i < 4; i++) {
                String rand = String.valueOf(random.nextInt(10));
                sRand += rand;
                g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
                g.drawString(rand, 13 * i + 6, 16);
            }
    
            g.dispose();
    
            return new ImageCode(sRand, 60, image);
        }
    
        /**
         * 生成随机背景条纹
         *
         * @param fc
         * @param bc
         * @return
         */
        private Color getRandColor(int fc, int bc) {
            Random random = new Random();
            if (fc > 255) {
                fc = 255;
            }
            if (bc > 255) {
                bc = 255;
            }
            int r = fc + random.nextInt(bc - fc);
            int g = fc + random.nextInt(bc - fc);
            int b = fc + random.nextInt(bc - fc);
            return new Color(r, g, b);
        }
    
    }
    
    

    7. 配置登录逻辑以及添加自定义的验证码过滤器到用户密码过滤器前面

    package com.example.demospringsecruity.config;
    
    import com.example.demospringsecruity.filter.ValidateCodeFilter;
    import com.example.demospringsecruity.handler.MyAuthenticationFailureHandler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.Filter;
    
    /**
     * @author john
     * @date 2020/1/6 - 10:07
     */
    @Configuration
    public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        ValidateCodeFilter validateCodeFilter;
        @Autowired
        MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    
    
        //手动将PasswordEncoder注入到ioc容器中
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            validateCodeFilter.setMyAuthenticationFailureHandler(myAuthenticationFailureHandler);
            // 表单登录
            http    //过滤器设置
                    // 将验证码过滤器配置到UsernamePasswordAuthenticationFilter前面
                    .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                    //登录设置
                    .formLogin()
                    .loginPage("/signin.html")     //设置登录路由
                    .loginProcessingUrl("/auth/form")  //设置登录处理url
                    .failureHandler(myAuthenticationFailureHandler)
                    .and()
                    // 身份认证设置
                    .authorizeRequests()
                    .antMatchers("/signin.html").permitAll() //该路由不需要身份认账
                    .antMatchers("/code/*").permitAll() //该路由不需要身份认账
                    .anyRequest()       //其他的路由均需要身份认证
                    .authenticated()
                    .and()
                    //先禁用防止跨站脚本攻击的csrf token
                    .csrf()
                    .disable();
        }
    
    }
    
    

    8. 自定义用户认证

    package com.example.demospringsecruity.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Component;
    
    /**
     * @author john
     * @date 2020/1/6 - 10:32
     */
    @Component
    @Slf4j
    public class MyUserDetailsService implements UserDetailsService {
        //这里可以注入mapper或者repository的dao对象来实现数据校验逻辑操作
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            log.info("用户名:" + username);
            //这里密码应该从数据库中取出,暂时先使用加密生成
            String password = passwordEncoder.encode("123456");
            return new User(username,
                    password,
                    true,                 // 账户是否可用
                    true,        // 账户是否过期
                    true,     // 密码是否过期
                    true,        //账户是否被锁定
                    AuthorityUtils.commaSeparatedStringToAuthorityList("admin") //授权集合
            );
        }
    }
    
    

    9. 静态登录页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录页面</title>
    </head>
    <body>
    <h2>标准登录页面</h2>
    <h3>表单登录</h3>
    <form action="/auth/form" method="post">
        <table>
            <tr>
                <td>用户名:</td>
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><input type="password" name="password"></td>
            </tr>
            <tr>
                <td>图形验证码</td>
                <td><input type="text" name="imageCode"><img src="/code/image" alt="图形验证码"></td>
            </tr>
            <tr>
                <td colspan="2">
                    <button type="submit">登录</button>
                </td>
            </tr>
        </table>
    </form>
    </body>
    </html>
    

    10. 测试

    验证码错误结果

    验证码正确,用户密码校验通过结果

    11. 代码资源

    链接:https://share.weiyun.com/5jKnFRj 密码:5vvs5x

  • 相关阅读:
    请求失败或服务未及时响应。有关详细信息,请参见事件日志或其他适用的错误日志
    12篇学通C#网络编程——第一篇 基础之进程线程(转)
    关于XP和win7前置音频插孔无声音的解决办法
    进程,线程,主线程,异步
    SQL 在什么情况下使用全表扫描
    性能的一些设置
    清除Windows 7通知区域的旧图标
    操作office
    数据库索引
    SCSI
  • 原文地址:https://www.cnblogs.com/ifme/p/12160893.html
Copyright © 2011-2022 走看看