一、理论部分

1、为什么要给密码加盐

我们在数据库中存入的密码一般不会是明文,都要通加MD5加密后存入,但是有些简单的密码加密后存入数据库也不安全,所有我们采用密码+盐再进行MD5加密存入数据库中。

数据存储形式如下:

mysql> select * from User;
+----------+----------------------------+----------------------------------+
| UserName | Salt | PwdHash |
+----------+----------------------------+----------------------------------+
| lichao | 1ck12b13k1jmjxrg1h0129h2lj | 6c22ef52be70e11b6f3bcf0f672c96ce |
| akasuna | 1h029kh2lj11jmjxrg13k1c12b | 7128f587d88d6686974d6ef57c193628 |

密码盐Salt 可以是任意字母、数字、或是字母或数字的组合,但必须是随机产生的,每个用户的 Salt 都不一样,

用户注册的时候,数据库中存入的不是明文密码,也不是简单的对明文密码进行散列,而是 MD5( 明文密码 + Salt),

也就是说,当用户登陆的时候,同样用这种算法验证。

MD5('' + '1ck12b13k1jmjxrg1h0129h2lj') = '6c22ef52be70e11b6f3bcf0f672c96ce'
MD5('' + '1h029kh2lj11jmjxrg13k1c12b') = '7128f587d88d6686974d6ef57c193628'

由于加了 Salt,即便数据库泄露了,但是由于密码都是加了 Salt 之后的散列,坏人们的数据字典已经无法直接匹配,明文密码被破解出来的概率也大大降低。

2、为什么要加随机数

当我们在浏览器中输入密码后,虽然这个密码被加密了,但要是被别人侦听到了,用同样的密码去请求还是会截获到请求的数据。

此时我们就需要针对不同的用户生成随机数,再给密码加密。然后后台再通过这个随机数进行解密。

二、实践

1、这里我们用的.NetCore MVC的形式,通过一个登录页面的方法我们进行登录页面,要进入登录的控制器中会生成一个随机数,将这个随机数存到session中,并将这个随机数返回到前台

private const string R_KEY = "R_KEY";
public IActionResult LoginIndex()
{
string r = EncryptorHelper.GetMD5(Guid.NewGuid().ToString());
HttpContext.Session.SetString(R_KEY, r);
LoginModel loginModel = new LoginModel() { R = r };
return View(loginModel);
}

loginMode是一个返回到页面的强类型视图

   public class LoginModel
{
/// <summary>
/// 账号
/// </summary>
[Required(ErrorMessage = "请输入账号")]
public string Account { get; set; } /// <summary>
/// 密码
/// </summary>
[Required(ErrorMessage = "请输入密码")]
public string Password { get; set; } /// <summary>
///
/// </summary>
public string R { get; set; }
}

2、前台通过隐藏标签来存这个随机数,还有展示密码和用户名输入框。

    <form asp-route="adminLogin" method="post">
<input type="hidden" id="r_random" value="@Model.R" />
<fieldset>
<label class="block clearfix">
<span class="block input-icon input-icon-right">
@Html.TextBoxFor(m => m.Account, new { @class = "form-control", placeholder = "用户名" })
<i class="ace-icon fa fa-user"></i>
</span>
</label> <label class="block clearfix">
<span class="block input-icon input-icon-right">
@Html.PasswordFor(m => m.Password, new { @class = "form-control", placeholder = "密码" })
<i class="ace-icon fa fa-lock"></i>
</span>
</label> <div class="space"></div> <div class="clearfix">
<label class="inline">
<input type="checkbox" id="RememberMe" name="RememberMe" value="true" class="ace" />
<span class="lbl"> 记住我</span>
</label>
<button type="button" id="myButton" data-loading-text="登录中..." class="width-35 pull-right btn btn-sm btn-primary">
<i class="ace-icon fa fa-key"></i>
<span class="bigger-110">登录</span>
</button>
</div>
<div class="space-4"></div>
</fieldset>
</form>

3、用户输入用户名和密码后点击登录首先会去数据中查这个用户的密码盐,这里前台页面已经有了用户输入的密码、随机数和密码盐,这里就可以对密码时行加密后传输了,代码如下

