A.
Entri
Otentikasi JWT (JSON Web Token) adalah mekanisme otentikasi dan otentikasi yang cukup seragam, konsisten antara server dan klien. Keuntungan dari JWT adalah memungkinkan kita untuk mengelola keadaan kurang dan skala dengan baik. Tidak mengherankan bahwa otorisasi dan otentikasi dengan bantuannya semakin banyak digunakan dalam aplikasi web modern.
Saat mengembangkan aplikasi dengan JWT, pertanyaan sering muncul: di mana dan bagaimana direkomendasikan untuk menyimpan token? Jika kami mengembangkan aplikasi web, kami memiliki dua opsi paling umum:
- Penyimpanan Web HTML5 (Penyimpanan lokal atau sessionStorage)
- Kue kering
Membandingkan metode ini, kita dapat mengatakan bahwa keduanya menyimpan nilai di browser klien, keduanya cukup mudah digunakan dan mewakili penyimpanan umum dari pasangan nilai kunci. Perbedaannya terletak pada lingkungan penyimpanan.
Penyimpanan Web (localStorage / sessionStorage) dapat diakses melalui JavaScript di domain yang sama. Ini berarti bahwa setiap kode JavaScript dalam aplikasi Anda memiliki akses ke Penyimpanan Web, dan ini menciptakan kerentanan terhadap serangan skrip lintas situs (XSS). Sebagai mesin penyimpanan, Penyimpanan Web tidak memberikan cara untuk mengamankan data Anda selama penyimpanan dan berbagi. Kita dapat menggunakannya hanya untuk data tambahan yang ingin kita simpan saat memperbarui (F5) atau menutup tab: status dan nomor halaman, filter, dll.
Token juga dapat dikirim melalui cookie browser. Cookie yang digunakan dengan bendera
httpOnly tidak terpengaruh XSS. httpOnly adalah bendera untuk akses ke membaca, menulis, dan menghapus cookie hanya di server. Mereka tidak akan dapat diakses melalui JavaScript pada klien, sehingga klien tidak akan tahu tentang token, dan otorisasi akan sepenuhnya diproses di sisi server.
Kami juga dapat menetapkan bendera
aman untuk memastikan bahwa cookie hanya dikirimkan melalui HTTPS. Dengan kelebihan ini, pilihan saya jatuh pada cookie.
Artikel ini menjelaskan pendekatan untuk menerapkan otentikasi dan otentikasi menggunakan cookie aman hanya JSON + JSON Web Token di ASP.NET Core Web Api bersama dengan SPA. Opsi dipertimbangkan di mana server dan klien berbeda asal.
Menyiapkan Lingkungan Pengembangan Lokal Anda
Untuk mengkonfigurasi dan men-debug hubungan klien-server dengan benar melalui HTTPS, saya sangat menyarankan Anda segera mengkonfigurasi lingkungan pengembangan lokal sehingga klien dan server memiliki koneksi HTTPS.
Jika Anda tidak segera melakukan ini dan mencoba membangun hubungan tanpa koneksi HTTPS, maka banyak detail akan muncul tanpa cookie aman dan kebijakan keamanan tambahan dalam produksi dengan HTTPS tidak akan berfungsi dengan benar.
Saya akan menunjukkan contoh mengkonfigurasi HTTPS pada OS Windows 10, server - ASP.NET Core, SPA - React.
Anda bisa mengonfigurasi HTTPS di ASP.NET Core menggunakan flag
Configure for HTTPS saat membuat proyek atau, jika kami tidak melakukan ini saat membuat, aktifkan opsi yang sesuai di Properties.
Untuk mengkonfigurasi SPA, Anda perlu memodifikasi skrip untuk
"mulai" , mengaturnya untuk
"mengatur HTTPS = true" . Setup saya adalah sebagai berikut:
'start': 'set HTTPS=true&&rimraf ./build&&react-scripts start'
Saya menyarankan Anda untuk melihat pengaturan HTTPS untuk lingkungan pengembangan di lingkungan lain di
create-react-app.dev/docs/using-https-in-developmentMengkonfigurasi ASP.NET Core Server
Pengaturan JWT
Dalam hal ini, penerapan JWT paling umum dari dokumentasi atau dari artikel apa pun, dengan opsi pengaturan tambahan
options.RequireHttpsMetadata = true;
karena lingkungan pengembangan kami menggunakan HTTPS:
Konfigurasikan Layanan services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.RequireHttpsMetadata = true; options.SaveToken = true; options.TokenValidationParameters = new TokenValidationParameters {
Mengkonfigurasi kebijakan CORS
Penting : Kebijakan CORS harus mengandung
AllowCredentials()
. Ini diperlukan untuk menerima permintaan dari
XMLHttpRequest.withCredentials dan mengirim cookie kembali ke klien. Lebih lanjut tentang ini akan ditulis nanti. Pilihan lain dikonfigurasi tergantung pada kebutuhan proyek.
Jika server dan klien memiliki asal yang sama, maka seluruh konfigurasi di bawah ini tidak diperlukan.
Konfigurasikan Layanan services.AddCors();
Konfigurasikan app.UseCors(x => x .WithOrigins("https://localhost:3000")
Menetapkan kebijakan cookie
Paksa kebijakan cookie ke httpOnly dan aman.
Jika memungkinkan, atur
MinimumSameSitePolicy = SameSiteMode.Strict;
- Ini meningkatkan keamanan cookie untuk jenis aplikasi yang tidak bergantung pada pemrosesan permintaan lintas-asal.
Konfigurasikan app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Strict, HttpOnly = HttpOnlyPolicy.Always, Secure = CookieSecurePolicy.Always });
Gagasan pertukaran token yang aman
Bagian ini adalah sebuah konsep. Kami akan melakukan dua hal:
- Masukkan token ke permintaan HTTP menggunakan httpOnly dan tanda aman.
- Menerima dan memvalidasi token aplikasi klien dari permintaan HTTP.
Untuk melakukan ini, kita perlu:
- Tulis token di cookie httpOnly saat masuk dan hapus dari sana saat masuk.
- Jika ada token di cookie, gantikan token di header HTTP setiap permintaan berikutnya.
- Jika tidak ada token di cookie, maka tajuk akan kosong, dan permintaan tidak akan diotorisasi.
Middleware
Gagasan utamanya adalah mengimplementasikan Custom Middleware untuk memasukkan token ke dalam permintaan HTTP yang masuk. Setelah otorisasi pengguna, kami menyimpan cookie di bawah kunci tertentu, misalnya:
".AspNetCore.Application.Id" . Saya merekomendasikan pengaturan nama yang tidak ada hubungannya dengan otorisasi atau token - dalam hal ini, cookie dengan token akan terlihat seperti semacam konstanta sistem yang
biasa -
biasa saja untuk aplikasi AspNetCore. Jadi ada kemungkinan lebih tinggi bahwa penyerang akan melihat banyak variabel sistem dan, tanpa memahami mekanisme otorisasi yang digunakan, akan melangkah lebih jauh. Tentu saja, jika dia tidak membaca artikel ini dan tidak secara khusus mencari konstan seperti itu.
Selanjutnya, kita perlu memasukkan token ini ke semua permintaan HTTP masuk berikutnya. Untuk melakukan ini, kita akan menulis beberapa baris kode Middleware. Ini tidak lain hanyalah pipa HTTP.
Konfigurasikan app.Use(async (context, next) => { var token = context.Request.Cookies[".AspNetCore.Application.Id"]; if (!string.IsNullOrEmpty(token)) context.Request.Headers.Add("Authorization", "Bearer " + token); await next(); }); app.UseAuthentication();
Kita bisa membawa logika ini ke layanan Middleware terpisah agar tidak menyumbat Startup.cs, idenya tidak akan berubah.
Untuk menulis nilai dalam cookie, kita hanya perlu menambahkan baris berikut ke logika otorisasi:
if (result.Succeeded) HttpContext.Response.Cookies.Append(".AspNetCore.Application.Id", token, new CookieOptions { MaxAge = TimeSpan.FromMinutes(60) });
Dengan menggunakan kebijakan cookie kami, cookie ini akan secara otomatis dikirim sebagai httpHanya dan aman. Tidak perlu mendefinisikan kembali kebijakan mereka dalam opsi cookie.
Di CookieOptions, Anda dapat mengatur MaxAge untuk menentukan masa pakai. Sangat berguna untuk menentukan bersama dengan JWT Lifetime ketika mengeluarkan token sehingga cookie menghilang setelah beberapa saat. Properti CookieOptions lain dikonfigurasikan tergantung pada persyaratan proyek.
Untuk keamanan yang lebih besar, saya sarankan menambahkan header berikut ke Middleware:
context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); context.Response.Headers.Add("X-Xss-Protection", "1"); context.Response.Headers.Add("X-Frame-Options", "DENY");
- Header X-Content-Type-Options digunakan untuk melindungi terhadap kerentanan sniffing MIME. Kerentanan ini dapat terjadi ketika situs mengizinkan pengguna untuk mengunduh konten, tetapi pengguna menyamarkan jenis file tertentu sebagai sesuatu yang lain. Ini dapat memberi penyerang kemampuan untuk mengeksekusi skrip skrip lintas situs atau kompromi situs web.
- Semua browser modern memiliki kemampuan filter XSS built-in yang mencoba menangkap kerentanan XSS sebelum halaman ditampilkan sepenuhnya kepada kami. Secara default, mereka diaktifkan di browser, tetapi pengguna mungkin lebih rumit dan menonaktifkannya. Menggunakan header X-XSS-Protection , kita sebenarnya dapat memberitahu browser untuk mengabaikan apa yang telah dilakukan pengguna dan menerapkan filter bawaan.
- X-Frame-Options memberi tahu browser bahwa jika situs Anda ditempatkan di dalam bingkai HTML, maka jangan tampilkan apa pun. Ini sangat penting ketika mencoba melindungi diri Anda dari upaya clickjacking.
Saya telah menggambarkan jauh dari semua berita utama. Ada banyak lagi cara untuk mencapai keamanan aplikasi web yang lebih besar. Saya menyarankan Anda untuk fokus pada daftar periksa keamanan dari sumber daya securityheaders.com.
Penyiapan klien SPA
Ketika klien dan server terletak pada asal yang berbeda, konfigurasi tambahan juga diperlukan pada klien. Anda harus membungkus setiap permintaan menggunakan
XMLHttpRequest.withCredentials .
Saya membungkus metode saya sebagai berikut:
import axios from "axios"; const api = axios.create({ baseURL: process.env.REACT_APP_API_URL }); api.interceptors.request.use(request => requestInterceptor(request)) const requestInterceptor = (request) => { request.withCredentials = true; return request; } export default api;
Kita dapat membungkus konfigurasi permintaan dengan cara apa pun, yang utama adalah bahwa
withCredentials = true ada di sana .
Properti
XMLHttpRequest.withCredentials menentukan apakah permintaan lintas domain harus dibuat menggunakan kredensial seperti cookie, header otorisasi, atau sertifikat TLS.
Bendera ini juga digunakan untuk menentukan apakah cookie yang dikirim dalam respons akan diabaikan. XMLHttpRequest dari domain lain tidak dapat menetapkan cookie pada domainnya sendiri jika flag withCredentials tidak disetel ke true sebelum membuat permintaan ini.
Dengan kata lain, jika Anda tidak menentukan atribut ini, maka cookie kami tidak akan disimpan oleh browser, mis. kami tidak akan dapat mengirim cookie kembali ke server, dan server tidak akan menemukan cookie yang diinginkan dengan JWT dan tidak akan menandatangani Bearer Token dalam pipa HTTP kami.
Untuk apa semua ini?
Di atas, saya menjelaskan cara pertukaran token yang tahan XSS. Mari kita pergi dan melihat hasil dari fungsionalitas yang diimplementasikan.
Jika Anda masuk ke Alat Pengembang, kami melihat bendera yang
didambakan httpHanya dan
aman :

