Spring-Security +Kaptcha 实现验证码功能

添加该功能是在原有功能上新增功能: SpringBoot +SpringSecurity+mysql 实现用户数据权限管理

本文仅做重点代码的和相关依赖说明:SpringBoot +SpringSecurity+mysql 实现用户数据权限管理  文章中,我们采用的了分布式架构搭建该项目,同时也期望将相关通过公共组件抽离进行封装。因此。我们在common-tool 模块中添加kaptcha jar 文件依赖。

pom.xml 文件如下:

	
		
			com.github.penggle
			kaptcha
			2.3.2
		

我们这里采用编码方式,实例化kaptcha的配置对象:com.google.code.kaptcha.impl.DefaultKaptcha

核心代码:

package com.zzg.kaptcha.single;

import java.util.Properties;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;

public class KaptchaSingle {
	private static KaptchaSingle instance;

	private KaptchaSingle() {
	};

	public static KaptchaSingle getInstance() {
		if (instance == null) {
			instance = new KaptchaSingle();
		}
		return instance;
	}

	/**
	 * 生成DefaultKaptcha 默认配置
	 * @return
	 */
	public DefaultKaptcha produce() {
		Properties properties = new Properties();
		properties.put("kaptcha.border", "no");
		properties.put("kaptcha.border.color", "105,179,90");
		properties.put("kaptcha.textproducer.font.color", "blue");
		properties.put("kaptcha.image.width", "100");
		properties.put("kaptcha.image.height", "50");
		properties.put("kaptcha.textproducer.font.size", "27");
		properties.put("kaptcha.session.key", "code");
		properties.put("kaptcha.textproducer.char.length", "4");
		properties.put("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
		properties.put("kaptcha.textproducer.char.string", "0123456789ABCEFGHIJKLMNOPQRSTUVWXYZ");
		properties.put("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
		properties.put("kaptcha.noise.color", "black");
		properties.put("kaptcha.noise.impl", "com.google.code.kaptcha.impl.DefaultNoise");
		properties.put("kaptcha.background.clear.from", "185,56,213");
		properties.put("kaptcha.background.clear.to", "white");
		properties.put("kaptcha.textproducer.char.space", "3");
		
		Config config = new Config(properties);
		DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
		defaultKaptcha.setConfig(config);
		return defaultKaptcha;
	}
}

注意:上述 核心代码还有可以优化的地方,主要集中于Properties 类 添加配置参数的方式,建议采用读取classpath 文件夹下的相关资源文件。

业务模块依赖通用组件模块:

1、生成验证码控制层:

package com.zzg.controller;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.zzg.kaptcha.single.KaptchaSingle;

@Controller
public class KaptchaController {
	

	@RequestMapping("/defaultKaptcha")
	public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
			throws Exception {
		
		
		byte[] captchaChallengeAsJpeg = null;
		ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
		try {
			// 代码方式创建:DefaultKaptcha
			KaptchaSingle single = KaptchaSingle.getInstance();
			DefaultKaptcha defaultKaptcha = single.produce();
			// 生产验证码字符串并保存到session中
			String createText = defaultKaptcha.createText();
			httpServletRequest.getSession().setAttribute("vrifyCode", createText);
			// 使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中
			BufferedImage challenge = defaultKaptcha.createImage(createText);
			ImageIO.write(challenge, "jpg", jpegOutputStream);
		} catch (IllegalArgumentException e) {
			httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}

		// 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
		captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
		httpServletResponse.setHeader("Cache-Control", "no-store");
		httpServletResponse.setHeader("Pragma", "no-cache");
		httpServletResponse.setDateHeader("Expires", 0);
		httpServletResponse.setContentType("image/jpeg");
		ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
		responseOutputStream.write(captchaChallengeAsJpeg);
		responseOutputStream.flush();
		responseOutputStream.close();
	}

}

2、编辑验证码拦截器:
 

package com.zzg.security.kaptch.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

/**
 * Kaptcha 拦截器
 * 
 * @author zzg
 *
 */
public class KaptchaFilter extends AbstractAuthenticationProcessingFilter {
	
	// SESSION 关于 验证码
	private static final String VRIFYCODE ="vrifyCode";

	// 拦截请求地址
	private String servletPath;

	public KaptchaFilter(String servletPath, String failureUrl) {
		super(servletPath);
		this.servletPath = servletPath;
		setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(failureUrl));
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// TODO Auto-generated method stub
		 HttpServletRequest req = (HttpServletRequest) request;
	        HttpServletResponse res = (HttpServletResponse) response;
	        if ("POST".equalsIgnoreCase(req.getMethod()) && servletPath.equals(req.getServletPath())) {
	            String expect = (String) req.getSession().getAttribute(VRIFYCODE);
	            if (expect != null && !expect.equalsIgnoreCase(req.getParameter(VRIFYCODE))) {
	                unsuccessfulAuthentication(req, res, new InsufficientAuthenticationException("输入的验证码不正确"));
	                return;
	            }
	        }
	        chain.doFilter(req, res);
	}

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException, IOException, ServletException {
		// TODO Auto-generated method stub
		return null;
	}

}

3、spring-security 配置文件,添加验证码拦截器,在验证用户密码拦截器之前:

package com.zzg.security.config;

import javax.sql.DataSource;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;

import com.zzg.security.kaptch.filter.KaptchaFilter;
import com.zzg.security.provider.SpringSecurityProvider;

/**
 * spring-security 配置文件
 * @author zzg
 *
 */

@Configuration
@EnableWebSecurity //开启Spring Security的功能
@EnableGlobalMethodSecurity(prePostEnabled=true)//开启注解控制权限
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
	
