springboot中使用SpringSecurity和ajax实现前后端的分离以及验证码的实现

基本思路
服务端通过 JSON字符串,告诉前端用户是否登录、认证;前端根据这些提示跳转对应的登录页、认证页。
实现代码
添加依赖:

 <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
 <!--thymeleaf视图解析器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
  <!--验证码的实现依赖 https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

具体实现

AuthenticationEntryPoint:未登录

public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
       
       httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(401);
        httpServletResponse.getWriter().write(Objects.requireNonNull(JsonUtils.obiectToJson(authError())));
    
    }
}

AuthenticationFailureHandler:登录失败

public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
    httpServletRequest.setCharacterEncoding("UTF-8");
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
//        获取的是验证码的错误的消息提醒
        String errmsg = (String) httpServletRequest.getAttribute("errmsg");
        if (StringUtils.isNotEmpty(errmsg)){
            httpServletResponse.getWriter().write(Objects.requireNonNull(JsonUtils.obiectToJson(error400(errmsg))));
        }else {
            httpServletResponse.getWriter().write(Objects.requireNonNull(JsonUtils.obiectToJson(error400("用户登录失败!"))));
        }
    }
}

AuthenticationSuccessHandler :登录成功

public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        
         httpServletResponse.setCharacterEncoding("UTF-8");

        httpServletResponse.setContentType("application/json;charset=UTF-8");

        httpServletResponse.getWriter().
                            write(Objects.requireNonNull(JsonUtils.obiectToJson(success("登录成功!"))));
                    
    }
}

AccessDeniedHandler:无权访问

public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        
       httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(403);
        httpServletResponse.getWriter().write(Objects.requireNonNull(JsonUtils.obiectToJson(new AjaxResult<>(ResultCode.AUTH_ERROR))));
        httpServletResponse.getWriter().flush();
    }
}

LogoutSuccessHandler:注销

public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        SecurityContextHolder.clearContext();
        httpServletResponse.getWriter().write(Objects.requireNonNull(JsonUtils.obiectToJson(new AjaxResult<>(ResultCode.LOG_OUT))));
    
    }
}

UserDetailsService:用户权限认证

/**
 * @author sxyuser
 */
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private UserDao userDao;

    @Resource
    HttpServletRequest request;

    @Resource
    private RoleDao roleDao;

    @Resource
    private PermissionMapper permissionMapper;
    /**
     * 根据用户名查询User对象,并装配角色,权限等信息
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

  Users loginInfo = userService.queryByUser(username);
//        给用户设置角色权限信息
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

        if (ObjectUtils.allNotNull(loginInfo)){

        Set<String> permsByUserId = permissionService.selectPermsByUserId(loginInfo.getId());

        List<Role> roleList = roleService.selectAssignedRole(loginInfo.getId());

        List<String> rList = new ArrayList<>();

//        循环得到角色信息
        for (Role perm : roleList) {
            if (perm != null) {
                rList.addAll(Arrays.asList(perm.getRoleKey().trim().split(",")));
            }
        }

//        将循环得到的角色信息存入授权的权限中
        for (String roleKey : rList) {
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + roleKey);
            grantedAuthorities.add(grantedAuthority);
        }
//        将循环得到的授权信息存入授权的权限中
        for (String perm : permsByUserId) {
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(perm);
            grantedAuthorities.add(grantedAuthority);
        }
//        将登录的信息保存到session中
        HttpSession session = request.getSession();
        session.setAttribute("loginInfo", loginInfo);
        System.out.println("grantedAuthorities:" + grantedAuthorities);
            return new User(loginInfo.getUsername(), loginInfo.getPassword(), grantedAuthorities);
        }else {
            throw new BadCredentialsException("用户名不存在!");
        }
    }
}

dao层

 /**
     * 通过用户名密码查询单条数据
     *
     * @return 实例对象
     */
    Users queryByUser(@Param("userName") String userName);

    /**
     * 根据用户ID查询权限
     *
     * @param id 用户ID
     * @return 权限列表
     */
    List<String> queryPermsByUserId(Integer id);

sql语句

   <!--登录功能-->
    <select id="queryByUser" resultMap="UserMap">
        select
        *
        from users
        where userName = #{userName}
    </select>

<!--查询权限-->
    <select id="queryPermsByUserId" resultType="string" parameterType="integer">
        SELECT distinct pr.perms
        FROM permission pr
        LEFT JOIN role_permission rp ON pr.id = rp.permission_id
        LEFT JOIN role r ON rp.role_id = r.role_id
        LEFT JOIN user_role ur ON r.role_id = ur.role_id
        LEFT JOIN users us ON us.id = ur.user_id
        WHERE us.id = #{id}
    </select>