Mari kita lakukan uji coba, coba keluarkan cookie dari klien:

Kami mengamati '', yaitu cookie tidak dapat diakses dari ruang dokumen, yang membuatnya tidak mungkin untuk membacanya dengan skrip.
Kita dapat mencoba untuk mendapatkan cookie ini dengan bantuan alat atau ekstensi tambahan, tetapi semua alat yang saya coba sebut implementasi asli dari ruang dokumen.
Proyek demo
Instruksi permulaan ada di README.MD
UPD: Perlindungan terhadap CSRF
Mengkonfigurasi ASP.NET Core Server
Layanan MiddlewareXsrfProtectionMiddleware.cs public class XsrfProtectionMiddleware { private readonly IAntiforgery _antiforgery; private readonly RequestDelegate _next; public XsrfProtectionMiddleware(RequestDelegate next, IAntiforgery antiforgery) { _next = next; _antiforgery = antiforgery; } public async Task InvokeAsync(HttpContext context) { context.Response.Cookies.Append( ".AspNetCore.Xsrf", _antiforgery.GetAndStoreTokens(context).RequestToken, new CookieOptions {HttpOnly = false, Secure = true, MaxAge = TimeSpan.FromMinutes(60)}); await _next(context); } }
MiddlewareExtensions.cs public static class MiddlewareExtensions { public static IApplicationBuilder UseXsrfProtection(this IApplicationBuilder builder, IAntiforgery antiforgery) => builder.UseMiddleware<XsrfProtectionMiddleware>(antiforgery); }
Konfigurasikan Layanan services.AddAntiforgery(options => { options.HeaderName = "x-xsrf-token"; }); services.AddMvc();
Konfigurasikan app.UseAuthentication(); app.UseXsrfProtection(antiforgery);
Pengaturan SPA
api.axios.js import axios from "axios"; import cookie from 'react-cookies'; const api = axios.create({ baseURL: process.env.REACT_APP_API_URL }); api.interceptors.request.use(request => requestInterceptor(request)) const requestInterceptor = (request) => { request.headers['x-xsrf-token'] = cookie.load('.AspNetCore.Xsrf') return request; } export default api;
Gunakan
Untuk melindungi metode API kami, Anda harus menambahkan atribut
[AutoValidateAntiforgeryToken]
untuk controller atau
[ValidateAntiForgeryToken]
untuk metode ini.