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

访客 阅读:237 2021-03-31 17:19:41 评论:0

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

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

pom.xml 文件如下:

	<!-- 依赖kaptcha图形生成器  --> 
		<dependency> 
			<groupId>com.github.penggle</groupId> 
			<artifactId>kaptcha</artifactId> 
			<version>2.3.2</version> 
		</dependency>

我们这里采用编码方式,实例化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),添加验证码页面:
 

<!DOCTYPE html> 
<html xmlns:th="http://www.thymeleaf.org"> 
<head> 
<meta content="text/html;charset=UTF-8"/> 
<title>登录</title> 
<link rel="stylesheet" th:href="@{static/css/bootstrap.min.css}"/> 
<style type="text/css"> 
body { padding: 20px; } 
.starter-template { width:350px; padding: 0 40px; text-align: center; } 
</style> 
</head> 
<body> 
	<p> 
		<a th:href="@{/index}"> INDEX</a> 
		<a th:href="@{/admin}"> | ADMIN</a> 
		<a th:href="@{/hello}"> | HELLO</a> 
		<br/> 
	</p> 
	<hr/> 
    <div class="starter-template"> 
     <p th:if="${param.logout}" class="bg-warning">已成功注销</p><!-- 1 --> 
	<p th:if="${param.error}" class="bg-danger">有错误,请重试</p> <!-- 2 --> 
	<h2>使用用户名密码登录</h2> 
	<form name="form"  th:action="@{/login}" action="/login" method="POST"> <!-- 3 --> 
		<div class="form-group"> 
			<label for="username">账号</label> 
			<input type="text" class="form-control" name="username" value="" placeholder="账号" /> 
		</div> 
		<div class="form-group"> 
			<label for="password">密码</label> 
			<input type="password" class="form-control" name="password" placeholder="密码" /> 
		</div> 
		<div class="form-group"> 
			<label for="remember-me">是否记住</label> 
			<input type="checkbox" name="remember-me"/> Remember me 
		</div> 
		<div class="form-group"> 
			<img alt="验证码" onclick = "this.src='/defaultKaptcha?d='+new Date()*1" src="/defaultKaptcha" /> 
			<input type="text" class="form-control" name="vrifyCode" placeholder="验证码" /> 
		</div> 
		<div class="form-group"> 
			<input type="submit" id="login" value="登录" class="btn btn-primary" /> 
		</div> 
 
	</form> 
    </div> 
</body> 
</html>

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

声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

发表评论
搜索
KIKK导航

KIKK导航

排行榜
关注我们

一个IT知识分享的公众号