AuthenticationProvider:前端交互

public class SelfAuthenticationProvider implements AuthenticationProvider {

    @Resource
    UserDetailsServiceImpl userDetailsService;

    @Resource
    HttpServletRequest request;

    @Resource
    HttpServletResponse response;


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String userName = (String) authentication.getPrincipal();

        String password = (String) authentication.getCredentials();


        UserDetails userInfo = userDetailsService.loadUserByUsername(userName);


        if (!EncryptPassword.decryptPwd(password, userInfo.getPassword())) {
            throw new BadCredentialsException("用户名密码不正确,请重新登陆!");
        }

//判断提交地址和提交方式来验证验证码的正误
        if ("/doLogin".equals(request.getServletPath()) && "post".equalsIgnoreCase(request.getMethod())) {

            response.setCharacterEncoding("UTF-8");

            response.setContentType("text/html;charset=UTF-8");
            //获取表单提交的验证码的值
            String verification = request.getParameter("code");
            HttpSession session = request.getSession();
            //获取下发的存在session中的验证码的值
            String captcha = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);

             if (captcha == null) {
                request.setAttribute("errmsg", "验证码不为空!");
                throw new BadCredentialsException("验证码不为空!");
            } else if (!captcha.equals(verification)) {
                request.setAttribute("errmsg", "验证码不匹配!");
                throw new BadCredentialsException("验证码不匹配!");
            } else if (StringUtils.isBlank(captcha)) {
                session.removeAttribute(Constants.KAPTCHA_SESSION_KEY);
                request.setAttribute("errmsg", "验证码已过期,请重新获取!");
                throw new BadCredentialsException("验证码已过期,请重新获取!");
            }
        }

        return new UsernamePasswordAuthenticationToken(userName, password, userInfo.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}

密码加密类

public class EncryptPassword {

    /**
     * SpringSecurity加密
     *
     * @param password 密码
     * @return 加密后的密码
     */
    public static String encryptionPwd(CharSequence password) {
        return new BCryptPasswordEncoder().encode(password);
    }


    /**
     * 加密密码和输入密码的对比
     * @param rawPassword 输入的原密码
     * @param encodedPassword 加密后的密码
     * @return
     */
    public static Boolean decryptPwd(CharSequence rawPassword, String encodedPassword){
        return new BCryptPasswordEncoder().matches(rawPassword, encodedPassword);
    }
}

WebSecurityConfigurerAdapter:登录拦截全局配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity security) throws Exception {

        security.cors().and()
//                .addFilterBefore(loginAuthenticationFilters(),UsernamePasswordAuthenticationFilter.class)
                //对请求进行授权
                .authorizeRequests()
                .antMatchers("/").permitAll()
//                注册地址
                .antMatchers("/path/toPage/reg").permitAll()
//                验证码地址
                .antMatchers("/captcha/captchaImage").permitAll()
                //任何请求
                .anyRequest()
                //需要登录以后才可以访问
                .authenticated()

                .and()
                .formLogin()
                .passwordParameter("password")
                .usernameParameter("username")
                .loginPage("/login")
                .loginProcessingUrl("/doLogin")
                // 登录成功
                .successHandler(ajaxAuthenticationSuccessHandler())
                // 登录失败
                .failureHandler(authenticationFailureHandler())
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(ajaxLogoutSuccessHandler())
                .permitAll()
                .and().csrf().disable()
                .httpBasic();

//        // 基于Token不需要session
//        security.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        security.exceptionHandling()
//                .authenticationEntryPoint(authenticationEntryPoint())
                .accessDeniedHandler(accessDeniedHandler());
        // 禁用缓存
        security.headers().cacheControl();

    }

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
        builder.authenticationProvider(selfAuthenticationProvider());
    }

    /**
     * 解决静态资源被拦截的问题
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/doc.html", "/fonts/**",
                "/img/**", "/swagger-ui.html", "/v2/**", "/swagger-resources/**",
                "/jquery/**", "/js/**", "/layer/**",
                "/script/**", "/ztree/**",
                "/css/**", "/bootstrap/**", "/webjars/**","/favicon.ico");
    }


    /**
     * 未登陆时返回 JSON 格式的数据给前端(否则为 html)
     *
     * @return
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new AjaxAuthenticationEntryPoint();
    }

    /**
     * 登录成功返回的 JSON 格式数据给前端(否则为 html)
     *
     * @return
     */
    @Bean
    public AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() {
        return new AjaxAuthenticationSuccessHandler();
    }

    /**
     * 登录失败返回的 JSON 格式数据给前端(否则为 html)
     *
     * @return
     */
    @Bean
    public AjaxAuthenticationFailureHandler authenticationFailureHandler() {
        return new AjaxAuthenticationFailureHandler();
    }


    /**
     * 注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
     *
     * @return
     */
    @Bean
    public AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler() {
        return new AjaxLogoutSuccessHandler();
    }

    /**
     * 无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
     *
     * @return
     */
    @Bean
    public AjaxAccessDeniedHandler accessDeniedHandler() {
        return new AjaxAccessDeniedHandler();
    }

    /**
     * 自定义安全认证
     *
     * @return
     */
    @Bean
    public SelfAuthenticationProvider selfAuthenticationProvider() {
        return new SelfAuthenticationProvider();
    }

}

