SpringSecurity默认是没有图片验证码功能的,假如我们需要在登录界面添加一个图片验证码的功能,我们可以在UsernamePasswordAuthenticationFilter过滤器之前写一个图片验证码过滤器,图片验证码过滤器的功能:首先判断请求地址是否需要图片验证码,如果需要就判断图片验证码是否正确,如果验证码正确则继续往下执行,否则抛出验证码错误异常;如果不需要就直接往下执行。

 

图片验证码

使用kaptcha插件来生成图片验证码,导入如下依赖

        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

kaptcha的一些配置

@Component
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha getDefaultKaptcha(){
        com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
        Properties properties = new Properties();
        properties.setProperty("kaptcha.border", "yes"); // 是否有边框
        properties.setProperty("kaptcha.border.color", "105,179,90"); // 验证码边框颜色
        // properties.setProperty("kaptcha.textproducer.char.string", "ABCDEFG23456789"); // 验证码,不设置默认也存在
        properties.setProperty("kaptcha.noise.color", "red"); // 干扰线的颜色
        properties.setProperty("kaptcha.textproducer.font.color", "blue"); // 字体颜色
        properties.setProperty("kaptcha.image.width", "110");
        properties.setProperty("kaptcha.image.height", "40");
        properties.setProperty("kaptcha.textproducer.font.size", "30");
        properties.setProperty("kaptcha.session.key", "code");
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

kaptcha使用

        DefaultKaptcha defaultKaptcha = new DefaultKaptcha ();
        //生产验证码字符串
        String code = defaultKaptcha.createText();
        //使用生产的验证码字符串返回一个BufferedImage对象
        BufferedImage image = defaultKaptcha.createImage(code);

省略图片验证码的生成过程,主要有两个步骤:

1. 使用DefaultKaptcha 类生成BufferedImage 对象,将BufferedImage封装到ImageCode类中,ImageCode主要有三个属性:

String code(验证码字符串)、LocalDateTime expireTime(过期时间)、BufferedImage image(图片)

2. 将生成的ImageCode存入Session中

 

图片验证码过滤器

为了在某些地址使用图片验证码,我们需要写一个过滤器。

@Component
public class ImageCodeFilter extends OncePerRequestFilter implements InitializingBean {

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    private Set<String> urls = new HashSet<>();

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        // 这里可以设置,哪些地址是需要图片验证码进行验证的
        urls.add("/authentication/form"); // 登录地址

    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        boolean action = false;
        // 判断请求地址是否需要图片验证码
        for (String url : urls) {
            if (antPathMatcher.match(url, httpServletRequest.getRequestURI())) {
                action = true;
                break;
            }
        }
        if (action) {
            try {
                // 验证验证码是否正确
                validate(httpServletRequest);
            } catch (ImageCodeException e) {
                // 验证码错误则抛出异常
                authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
                return;
            }
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

    private void validate(HttpServletRequest request) {
        ImageCode imageCodeSession = (ImageCode)request.getSession().getAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
        String imageCodeRequest = request.getParameter("imageCode");
        if (imageCodeRequest == null || imageCodeRequest.isEmpty()) {
            throw new ImageCodeException("图片验证码不能为空");
        }
        if (imageCodeSession == null) {
            throw new ImageCodeException("验证码不存在");
        }
        if (imageCodeSession.isExpired()) {
            request.getSession().removeAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
            throw new ImageCodeException("验证码已过期");
        }
        if(!imageCodeRequest.equalsIgnoreCase(imageCodeSession.getCode())) {
            throw new ImageCodeException("验证码错误");
        }
        request.getSession().removeAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
    }
}

 

配置过滤器

将ImageCodeFilter过滤器设置在UsernamePasswordAuthenticationFilter之前

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserService myUserService;

    @Autowired
    private MyAuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    private MyAuthenticationFailHandler authenticationFailHandler;

    @Autowired
    private ImageCodeFilter imageCodeFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(imageCodeFilter, UsernamePasswordAuthenticationFilter.class) // 将ImageCodeFilter过滤器设置在UsernamePasswordAuthenticationFilter之前
                .authorizeRequests()
                .antMatchers("/authentication/*","/login/*","/code/*") // 不需要登录就可以访问
                .permitAll()
                .antMatchers("/user/**").hasAnyRole("USER") // 需要具有ROLE_USER角色才能访问
                .antMatchers("/admin/**").hasAnyRole("ADMIN") // 需要具有ROLE_ADMIN角色才能访问
                .anyRequest().authenticated()
                .and()
                    .formLogin()
                    .loginPage("/authentication/login") // 访问需要登录才能访问的页面,如果未登录,会跳转到该地址来
                    .loginProcessingUrl("/authentication/form")
                    .successHandler(authenticationSuccessHandler)
                    .failureHandler(authenticationFailHandler)
                ;
    }

    // 密码加密方式
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    // 重写方法,自定义用户
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.inMemoryAuthentication().withUser("lzc").password(new BCryptPasswordEncoder().encode("123456")).roles("ADMIN","USER");
//        auth.inMemoryAuthentication().withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
        auth.userDetailsService(myUserService); // 注入MyUserService,这样SpringSecurity会调用里面的loadUserByUsername(String s)
    }
}

 

登录页面

<form th:action="@{/authentication/form}" method="post">
                <div class="form-group">
                    <label for="username">Username</label>
                    <input type="text" class="form-control" id="username" name="username" placeholder="Enter username">
                </div>
                <div class="form-group">
                    <label for="Password">Password:</label>
                    <input type="password" class="form-control" id="Password" name="password" placeholder="Enter password">
                </div>
                <div class="form-group">
                    <label for="imageCode">imageCode:</label>
                    <input type="text" class="form-control" id="imageCode" name="imageCode" placeholder="Enter imageCode">
                    <img src="/code/image">
                </div>
                <div class="form-group" th:if="${param.error}">
                    <p th:if="${session.SPRING_SECURITY_LAST_EXCEPTION}">
                        <p th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></p>
                    </p>
                </div>
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>

 

代码地址  https://github.com/923226145/SpringSecurity/tree/master/chapter4

 

 

 

 

 

12-06 07:56