目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

使用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 email 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前六位 + "原始密码" + md3
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}

image.png

登录

 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		}

作者:Soulboy