分享程式代碼相關筆記
目前文章總數:157 篇
最後更新:2024年 12月 07日
1. 檢視頁面 | : | 這邊用兩個登入頁,管理者、一般用戶,為了第二部分說明Session重複的問題 |
2. 控制器 | : | 頁面檢視的控制器,說明如何使用圖形驗證碼、並建立Session驗證 |
3. 共用函式邏輯 | : | CaptchaUtil 是產生圖片驗證碼的函式 ; SessionUtil 是紀錄圖片驗證的Session Key |
4. 客製化屬性 | : | CaptchaBindingAttribute 實現圖形驗證碼Action對Action的關係,避免第二部分的問題發生 |
以下是用戶登入的畫面,需要輸入帳號、密碼、驗證碼
@model WebSiteCaptchaLoginExample.Models.LoginViewModel
@{
ViewData["Title"] = "用戶-登入";
}
<h1>@ViewData["Title"]</h1>
<p>用戶登入頁面</p>
<form method="post" asp-controller="Home" asp-action="UserLoginVerify">
<table>
<tr>
<td>帳號:</td>
<td><input type="text" asp-for="SubmitData.Account" /></td>
<td></td>
</tr>
<tr>
<td>密碼:</td>
<td><input type="text" asp-for="SubmitData.Password" /></td>
<td></td>
</tr>
<tr>
<td>驗證碼:</td>
<td><input type="text" asp-for="SubmitData.InputCaptcha" /></td>
<td></td>
</tr>
<tr>
<td></td>
<td><img style='width:180px;height:60px;border:1px solid #ccc;' id='captchaImage' class='cursor-pointer absolute r-0 t-0' src='@Model.Chapcha' alt="验证码"></td>
<td></td>
</tr>
<tr>
<td></td>
<td> <button type="submit">用戶登入</button></td>
<td></td>
</tr>
</table>
</form>
以下是管理員的登入,與用戶登入相同,但 asp-action=”AdminLoginVerify”
@model WebSiteCaptchaLoginExample.Models.LoginViewModel
@{
ViewData["Title"] = "管理員-登入";
}
<h1>@ViewData["Title"]</h1>
<p>管理員登入頁面</p>
<form method="post" asp-controller="Home" asp-action="AdminLoginVerify">
<table>
<tr>
<td>帳號:</td>
<td><input type="text" asp-for="SubmitData.Account" /></td>
<td></td>
</tr>
<tr>
<td>密碼:</td>
<td><input type="text" asp-for="SubmitData.Password" /></td>
<td></td>
</tr>
<tr>
<td>驗證碼:</td>
<td><input type="text" asp-for="SubmitData.InputCaptcha" /></td>
<td></td>
</tr>
<tr>
<td></td>
<td><img style='width:180px;height:60px;border:1px solid #ccc;' id='captchaImage' class='cursor-pointer absolute r-0 t-0' src='@Model.Chapcha' alt="验证码"></td>
<td></td>
</tr>
<tr>
<td></td>
<td> <button type="submit">管理者登入</button></td>
<td></td>
</tr>
</table>
</form>
實現了一共4個
1. 用戶登入
/// <summary>
/// 用戶登入
/// </summary>
CaptchaBinding(CaptchaBindingName = nameof(UserLogin), Generate = true)]
public IActionResult UserLogin()
{
//使用 CaptchaUtil.GetCapChatImg 產生Base64的驗證碼圖形
var result = new LoginViewModel()
{
Chapcha = $@"data:image/jpeg;base64,{Convert.ToBase64String(CaptchaUtil.GetCapChatImg(SessionUtil.CaptCha))}",
};
return View(result);
}
2. 用戶登入按鈕-進行登入
/// <summary>
/// 用戶登入按鈕-進行登入
/// </summary>
[HttpPost]
[CaptchaBinding(CaptchaBindingName = nameof(UserLogin))]
public IActionResult UserLoginVerify(LoginViewModel inputData)
{
//進行登入時會依照綁定的"設置名稱" 取得對應頁面的-圖形驗證碼,就不會造成A頁面卻吃到B頁面驗證碼的錯誤
if (inputData.SubmitData.InputCaptcha == SessionUtil.CaptCha)
return Ok("驗證成功");
else
return Ok("登入失敗");
}
3. 管理者登入
/// <summary>
/// 管理者登入
/// </summary>
[CaptchaBinding(CaptchaBindingName = nameof(AdminLogin), Generate = true)]
public IActionResult AdminLogin()
{
var result = new LoginViewModel()
{
Chapcha = $@"data:image/jpeg;base64,{Convert.ToBase64String(CaptchaUtil.GetCapChatImg(SessionUtil.CaptCha))}",
};
return View(result);
}
4. 管理者登入按鈕-進行登入
[HttpPost]
[CaptchaBinding(CaptchaBindingName = nameof(AdminLogin))]
public IActionResult AdminLoginVerify(LoginViewModel inputData)
{
if (inputData.SubmitData.InputCaptcha == SessionUtil.CaptCha)
return Ok("驗證成功");
else
return Ok("登入失敗");
}
CaptchaUtil.cs 實現了產生圖形驗證碼,只要將指定的字串放進即可生成 byte[] 資料流
以下是生成的圖片樣式:
/// <summary>
/// 產生驗證碼圖片
/// </summary>
/// <param name="captCha">驗證碼文字</param>
/// <param name="bmpWidth">產生圖片寬</param>
/// <param name="bmpHeight">產生圖片高</param>
/// <param name="noisePotCount">雜點數量</param>
/// <param name="noiseLineCount">雜訊線條數量</param>
/// <returns></returns>
public static byte[] GetCapChatImg(string captCha,
int bmpWidth = 200,
int bmpHeight = 80,
int noisePotCount = 60,
int noiseLineCount = 90)
{
using (Bitmap bmp = new Bitmap(bmpWidth, bmpHeight))
{
int xAxis1;
int yAxis1;
int xAxis2;
int yAxis2;
var random = new Random();
Graphics graphItem = Graphics.FromImage(bmp);
var fontStyle = Enum.GetValues(typeof(FontStyle)).Cast<FontStyle>().ToArray();
var randomFontStyle = fontStyle[random.Next(0, fontStyle.Length)];
Font font = new Font("Courier New", random.Next(36, 46), randomFontStyle);
//設定圖片背景
graphItem.Clear(Color.White);
//產生雜點
var noiseWidth = bmpWidth / 4;
var noiseHeight = bmpHeight / 4;
for (int noisePots = 0; noisePots < noisePotCount; noisePots++)
{
xAxis1 = random.Next(0, bmp.Width);
yAxis1 = random.Next(0, bmp.Height);
bmp.SetPixel(xAxis1, yAxis1, Color.Brown);
}
//產生擾亂弧線
for (int noiseLines = 0; noiseLines < noiseLineCount; noiseLines++)
{
xAxis1 = random.Next(bmp.Width - noiseWidth);
yAxis1 = random.Next(bmp.Height - noiseHeight);
xAxis2 = random.Next(1, noiseWidth);
yAxis2 = random.Next(1, noiseHeight);
var startAngle = random.Next(0, 90);
var sweepAngle = random.Next(-270, 270);
graphItem.DrawArc(new Pen(Brushes.Gray), xAxis1, yAxis1, xAxis2, yAxis2, startAngle, sweepAngle);
}
var randomDrawX = random.Next(3, 30);
var randomDrawY = random.Next(3, 12);
graphItem.DrawString(captCha, font, GetRandomBrushes(), randomDrawX, randomDrawY);
using (var memoryStream = new MemoryStream())
{
bmp.Save(memoryStream, ImageFormat.Gif);
memoryStream.Close();
return memoryStream.GetBuffer();
}
}
//筆刷顏色,為了用戶體驗與安全性,RGB隨機性在96~160
Brush GetRandomBrushes(byte startRGB = 96, byte endRGB = 160)
{
Random r = new Random();
int red = r.Next(startRGB, endRGB + 1);
int green = r.Next(startRGB, endRGB + 1);
int blue = r.Next(startRGB, endRGB + 1);
return new SolidBrush(Color.FromArgb(red, green, blue));
}
}
另外也提供自動生成隨機字串的函式
private const string baseNumberWord = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
/// <summary>
/// 取得指定長度的字串
/// </summary>
public static string GetRandomCaptcha(int length = 5)
{
var random = new Random();
var strBuilder = new StringBuilder(32);
for (var index = 0; index < length; index++)
{
strBuilder.Append(baseNumberWord.Substring(random.Next(baseNumberWord.Length), 1));
}
return strBuilder.ToString();
}
SessionUtil.cs 目的是紀錄產生後的圖形驗證碼,以便於在用戶提交後可以比對
1. CaptChaBindName | : | 圖形驗證碼綁定名稱 - 使驗證碼與某些Action具有關聯性 |
2. CaptCha | : | 圖形驗證碼 |
/// <summary>
/// 圖形驗證碼綁定名稱 - 使驗證碼與某些Action具有關聯性
/// </summary>
public static string CaptChaBindName
{
get
{
return _httpContextAccessor?.HttpContext?.Session.GetString("CaptChaBindName") ?? string.Empty;
}
set
{
_httpContextAccessor?.HttpContext?.Session.SetString("CaptChaBindName", value);
}
}
/// <summary>
/// 圖形驗證碼
/// </summary>
public static string CaptCha
{
get
{
return _httpContextAccessor?.HttpContext?.Session.GetString($@"{CaptChaBindName}_CaptCha") ?? string.Empty;
}
set
{
_httpContextAccessor?.HttpContext?.Session.SetString($@"{CaptChaBindName}_CaptCha", value);
}
}
宣告中 [AttributeUsage(AttributeTargets.Method)] 表示先過濾Http Method(Get、Post、Put …)
並且支援攜帶參數 CaptchaBindingName 與 Generate 實現Action間,確認是共用同一組驗證碼
[AttributeUsage(AttributeTargets.Method)]
public class CaptchaBindingAttribute : Attribute, IActionFilter
{
/// <summary>
/// 圖形驗證碼綁定名稱
/// </summary>
public string CaptchaBindingName { get; set; } = string.Empty;
public bool Generate { get; set; } = false;
//1. 有綁定 [CaptchaBinding] 的Action才會觸發
public void OnActionExecuting(ActionExecutingContext context)
{
try
{
//2. 配置當前"設定名稱"
var attribute = context.ActionDescriptor.EndpointMetadata.OfType<CaptchaBindingAttribute>().FirstOrDefault();
if (attribute != null)
{
SessionUtil.CaptChaBindName = attribute.CaptchaBindingName;
//3. 配置時為產生 Generate = true 才會生成字符串
if (Generate)
{
SessionUtil.CaptCha = CaptchaUtil.GetRandomCaptcha(5);
}
}
}
catch
{
//直接拋棄,不影響正式流程
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
範例檔案:範例檔案,下載後,啟動程式後
開啟兩個頁籤,先開用戶登入(不要關閉) -> 然後再開啟管理員登入(不要關閉)
這時進行用戶登入,帳號、密碼隨意填寫,但是圖形驗證碼填入正確的
可以發現我們會登入失敗,因為SessionUtil.Captchr 因為讀取頁面順序的關係
實際上圖形驗證碼SessionUtil.Captchr已經變成管理用登入的圖片
範例檔案裡面已經提供加入 CaptchaBindingAttribute 的Attribute,以下是實現順序:
a. 參考:第一部分-Step 5:共用函式邏輯-紀錄Session 擴增一個 CaptChaBindName 可以很好的解決此問題
b. 參考:第一部分-Step 6:客製化屬性,新增一個Class 讓Action 可以掛載
c. 參考:第一部分-Step 3:控制器 讓Action 可以掛載
掛載在控制器上
[HttpPost]
[CaptchaBinding(CaptchaBindingName = nameof(UserLogin))]
public IActionResult UserLoginVerify(LoginViewModel inputData)
{
}
再次登入後即使開不同頁面,也只會吃自己頁面的圖形驗證碼