文章目录
传统方式:
客户端发送一个密码明文,后端用md5加密明文,判断加密后的与数据库存放的密码是否一致
改进:客户端发送的时候将密码明文md5加密,后端再用md5加密一次,判断加密后的与数据库存放的密码是否一致
改进升级版:客户端发送的时候将密码明文和salt混起来再用md5加密,后端把发送过来的再加salt并用md5加密,判断加密后的与数据库存放的密码是否一致
在实际应用中,js-md5 库可以被用来生成 MD5 哈希值,这通常用于验证数据的完整性或在不需要高度安全性的场景下存储密码的哈希值(注意,由于 MD5 已知存在碰撞性漏洞,因此在需要高安全性的场景,如密码存储,推荐使用更安全的哈希算法,如 SHA-256
pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>seckill</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>seckill</name>
<description>seckill</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 工程创建需要引入的相关jar -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!--md5依赖-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.2</version>
<scope>compile</scope>
</dependency>
<!-- 引入validation依赖,完成校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.4.5</version>
</dependency>
<!--pool2 对象池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.9.0</version>
</dependency>
<!--引入hutool依赖-工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
LoginController.java
java
package com.example.controller;
import com.example.service.UserService;
import com.example.vo.LoginVo;
import com.example.vo.RespBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@RequestMapping("/login")
@Slf4j
public class LoginController {
//装配UserService
@Resource
private UserService userService;
//编写方法,可以进入到登录页面
@RequestMapping("/toLogin")
public String toLogin() {
return "login";//到templates/login.html
}
//处理用户登录请求
@RequestMapping("/doLogin")
@ResponseBody
public RespBean doLogin(LoginVo loginVo,
HttpServletRequest request,
HttpServletResponse response) {
//log.info("{}",loginVo);
return userService.doLogin(loginVo,request,response);
}
}
UserMapper.java
java
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.pojo.User;
public interface UserMapper extends BaseMapper<User> {
}
User.java
java
package com.example.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("seckill_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID,手机号码
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)//不是自增长的
private Long id;
private String nickname;
/**
* MD5(MD5(pass明文+固定salt1)+salt2)
*/
private String password;
private String salt;
/**
* 头像
*/
private String head;
/**
* 注册时间
*/
private Date registerDate;
/**
* 最后一次登录时间
*/
private Date lastLoginDate;
/**
* 登录次数
*/
private Integer loginCount;
}
UserServiceImpl.java
java
package com.example.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mapper.UserMapper;
import com.example.pojo.User;
import com.example.service.UserService;
import com.example.util.MD5Util;
import com.example.util.ValidatorUtil;
import com.example.vo.LoginVo;
import com.example.vo.RespBean;
import com.example.vo.RespBeanEnum;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Resource
private UserMapper userMapper;
@Override
public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
//接收到mobile和password[midPass]
String mobile = loginVo.getMobile();
String password = loginVo.getPassword();
//判断手机号/id 和密码是否为空
if(!StringUtils.hasText(mobile) || !StringUtils.hasText(password)) {
return RespBean.error(RespBeanEnum.LOGIN_ERROR);
}
//校验手机号码是否合格
if(!ValidatorUtil.isMobile(mobile)) {
return RespBean.error(RespBeanEnum.MOBILE_ERROR);
}
//查询DB,看看用户是否存在
User user = userMapper.selectById(mobile);
if(null == user) {//说明用户不存在
return RespBean.error(RespBeanEnum.LOGIN_ERROR);
}
//如果用户存在,则比对密码
//注意,从loginVo取出的密码是中间密码(即客户端经过一次加密加盐处理的密码)
if(!MD5Util.midPassToDBPass(password,user.getSalt()).equals(user.getPassword())){
return RespBean.error(RespBeanEnum.LOGIN_ERROR);
}
//登录成功
return RespBean.success();
}
}
UserService.java
java
package com.example.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.pojo.User;
import com.example.vo.LoginVo;
import com.example.vo.RespBean;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 传统方式 在接口中 定义方法/声明方法,然后在实现类中进行实现
* 在mybatis-plus中,我们可以继承父接口IService
* 这个IService接口声明很多方法,比如crud
* 如果默认提供方法不能满足需求,可以再声明需要的方法,然后在实现类中实现
*/
public interface UserService extends IService<User> {
//方法 完成用户的登录校验
RespBean doLogin(LoginVo loginVo, HttpServletRequest request,
HttpServletResponse response);
}
MD5Util.java
java
package com.example.util;
import org.apache.commons.codec.digest.DigestUtils;
/**
* 工具类,根据前面密码设计方案提供相应的方法
*/
public class MD5Util {
public static String md5(String src) {
return DigestUtils.md5Hex(src);
}
//前端使用的盐
private static final String SALT = "4tIY5VcX";
//加密加盐 md5(password明文+salt1)
public static String inputPassToMidPass(String inputPass) {
System.out.println("SALT.charAt(0)->"+SALT.charAt(0));
System.out.println("SALT.charAt(6)->"+SALT.charAt(6));
String str = SALT.charAt(0) + inputPass + SALT.charAt(6);
return md5(str);
}
//加密加盐 MidPass + salt2转成DB中的密码
//md5(md5(password明文+salt1)+salt2)
public static String midPassToDBPass(String midPass,String salt) {
System.out.println("SALT.charAt(1)->"+salt.charAt(1));
System.out.println("SALT.charAt(2)->"+salt.charAt(2));
String str = salt.charAt(1) + midPass + salt.charAt(2);
return md5(str);
}
//将password明文转成DB中的密码
public static String inputPassToDBPass(String inputPass,String salt){
String midPass = inputPassToMidPass(inputPass);
String dbPass = midPassToDBPass(midPass,salt);
return dbPass;
}
}
MD5UtilTest.java
java
package com.example.util;
import org.junit.jupiter.api.Test;
/**
* 测试MD5Util
*/
public class MD5UtilTest {
@Test
public void fun1(){
//密码明文"123456"
//获取到密码明文"123456"的中间密码[即客户端加密加盐后],在网络上传输的密码
//第一次加密加盐处理
//这个加密加盐会在客户端/浏览器完成
System.out.println(MD5Util.inputPassToMidPass("123456"));
//中间密码"d5c1b5290593325eebe18c9b7b146d31"的对应的DB密码
System.out.println(MD5Util.midPassToDBPass("2028ad83f1997056c7d60e16c36d10a7","tjAyuopC"));
//
//
// //密码明文 "123456"直接得到存放在DB密码
System.out.println(MD5Util.inputPassToDBPass("123456","tjAyuopC"));
}
}
ValidatorUtil.java
java
package com.example.util;
import org.junit.jupiter.api.Test;
import org.springframework.util.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 完成一些校验,比如手机号格式是否正确
* 正则
*/
public class ValidatorUtil {
//校验手机号码的正则表达式
private static final Pattern mobile_pattern = Pattern.compile("^[1][3-9][0-9]{9}$");
//编写方法
public static boolean isMobile(String mobile) {
if(!StringUtils.hasText(mobile)) {
return false;
}
Matcher matcher = mobile_pattern.matcher(mobile);
return matcher.matches();
}
//测试校验方法
@Test
public void t1() {
String mobile = "0133333333";
System.out.println(isMobile(mobile));
String mobile2 = "13333333333";
System.out.println(isMobile(mobile2));
}
}
LoginVo.java
java
package com.example.vo;
import lombok.Data;
/**
* 接收用户登录时,发送的信息(moblie,password)
*/
@Data
public class LoginVo {
private String mobile;
private String password;
}
RespBean.java
java
package com.example.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RespBean {
private long code;
private String message;
private Object obj;
//成功后 同时携带数据
public static RespBean success(Object data) {
return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBeanEnum.SUCCESS.getMessage(), data);
}
//成功后 不携带数据
public static RespBean success() {
return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBeanEnum.SUCCESS.getMessage(), null);
}
//失败 不携带数据
public static RespBean error(RespBeanEnum respBeanEnum) {
return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), null);
}
//失败 携带数据
public static RespBean error(RespBeanEnum respBeanEnum,Object data) {
return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), data);
}
}
RespBeanEnum.java
java
package com.example.vo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
/**
* 枚举类
*/
@Getter
@ToString
@AllArgsConstructor
public enum RespBeanEnum {
//通用
SUCCESS(200,"SUCCESS"),
ERROR(500,"服务端异常"),
//登录
LOGIN_ERROR(500210,"用户id或者密码错误"),
MOBILE_ERROR(500211,"手机号码格式不正确"),
MOBILE_NOT_EXIST(500213,"手机号码不存在");
private final Integer code;
private final String message;
}
SeckillApplication.java
java
package com.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.mapper")
public class SeckillApplication {
public static void main(String[] args) {
SpringApplication.run(SeckillApplication.class, args);
}
}
UserMapper.xml
java
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.example.pojo.User">
<id column="id" property="id"/>
<result column="nickname" property="nickname"/>
<result column="password" property="password"/>
<result column="salt" property="salt"/>
<result column="head" property="head"/>
<result column="register_date" property="registerDate"/>
<result column="last_login_date" property="lastLoginDate"/>
<result column="login_count" property="loginCount"/>
</resultMap>
</mapper>
login.html
html
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
<script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
<!-- md5.js -->
<script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
<!-- common.js -->
<script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>
<body>
<div class="container">
<div class="welcome">
<div class="pinkbox">
<!-- 登录表单 -->
<div class="signin">
<h1>用户登录</h1>
<form class="more-padding" id="loginForm" method="post" autocomplete="off">
<input style="background-color: darkgrey;" id="mobile" name="mobile" type="text" placeholder="手机号码"
required="true"/>
<input style="background-color: darkgrey;" id="password" name="password" type="password"
placeholder="密码" required="true"/>
<button class="button sumbit" type="button" onclick="login()">Login</button>
</form>
</div>
</div>
</div>
</div>
</body>
<script>
function login() {
doLogin();
}
function doLogin() {
//得到用户在登录表单填写的密码
var inputPass = $("#password").val();
//客户端盐
var salt = g_passsword_salt;
var str = "" + salt.charAt(0) + inputPass + salt.charAt(6);
var password = md5(str);
console.log("inputPass->", inputPass)
console.log("salt->", salt)
console.log("password->", password)
$.ajax({
url: "/login/doLogin",
type: "POST",
data: {
mobile: $("#mobile").val(),
password: password
},
success: function (data) {
if (data.code == 200) {
alert(data.message)
//如果code是200,说明登录成功., 就直接进入其他页面
//window.location.href=""
} else {
alert(data.message)
}
},
error: function () {
alert("失败");
}
});
}
</script>
</html>
application.yaml
yaml
spring:
thymeleaf:
#关闭缓存
cache: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 123456
# 数据库连接池
hikari:
#连接池名
pool-name: dd_Poll
#最小空闲连接
minimum-idle: 5
#空闲连接存活最大时间,默认60000(10分钟)
idle-timeout: 60000
# 最大连接数,默认是10
maximum-pool-size: 10
#从连接池返回来的连接自动提交
auto-commit: true
#连接最大存活时间。0表示永久存活,默认180000(30分钟)
max-lifetime: 180000
#连接超时时间,默认30000(30秒)
connection-timeout: 30000
#测试连接是否可用的查询语句
connection-test-query: select 1
#mybatis-plus配置
mybatis-plus:
#配置mapper.xml映射文件
mapper-locations: classpath*:/mapper/*Mapper.xml
#配置mybatis数据返回类型别名
type-aliases-package: com.example.pojo
#mybatis sql 打印
#logging:
# level:
# com.example.mapper: debug
#server:
# port: 9999
sql
sql
DROP TABLE IF EXISTS `seckill_user`;
CREATE TABLE `seckill_user` (
`id` BIGINT(20) NOT NULL COMMENT '用户ID,设为主键,唯一手机号',
`nickname` VARCHAR(255) NOT NULL DEFAULT '',
`password` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'MD5(MD5(pass明文+固定salt)+salt)',
`salt` VARCHAR(10) NOT NULL DEFAULT '',
`head` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '头像',
`register_date` DATETIME DEFAULT NULL COMMENT '注册时间',
`last_login_date` DATETIME DEFAULT NULL COMMENT '最后一次登录时间',
`login_count` INT(11) DEFAULT '0' COMMENT '登录次数',
PRIMARY KEY(`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;