$(function () {
$('#myButton').click(function () {
if ($('form').valid()) {
var account = $('#Account').val();
var password = $('#Password').val();
var r = $('#r_random').val();
$.get('@Url.RouteUrl("getSalt")?account=' + account, function (salt) {
password = $.md5(password + salt);
password = $.md5(password + r);
$.post('@Url.RouteUrl("adminLogin")', { "Account": account, "Password": password }, function (data) {
if (data.status) {
$('#error_msg').html('登陆成功,正在进入系统...');
window.location.href = '@Url.RouteUrl("mainIndex")';
} else {
$('#error_msg').html(data.message);
}
}) });
}
}); });

4、数据提交到后台再进行处理

[HttpPost]
[Route("login")]
public IActionResult LoginIndex(LoginModel model)
{
string r = HttpContext.Session.GetString(R_KEY);
r = r ?? "";
if (!ModelState.IsValid)
{
AjaxData.Message = "请输入用户账号和密码";
return Json(AjaxData);
}
var result = _sysUserService.validateUser(model.Account, model.Password, r);
AjaxData.Status = result.Item1;
AjaxData.Message = result.Item2;
if (result.Item1)
{
_authenticationService.signIn(result.Item3, result.Item4.Name);
}
return Json(AjaxData);
}
如果登录信息没有问题我们会调用_authenticationService.signIn方法来保存登录状态,也就是将token信息和用户名信息存入:
   /// <summary>
/// 保存等状态
/// </summary>
/// <param name="token"></param>
/// <param name="name"></param>
public void signIn(string token, string name)
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(ClaimTypes.Sid, token));
claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, name));
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
_httpContextAccessor.HttpContext.SignInAsync(CookieAdminAuthInfo.AuthenticationScheme, claimsPrincipal); }
在 _sysUserService.validateUser方法中我们将用户名密码还有随机数再次传入,验证登录状态,在这个方法中我们校验了用户是否被锁,用户登录日志记录、登录成功后写入token表和密码的匹配,

在进行密码匹配时我们将用户数据库中的密码和随机数进行MD5加密后与用户传入的密码进行匹配。代码如下:

  /// <summary>
/// 验证登录状态
/// </summary>
/// <param name="account">登录账号</param>
/// <param name="password">登录密码</param>
/// <param name="r">登录随机数</param>
/// <returns></returns>
public (bool Status, string Message, string Token, SysUser User) validateUser(string account, string password, string r)
{
var user = getByAccount(account);
if (user == null)
return (false, "用户名或密码错误", null, null);
if (!user.Enabled)
return (false, "你的账号已被冻结", null, null); if (user.LoginLock)
{
if (user.AllowLoginTime > DateTime.Now)
{
return (false, "账号已被锁定" + ((int)(user.AllowLoginTime - DateTime.Now).Value.TotalMinutes + ) + "分钟。", null, null);
}
} var md5Password = EncryptorHelper.GetMD5(user.Password + r);
//匹配密码
if (password.Equals(md5Password, StringComparison.InvariantCultureIgnoreCase))
{
user.LoginLock = false;
user.LoginFailedNum = ;
user.AllowLoginTime = null;
user.LastLoginTime = DateTime.Now;
user.LastIpAddress = ""; //登录日志
user.SysUserLoginLogs.Add(new SysUserLoginLog()
{
Id = Guid.NewGuid(),
IpAddress = "",
LoginTime = DateTime.Now,
Message = "登录:成功"
});
//单点登录,移除旧的登录token var userToken = new SysUserToken()
{
Id = Guid.NewGuid(),
ExpireTime = DateTime.Now.AddDays()
};
user.SysUserTokens.Add(userToken);
_sysUserRepository.DbContext.SaveChanges();
return (true, "登录成功", userToken.Id.ToString(), user);
}
else
{
//登录日志
user.SysUserLoginLogs.Add(new SysUserLoginLog()
{
Id = Guid.NewGuid(),
IpAddress = "",
LoginTime = DateTime.Now,
Message = "登录:密码错误"
});
user.LoginFailedNum++;
if (user.LoginFailedNum > )
{
user.LoginLock = true;
user.AllowLoginTime = DateTime.Now.AddHours();
}
_sysUserRepository.DbContext.SaveChanges();
}
return (false, "用户名或密码错误", null, null);
}

