完成用户登录、登出、修改密码

This commit is contained in:
falcon 2020-05-12 11:24:56 +08:00
parent b8e59f524a
commit 791f7d48f3
24 changed files with 456 additions and 41 deletions

View File

@ -1,25 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using FAuth.Models;
namespace FAuth.Controllers
{
/// <summary>
/// 用户控制器
/// </summary>
public class AppController:ControllerBase<AppController>
{
public AppController(ILogger<AppController> logger,IServiceProvider service) : base(logger,service) {
}
public IActionResult Index() {
return PartialView();
}
}
}

View File

@ -1,9 +1,11 @@
using System;
using Falcon.Extend;
using FAuth.DataBase;
using FAuth.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace FAuth.Controllers
{
@ -29,6 +31,10 @@ namespace FAuth.Controllers
/// 数据缓冲
/// </summary>
public ICacheProvider Cache { get; set; }
/// <summary>
/// Cookie中定义的键
/// </summary>
public CookieKeyDefine CookieKeys { get; set; }
/// <summary>
/// 通过日志组件和服务集合生成控制器
@ -40,6 +46,7 @@ namespace FAuth.Controllers
this.Services = service;
this.Db = service.GetService<FAuthDb>();
this.Cache = service.GetService<ICacheProvider>();
this.CookieKeys = Services.GetService<IOptions<CookieKeyDefine>>().Value;
}
}

View File

@ -1,8 +1,9 @@
using System;
using System.Threading.Tasks;
using FAuth.Extensions;
using FAuth.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
namespace FAuth.Controllers.api
@ -10,7 +11,9 @@ namespace FAuth.Controllers.api
/// <summary>
/// api控制器基类
/// </summary>
[ApiController, Route("api/[Controller]/[Action]")]
[Area("api")]
//[ApiController]
[Route("api/[Controller]/[Action]")]
[ServiceFilter(typeof(ApiExceptionFilterAttribute))]
[ProducesResponseType(typeof(ApiErrorResult),500)]
[ProducesResponseType(typeof(ApiErrorResult),400)]
@ -18,5 +21,9 @@ namespace FAuth.Controllers.api
{
public ApiControllerBase(ILogger<LoggerType> logger,IServiceProvider service) : base(logger,service) {
}
//public override Task OnActionExecutionAsync(ActionExecutingContext context,ActionExecutionDelegate next) {
// return base.OnActionExecutionAsync(context,next);
//}
}
}

View File

@ -1,14 +1,14 @@
using System;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc;
using Falcon.Extend;
using FAuth.DataBase;
using System.Collections.Generic;
using System.Linq;
using Falcon.Extend;
using FAuth.DataBase.Tables;
using Microsoft.EntityFrameworkCore;
using FAuth.Extensions;
using FAuth.Extensions.Account;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FAuth.Controllers.api
{
@ -17,7 +17,10 @@ namespace FAuth.Controllers.api
/// </summary>
public class AppController:ApiControllerBase<AppController>
{
public AccountHelper Account { get; set; }
public AppController(ILogger<AppController> logger,IServiceProvider service) : base(logger,service) {
this.Account = this.Services.GetService<AccountHelper>();
}
/// <summary>
@ -81,6 +84,21 @@ namespace FAuth.Controllers.api
}
}
/// <summary>
/// 通过管理员凭据获取应用列表
/// </summary>
/// <param name="adminTicket">管理员凭据</param>
/// <returns>应用列表</returns>
[HttpGet]
[ProducesResponseType(typeof(IEquatable<Apps>),200)]
public IEnumerable<Apps> GetAppList(string adminTicket) {
if(this.Account.IsNotSystemAdmin(adminTicket)) {
throw new ApiException("需要提供管理员票据");
}
var qu = this.Db.Apps;
return qu.ToList();
}
}
}

View File

@ -89,7 +89,7 @@ namespace FAuth.Controllers.api
/// <returns>用户信息</returns>
[HttpGet]
[ProducesResponseType(typeof(UserInfo),200)]
public UserInfo GetUserByTicket([BindRequired]string ticket) {
public UserInfo GetUserByTicket(string ticket) {
if(ticket.IsNullOrEmpty()) {
throw new ApiArgumentNullException(nameof(ticket));
}
@ -106,6 +106,7 @@ namespace FAuth.Controllers.api
Id = fir.Id,
LastLoginDatetime = fir.LastLoginDatetime,
UserName = fir.UserName,
Name = fir.Name,
};
}

View File

View File

