在系统的研发过程中,为了增加系统安全性,防止一些不良用户的恶意攻击,很多系统都会采用生成并验证验证码的方式、滑动解锁的方式让用户进行一些操作之后才能让用户登录,本文我们就简单讲讲如何生成图片验证码,如何验证图片验证码。
一、图片验证码的生成
1、首先我们先生成一个验证码,验证码的生成规则多种多样,我们这里就不在赘述了,可以参考文章(验证码生成与发送)。
2、提供图片width, height、imageType参数构建BufferedImage对象,BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类。
3、通过构建的BufferedImage对象获取Graphics对象,该对象可以在图像上进行各种绘制操作。
4、通过Graphics对象绘制干扰线、画字符串
5、构建ByteArrayOutputStream对象,将绘制好的image信息写入ByteArrayOutputStream。
6、将ByteArrayOutputStream转化成Byte数组写入到HttpServletResponse,通过流动形式输出到客户端。
详细代码如下:
public class ImageVerifyCodeUtils {
private static Random random = new Random();
private static int width = 80;// 图片宽
private static int height = 38;// 图片高
private static int lineSize = 40;// 干扰线数量
/*
* 获得字体
*/
private static Font getFont() {
return new Font("Fixedsys", Font.CENTER_BASELINE, 18);
}
/*
* 获得颜色
*/
private static Color getRandColor(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc - 16);
int g = fc + random.nextInt(bc - fc - 14);
int b = fc + random.nextInt(bc - fc - 18);
return new Color(r, g, b);
}
/**
* @Comment 绘制干扰线
* @Author Ron
* @Date 2017年9月15日 下午4:31:04
* @return
*/
private static void drowLine(Graphics g) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(13);
int yl = random.nextInt(15);
g.drawLine(x, y, x + xl, y + yl);
}
/**
* @Comment 绘制字符串
* @Author Ron
* @Date 2017年9月15日 下午6:06:25
* @return
*/
private static void drowString(Graphics g,String vchar,int i) {
g.setFont(getFont());
g.setColor(new Color(random.nextInt(101), random.nextInt(111), random
.nextInt(121)));
g.translate(random.nextInt(3), random.nextInt(3));
g.drawString(vchar, 13 * i, 25);
}
/**
* @Comment 获取随机验证码
* @Author Ron
* @Date 2017年9月15日 下午4:24:02
* @return
*/
public static void getRandcode(HttpServletRequest request,HttpServletResponse response,String verifyCode) {
// BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
BufferedImage image = new BufferedImage(width, height,BufferedImage.TYPE_INT_BGR);
// 产生Image对象的Graphics对象,该对象可以在图像上进行各种绘制操作
Graphics g = image.getGraphics();
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));
g.setColor(getRandColor(110, 133));
// 绘制干扰线
for (int i = 0; i <= lineSize; i++) {
drowLine(g);
}
//画字符串
for (int i = 0; i < verifyCode.length(); i++) {
drowString(g,String.valueOf(verifyCode.charAt(i)), i);
}
g.dispose();
try {
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
ImageIO.write(image, "png", tmp);
tmp.close();
Integer contentLength = tmp.size();
response.setHeader("content-length", contentLength + "");
response.getOutputStream().write(tmp.toByteArray());// 将内存中的图片通过流动形式输出到客户端
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
response.getOutputStream().flush();
response.getOutputStream().close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
二、Spring Session存储验证码
对于一些比较小型的系统,类似于验证码这种临时的数据,一般情况下都会选择存储在Session中。
在很多的应用服务器中,都会将HTTP session状态保存在JVM中,这个JVM与运行应用程序代码的JVM是同一个,因为这样易于实现,并且速度很快。当新的应用服务器实例加入或离开集群时,HTTP session会基于现有的应用服务器实例进行重新平衡。在弹性的云环境中,我们会拥有上百个应用服务器实例,并且实例的数量可能在任意时刻增加或减少,这样的话,我们就会遇到一些问题:
- 重平衡HTTP session可能会成为性能瓶颈。
- 为了存储大量的session,会需要很大的堆空间,这会导致垃圾收集,从而对性能产生负面影响。
- 云基础设施通常会禁止TCP多播(multicast),但是session管理器常常会使用这种机制来发现哪一个应用服务器实例加入或离开了集群。
因此,更为高效的办法是将HTTP session状态保存在独立的数据存储中,这个存储位于运行应用程序代码的JVM之外。例如,我们可以将100个Tomcat实例配置为使用Redis来存储session状态,当Tomcat实例增加或减少的时候,Redis中所存储的session并不会受到影响。同时,因为Redis是使用C语言编写的,所以它可以使用上百GB甚至TB级别的RAM,它不会涉及到垃圾收集的问题。
对于像Tomcat这样的开源服务器,很容易找到session管理器的替代方案,这些替代方案可以使用外部的数据存储,如Redis或Memcached。但是,这些配置过程可能会比较复杂,而且每种应用服务器都有所差别。对于闭源的产品,如WebSphere和Weblogic,寻找它们的session管理器替代方案不仅非常困难,在有些时候,甚至是无法实现的。
Spring Session提供了一种独立于应用服务器的方案,这种方案能够在Servlet规范之内配置可插拔的session数据存储,不依赖于任何应用服务器的特定API。这就意味着Spring Session能够用于实现了servlet规范的所有应用服务器之中(Tomcat、Jetty、 WebSphere、WebLogic、JBoss等),它能够非常便利地在所有应用服务器中以完全相同的方式进行配置。我们还可以选择任意最适应需求的外部session数据存储。这使得Spring Session成为一个很理想的迁移工具,帮助我们将传统的JavaEE应用转移到云中,使其成为满足 12-factor(https://12factor.net/)的应用。
如何整合Spring Session(我们使用Redis存储数据),分为以下几步:
2.1、安装部署Redis
2.2、配置Spring Session依赖
配置Spring Session依赖很简单,直接在pom.xml中增加Spring Session依赖即可,因为我们使用的是Redis存储数据,所以我们只要依赖spring-session-data-redis。
<!-- spring-session-data-redis依赖 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>${spring_session_data_redis_version}</version>
</dependency>
2.3、整合Redis客户端,配置Redis参数
在pom.xml中添加Redis客户端依赖
<!-- Redis客户端配置 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${redis_clients_version}</version>
</dependency>
在resources目录下的config.properties文件中写入Redis信息(主机地址,端口等)
#Redis信息配置
#绑定的主机地址
redis.host=127.0.0.1
#指定Redis监听端口,默认端口为6379
redis.port=6379
#授权密码(本例子没有使用)
redis.password=123456
#最大空闲数:空闲链接数大于maxIdle时,将进行回收
redis.maxIdle=100
#最大连接数:能够同时建立的“最大链接个数”
redis.maxTotal=300
#最大等待时间:单位ms
redis.maxWait=1000
#使用连接时,检测连接是否成功
redis.testOnBorrow=true
#当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
redis.timeout=10000
在resources目录下新建一个spring-context-redis.xml文件,开始配置Redis。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<context:annotation-config/>
<!-- 引入properties配置文件 -->
<context:property-placeholder ignore-unresolvable="true" location="classpath:config.properties" />
<bean id="redisHttpSessionConfiguration"
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="600" />
</bean>
<!-- jedis 配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" >
<property name="maxTotal" value="${redis.maxTotal}"/>
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<!-- redis服务器中心 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<property name="poolConfig" ref="poolConfig" />
<property name="port" value="${redis.port}" />
<property name="hostName" value="${redis.host}" />
<property name="timeout" value="${redis.timeout}" ></property>
</bean>
</beans>
因为我们在web.xml中已经配置,所有以spring-context开头的xml文件,在上下文初始化时都会载入,所以上下文初始化时,Redis的相关内容也会载入并进行初始化。
2.4、配置Spring Session过滤器
在web.xml中,加入如下配置:
<!-- Spring Session 过滤器start -->
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring Session 过滤器end -->
所有信息的配置完成之后,我们使用Spring Session的方式和传统的使用方法基本一致,比如我们本实例中我们需要存储验证码,我们只需要按照如下方式操作就行:
request.getSession().setAttribute("loginVerifyCode", 验证码);
获取验证码:
request.getSession().getAttribute("loginVerifyCode");