5、如果后台登录验证都通过了我们会返回到登录首页,在第3步时  window.location.href = '@Url.RouteUrl("mainIndex")';

当然在进入这个首页时会进行用户身份校验,我们把这个校验写在方法过滤器中吧,只要把这个过滤器标签的都需求进行校验用户登录信息,如果没有用户信息就返回到登录首页面。代码如下:

  /// <summary>
/// 登录状态过滤器
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class AdminAuthFilter : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context)
{ } /// <summary>
///
/// </summary>
/// <param name="context"></param>
public void OnResourceExecuting(ResourceExecutingContext context)
{
var _adminAuthService = EnginContext.Current.Resolve<IAdminAuthService>();
var user = _adminAuthService.getCurrentUser();
if (user == null || !user.Enabled)
context.Result = new RedirectToRouteResult("adminLogin", new { returnUrl = context.HttpContext.Request.Path });
}
}
_adminAuthService.getCurrentUser(),在这个方法中我们拿到进求过来的tokenid,代码如下:
 /// <summary>
/// 获取当前登录用户
/// </summary>
/// <returns></returns>
public SysUser getCurrentUser()
{
var result = _httpContextAccessor.HttpContext.AuthenticateAsync(CookieAdminAuthInfo.AuthenticationScheme).Result;
if (result.Principal == null)
return null;
var token = result.Principal.FindFirstValue(ClaimTypes.Sid);
return _sysUserService.getLogged(token ?? "");
}

拿到tokenId值后会调用_sysUserService.getLogged方法,在这个方法中我们通过tokenId获取到了token.通过token获取到了用户信息,再将用户信息返回,并将token信息写入到缓存中

 /// <summary>
/// 通过当前登录用户的token 获取用户信息,并缓存
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public SysUser getLogged(string token)
{
SysUserToken userToken = null;
SysUser sysUser = null; _memoryCache.TryGetValue<SysUserToken>(token, out userToken);
if (userToken!=null)
{
_memoryCache.TryGetValue(String.Format(MODEL_KEY, userToken.SysUserId), out sysUser);
}
if (sysUser != null)
return sysUser; Guid tokenId = Guid.Empty; if (Guid.TryParse(token, out tokenId))
{
var tokenItem = _sysUserTokenRepository.Table.Include(x => x.SysUser)
.FirstOrDefault(o => o.Id == tokenId);
if (tokenItem != null)
{
_memoryCache.Set(token, tokenItem, DateTimeOffset.Now.AddHours());
//缓存
_memoryCache.Set(String.Format(MODEL_KEY, tokenItem.SysUserId), tokenItem.SysUser, DateTimeOffset.Now.AddHours());
return tokenItem.SysUser;
}
}
return null;
}

校验通过后会将主页呈现给用户。

6、用户登出的代码如下:

/// <summary>
/// 退出登录
/// </summary>
public void signOut()
{
_httpContextAccessor.HttpContext.SignOutAsync(CookieAdminAuthInfo.AuthenticationScheme);
}

到此,整个登录模块就完成了。

打个广告:如果你喜欢这篇文章的话,有需求微信大量投票或点赞的朋友可以给我介绍哦,QQ:3282079595。

 

