目录

Life in Flow

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

X

使用Hash加盐对Password加密

数据表

mis_user数据表是MIS系统的用户表,所有能登陆MIS系统的帐户都保存在这张表里面。首先你要明确:不是医院所有的工作人员都能登陆MIS系统。例如医护人员可以登录MIS系统,但是保安就不需要用MIS系统,所以我们在mis_user表中给需要使用MIS系统的用户创建帐户。

序号列名类型备注
1idINTEGER主键
2usernameVARCHAR用户名
3passwordVARCHAR密码
4nameVARCHAR姓名
5sexVARCHAR性别
6telVARCHAR电话(用于接收重置密码的短信验证码)
7emailVARCHAR邮箱(用于接收重置密码的邮件验证码)
8dept_idINTEGER隶属的部门ID
9jobVARCHAR职务(医生、护士等)
10ref_idINTEGER关联ID(例如医生ID,护士ID等)
11statusTINYINT1有效,2离职,3禁用
12create_timeDATE创建日期

使用哈希算法对密码加密

1. 哈希加密

用户的密码需要加密之后再保存到数据库中,这里我选择不可逆的哈希算法加密用户的密码。哈希算法有两种常见的实现方案:MD5和SHA。哪种安全性都挺高的,无法暴力破解。

2. 哈希字典反破解

虽然算法层面破解不了哈希算法,于是就有人另辟蹊径了。他把各种字符串文字都用哈希算法生成加密结果,俗称哈希字典。然后把要破解的哈希值,代入哈希字典,看看能跟哪个记录对得上,于是就得出原始数据是什么了。现在网上就有哈希字典可以下载,所以用哈希加密的结果很容被破解。
图片描述

3. 多次哈希加密

为了防御哈希字典破解,我们可以对数据做多次哈希加密。比如说第一次用MD5加密,然后再用SHA加密。虽然这么做也能起到一定的防破解作用。

图片描述

因为哈希值通常是16、32、64个字符组成,所以用哈希字典破解了SHA算法原始数据之后,黑客马上就能识别出来原始数据是用哈希加密过的,于是套用到哈希字典再解密一次。

4. 原始数据加盐混淆

如果我们混淆了原始数据,那么即便黑客破解了原始数据也无法使用。比如说我们对用户原始密码生成哈希值,用哈希值前六位和后三位字符,与原始密码拼接,然后再用哈希算法生成加密结果。即便黑客破解了原始数据,但是这个原始数据并不是用户的密码,黑客用这个混淆过的密码字符串是无法登陆系统的。
图片描述

String md5 = MD5("原始密码")
String temp = md前六位 + "原始密码" + md后3位
temp = MD5(temp)

当然了还有更狠的混淆做法。例如把先原始密码字符顺序颠倒,然后在每个字符之间插入哈希值的某两个字符。黑客面对这样的混淆结果非常挠头,根本猜不到混淆的字符串哪里是原始密码。

DAO层

<?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.hospital.api.db.dao.MisUserDao">
    <select id="searchUserPermissions" parameterType="int" resultType="String">
        SELECT p."permission_code" AS "permission"
        FROM HOSPITAL.MIS_USER u
                 JOIN HOSPITAL.MIS_USER_ROLE ur ON u."id" = ur."user_id"
                 JOIN HOSPITAL.MIS_ROLE_PERMISSION rp ON rp."role_id" = ur."role_id"
                 JOIN HOSPITAL.MIS_PERMISSION p ON rp."permission_id" = p."id"
        WHERE u."id" = ${userId}
    </select>
    <select id="login" parameterType="Map" resultType="Integer">
        SELECT "id"
        FROM HOSPITAL.MIS_USER
        WHERE "username" = #{username}
            AND "password" = #{password}
    </select>
</mapper>

Service层

如果我们混淆了原始数据,那么即便黑客破解了原始数据也无法使用。比如说我们对用户原始密码生成哈希值,用哈希值前六位和后三位字符,与原始密码拼接,然后再用哈希算法生成加密结果。即便黑客破解了原始数据,但是这个原始数据并不是用户的密码,黑客用这个混淆过的密码字符串是无法登陆系统的

