V0版本:
实现最简单的登录,写个接口去数据库查有没有前端传来的账号和密码,查到且密码正确返回“登录成功”,其余情况统一返回“账号或密码错误”。
V1版本:
用session代替cookie 先导入依赖,将session加入到redis中
1.导入依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2. 设置拦截器
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("拦截到请求requestURI:{}", requestURI);
// 这里可以使用false来避免自动创建新的空会话
HttpSession session = request.getSession();
// 检查用户是否已登录
if (session != null && session.getAttribute("userId") != null) {
// 用户已登录
log.info("用户已登录放行");
return true;
} else {
// 用户未登录
log.info("用户未登录,以跳转到登录页面");
response.setStatus(401);
response.sendRedirect("http://localhost:8080/login.html");
return false; // 终止请求处理
}
}
}
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有
//选择排除不需要拦截的地址
.excludePathPatterns("/login","/login.html","/regist","/regist.html");
}
}
3. 给session加一个userId字段,用于拦截器的处理
HttpSession session = request.getSession();
session.setAttribute("userId", dbUser.getId());
System.out.println("Session中创建的userId:" + session.getAttribute("userId"));
V2版本:
对前端传来的密码进行加盐和加密处理
String password = loginDTO.getPassword();
String userSalt = dbUser.getSalt();
String DBpassword = dbUser.getPassword();
String saltPassword = password+userSalt;
String md5SaltPwd = DigestUtils.md5DigestAsHex(saltPassword.getBytes());
if(!md5SaltPwd.equals(DBpassword)){
return R.error("用户名或者密码错误");
}
V3版本:
1. 生成验证码的工具类
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.Random;
public class ValidateCodeUtil {
private static Random random = new Random();
private int width = 165; //验证码的宽
private int height = 45; //验证码的高
private int lineSize = 30; //验证码中夹杂的干扰线数量
private int randomStrNum = 4; //验证码字符个数
private String randomString = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWSYZ";
private final String sessionKey = "JCCODE";
//字体的设置
private Font getFont() {
return new Font("Times New Roman", Font.ROMAN_BASELINE, 40);
}
//颜色的设置
private static Color getRandomColor(int fc, int bc) {
fc = Math.min(fc, 255);
bc = Math.min(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 - 12);
return new Color(r, g, b);
}
//干扰线的绘制
private void drawLine(Graphics g) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(20);
int yl = random.nextInt(10);
g.drawLine(x, y, x + xl, y + yl);
}
//随机字符的获取
private String getRandomString(int num){
num = num > 0 ? num : randomString.length();
return String.valueOf(randomString.charAt(random.nextInt(num)));
}
//字符串的绘制
private String drawString(Graphics g, String randomStr, int i) {
g.setFont(getFont());
g.setColor(getRandomColor(108, 190));
//System.out.println(random.nextInt(randomString.length()));
String rand = getRandomString(random.nextInt(randomString.length()));
randomStr += rand;
g.translate(random.nextInt(3), random.nextInt(6));
g.drawString(rand, 40 * i + 10, 25);
return randomStr;
}
//生成随机图片
public void getRandomCodeImage(HttpServletRequest request, HttpServletResponse response){
HttpSession session = request.getSession();
// BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Graphics g = image.getGraphics();
g.fillRect(0, 0, width, height);
g.setColor(getRandomColor(105, 189));
g.setFont(getFont());
// 干扰线
for (int i = 0; i < lineSize; i++) {
drawLine(g);
}
// 随机字符
String randomStr = "";
for (int i = 0; i < randomStrNum; i++) {
randomStr = drawString(g, randomStr, i);
}
System.out.println("随机字符:"+randomStr);
g.dispose();
//移除之前的session中的验证码信息
session.removeAttribute(sessionKey);
//重新将验证码放入session
session.setAttribute(sessionKey, randomStr);
try {
// 将图片以png格式返回,返回的是图片
ImageIO.write(image, "PNG", response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
//生成随机图片的base64编码字符串
public String getRandomCodeBase64(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
// BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Graphics g = image.getGraphics();
g.fillRect(0, 0, width, height);
g.setColor(getRandomColor(105, 189));
g.setFont(getFont());
//干扰线
for (int i = 0; i < lineSize; i++) {
drawLine(g);
}
//随机字符
String randomStr = "";
for (int i = 0; i < randomStrNum; i++) {
randomStr = drawString(g, randomStr, i);
}
System.out.println("随机字符:"+randomStr);
g.dispose();
session.removeAttribute(sessionKey);
session.setAttribute(sessionKey, randomStr);
String base64String = "";
try {
// 直接返回图片
// ImageIO.write(image, "PNG", response.getOutputStream());
//返回 base64
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", bos);
byte[] bytes = bos.toByteArray();
Base64.Encoder encoder = Base64.getEncoder();
base64String = encoder.encodeToString(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return base64String;
}
}
2. 基于工具类的方法类
import com.example.votedemo.common.ValidateCodeUtil;
import com.google.common.util.concurrent.RateLimiter;
import io.swagger.annotations.ApiOperation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class ValidateCodeController {
//返回验证码图片
@GetMapping("/getCaptchaImg")
public void getCaptchaImg(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
try {
response.setContentType("image/png");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expire", "0");
response.setHeader("Pragma", "no-cache");
ValidateCodeUtil validateCode = new ValidateCodeUtil();
// getRandomCodeImage方法会直接将生成的验证码图片写入response
validateCode.getRandomCodeImage(request, response);
// System.out.println("session里面存储的验证码为:"+session.getAttribute("JCCODE"));
} catch (Exception e) {
e.printStackTrace();
}
}
// 验证码校验
@GetMapping("/checkCaptcha")
@ApiOperation("验证码校验接口")
public boolean getCheckCaptcha(@RequestParam("code") String code, HttpSession session) {
try {
//toLowerCase() 不区分大小写进行验证码校验
String sessionCode= String.valueOf(session.getAttribute("JCCODE")).toLowerCase();
System.out.println("session里的验证码:"+sessionCode);
String receivedCode=code.toLowerCase();
System.out.println("用户的验证码:"+receivedCode);
return !sessionCode.equals("") && !receivedCode.equals("") && sessionCode.equals(receivedCode);
} catch (Exception e) {
return false;
}
}
// 例如,创建一个每秒允许生成1个验证码的限流器
RateLimiter rateLimiter = RateLimiter.create(1.0); // 参数表示每秒令牌生成速率
// 生成验证码,返回的是 base64
@GetMapping("/getCaptchaBase64")
@ApiOperation("返回base64验证码接口")
public Object getCaptchaBase64(HttpServletRequest request, HttpServletResponse response) {
if (!rateLimiter.tryAcquire()) {
// 如果获取令牌失败(即超过限流阈值)
Map<String, String> result = new HashMap<>();
result.put("message", "请求过于频繁,请稍后再试");
return result;
}
Map result = new HashMap();
try {
response.setContentType("image/png");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Expire", "0");
response.setHeader("Pragma", "no-cache");
ValidateCodeUtil validateCode = new ValidateCodeUtil();
// 返回base64
String base64String = validateCode.getRandomCodeBase64(request, response);
result.put("url", "data:image/png;base64," + base64String);
result.put("message", "created successfull");
//http://tool.chinaz.com/tools/imgtobase/ base64直接转为图片网站
System.out.println("结果:" + result.get("url"));
} catch (Exception e) {
System.out.println(e);
}
return result;
}
}
3. 功能使用
@Autowired
ValidateCodeController validateCodeController;
@PostMapping("/login")
@Transactional
@ApiOperation("用户登录接口")
public R<String> login(@RequestBody LoginDTO loginDTO, HttpServletRequest request){
// 登录
// 先进行验证码校验
boolean captchaVerified = validateCodeController.getCheckCaptcha(loginDTO.getCaptcha(), request.getSession());
if (!captchaVerified) {
return R.error("验证码错误");
}
......