	 /**
     * עSpringSecurityProvider
     */
    @Autowired
    private SpringSecurityProvider provider;
    
    /**
     *AuthenticationSuccessHandler
     */
    @Autowired
    private AuthenticationSuccessHandler securityAuthenticationSuccessHandler;
    /**
     *  AuthenticationFailureHandler
     */
    @Autowired
    private AuthenticationFailureHandler securityAuthenticationFailHandler;
    
    @Autowired
    private DataSource dataSource; 
    
   

    /**
	 * 定义需要过滤的静态资源(等价于HttpSecurity的permitAll)
	 */
	@Override
	public void configure(WebSecurity webSecurity) throws Exception {
		webSecurity.ignoring().antMatchers("static/css/**");
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// TODO Auto-generated method stub
		http.authorizeRequests()
        .antMatchers("/login")
        .permitAll() // 登入界面不需要权限
        .antMatchers("/defaultKaptcha")
        .permitAll() // 图像验证码不需要权限
        .anyRequest().authenticated()      
        .and()
        .formLogin()
        .loginPage("/login")    // 登入页面
        .successHandler(securityAuthenticationSuccessHandler)  //自定义成功处理器
        .failureHandler(securityAuthenticationFailHandler)     //自定义失败处理器
        .permitAll()
        .and()
        .logout();
	
		// 当通过JDBC方式记住密码时必须设置 key,key 可以为任意非空(null 或 "")字符串,但必须和 RememberMeService 构造参数的
        // key 一致,否则会导致通过记住密码登录失败
        http.authorizeRequests()
                .and()
                .rememberMe()
                .rememberMeServices(rememberMeServices())
                .key("INTERNAL_SECRET_KEY");
        
        //在认证用户名之前认证验证码,如果验证码错误,将不执行用户名和密码的认证
        http.addFilterBefore(new KaptchaFilter("/login", "/login?error"), UsernamePasswordAuthenticationFilter.class); 
        
	}



	@Override
	protected void configure(AuthenticationManagerBuilder builder) throws Exception {
		// 自定义身份验证提供者
		builder.authenticationProvider(provider);
	}
	
	
    
    /**
     * 返回 RememberMeServices 实例
     *
     * @return the remember me services
     */
    @Bean
    public RememberMeServices rememberMeServices() {
        JdbcTokenRepositoryImpl rememberMeTokenRepository = new JdbcTokenRepositoryImpl();
     
        // 此处需要设置数据源,否则无法从数据库查询验证信息
        rememberMeTokenRepository.setDataSource(dataSource);
        // 启动创建表,创建成功后注释掉
        // rememberMeTokenRepository.setCreateTableOnStartup(true);

        // 此处的 key 可以为任意非空值(null 或 ""),单必须和起前面
        // rememberMeServices(RememberMeServices rememberMeServices).key(key)的值相同
        PersistentTokenBasedRememberMeServices rememberMeServices =
                new PersistentTokenBasedRememberMeServices("INTERNAL_SECRET_KEY", provider.getUserDetailsService(), rememberMeTokenRepository);

        // 该参数不是必须的,默认值为 "remember-me", 但如果设置必须和页面复选框的 name 一致
        rememberMeServices.setParameter("remember-me");
        return rememberMeServices;
    }
}

用户登入页面(login.html),添加验证码页面:
 





登录




	

INDEX | ADMIN | HELLO


已成功注销

有错误,请重试

使用用户名密码登录

Remember me
验证码

至此,spring-security 模块添加验证码功能完毕。

你可能感兴趣的:(springsecurity,微服务springboot)