package com.example.hospital.api.service.impl;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import com.example.hospital.api.db.dao.MisUserDao;
import com.example.hospital.api.service.MisUserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Map;
@Service
public class MisUserServiceImpl implements MisUserService {
    @Resource
    private MisUserDao misUserDao;

    @Override
    public Integer login(Map param) {
        String username = MapUtil.getStr(param, "username");
        String password = MapUtil.getStr(param, "password");
        MD5 md5 = MD5.create();
        //用户名的MD5值
        String tempName = md5.digestHex(username);
        //提取用户名的MD5值前六位
        String tempStart = StrUtil.subWithLength(tempName, 0, 6);
        //提取用户名的MD5值后三位
        String tempEnd = StrUtil.subSuf(tempName, tempName.length() - 3);
        password = md5.digestHex(tempStart + password + tempEnd);
        //替换password为最新MD5值
        param.replace("password", password);
        Integer userId = misUserDao.login(param);
        return userId;
    }
}

当然了还有更狠的混淆做法。例如把先原始密码字符顺序颠倒,然后在每个字符之间插入哈希值的某两个字符。黑客面对这样的混淆结果非常挠头,根本猜不到混淆的字符串哪里是原始密码

Controller层

LoginForm

package com.example.hospital.api.controller.form;

import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

@Data
public class LoginForm {
    @NotBlank(message = "username不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{5,50}$", message = "username内容不正确")
    private String username;

    @NotBlank(message = "password不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{5,50}$", message = "password内容不正确")
    private String password;
}

MisUserController

package com.example.hospital.api.controller.form;

import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import com.example.hospital.api.common.R;
import com.example.hospital.api.service.MisUserService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/mis_user")
public class MisUserController {
    @Resource
    private MisUserService misUserService;

    @PostMapping("/login")
    public R login(@RequestBody @Valid LoginForm form) {
        // 把form转换成Map
        Map param = BeanUtil.beanToMap(form);
        Integer userId = misUserService.login(param);
        //登陆成功
        if (userId != null) {
            //颁发令牌
            StpUtil.login(userId);
            String token = StpUtil.getTokenValue();
            List<String> permissions = StpUtil.getPermissionList();
            return R.ok().put("result", true).
                    put("token", token).
                    put("permissions", permissions);
        }
        return R.ok().put("result", false);
    }

    @GetMapping("/logout")
    @SaCheckLogin
    public R logout() {     
        //删除redis缓存token,会从http请求头中提取token,返回空R对象
        StpUtil.logout();
        return R.ok();
    }
}

image.png

登录

<script>
import { isUsername, isPassword } from '../utils/validate.js';
import router from '../router/index.js';
export default {
    data: function() {
        return {
            username: null,
            password: null,
            qrCodeVisible: false,
            qrCode: '',
            uuid: null,
            qrCodeTimer: null,
            loginTimer: null
        };
    },

    methods: {
		login:function(){
			let that=this
			//校验表单输入数据的格式正否正确
			if(!isUsername(that.username)){
				ElMessage({
					message: '用户名格式不正确',
					type: 'error',
					duration: 1200
				});
			}
			else if(!isPassword(that.password)){
				ElMessage({
					message: '密码格式不正确',
					type: 'error',
					duration: 1200
				});
			}
			else{
				let data = { username: that.username, password: that.password};
				that.$http("/mis_user/login","POST",data,true,function(resp){
					if(resp.result){
						let permissions=resp.permissions
						let token=resp.token
						//将返回数据写入localStorage
						localStorage.setItem("permissions",permissions)
						localStorage.setItem("token",token)
						router.push({name:"Home"})
					}
					else{
						ElMessage({
							message: '登录失败',
							type: 'error',
							duration: 1200
						});
					}
				})
			}
		}
    }
};
</script>

登出

logout:function(){
			let that=this
			that.$http('/mis_user/logout',"GET",null,true,function(resp){
				localStorage.removeItem("permissions")
				localStorage.removeItem("token")
				that.$router.push({name:'Login'})
			})
		}

作者:Soulboy