目录
在登录页面加入图形验证码
一、验证码的生成与校验
1. 创建生成验证码的工具类
package com.Util;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
public class ImageCodeUtils {
/**
* 图片的宽度
*/
private int width = 160;
/**
* 图片的高度
*/
private int height = 40;
/**
* 验证码字符个数
*/
private int codeCount = 4;
/**
* 验证码干扰线数
*/
private int lineCount = 20;
/**
* 验证码
*/
private String code = null;
private BufferedImage buffImg = null;
Random random = new Random();
public ImageCodeUtils() {
createImage();
}
public ImageCodeUtils(int width, int height) {
this.width = width;
this.height = height;
createImage();
}
public ImageCodeUtils(int width, int height, int codeCount) {
this.width = width;
this.height = height;
this.codeCount = codeCount;
createImage();
}
public ImageCodeUtils(int width, int height, int codeCount, int lineCount) {
this.width = width;
this.height = height;
this.codeCount = codeCount;
this.lineCount = lineCount;
createImage();
}
/**
* 生成图片
*/
private void createImage() {
// 字体的宽度
int fontWidth = width / codeCount;
// 字体的高度
int fontHeight = height - 5;
int codeY = height - 8;
// 图像buffer
buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = buffImg.getGraphics();
// 设置背景色
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
// 设置字体
//Font font1 = getFont(fontHeight);
Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
g.setFont(font);
// 设置干扰线
for (int i = 0; i < lineCount; i++) {
int xs = random.nextInt(width);
int ys = random.nextInt(height);
int xe = xs + random.nextInt(width);
int ye = ys + random.nextInt(height);
g.setColor(getRandColor(1, 255));
g.drawLine(xs, ys, xe, ye);
}
// 添加噪点
float yawpRate = 0.01f;
int area = (int) (yawpRate * width * height);
for (int i = 0; i < area; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
buffImg.setRGB(x, y, random.nextInt(255));
}
// 得到随机字符
String str1 = randomStr(codeCount);
this.code = str1;
for (int i = 0; i < codeCount; i++) {
String strRand = str1.substring(i, i + 1);
g.setColor(getRandColor(1, 255));
// a为要画出来的东西,x和y表示要画的东西最左侧字符的基线位于此图形上下文坐标系的 (x, y) 位置处
g.drawString(strRand, i*fontWidth+3, codeY);
}
}
/**
* 得到随机字符串
* @param n
* @return
*/
private String randomStr(int n) {
String str1 = "ABCDEFGHJKMNOPQRSTUVWXYZabcdefghjkmnopqrstuvwxyz1234567890";
String str2 = "";
int len = str1.length() - 1;
double r;
for (int i = 0; i < n; i++) {
r = (Math.random()) * len;
str2 = str2 + str1.charAt((int) r);
}
return str2;
}
/**
* 得到随机颜色
* @param fc
* @param bc
* @return
*/
private Color getRandColor(int fc, int bc) {
if (fc > 255){
fc = 255;
}
if (bc > 255){
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
/**
* 产生随机字体
*/
private Font getFont(int size) {
Random random = new Random();
Font[] font = new Font[5];
font[0] = new Font("Ravie", Font.PLAIN, size);
font[1] = new Font("Antique Olive Compact", Font.PLAIN, size);
font[2] = new Font("Fixedsys", Font.PLAIN, size);
font[3] = new Font("Wide Latin", Font.PLAIN, size);
font[4] = new Font("Gill Sans Ultra Bold", Font.PLAIN, size);
return font[random.nextInt(5)];
}
/**
* 扭曲方法
* @param g
* @param w1
* @param h1
* @param color
*/
private void shear(Graphics g, int w1, int h1, Color color) {
shearX(g, w1, h1, color);
shearY(g, w1, h1, color);
}
private void shearX(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(2);
boolean borderGap = true;
int frames = 1;
int phase = random.nextInt(2);
for (int i = 0; i < h1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(0, i, w1, 1, (int) d, 0);
if (borderGap) {
g.setColor(color);
g.drawLine((int) d, i, 0, i);
g.drawLine((int) d + w1, i, w1, i);
}
}
}
private void shearY(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(40) + 10;
boolean borderGap = true;
int frames = 20;
int phase = 7;
for (int i = 0; i < w1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(i, 0, 1, h1, 0, (int) d);
if (borderGap) {
g.setColor(color);
g.drawLine(i, (int) d, i, 0);
g.drawLine(i, (int) d + h1, i, h1);
}
}
}
public void write(OutputStream sos) throws IOException {
ImageIO.write(buffImg, "png", sos);
sos.close();
}
public BufferedImage getBuffImg() {
return buffImg;
}
public String getCode() {
return code.toLowerCase();
}
}
2. 写一个 Controller
方法:前端访问,调用一下工具类生成验证码图片并返回
引入一下依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>

3. 实现验证码验证
1. 获取验证码

已经获取到验证码了,那么下一步怎么验证验证码呢?

2. 验证码请求过程

游览器那么多,服务器怎么知道是哪个游览器请求的?
这就可以使用到 Cookie 和 Session

3. 验证码的校验
把字符串形式的验证码存到 Session

从Session 中获取验证码

4. 原理说明

5. 验证
提示验证码错误
先查看验证验证码的SessionID是否和获取验证码的SessionID 一样

可以看到,两个是不同的SessionID ,服务器根据ID去获取的值是不同的,所以验证码错误

那么为什么是不同的呢?
这是因为 Vue 开启了代理转发,所以每次请求的 Cookie中JSESSIONID 值会发生变化
如何解决呢?
官方的解决方案是代理转发的 请求 URL 前缀和服务器请求路径的项目名相同就没问题了
原来的请求路径

修改后的请求路径
注意:vue 的配置文件修改后需要重启

修改服务器的 URL ,修改后重启服务器


可以看到SessionID值是一样的了


验证成功

6. 总结
- 会话开始:当一个用户首次访问一个网站,服务器会创建一个新的HttpSession对象,并生成一个唯一的会话ID(通常称为JSESSIONID)。
- 会话ID存储在Cookie中:服务器会在响应中包含一个Set-Cookie头,将这个会话ID作为cookie的一部分发送给浏览器。浏览器会存储这个cookie。
- 后续请求:当用户继续与网站交互时,浏览器会自动在每个后续请求中包含这个会话ID的cookie。这使得服务器能够识别出请求来自于哪个具体的会话。
- 服务器识别会话:服务器接收到请求后,会读取请求头中的cookie,提取出会话ID,然后使用这个ID在服务器端的会话存储中查找相应的HttpSession对象。
- 会话数据访问:一旦找到了HttpSession对象,服务器就可以从这个对象中读取或写入数据,比如用户信息、登录状态等。
- 会话结束:当用户关闭浏览器或会话超时,HttpSession对象会被销毁,除非服务器端有特别的配置来延长会话的存活时间。
二、JWT登录鉴权
JWT 全程 JSON Web Token
1. 为什么要做登录鉴权?
安全考虑,需要登录之后有操作权限了之后才能访问API接口
2. 什么是 JWT
JWT(JSON Web Token)是一种在网络应用中用于身份验证和授权的令牌。你可以把它想象成一张电子版的“身份证”或“通行证”。
当你登录一个网站后,服务器会生成一个JWT,JWT 本质就是一条字符串,它把你的身份信息(比如用户名)保存到一个JSON字符串中,然后进行编码得到一个token 令牌,然后把这个令牌发回给你的浏览器。之后,每当你的浏览器想要访问受保护的资源时,它都会带上这个JWT。
3. JWT相比传统的鉴权方式的优点
1. Session 认证
我们知道 HTTP 是一种无状态的协议,HTTP协议在设计上不保留任何两次请求之间的信息。换句话说,当你向一个网站发送请求时,比如浏览一个网页,这个请求是独立的,它并不依赖于你之前对该网站做的任何事情。一旦服务器处理完你的请求并返回了响应,它就会忘记这次交互,就像从来没有发生过一样。
所以为了让服务器知道是谁在访问,我们会在游览器第一次登陆成功的时候,创建一个SessionID,然后把用户信息保存在 Session 对象中,最后把SessionID放到 Cookie 返回给游览器,这样下次游览器再访问的时候就知道是谁了,这就是基于Session 认证的过程
2. Session 认证的缺点
由于基于Cookie,而cookie无法跨域,所以session的认证也无法跨域
Session 是保存在服务器的,会使服务器的开销增大
3. JWT 的优点
简洁,
无状态存储,以加密的形式保存在客户端,
时效性,可以设置多少时间失效
4. 创建 JWT
1. 引入依赖
<dependency>
<groupId>io.github.qyg2297248353.components</groupId>
<artifactId>jsonwebtoken-jjwt</artifactId>
<version>2.0.0</version>
</dependency>
2. 创建JWT 工具类
package com.Util;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Map;
public class JwtUtil {
// 私钥
private static String privateKey = "12345678901234567890123456789012abcdefghijklmn";
/*
* 生成token
* @param claims 要加密的数据
* @return
* */
public static String generateToken(Map<String, Object> claims) {
// 使用JWT构建器构建令牌
// 添加负载(claims),存储用户信息
// 使用HS256算法和私钥进行签名,确保令牌的完整性和安全性
// 最后将令牌以JSON格式编码,并压缩为紧凑字符串格式
String token = Jwts.builder()
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, privateKey) // HS256加密,密钥长度必须大于等于256 bit
.compact();
return token;
}
}
3. 生成一个token
在 Service 层调JWT工具类生成 token

登录成功后返回的 token

那么如何在每次请求都带上这个 token 呢?
可以把token 放到 Cookie 里
5. 使用 JWT
1. Vue 导入依赖
npm i js-cookie@2.2.0 -S
2. 前端创建 Cookie 工具类

3. 在登录组件设置 token
引入token 工具类的方法
![]()

4. 判断是否有 token
在路由器文件的路由守卫中判断,如果有 token 则放行
![]()
![]()
验证

5. 发送 token
在自定义 axios 文件的请求拦截器中添加 token 自定义 axios

可以看到在登录成功后,请求头里有 token

6. 服务器验证token
服务器在哪个 Controller 里判断?在所有 Controller 都判断一下比较麻烦
这就可以使用拦截器,在拦截器中统一判断
1. 创建登录拦截器

2. 配置拦截器
在spring 配置文件配置拦截器

<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/> <!--拦截所有请求-->
<mvc:exclude-mapping path="/Admin/Login"/> <!--放行登录操作-->
<mvc:exclude-mapping path="/ImageCode/Captcha"/> <!--放行请求验证码-->
<bean class="com.Interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
这样就可以在每次请求资源时都带有token,服务器根据 token 判断是否有权限访问数据
目前,在 Vue 的路由守卫有判断是否登录(token)以及在服务器的拦截器也有判断
2419

被折叠的 条评论
为什么被折叠?