.NetCore 登录(密码盐+随机数)的更多相关文章

  1. HTTP协议下保证登录密码不被获取最健壮方式

    原文:http://www.cnblogs.com/intsmaze/p/6009648.html HTTP协议下保证登录密码不被获取最健壮方式   说到在http协议下用户登录如何保证密码安全这个问 ...

  2. C# 密码盐码加密

    每次新建账号密码的时候都需要获取一下新的盐码,之后用使用MD5为用户密码加密 /// <summary> /// 获取新的密码盐码 /// </summary> /// < ...

  3. 利用 John the Ripper 破解用户登录密码

    一.什么是 John the Ripper ? 看到这个标题,想必大家都很好奇,John the Ripper 是个什么东西呢?如果直译其名字的话就是: John 的撕裂者(工具). 相比大家都会觉得 ...

  4. MySQL 用户登录密码和远程登录权限问题

    1.mysql数据库,忘记root用户登录密码. 解决如下: a.重置密码 #/etc/init.d/mysqld stop #mysqld_safe --user=mysql --skip-gran ...

  5. 家庭路由器设置以及win10链接无线不显示登录密码 直接提示链接出错问题解决

    家庭路由器设置 网线插入WAN口,用网客户端接在LAN口,就是路由器模式 LAN→WAN设置:电脑→第二个路由器LAN→进入设置界面: 网络参数→WAN口设置→WAN口连接类型→动态IP→保存. 网络 ...

  6. WordPress忘记登录密码

    后台的登录密码使用的是md5加密的,有时候会忘记登录密码,那么可以修改数据库,把密码改为你知道的字符串的md5加密值 如 hello对应的md5加密值为:5d41402abc4b2a76b9719d9 ...

  7. 使用BCrypt算法加密存储登录密码用法及好处

    //导入import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** *使用BCrypt算法加密存储登录密码 ...

  8. win10怎么取消登录密码

    win10安装后每次登录都需要输入密码,挺烦的,查了下,原来windows10无密码登录设置挺方便. 1. 按下win+x组合键,如下图所示 2. 在弹出菜单选择运行,如下图所示 或者直接按win+r ...

  9. Client默认用户及登录密码(转)

    Client默认用户及登录密码 SAP系统(如ERP.CRM等)安装完成,初始化状态下有若干个客户端(Client).如果是生产系统,一般只有000.001.066等三个Client:如果是IDES系 ...

随机推荐

  1. 如何使用PL/SQL工具批量导出表、存储过程、序列

    PLSQL作为Oracle数据库进行操作常用工具,可以很方便的对表以及数据进行处理.工作中如果遇到数据库转移,需要将老数据库中的建表.建序列和存储过程语句导出,然后导入到新的数据库中这样序列号会自动, ...

  2. 【Spring注解驱动开发】如何使用@Value注解为bean的属性赋值,我们一起吊打面试官!

    写在前面 在之前的文章中,我们探讨了如何向Spring的IOC容器中注册bean组件,讲解了有关bean组件的生命周期的知识.今天,我们就来一起聊聊@Value注解的用法. 项目工程源码已经提交到Gi ...

  3. 自描述C++部分面试题集

    1.谈谈啥叫对象成员以及对象成员的构造函数调用调用方式. 在类中定义的数据成员一般都是基本的数据类型.但是类中的成员也可以是对象,叫做对象成员. C++中对对象的初始化时非常重要的操作,当创建一个对象 ...

  4. 从浏览器地址栏输入url到显示页面的步骤(以HTTP为例)

    在浏览器地址栏输入URL 浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤 如果资源未缓存,发起新请求 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证. 检验 ...

  5. ShuffleNetV1/V2简述 | 轻量级网络

    ShuffleNet系列是轻量级网络中很重要的一个系列,ShuffleNetV1提出了channel shuffle操作,使得网络可以尽情地使用分组卷积来加速,而ShuffleNetV2则推倒V1的大 ...

  6. 洛谷 P4042 [AHOI2014/JSOI2014]骑士游戏

    题意 有\(n\)个怪物,可以消耗\(k\)的代价消灭一个怪物或者消耗\(s\)的代价将它变成另外一个或多个新的怪物,求消灭怪物$的最小代价 思路 \(DP\)+最短路 这几天做的第一道自己能\(yy ...

  7. 区间dp(能量项链)

    [题目大意] 在Mars星球上,每个Mars人都随身佩带着一串能量项链.在项链上有N颗能量珠.能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数.并且,对于相邻的两颗珠子,前一颗珠子的尾标记 ...

  8. SCSS笔记

    SASS是成熟,稳定,强大的 CSS预处理器 ,而 SCSS 是SASS3版本当中引入的新语法特性,完全兼容CSS3的同时继承了CSS强大的动态功能. CSS书写代码规模较大的web应用时,容易造成选 ...

  9. 通过注入DLL后使用热补丁钩取API

    通过注入DLL后使用热补丁钩取API 0x00 对比修改API的前五个字节钩取API 对前一种方法钩取API的流程梳理如下: 注入相应的DLL 修改原始AI的函数的前五个字节跳往新函数(钩取API) ...

  10. Centos8 - 图形界面和命令行切换

    查看目前默认的启动方式 systemctl get-default 命令行模式:multi-user.target 图形界面模式:graphical.target 设置为图形界面模式 systemct ...