使用Hash加盐对Password加密
数据表
mis_user
数据表是 MIS 系统的用户表,所有能登陆 MIS 系统的帐户都保存在这张表里面。首先你要明确:不是医院所有的工作人员都能登陆 MIS 系统。例如医护人员可以登录 MIS 系统,但是保安就不需要用 MIS 系统,所以我们在 mis_user
表中给需要使用 MIS 系统的用户创建帐户。
序号 | 列名 | 类型 | 备注 |
---|---|---|---|
1 | id | INTEGER | 主键 |
2 | username | VARCHAR | 用户名 |
3 | password | VARCHAR | 密码 |
4 | name | VARCHAR | 姓名 |
5 | sex | VARCHAR | 性别 |
6 | tel | VARCHAR | 电话(用于接收重置密码的短信验证码) |
7 | VARCHAR | 邮箱(用于接收重置密码的邮件验证码) | |
8 | dept_id | INTEGER | 隶属的部门 ID |
9 | job | VARCHAR | 职务(医生、护士等) |
10 | ref_id | INTEGER | 关联 ID(例如医生 ID,护士 ID 等) |
11 | status | TINYINT | 1 有效,2 离职,3 禁用 |
12 | create_time | DATE | 创建日期 |
使用哈希算法对密码加密
1. 哈希加密
用户的密码需要加密之后再保存到数据库中,这里我选择不可逆的哈希算法加密用户的密码。哈希算法有两种常见的实现方案:MD5 和 SHA。哪种安全性都挺高的,无法暴力破解。
2. 哈希字典反破解
虽然算法层面破解不了哈希算法,于是就有人另辟蹊径了。他把各种字符串文字都用哈希算法生成加密结果,俗称哈希字典。然后把要破解的哈希值,代入哈希字典,看看能跟哪个记录对得上,于是就得出原始数据是什么了。现在网上就有哈希字典可以下载,所以用哈希加密的结果很容被破解。
3. 多次哈希加密
为了防御哈希字典破解,我们可以对数据做多次哈希加密。比如说第一次用 MD5 加密,然后再用 SHA 加密。虽然这么做也能起到一定的防破解作用。
因为哈希值通常是 16、32、64 个字符组成,所以用哈希字典破解了 SHA 算法原始数据之后,黑客马上就能识别出来原始数据是用哈希加密过的,于是套用到哈希字典再解密一次。
4. 原始数据加盐混淆
如果我们混淆了原始数据,那么即便黑客破解了原始数据也无法使用。比如说我们对用户原始密码生成哈希值,用哈希值前六位和后三位字符,与原始密码拼接,然后再用哈希算法生成加密结果。即便黑客破解了原始数据,但是这个原始数据并不是用户的密码,黑客用这个混淆过的密码字符串是无法登陆系统的。
1String md5 = MD5("原始密码")
2String temp = md前六位 + "原始密码" + md后3位
3temp = MD5(temp)
当然了还有更狠的混淆做法。例如把先原始密码字符顺序颠倒,然后在每个字符之间插入哈希值的某两个字符。黑客面对这样的混淆结果非常挠头,根本猜不到混淆的字符串哪里是原始密码。
DAO 层
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper
3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5<mapper namespace="com.example.hospital.api.db.dao.MisUserDao">
6 <select id="searchUserPermissions" parameterType="int" resultType="String">
7 SELECT p."permission_code" AS "permission"
8 FROM HOSPITAL.MIS_USER u
9 JOIN HOSPITAL.MIS_USER_ROLE ur ON u."id" = ur."user_id"
10 JOIN HOSPITAL.MIS_ROLE_PERMISSION rp ON rp."role_id" = ur."role_id"
11 JOIN HOSPITAL.MIS_PERMISSION p ON rp."permission_id" = p."id"
12 WHERE u."id" = ${userId}
13 </select>
14 <select id="login" parameterType="Map" resultType="Integer">
15 SELECT "id"
16 FROM HOSPITAL.MIS_USER
17 WHERE "username" = #{username}
18 AND "password" = #{password}
19 </select>
20</mapper>
Service 层
如果我们混淆了原始数据,那么即便黑客破解了原始数据也无法使用。比如说我们对用户原始密码生成哈希值,用哈希值前六位和后三位字符,与原始密码拼接,然后再用哈希算法生成加密结果。即便黑客破解了原始数据,但是这个原始数据并不是用户的密码,黑客用这个混淆过的密码字符串是无法登陆系统的
1package com.example.hospital.api.service.impl;
2
3import cn.hutool.core.map.MapUtil;
4import cn.hutool.core.util.StrUtil;
5import cn.hutool.crypto.digest.MD5;
6import com.example.hospital.api.db.dao.MisUserDao;
7import com.example.hospital.api.service.MisUserService;
8import org.springframework.stereotype.Service;
9
10import javax.annotation.Resource;
11import java.util.Map;
12@Service
13public class MisUserServiceImpl implements MisUserService {
14 @Resource
15 private MisUserDao misUserDao;
16
17 @Override
18 public Integer login(Map param) {
19 String username = MapUtil.getStr(param, "username");
20 String password = MapUtil.getStr(param, "password");
21 MD5 md5 = MD5.create();
22 //用户名的MD5值
23 String tempName = md5.digestHex(username);
24 //提取用户名的MD5值前六位
25 String tempStart = StrUtil.subWithLength(tempName, 0, 6);
26 //提取用户名的MD5值后三位
27 String tempEnd = StrUtil.subSuf(tempName, tempName.length() - 3);
28 password = md5.digestHex(tempStart + password + tempEnd);
29 //替换password为最新MD5值
30 param.replace("password", password);
31 Integer userId = misUserDao.login(param);
32 return userId;
33 }
34}
当然了还有更狠的混淆做法。例如把先原始密码字符顺序颠倒,然后在每个字符之间插入哈希值的某两个字符。黑客面对这样的混淆结果非常挠头,根本猜不到混淆的字符串哪里是原始密码
Controller 层
LoginForm
1package com.example.hospital.api.controller.form;
2
3import lombok.Data;
4import javax.validation.constraints.NotBlank;
5import javax.validation.constraints.Pattern;
6
7@Data
8public class LoginForm {
9 @NotBlank(message = "username不能为空")
10 @Pattern(regexp = "^[a-zA-Z0-9]{5,50}$", message = "username内容不正确")
11 private String username;
12
13 @NotBlank(message = "password不能为空")
14 @Pattern(regexp = "^[a-zA-Z0-9]{5,50}$", message = "password内容不正确")
15 private String password;
16}
MisUserController
1package com.example.hospital.api.controller.form;
2
3import cn.dev33.satoken.annotation.SaCheckLogin;
4import cn.dev33.satoken.stp.StpUtil;
5import cn.hutool.core.bean.BeanUtil;
6import com.example.hospital.api.common.R;
7import com.example.hospital.api.service.MisUserService;
8import org.springframework.web.bind.annotation.*;
9
10import javax.annotation.Resource;
11import javax.validation.Valid;
12import java.util.List;
13import java.util.Map;
14
15@RestController
16@RequestMapping("/mis_user")
17public class MisUserController {
18 @Resource
19 private MisUserService misUserService;
20
21 @PostMapping("/login")
22 public R login(@RequestBody @Valid LoginForm form) {
23 // 把form转换成Map
24 Map param = BeanUtil.beanToMap(form);
25 Integer userId = misUserService.login(param);
26 //登陆成功
27 if (userId != null) {
28 //颁发令牌
29 StpUtil.login(userId);
30 String token = StpUtil.getTokenValue();
31 List<String> permissions = StpUtil.getPermissionList();
32 return R.ok().put("result", true).
33 put("token", token).
34 put("permissions", permissions);
35 }
36 return R.ok().put("result", false);
37 }
38
39 @GetMapping("/logout")
40 @SaCheckLogin
41 public R logout() {
42 //删除redis缓存token,会从http请求头中提取token,返回空R对象
43 StpUtil.logout();
44 return R.ok();
45 }
46}
登录
1<script>
2import { isUsername, isPassword } from '../utils/validate.js';
3import router from '../router/index.js';
4export default {
5 data: function() {
6 return {
7 username: null,
8 password: null,
9 qrCodeVisible: false,
10 qrCode: '',
11 uuid: null,
12 qrCodeTimer: null,
13 loginTimer: null
14 };
15 },
16
17 methods: {
18 login:function(){
19 let that=this
20 //校验表单输入数据的格式正否正确
21 if(!isUsername(that.username)){
22 ElMessage({
23 message: '用户名格式不正确',
24 type: 'error',
25 duration: 1200
26 });
27 }
28 else if(!isPassword(that.password)){
29 ElMessage({
30 message: '密码格式不正确',
31 type: 'error',
32 duration: 1200
33 });
34 }
35 else{
36 let data = { username: that.username, password: that.password};
37 that.$http("/mis_user/login","POST",data,true,function(resp){
38 if(resp.result){
39 let permissions=resp.permissions
40 let token=resp.token
41 //将返回数据写入localStorage
42 localStorage.setItem("permissions",permissions)
43 localStorage.setItem("token",token)
44 router.push({name:"Home"})
45 }
46 else{
47 ElMessage({
48 message: '登录失败',
49 type: 'error',
50 duration: 1200
51 });
52 }
53 })
54 }
55 }
56 }
57};
58</script>
登出
1logout:function(){
2 let that=this
3 that.$http('/mis_user/logout',"GET",null,true,function(resp){
4 localStorage.removeItem("permissions")
5 localStorage.removeItem("token")
6 that.$router.push({name:'Login'})
7 })
8 }