Spring-Security +Kaptcha 实现验证码功能
添加该功能是在原有功能上新增功能: 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.作者投稿可能会经我们编辑修改或补充。