前端页面:用户名和密码的name属性需要为username,password,如果不使用这两个可以在config文件中进行修改

.passwordParameter("password")
.usernameParameter("username")
<form class="form-signin" role="form" id="signupForm" autocomplete="off">
        <h2 class="form-signin-heading"><i class="glyphicon glyphicon-log-in"></i> 用户登录</h2>
		  <div class="form-group has-success has-feedback">
			<input type="text" class="form-control" id="username" name="username" placeholder="请输入登录账号" autofocus>
			<span class="glyphicon glyphicon-users form-control-feedback"></span>
		  </div>
		  <div class="form-group has-success has-feedback">
			<input type="password" class="form-control" id="password" name="password" placeholder="请输入登录密码" style="margin-top:10px;">
			<span class="glyphicon glyphicon-lock form-control-feedback"></span>
		  </div>

          <div class="form-group has-success has-feedback">
              <input type="text" class="form-control" id="code" name="code" placeholder="请输入验证码" style="margin-top:10px;margin-bottom: 10px;">
              <img th:src="@{/captcha/captchaImage(type='math')}" class="imgcode" width="100%" alt="验证码"/>
          </div>
          <div class="checkbox" style="text-align:right;"><a th:href="@{/path/toPage/reg}" href="reg.html">我要注册</a></div>

          <button class="btn btn-lg btn-success btn-block" type="submit" id="btnSubmit"> 登录 </button>
      </form>
<script th:inline="javascript"> var ctx = [[@{/}]];</script>

js的写法,需要引入表单验证的js,也可以使用其他方式的表单提交方式,需要注意的是ajax中的url地址需要和config中的loginProcessingUrl("/doLogin")的地址保持相同。

$(function() {
    validateRule();
    //刷新切换验证码
    $('.imgcode').click(function () {
        var url = ctx + "captcha/captchaImage?type=math&s=" + Math.random();
        $(".imgcode").attr("src", url);
    });
});


//submitHandler 当表度单通过验证,提交表单
function login() {
    let serverMsg = '';
    let username = $("#username").val();
    let password = $("#password").val();
    let code = $("#code").val();
    $.ajax({
        type: "post",
        url: ctx + "doLogin",
        data: {
            "username": username,
            "password": password,
            "code": code
        },
        dataType: "json",
        async: false,
        xhrFields: {
            withCredentials: true
        },
        success: function(result) {
            console.log("输出结果:", result.data);
            console.log("输出提示:", result.msg);
            console.log("输出错误", errmsg);
            serverMsg = result.data;
            if (result.code === 200) {
                layer.msg(serverMsg, {
                    icon: 1,
                    time: 3000
                }, function () {
                    location.href = ctx + "user/main";
                });
            } else {
                layer.msg(serverMsg, {
                    icon: 2,
                    time: 3000
                },function () {
                    //清除表单内容
                    $("#signupForm").clearForm();
                });
            }
        },
        error:function (error) {
            console.log("错误信息",this.error);
        }
    });
}

function validateRule() {
    var icon = "<i class='fa fa-times-circle'></i>  ";
    $("#signupForm").validate({
        rules: {
            username: {
                required: true
            },
            password: {
                required: true
            },
            code: {
                required: true
            }
        },
        messages: {
            username: {
                required: icon + "用户名不能为空!",
            },
            password: {
                required: icon + "密码不能为空!",
            },
            code: {
                required: icon + "验证码不能为空!"
            }
        },
        submitHandler: function() {
            login();
        }
    })
}

验证码的后台实现

验证码的配置

/**
 * 验证码配置
 *
 * @author sxy
 */