@ -7,12 +7,12 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using FAuth.Models;
namespace FAuth.Controllers
namespace FAuth.Controllers.web
{
/// <summary>
/// Home控制器
/// </summary>
public class HomeController:ControllerBase<HomeController>
public class HomeController:WebControllerBase<HomeController>
{
public HomeController(ILogger<HomeController> logger,IServiceProvider service) : base(logger,service) {
}

View File

@ -6,13 +6,17 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using FAuth.Models;
using FAuth.Extensions;
using Microsoft.Extensions.DependencyInjection;
using api = FAuth.Controllers.api;
using Falcon.Extend;
namespace FAuth.Controllers
namespace FAuth.Controllers.web
{
/// <summary>
/// 用户控制器
/// </summary>
public class UserController:ControllerBase<UserController>
public class UserController:WebControllerBase<UserController>
{
public UserController(ILogger<UserController> logger,IServiceProvider service) : base(logger,service) {
}
@ -21,5 +25,12 @@ namespace FAuth.Controllers
return PartialView();
}
public IActionResult Login() {
return PartialView();
}
public IActionResult ResetPassword() {
return PartialView();
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace FAuth.Controllers.web
{
/// <summary>
/// Web页面控制器基类
/// </summary>
/// <typeparam name="LoggerType"></typeparam>
//[Area("web"), Route("web/[Controller]/[Action]")]
public abstract class WebControllerBase<LoggerType>:ControllerBase<LoggerType>
{
public WebControllerBase(ILogger<LoggerType> logger,IServiceProvider service) : base(logger,service) {
}
}
}

View File

@ -0,0 +1,79 @@
using System;
using Microsoft.AspNetCore.CookiePolicy;
using Microsoft.AspNetCore.Http;
namespace FAuth.Extensions
{
/// <summary>
/// cookie帮助类
/// </summary>
public static class CookieHelper
{
/// <summary>
/// 设置Cookie值
/// </summary>
/// <param name="httpContext">http上下文</param>
/// <param name="key">Cookie的键</param>
/// <param name="value">cookie值</param>
/// <param name="minutes">cookie有效期。分钟无限传null或不传</param>
public static void SetCookies(this HttpContext httpContext,string key,string value,int? minutes = null) {
SetCookies(httpContext.Response,key,value,minutes);
}
/// <summary>
/// 设置Cookie值
/// </summary>
/// <param name="response">http响应</param>
/// <param name="key">Cookie的键</param>
/// <param name="value">cookie值</param>
/// <param name="minutes">cookie有效期。分钟无限传null或不传</param>
public static void SetCookies(this HttpResponse response,string key,string value,int? minutes = null) {
if(minutes.HasValue) {
response.Cookies.Append(key,value,new CookieOptions {
Expires = DateTime.Now.AddMinutes(minutes.Value)
});
} else {
response.Cookies.Append(key,value);
}
}
/// <summary>
/// 删除cookie
/// </summary>
/// <param name="httpContext">http上下文</param>
/// <param name="key">要删除的键</param>
public static void DeleteCookies(this HttpContext httpContext,string key) {
DeleteCookies(httpContext.Response,key);
}
/// <summary>
/// 删除cookie
/// </summary>
/// <param name="response">http响应</param>
/// <param name="key">要删除的键</param>
public static void DeleteCookies(this HttpResponse response,string key) {
response.Cookies.Delete(key);
}
/// <summary>
/// 获取cookie中的值
/// </summary>
/// <param name="httpContext">http上下文</param>
/// <param name="key">cookie的键</param>
/// <returns>保存的值</returns>
public static string GetCookiesValue(this HttpContext httpContext,string key) {
return GetCookiesValue(httpContext.Request,key);
}
/// <summary>
/// 获取cookie中的值
/// </summary>
/// <param name="request">http请求</param>
/// <param name="key">cookie的键</param>
/// <returns>保存的值</returns>
public static string GetCookiesValue(this HttpRequest request,string key) {
if(request.Cookies.TryGetValue(key,out string value)) {
return value;
}
return string.Empty;
}
}
}

View File

@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="Falcon.Extend" Version="1.2.1" />
<PackageReference Include="jquery.cookie" Version="1.4.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="3.1.3" />

View File

@ -0,0 +1,17 @@
using Microsoft.Extensions.Options;
namespace FAuth.Models
{
/// <summary>
/// Cookie中使用的键定义
/// </summary>
public class CookieKeyDefine:IOptions<CookieKeyDefine>
{
public CookieKeyDefine Value => this;
/// <summary>
/// 保存用户凭据的键
/// </summary>
public string UserKey { get; set; } = "_userCK";
}
}

View File

@ -19,6 +19,10 @@ namespace FAuth.Models
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 用户名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 上次登录时间
/// </summary>
public DateTimeOffset? LastLoginDatetime { get; set; }

View File

@ -7,6 +7,7 @@ using FAuth.DataBase;
using FAuth.Extensions;
using FAuth.Extensions.Account;
using FAuth.Extensions.Decryptor;
using FAuth.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
@ -85,6 +86,8 @@ namespace FAuth
services.AddUserTicketDryptor(UTDO);
//注册api错误处理器
services.AddScoped<ApiExceptionFilterAttribute>();
//×¢²áCookie¼üÅäÖÃ
services.Configure<CookieKeyDefine>(this.Configuration.GetSection("CookieKeyDefine"));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -1,5 +1,5 @@
@{
ViewBag.Title = "首页";
ViewBag.Title = "统一登录平台";
}
<div class="text-center">

View File

@ -1,4 +1,6 @@
<!DOCTYPE html>
@using Microsoft.Extensions.Options
@inject IOptions<CookieKeyDefine> CKD
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
@ -30,8 +32,21 @@
<li class="nav-item">
<a class="nav-link text-dark" fajax-updata="main" asp-controller="User" asp-action="Index">用户管理</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" target="_blank" href="/api">接口文档</a>
</li>
</ul>
</div>
<div id="userBar">
<div id="login">
<a asp-controller="user" asp-action="login" fajax-updata="main">登录</a>
</div>
<div id="logout">
<span>欢迎: <span id="userName"></span></span>
<a asp-controller="user" asp-action="ResetPassword" fajax-updata="main">修改密码</a>
<a href="#" id="logOut">退出</a>
</div>
</div>
</div>
</nav>
</header>
@ -47,9 +62,58 @@
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery.cookie/jquery.cookie-1.4.1.min.js"></script>
<script src="~/lib/jquery.unobtrusive-ajax/jquery.unobtrusive-ajax.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
<script type="text/javascript">
//设置用户key
myJs.userKey="@CKD.Value.UserKey";
//刷新用户条
function RefreshUserBar() {
var ticket =myJs.getTicket();
if (!ticket) {
$("#logout").hide();
$("#login").show();
}
if (ticket) {
var url = "/api/User/GetUserByTicket";
var data = { ticket: ticket };
myJs.get(url, data, function (d) {
$("#userName").html(d.Name);
$("#logout").show();
$("#login").hide();
}, function (c,i,m) {
$("#logout").hide();
$("#login").show();
myJs.msg(m);
myJs.removeTicket();
});
}
}
function logOut() {
var ticket = myJs.getTicket();
var url = "/api/User/Logout";
var data = { ticket: ticket };
myJs.post(url, data, function (d) {
myJs.removeTicket(ticket);
RefreshUserBar();
}, function (s,i,m) {
myJs.msg(m);
});
}
$(function () {
RefreshUserBar();
$("#logOut").click(function () {
logOut();
return false;
});
});
</script>
@RenderSection("Scripts",required: false)
</body>
</html>

View File

@ -0,0 +1,22 @@
@inject IOptions<CookieKeyDefine> CKD
<div id="logInForm">
<form fajax-success="loginOnSucc" fajax-failure="loginOnError" method="post" action="/api/User/Login">
<label>用户名:<input type="text" name="userName" /></label>
<label>密码:<input type="password" name="password" /></label>
<input type="submit" value="登录" />
<span id="errormsg"></span>
</form>
</div>
<script type="text/javascript">
function loginOnSucc(d) {
var _userKey = "@CKD.Value.UserKey";
$.cookie(_userKey, d.Ticket);
if (RefreshUserBar) RefreshUserBar();
$("#logInForm").hide();
}
function loginOnError(d) {
var msg = d.responseJSON.Message;
$("#errormsg").text(msg);
}
</script>

View File

@ -0,0 +1,28 @@

<form method="post" id="ResetPasswordForm">
<label>新密码:<input type="password" name="np" /></label>
<label>再次输入:<input type="password" name="np1" /></label>
<input type="button" id="submit" value="提交" />
<span id="errMsg"></span>
</form>
<script type="text/javascript">
$(function () {
$("#submit").click(function () {
debugger;
var p1 = $("input[name='np']").val();
var p2 = $("input[name='np1']").val();
if (p1 != p2) {
$("#errMsg").html("两次输入的密码不一致");
return;
}
var url = "api/user/changepassword";
var ticket = myJs.getTicket();
myJs.post(url, { ticket:ticket, nPassword:p1 }, function (d) {
$("#ResetPasswordForm").html("<span>修改成功</span>");
}, function (s, i, m) {
$("#ResetPasswordForm #errMsg").html("<span>" + m + "编号:" + i + "</span>");
})
});
});
</script>

View File

@ -1,4 +1,5 @@
@using FAuth
@using FAuth.Models
@using Microsoft.Extensions.Options
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper FAuth.Extensions.TagHelpers.*, FAuth

View File

@ -2,3 +2,44 @@
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
var myJs = {};
myJs.get = function (url, data, success, error) {
$.ajax({
url: url,
type: "get",
data: data,
success: function (d) {
success(d);
},
error: function (d) {
error(d.status, d.responseJSON.Id, d.responseJSON.Message)
},
});
}
myJs.post = function (url, data, success, error) {
$.ajax({
url: url,
type: "post",
data: data,
success: function (d) {
success(d);
},
error: function (d) {
error(d.status, d.responseJSON.Id, d.responseJSON.Message)
},
});
}
myJs.msg = function (msg) {
alert(msg);
}
//保存用户凭据的键在_layout视图设置
myJs.userKey = "";
//获取登录的用户票据
myJs.getTicket = function () {
return $.cookie(myJs.userKey);
}
//移除用户登录票据
myJs.removeTicket = function () {
$.removeCookie(myJs.userKey);
}

View File

@ -0,0 +1,2 @@
/*! jquery.cookie v1.4.1 | MIT */
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}});

View File

@ -0,0 +1,117 @@
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
// If we can't parse the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setTime(+t + days * 864e+5);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {};
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');
if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) === undefined) {
return false;
}
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return !$.cookie(key);
};
}));