一、理论部分

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. Java基础:变量、常量、作用域

    JAVA基础:变量.常量.作用域 变量:可以变化的量.Java是强类型语言,每个变量都必须声明类型. Java变量是程序中最基本的存储单元,要素包括变量名,类型和作用域. //类变量 static s ...

  2. JavaScript基础对象创建模式之沙盘模式(026)

    沙盘模式可以弥补命名空间模式中的两项不足之处: 使用唯一全局对象作为程序的全局变量入口,使得无法在同一程序中使用两个不同版本的API,因此它们使用的是同一个唯一的全局对象名,如MYAPP: 较长的嵌套 ...

  3. Docker文件系统实战

    关键词:Docker 联合文件系统 镜像 容器 云信私有化 在本文中,我们来实战构建一个Docker镜像,然后实例化容器,在Docker的生命周期中详细分析一下Docker的文件存储情况和Docker ...

  4. '%' For instance '%d'

    with each % indicating where one of the other (second, third, ...) arguments is to be substituted, a ...

  5. python数据结构与算法(一)

    1.序列中的N个元素赋值给变量 data = [1,2,3,"string!",["python","php"]] num_1,num_2, ...

  6. Python3笔记003 - 1.3 python开发工具

    第1章 认识python 1.3 python开发工具 IDLE(python自带的python shell) Pycharm(python开发的,选择专业版) 1.进入IDLE模式: C:\Prog ...

  7. java语言基础(九)_final_权限_内部类

    final关键字 final关键字代表最终.不可改变的. 常见四种用法: 可以用来修饰一个类 可以用来修饰一个方法 还可以用来修饰一个局部变量 还可以用来修饰一个成员变量 1)修饰一个类 public ...

  8. 听说你还不知道CompletableFuture?

    java8已经在日常开发编码中非常普遍了,掌握运用好它可以在开发中运用几行精简代码就可以完成所需功能.今天将介绍CompletableFuture的在生产环境如何使用实践.CompletableFut ...

  9. HTML5(八)Web Workers

    HTML 5 Web Workers web worker 是运行在后台的 JavaScript,不会影响页面的性能. 什么是 Web Worker? 当在 HTML 页面中执行脚本时,页面的状态是不 ...

  10. Git超详细用法,通俗易懂

    创建本地仓库 和 远端共享仓库 直接下载安装包:Git下载地址 安装 git,查看 git 版本,git version 配置项目的 git 账号 git config --global user.n ...