@Configuration
public class CaptchaConfig {
    @Bean(name = "captchaProducer")
    public DefaultKaptcha getKaptchaBean() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty(KAPTCHA_BORDER, "yes");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
        // 验证码图片宽度 默认为200
        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
        // 验证码图片高度 默认为50
        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
        // KAPTCHA_SESSION_KEY
        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
        // 验证码文本字符长度 默认为5
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google
        // .code.kaptcha.impl.ShadowGimpy
        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

    @Bean(name = "captchaProducerMath")
    public DefaultKaptcha getKaptchaBeanMath() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty(KAPTCHA_BORDER, "yes");
        // 边框颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
        // 验证码图片宽度 默认为200
        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
        // 验证码图片高度 默认为50
        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
        // KAPTCHA_SESSION_KEY
        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
       // 验证码文本字符间距 默认为2
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
        // 验证码文本字符长度 默认为5
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
        // 验证码文本生成器
        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.demo.springboot.config.KaptchaTextCreator");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
        // 验证码噪点颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
        // 干扰实现类
        properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google
        // .code.kaptcha.impl.ShadowGimpy
        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

注意里面的KAPTCHA_TEXTPRODUCER_IMPL属性,这个对应自己项目的本地路径。

验证码文本生成器

/**
 * 验证码文本生成器
 *
 * @author sxy
 */
public class KaptchaTextCreator extends DefaultTextCreator {

    private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");

    @Override
    public String getText() {
        int result = 0;
        Random random = new SecureRandom();
        int x = random.nextInt(10);
        int y = random.nextInt(10);
        StringBuilder suChinese = new StringBuilder();
        int randomoperands = (int) Math.round(Math.random() * 2);
        if (randomoperands == 0) {
            result = x * y;
            suChinese.append(CNUMBERS[x]);
            suChinese.append("*");
            suChinese.append(CNUMBERS[y]);
        } else if (randomoperands == 1) {
            if (!(x == 0) && y % x == 0) {
                result = y / x;
                suChinese.append(CNUMBERS[y]);
                suChinese.append("/");
                suChinese.append(CNUMBERS[x]);
            } else {
                result = x + y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("+");
                suChinese.append(CNUMBERS[y]);
            }
        } else if (randomoperands == 2) {
            if (x >= y) {
                result = x - y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("-");
                suChinese.append(CNUMBERS[y]);
            } else {
                result = y - x;
                suChinese.append(CNUMBERS[y]);
                suChinese.append("-");
                suChinese.append(CNUMBERS[x]);
            }
        } else {
            result = x + y;
            suChinese.append(CNUMBERS[x]);
            suChinese.append("+");
            suChinese.append(CNUMBERS[y]);
        }
        suChinese.append("=?@").append(result);
        return suChinese.toString();
    }
}

验证码的controller

/**
 * 图片验证码(支持算术形式)
 *
 * @author sxy
 */
@Controller
@RequestMapping("/captcha")
public class CaptchaController{

    @Resource(name = "captchaProducer")
    private Producer captchaProducer;

    @Resource(name = "captchaProducerMath")
    private Producer captchaProducerMath;

    /**
     * 验证码生成
     */
    @GetMapping(value = "/captchaImage")
    public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response) {
        ServletOutputStream out = null;
        try {
            HttpSession session = request.getSession();
            response.setDateHeader("Expires", 0);
            response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
            response.addHeader("Cache-Control", "post-check=0, pre-check=0");
            response.setHeader("Pragma", "no-cache");
            response.addHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
            response.setContentType("image/jpeg");

            String type = request.getParameter("type");
            String capStr;
            String code = null;
            BufferedImage bi = null;
            if ("math".equals(type)) {
                String capText = captchaProducerMath.createText();
                capStr = capText.substring(0, capText.lastIndexOf("@"));
                code = capText.substring(capText.lastIndexOf("@") + 1);
                bi = captchaProducerMath.createImage(capStr);
            } else if ("char".equals(type)) {
                capStr = code = captchaProducer.createText();
                bi = captchaProducer.createImage(capStr);
            }
            session.setAttribute(Constants.KAPTCHA_SESSION_KEY, code);
            out = response.getOutputStream();
            assert bi != null;
            ImageIO.write(bi, "jpg", out);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

注意:在启动类上一定不要加@ComponentScan注解,加了之后不知道什么原因,controller层的映射地址全访问不到,具体原因不知。

参考文章
https://www.jianshu.com/p/9d841e055efb
https://ruoyi.vip/

猜你喜欢

转载自blog.csdn.net/weixin_43897590/article/details/107214302