یادگیری امنیت JWT با استفاده از KumuluzEE – امور مالی یک لیگ از محیط زیست

1. مقدمه
امروزه نگرانیهای بیشتری در مورد عملکرد داریم و در عین حال میخواهیم بدانیم که چگونه سیستمها میتوانند سریع و قابل اعتماد ارتباط برقرار کنند. خیلی وقت ها می خواهیم اطلاعاتی را ارسال کنیم و تا حد امکان محرمانه و ایمن نگه داریم. گاهی اوقات دادههای حساس باید از طریق وب به صورت عمومی حرکت کنند و اقداماتی را در انتهای دیگر سیم ایجاد کنند. بیشتر، ما می خواهیم اقداماتی را ایجاد کنیم که باعث جهش داده ها شود. در این موارد، ما فقط به دنبال محافظت از داده های خود نیستیم. ما میخواهیم مطمئن شویم که اقداماتی که با ارسال دادههای ما انجام میشوند قابل اعتماد هستند. ما می توانیم از داده های خود به روش های مختلفی محافظت کنیم. معمولاً ما داده ها را از طریق a ارسال می کنیم TLS
(Transport Layer Security) اتصال ایمن. این تضمین می کند که داده های ما از طریق سیم رمزگذاری می شوند. ما از گواهی ها برای ایجاد روابط قابل اعتماد بین دو طرف و رسیدن به این هدف استفاده می کنیم.
در این مقاله، من می خواهم به بحث در مورد JWT
استاندارد و بیشتر ببینید چگونه می توانیم ادغام کنیم JWT
به یک مشترک Enterprise
کاربرد. در این صورت نگاهی خواهیم داشت KumuluzEE
.
بیایید نگاهی به مفاهیم اساسی بیندازیم. JWT
یا JSON Web Token یا بهتر است بگوییم JavaScript Object Notation Web Token استانداردی است که در RFC7519 تعریف شده است. این استاندارد مانند همه بوده است RFC
استانداردهای (Request For Comments) تعریف شده، نوشته و منتشر شده است IETF
(گروه ویژه مهندسی اینترنت). می توان آن را به چند روش تعریف کرد. به طور کلی می توان گفت که JWT
یک شکل فشرده و ایمن برای انتقال ادعاها بین دو طرف است. یکی از راههای سادهسازی ادعا، اساساً توصیف آن به عنوان یک جفت نام/مقدار که حاوی اطلاعات است. ما برای تضمین چند جنبه مهم ارتباط اینترنتی خود به این اطلاعات نیاز داریم. ما باید مطمئن شویم که اطلاعاتی که دریافت می کنیم در وهله اول معتبر و قابل اعتماد است. سپس باید آن را تأیید کنیم. اساساً همین است.
به منظور پیاده سازی این استاندارد، می توانیم از چندین فریمورک استفاده کنیم که می تواند به ما در پیاده سازی یک برنامه سازمانی جاوا کمک کند. چکمه فنری به طور گسترده استفاده می شود. بسیاری از اوقات نیز تحت نام دیگری در نرم افزارهای خاص از سازمان های خاصی مانند بانک ها و سایر سازمان های مالی پیچیده می شود. برای مثال ما، تصمیم گرفتم کار متفاوتی انجام دهم. بهجای چکمههای بهار، میخواهیم به نمونهای با آن نگاهی بیندازیم KumuluzEE
. نکته این است که دقیقاً چه چیزی را شناسایی کنید JWT
است و به نظر می رسد. برنامه های کاربردی Java Enterprise اساساً برنامه هایی هستند که می توانند در یک سرور برنامه مستقر شوند یا فقط به تنهایی از طریق استفاده از یک سرور تعبیه شده اجرا شوند. به عنوان مثال، برنامه های Spring Boot بر روی سرور Tomcat تعبیه شده اجرا می شوند. در این مقاله تمرکز ما بر روی آن خواهد بود KumuluzEE
. درست مانند Spring Boot، همچنین حاوی یک سرور جاسازی شده است. با این تفاوت که در این مورد جتی نامیده می شود. این در ترکیب با Weld به منظور ارائه CDI (تزریق وابستگی زمینه) استفاده می شود. همه جاوا EE
و Jakarta EE
استانداردهای فناوری با این امر سازگار است framework
.
2. مثال موردی
به منظور مثال زدن چگونه JWT
به شکل اولیهاش کار میکند، باید راهی برای ارائه آن میاندیشیدم. نمونه های کلاسیک که در آن امنیت یک نگرانی است، بانک ها هستند. با این حال، ساخت یک برنامه بانکی کامل برای نشان دادن چگونگی JWT
کارها اتلاف وقت خواهند بود و شاید مفاهیم زیادی درگیر شوند. در عوض آنچه من ساختم یک سیستم بانکی بسیار ساده است. نگرانی اصلی ما این است که نشان دهیم چگونه داده ها از طریق سیم جریان می یابد و چگونه کاربران به مناطق خاصی از برنامه ما دسترسی پیدا می کنند. همچنین قصد ندارم در مورد TLS یا نحوه ارسال اطلاعات رمزگذاری شده از طریق سیم صحبت کنم. ما تمرکز خود را حفظ خواهیم کرد JWT
در خالص ترین شکل آن
مورد ما یک سیستم بانکی است که توسط گروهی مدافع طبیعت و محیط زیست استفاده می شود. این فقط یک راه سرگرم کننده برای نشان دادن چگونگی است JWT
کار می کند. شخصیت اصلی این لیگ طبیعت لوسی است که در تمام مقالات من در حال تبدیل شدن به یک شخصیت رایج است.
3. معماری
قبل از شروع، اجازه دهید فقط برنامه در حال اجرا خود را ترسیم کنیم. این یک برنامه بسیار ساده است، اما کشیدن آن هنوز هم چیز خوبی است:
دلیل این که این خیلی ساده است این است که از آنجا JWT
در هر درخواست بررسی می شود و هر درخواست در برابر کلید عمومی تأیید می شود، سپس می دانیم که تا زمانی که رمز صحیح را برای هر درخواست ارسال کنیم، قادر به انجام آن خواهیم بود. JWT
را می توان با OAuth2، Okta SSO یا هر مکانیزم مجوز دیگری ادغام کرد. در این مورد، کاری که ما انجام می دهیم، احراز هویت و مجوز است. در برنامه ما، ما می خواهیم استفاده کنیم JWT
و با آن، پیام ما را با استفاده از یک امضا تأیید اعتبار کنید. هر چند ما وارد برنامه نمی شویم. در عوض، ما به کاربران اجازه می دهیم تا پس از احراز هویت موفق، از برنامه ما استفاده کنند. در این مرحله، دیدن آن آسان است JWT
در هسته آن در واقع بخش بسیار کوچکی از یک برنامه کامل است. با این حال، برخی از قابلیت ها باید اضافه شوند. اینها منابعی هستند که ما نیاز داریم:
- سیستم تعادل
- سیستم اعتباری
فقط بگوییم که سیستم پایه ما فقط درخواست های پول و اعتبار را ثبت می کند. اساساً فقط مقادیر را جمع می کند. بیایید همچنین فرض کنیم که برخی افراد می توانند اعتبار دریافت کنند و برخی دیگر نمی توانند. برخی از افراد می توانند پول ذخیره کنند و افراد دیگر می توانند اعتبار دریافت کنند.
4. انتخاب فن آوری ها
همانطور که در مقدمه ذکر شد، استفاده خواهیم کرد KumuluzEE
به عنوان چارچوب برنامه سازمانی ما، و ما یک برنامه کاربردی فوقالعاده را به گونهای پیادهسازی خواهیم کرد که بتوانیم به اصول اولیه نگاه کنیم JWT
اصطلاحات و مفاهیم
مطمئن شوید که نسخه جاوا صحیح را دارید. در این مرحله به حداقل جاوا 17 SDK نیاز داریم. ما به maven، git، یک IDE سازگار با جاوا مانند IntelliJ و یک پوسته نیاز داریم.
5. راه اندازی
برای شروع برنامه ما، چند مورد داریم KumuluzEE
وابستگی ها این عمدتا به این دلیل است KumuluzEE
درست مانند Spring Boot به چند وابستگی نیاز دارد. اجازه دهید نگاهی کوتاه به فایل POM بیندازیم:
com.kumuluz.ee.openapi
kumuluzee-openapi-mp
com.kumuluz.ee.openapi
kumuluzee-openapi-mp-ui
com.kumuluz.ee
kumuluzee-microProfile-3.3
ch.qos.logback
logback-core
ch.qos.logback
logback-classic
org.jetbrains.kotlin
kotlin-stdlib
org.assertj
assertj-core
test
org.junit.jupiter
junit-jupiter
test
io.mockk
mockk-jvm
test
com.ninja-squad
springmockk
test
io.kotest
kotest-assertions-core-jvm
test
بیایید به طور خلاصه در مورد چند وابستگی بحث کنیم. با خواندن این مطلب، لطفا ما را دنبال کنید pom.xml
فایل از بالا به پایین این برای درک توضیح زیر مهم است.
ما به بستهای از وابستگیها نیاز داریم تا بتوانیم برنامه کاربردی خود را عملی کنیم. ، خوشبختانه، KumuluzEE
، کتابخانه های Microprofile را در اختیار ما قرار می دهد که حاوی بسته های استاندارد اولیه برای شروع این برنامه است. این همه در موجود است KumuluzEE
-کتابخانه میکروپروفایل برای اینکه بتوانیم برنامه خود را با تمام موارد پیکربندی کنیم JWT
پارامترهایی که نیاز داریم، باید یک کتابخانه MicroProfile به آن اضافه کنیم. در عین حال، ما به یک کتابخانه پردازش JSON نیاز داریم. این همان کاری است که جانسون کور انجام می دهد. ما البته به هسته اصلی نیاز داریم KumuluzEE
برای کار کردن Jetty سرور اصلی است که سرور را اجرا می کند KumuluzEE
چارچوب به همین دلیل است که ما در وابستگی های خود به آن نیاز داریم. با توجه به نیاز ما CDI
، ما همچنین به کتابخانه ای نیاز داریم که از آن پشتیبانی کند. برای فعال کردن نقاط پایانی REST، به کتابخانه بقیه نیاز داریم KumuluzEE
. برای اینکه API خود را بدست آوریم، به یک کتابخانه Geronimo نیاز داریم. این تضمین می کند که ما اجرای آن را داریم JSR-374
موجود است. ما همچنین نیاز به تفسیر خود داریم JWT
و آن JSON-formatted
مطالب
لومبوک فی نفسه واقعا مورد نیاز نیست. این فقط همه چیز را زیبا و براق می کند! داشتن Logback نیز مهم است تا بتوانیم گزارشها را بهتر تفسیر کنیم و نتایج خود را درک کنیم.
حالا بیایید نگاهی به ما بیندازیم resources
پوشه
برای شروع، اجازه دهید ابتدا آنچه را که انتظار داریم در این پوشه پیدا کنیم، درک کنیم. ما باید برنامه خود را با چیزی مرتبط با آن پیکربندی کنیم JWT
، Logback و در نهایت، باید در مورد لوبیاهایی که قرار است بسازیم چیزی بگوییم.
بیایید ساده ترین فایل را در آنجا بررسی کنیم. beans.xml را می توان در META-INF یافت:
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
xmlns:weld="http://jboss.org/schema/weld/beans"
bean-discovery-mode="all">
name="org.jesperancinha.fintech.model.Accounts"/>
این فقط یک فایل معمولی و همانطور که ممکن است اکنون فکر می کنید، کمی یک فایل قدیمی است. در این مرحله، ایده فقط به دست آوردن است KumuluzEE
در حال اجرا ما یک اقدام حذف داریم. این به Weld میگوید که در اسکن کردن برای عملکرد beans، کلاس Accounts را در نظر نگیرد. این مهم است زیرا با پیاده سازی که استفاده می کنیم، Weld
اساساً هر کلاس با سازنده خالی را به عنوان یک لوبیا در نظر می گیرد. بعداً خواهیم دید که چرا نمیخواهیم حسابها به عنوان یک لوبیا در نظر گرفته شوند. در حال حاضر، به خاطر داشته باشید که ما در حال ارائه درخواستهایی تحت محدوده درخواست هستیم. این منطقی است زیرا هر درخواست می تواند کاربر متفاوتی داشته باشد.
حالا ببینیم چطور”logback
” اجرا می شود. همچنین در یافت می شود META-INF
:
name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
level="INFO">
ref="STDOUT"/>
این فقط یک پیکربندی بسیار ساده برای ما است logs
.
در نهایت، شاید مهمترین فایل برنامه ما باشد. این قالب پیکربندی است. در این مرحله، ذکر این نکته ضروری است که برخی از فایلهایی که در این پروژه ایجاد کردهام بخشی از ساختار قالب هستند. بعداً در مورد آن بیشتر توضیح خواهم داد. این فایل قالب قرار است به یک فایل config.yml تبدیل شود که توسط MicroProfile خوانده می شود. این فایل در ریشه منابع قرار دارد:
kumuluzee:
name: your-financeje-banking
version: 1.0.0
jwt-auth:
public-key: {{ publicKey }}
issuer: {{ issuer }}
healthy: true
بعداً خواهیم دید که دقیقاً همه این ویژگی ها به چه معنا هستند. همه آنها خود توضیحی هستند. کلید عمومی و صادرکننده همه پارامترهایی هستند که جایگزین خواهند شد. در ادامه آن را بررسی خواهیم کرد. اسکریپت های bash ما مطمئن می شوند که جایگزین می شوند.
ما تقریباً آماده کدنویسی هستیم، اما ابتدا اجازه دهید نگاهی به برنامه خود بیندازیم JWT
ساختار نشانه
6. کد عملی
بیایید برنامه بسیار کوچک خود را بسازیم. این بخش توضیح می دهد که چگونه می توانیم برنامه خود را با آن کار کنیم JWT
. چیزی که میخواهیم ببینیم این است که آیا میتوانیم کاربرانی را برای دسترسی به برخی از ما مشخص کنیم REST
روش ها و نه دیگران
یکی از راههای شروع به بررسی این کد این است که ابتدا نگاهی به دشت خود بیندازید JWT
نشانه این مثال مدیر ما است:
{
"iss": "joaofilipesabinoesperancinha",
"jti": "01MASTERFINANCE",
"sub": "admin",
"aud": "nature",
"upn": "admin",
"groups": [
"user",
"admin",
"client",
"credit"
],
"user_id": 1,
"access": "TOP",
"name": "Admin"
}
هر یک از این نام ها در ما JSON
به عنوان ادعا نامیده می شود. در مثال ما، چند ادعای Reserved را می بینیم:
- “
iss
” — این صادر کننده نشانه است. ما می توانیم خودسرانه مقداری را برای این انتخاب کنیم. مقدار این پارامتر باید با متغیر صادرکننده مطابقت داشته باشد تا در config.yml که قبلا دیده ایم جایگزین شود. - “
jti
” — این یک شناسه منحصربهفرد برای رمز است. برای مثال میتوانیم از این ادعا برای جلوگیری از استفاده دو بار یا بیشتر از یک توکن استفاده کنیم. - “
sub
” — این موضوع توکن است. می تواند کاربر یا هر چیزی باشد که ما دوست داریم. مهم است به خاطر داشته باشید که می تواند به عنوان یک شناسه، کلید، نام گذاری یا هر چیزی که می خواهیم نیز استفاده شود. - “
upn
” — نام اصلی کاربر. این برای شناسایی اصلی مورد استفاده کاربر استفاده می شود. - “
groups
” — این آرایه ای از گروه هایی است که کاربر فعلی به آنها تعلق دارد. اساساً این مشخص می کند که یک درخواست با این توکن چه کاری می تواند انجام دهد. سپس در توکن ما، چند ادعای سفارشی را مشاهده می کنیم. ما می توانیم از این نیز به خوبی Reserved استفاده کنیم. ادعا می کند - “
user_id
” — ما از این برای تنظیم شناسه کاربر استفاده خواهیم کرد. - “
access
” — ما سطح دسترسی کاربر را تعیین می کنیم. - “
name
” – نام کاربر.
7. کد عملی
بیایید خلاصه ای از آنچه را که تا به حال می دانیم بسازیم. ما می دانیم که با توکن ها با ساختاری که تعیین کرده ایم ارتباط برقرار خواهیم کرد. علاوه بر این، ما پیکربندی برنامه خود، پیکربندی logback را تنظیم کردهایم و در نهایت، یک پیکربندی سفارشی برای جستجوی bean سازمانی تنظیم کردهایم.
بیایید مدل بسته بندی را بررسی کنیم. در اینجا ما 3 کلاس را پیدا خواهیم کرد. این کلاسها اساساً فقط نشاندهنده تجمیع حسابها و نمایندگی بین آنها هستند client
و account
. به این ترتیب می توانیم با مشاهده فایل kotlin Model.kt Where شروع کنیم Client
واقع در:
data class Client constructor(
@JsonProperty
var name: String ?= null
)
این کلاس مدل اول نماینده مشتری ما است. ما client
برای مورد ما فقط یک نام دارد. این نام کاربری است که توسط “jwt
“نام ویژگی.
علاوه بر این، ما داریم Account
:
data class Account(
@JsonProperty
val accountNumber: String?,
@JsonProperty
val client: Client? = null,
@JsonProperty
var currentValue: BigDecimal = BigDecimal.ZERO,
@JsonProperty
var creditValue: BigDecimal = BigDecimal.ZERO
) {
fun addCurrentValue(value: Long) = Account(
accountNumber, client, currentValue
.add(BigDecimal.valueOf(value)), creditValue
)
fun addCreditValue(value: Long): Account = Account(
accountNumber, client, currentValue, currentValue
.add(BigDecimal.valueOf(value))
)
}
در این کلاس، ما اساسا یک accountNumber، یک مشتری، یک currentValue و در نهایت یک creditValue را تنظیم می کنیم. توجه داشته باشید که ما همه مقادیر را روی 0 پیش فرض قرار می دهیم. ما همچنین از BigDecimal استفاده می کنیم، صرفاً به این دلیل که با پول سروکار داریم. پول باید دقیق باشد و نمی تواند متحمل جمع شدن یا ریزش سیستم شود. به عبارت دیگر و به عنوان مثال، عددی مانند 0.0000000000000000000000000000000000000000000000000001
یورو باید همیشه این عدد باقی بماند. همچنین، میخواهیم مقادیری را به حساب خود اضافه کنیم. اینجاست که متد addCurrentValue وجود دارد. به همین دلایل، ما نیز اعتبار خود را با addCreditValue
.
در نهایت، در آخرین قطعه از تنظیمات داده ما با کلاس مواجه می شویم Accounts
:
open class Accounts constructor(
open val accountMap: MutableMap<String, Account> = mutableMapOf()
)
این اساساً فقط یک جمعآوری کننده همه حسابهای ما است. ما از محتوای نقشه آن برای تقلید از رفتار یک پایگاه داده استفاده خواهیم کرد.
حالا بیایید به بسته کنترلر نگاه کنیم. این جایی است که ما برنامه خود را در حال اجرا با مدل داده خود ایجاد می کنیم. ابتدا بیایید نگاهی به کلاس بیندازیم BankApplication
:
@LoginConfig(authMethod = "MP-JWT")
@ApplicationPath("https://dev.to/")
@DeclareRoles("admin", "creditor", "client", "user")
class BankApplication : Application()
با این 3 نکته مهم را می گوییم. با حاشیه نویسی LoginConfig، آن را برای استفاده و درک تعریف می کنیم JWT
توکن ها طبق MicroProfile. ApplicationPath ریشه برنامه را تعریف می کند. اینجاست که URL برنامه شروع می شود. در مثال ما، HTTP://localhost:8080 خواهد بود. در نهایت، DeclareRoles نقش هایی را که قرار است مورد استفاده قرار گیرد و توسط برنامه ما پذیرفته شود را تعریف می کند. نقش ها و گروه ها در این شرایط اصطلاحات قابل تعویض هستند.
برای اینکه تزریق به طور موثر کار کند، یک حاشیه نویسی خاص برای شناسایی نقشه حساب ایجاد می کنیم:
annotation class AccountsProduct
در مرحله بعد، یک AccountsFactory کارخانه شی کش ایجاد می کنیم:
class AccountsFactory : Serializable {
@Produces
@AccountsProduct
@ApplicationScoped
fun accounts(): Accounts = Accounts(mutableMapOf())
companion object {
@Throws(JsonProcessingException::class)
fun createResponse(
currentAccount: Account,
name: JsonString,
accounts: Accounts,
log: Logger,
objectMapper: ObjectMapper,
principal: Principal?,
jsonWebToken: JsonWebToken?
): Response {
val jsonObject = Json.createObjectBuilder()
.add("balance", currentAccount.currentValue)
.add("client", name)
.build()
accounts.accountMap[name.string] = currentAccount
log.info("Principal: {}", objectMapper.writeValueAsString(principal))
log.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken))
return Response.ok(jsonObject)
.build()
}
}
}
این کارخانه دلیلی است که ما جستجو را به طور خاص برای آن غیرفعال کردیم Accounts
. به جای اینکه به فرآیند جستجو اجازه دهیم یک bean ایجاد کند، نمونه جمعآوری را خودمان ایجاد میکنیم. استفاده از حاشیه نویسی Produces، به ما اجازه می دهد که لوبیا را ایجاد کنیم. با استفاده از حاشیه نویسی سفارشی خود، AccountsProduct، استفاده از این لوبیا را خاص تر می کنیم. در نهایت با استفاده از ApplicationScoped
، محدوده آن را به عنوان بودن تعریف می کنیم Application
دامنه. به عبارت دیگر، لوبیای جمعآوری حساب بهعنوان یک شی تکتنه در سراسر برنامه عمل میکند.
“createResponse
“فقط یک روش عمومی برای ایجاد پاسخ های JSON است.
آنچه اکنون به آن نیاز داریم دو “منبع” است. این در اصل همان است که “Controllers
“در بهار. نام متفاوتی است، اما دقیقاً همان کاربرد را دارد.
بیایید نگاه کنیم AccountsResource
کلاس:
@Path("accounts")
@RequestScoped
@Produces(MediaType.APPLICATION_JSON)
open class AccountResource {
@Inject
@AccountsProduct
open var accounts: Accounts? = null
@Inject
open var principal: Principal? = null
@Inject
open var jsonWebToken: JsonWebToken? = null
@Inject
@Claim("access")
open var access: JsonString? = null
@Claim("iat")
@Inject
open var iat: JsonNumber? = null
@Inject
@Claim("name")
open var name: JsonString? = null
@Inject
@Claim("user_id")
open var userId: JsonNumber? = null
@POST
@RolesAllowed("admin", "client", "credit")
@Throws(JsonProcessingException::class)
open fun createAccount(): Response = createResponse(
requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account(
client = Client(name = requireNotNull(name).string),
accountNumber = UUID.randomUUID().toString()
)
)
@POST
@RolesAllowed("admin", "user")
@Path("user")
@Throws(JsonProcessingException::class)
open fun createUser(): Response {
return createResponse(
requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account(
client = Client(name = requireNotNull(name).string),
accountNumber = UUID.randomUUID().toString()
)
)
}
@GET
@RolesAllowed("admin", "client")
@Throws(JsonProcessingException::class)
open fun getAccount(): Response? {
return createResponse(
requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: return Response.serverError()
.build()
)
}
@PUT
@RolesAllowed("admin", "client")
@Consumes(MediaType.APPLICATION_JSON)
@Throws(
JsonProcessingException::class
)
open fun cashIn(transactionBody: TransactionBody): Response? {
val userAccount =
requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: return Response.serverError()
.build()
val currentAccount = userAccount.addCurrentValue(transactionBody.saldo?: 0)
requireNotNull(accounts).accountMap[requireNotNull(name).string] = currentAccount
return createResponse(currentAccount)
}
@GET
@Path("all")
@Produces(MediaType.APPLICATION_JSON)
@Throws(
JsonProcessingException::class
)
open fun getAll(): Response? {
val allAccounts = ArrayList(
requireNotNull(accounts).accountMap
.values
)
logger.info("Principal: {}", objectMapper.writeValueAsString(principal))
logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken))
return Response.ok(allAccounts)
.build()
}
@GET
@Path("summary")
@Throws(JsonProcessingException::class)
open fun getSummary(): Response? {
val totalCredit = requireNotNull(accounts).accountMap
.values
.map(Account::currentValue)
.stream()
.reduce { result, u -> result.add(u) }
.orElse(BigDecimal.ZERO)
val jsonObject = Json.createObjectBuilder()
.add("totalCurrent", totalCredit)
.add("client", "Mother Nature Dream Team")
.build()
logger.info("Summary")
logger.info("Principal: {}", objectMapper.writeValueAsString(principal))
logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken))
return Response.ok(jsonObject)
.build()
}
@GET
@RolesAllowed("admin", "client")
@Path("jwt")
open fun getJWT(): Response? {
val jsonObject = Json.createObjectBuilder()
.add("jwt", requireNotNull(jsonWebToken).rawToken)
.add("userId", requireNotNull(userId).doubleValue())
.add("access", requireNotNull(access).string)
.add("iat", requireNotNull(iat).doubleValue())
.build()
return Response.ok(jsonObject)
.build()
}
@Throws(JsonProcessingException::class)
private fun createResponse(currentAccount: Account): Response =
AccountsFactory.createResponse(
currentAccount,
requireNotNull(name),
requireNotNull(accounts),
logger,
objectMapper,
principal,
jsonWebToken
)
companion object {
val objectMapper: ObjectMapper = ObjectMapper()
val logger: Logger = LoggerFactory.getLogger(AccountResource::class.java)
}
}
کمی وقت بگذارید و با جزئیات بیشتر به این کلاس نگاه کنید. این Path
annotation نحوه دستیابی به این منبع را از ریشه تعریف می کند. به یاد داشته باشید که ما از “https://dev.to/” به عنوان ریشه استفاده می کنیم. در این مورد، “حساب ها” نقطه دسترسی ریشه ما برای این منبع است. همه منابع ما، در مورد ما فقط دو مورد با scope RequestResource در حال اجرا هستند. با حاشیهنویسی تولید میکند که تمام پاسخها به همه درخواستها بدون در نظر گرفتن نوع آنها به شکل پیامهای فرمتشده JSON باشد.
برای تزریق ما aggregator
ما فقط از ترکیب حاشیه نویسی Inject و استفاده می کنیم AccountsProduct
حاشیه نویسی:
@Inject
@AccountsProduct
open var accounts: Accounts? = null
این با آنچه ما در کارخانه تعریف کردیم مطابقت دارد.
علاوه بر این، ما دو عنصر مهم امنیت را نیز تزریق می کنیم. الف principal
و jsonWebToken
:
@Inject
open var principal: Principal? = null
@Inject
open var jsonWebToken: JsonWebToken? = null
هر دو JsonWebToken
و Principal
همینطور خواهد بود و ما آن را در گزارش های خود خواهیم دید.
در منابع خود، ما همیشه میتوانیم ادعاهایی را از یک درخواست با یک توکن خاص تزریق کنیم:
@Inject
@Claim("name")
open var name: JsonString? = null
@Inject
@Claim("user_id")
open var userId: JsonNumber? = null
این با ترکیبی از Inject
و Claim
حاشیه نویسی نام قرار داده شده در زیر Claim
annotation تعریف می کند که کدام ادعا را می خواهیم تزریق کنیم. ما باید مراقب نوع تعریف پارامترهایمان باشیم. در مثال ما فقط به r نیاز داریم JsonString
و JsonNumber
انواع
ابتدا، بیایید نحوه ایجاد حسابها و کاربران را بررسی کنیم:
@POST
@RolesAllowed("admin", "client", "credit")
@Throws(JsonProcessingException::class)
open fun createAccount(): Response = createResponse(
requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account(
client = Client(name = requireNotNull(name).string),
accountNumber = UUID.randomUUID().toString()
)
)
@POST
@RolesAllowed("admin", "user")
@Path("user")
@Throws(JsonProcessingException::class)
open fun createUser(): Response {
return createResponse(
requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account(
client = Client(name = requireNotNull(name).string),
accountNumber = UUID.randomUUID().toString()
)
)
}
ایجاد اکانت و کاربران
هدف در اینجا این است که بتوانیم متدها را از هم جدا کنیم و به آنها مجوزهای مختلفی بدهیم. در مثال ما، هر دو فقط یک حساب ایجاد می کنند، اما توجه به این نکته مهم است که فقط کاربران دارای نقش کاربر می توانند از متد createUser استفاده کنند. به همین ترتیب، فقط کاربران با نقش مشتری و اعتبار می توانند به روش createAccount دسترسی داشته باشند.
بیایید اکنون به طور مفصل به روش درخواست PUT این منبع نگاه کنیم:
@PUT
@RolesAllowed("admin", "client")
@Consumes(MediaType.APPLICATION_JSON)
@Throws(
JsonProcessingException::class
)
open fun cashIn(transactionBody: TransactionBody): Response? {
val userAccount =
requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: return Response.serverError()
.build()
val currentAccount = userAccount.addCurrentValue(transactionBody.saldo?: 0)
requireNotNull(accounts).accountMap[requireNotNull(name).string] = currentAccount
return createResponse(currentAccount)
}
نقدینگی
ما آن حاشیه نویسی را می دانیم PUT
نشان می دهد که این روش فقط با درخواست های نوع قابل دسترسی است PUT
. سپس Annotation Path به Jetty می گوید که مسیر این روش یک مقدار است. این نیز به عنوان شناخته شده است PathParam
. در نهایت، میتوانیم تعریف کنیم که این روش فقط برای کاربرانی که نقشهای مدیر یا مشتری دارند مجاز باشد. سپس مقدار ورودی با استفاده از PathParam به متغیر Long مقدار ما منتقل می شود.
اگر هیچ نقشی تعریف نکنیم، هر کاربری که توکن مناسبی داشته باشد میتواند به این روشها دسترسی داشته باشد.
این CreditResource
به همین ترتیب اجرا می شود:
@Path("credit")
@RequestScoped
@Produces(MediaType.APPLICATION_JSON)
open class CreditResource {
@Inject
@AccountsProduct
open var accounts: Accounts? = null
@Inject
open var principal: Principal? = null
@Inject
open var jsonWebToken: JsonWebToken? = null
@Inject
@Claim("access")
open var access: JsonString? = null
@Inject
@Claim("iat")
open var iat: JsonNumber? = null
@Inject
@Claim("name")
open var name: JsonString? = null
@Inject
@Claim("user_id")
open var userId: JsonNumber? = null
@GET
@RolesAllowed("admin", "credit")
@Throws(JsonProcessingException::class)
open fun getAccount(): Response = requireNotNull(accounts).let { accounts ->
createResponse(
accounts.accountMap[requireNotNull(name).string] ?: return Response.serverError().build()
)
}
@PUT
@RolesAllowed("admin", "credit")
@Consumes(MediaType.APPLICATION_JSON)
@Throws(
JsonProcessingException::class
)
open fun cashIn(transactionBody: TransactionBody) = requireNotNull(accounts).let { accounts ->
requireNotNull(name).let { name ->
accounts.accountMap[name.string] = (accounts.accountMap[name.string] ?: return Response.serverError()
.build()).addCreditValue(transactionBody.saldo?: 0L)
createResponse(
(accounts.accountMap[name.string] ?: return Response.serverError()
.build()).addCreditValue(transactionBody.saldo?: 0L)
)
}
}
@GET
@Path("all")
@Produces(MediaType.APPLICATION_JSON)
@Throws(
JsonProcessingException::class
)
open fun getAll(): Response? {
val allAccounts = ArrayList(
requireNotNull(accounts).accountMap
.values
)
logger.info("Principal: {}", objectMapper.writeValueAsString(principal))
logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken))
return Response.ok(allAccounts)
.build()
}
@GET
@Path("summary")
@Produces(MediaType.APPLICATION_JSON)
@Throws(
JsonProcessingException::class
)
open fun getSummary(): Response? {
val totalCredit = requireNotNull(accounts).accountMap
.values
.map(Account::creditValue)
.stream()
.reduce { total, v -> total.add(v) }
.orElse(BigDecimal.ZERO)
val jsonObject = Json.createObjectBuilder()
.add("totalCredit", totalCredit)
.add("client", "Mother Nature Dream Team")
.build()
logger.info("Summary")
logger.info("Principal: {}", objectMapper.writeValueAsString(principal))
logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken))
return Response.ok(jsonObject)
.build()
}
@GET
@RolesAllowed("admin", "client")
@Path("jwt")
open fun getJWT(): Response? {
val jsonObject = Json.createObjectBuilder()
.add("jwt", requireNotNull(jsonWebToken).rawToken)
.add("userId", requireNotNull(userId).doubleValue())
.add("access", requireNotNull(access).string)
.add("iat", requireNotNull(iat).doubleValue())
.build()
return Response.ok(jsonObject)
.build()
}
@Throws(JsonProcessingException::class)
private fun createResponse(currentAccount: Account): Response {
return AccountsFactory.createResponse(
currentAccount,
requireNotNull(name),
requireNotNull(accounts),
logger,
objectMapper,
principal,
jsonWebToken
)
}
companion object {
val objectMapper: ObjectMapper = ObjectMapper()
val logger: Logger = LoggerFactory.getLogger(CreditResource::class.java)
}
}
تنها تفاوت این است که به جای استفاده از نقش ها admin
و client
ما در حال حاضر استفاده می کنیم admin
و credit
نقش ها همچنین توجه داشته باشید که هیچ گاه حساب کاربری در این مورد ایجاد نخواهد شد resource
. این فقط از طریق حساب کاربری امکان پذیر است resource
.
اکنون که میدانیم کد چگونه پیادهسازی میشود، ابتدا مرور میکنیم که کدام روشها را در خود در دسترس قرار دادهایم REST
خدمات
8. استفاده از برنامه
بیایید لیست خدمات مورد استفاده را بررسی کنیم:
نوع، URL، بارگیری، نتیجه، نقشهای مجاز
POST,http://localhost:8080/accounts,n/a,Created account,admin/client/credit
POST,http://localhost:8080/accounts/user,n/a,Created user,admin/user
GET,http://localhost:8080/accounts,n/a,حساب منطبق, admin/client
PUT,http://localhost:8080/accounts,{saldo: Long}, موجودی فعلی, admin/client
GET,http://localhost:8080/accounts/all,n/a,همه حسابهای جاری,همه
GET,http://localhost:8080/accounts/summary,n/a,مجموع همه موجودی ها,همه
GET,http://localhost:8080/credit,n/a,حساب منطبق, admin/client
PUT,http://localhost:8080/credit,{saldo: Long}, اعتبار فعلی, admin/client
GET,http://localhost:8080/credit/all,n/a,همه اعتبارات,همه
GET,http://localhost:8080/credit/summary,n/a,جمع اعتبارات,همه
همه نقاط پایانی
9. ایجاد محیط تست
من ایجاد کرده ام bash
فایل در پوشه ریشه این فایل “setupCertificates.sh” نام دارد. بیایید نگاهی به آن بیندازیم تا ایده ای از آنچه انجام می دهد داشته باشیم:
#!/bin/bash
mkdir -ص فایل های مالی شما
سی دی فایل های مالی شما || خروج
openssl genrsa -خارج baseKey.pem openssl pkcs8 -توپ 8 - اطلاع رسانی PEM -در baseKey.pem -خارج privateKey.pem -نوکریپت
openssl rsa -در baseKey.pem -فروشگاه - چیدمان PEM -خارج publicKey.pem
اکو -e '\033[1;32mFirst test\033[0m'
java -jar ../your-finance-jwt-generator/target/your-finance-jwt-generator.jar \
-p ../jwt-plain-tokens/jwt-token-admin.json \
-key ../your-finance-files/privateKey.pem >> token.jwt
CERT_PUBLIC_KEY=$(cat ../your-finance-files/publicKey.pem)
CERT_ISSUER="joaofilipesabinoesperancinha"
echo -e "\e[96mGenerated public key: \e[0m $CERT_PUBLIC_KEY"
echo -e "\e[96mIssued by: \e[0m $CERT_ISSUER"
echo -e "\e[96mYour token is: \e[0m $(cat token.jwt)"
cp ../your-financeje-banking/src/main/resources/config-template ../your-financeje-banking/src/main/resources/config_copy.yml
CERT_CLEAN0=${CERT_PUBLIC_KEY//"https://dev.to/"/"\/"}
CERT_CLEAN1=${CERT_CLEAN0//$'\r\n'/}
CERT_CLEAN2=${CERT_CLEAN1//$'\n'/}
CERT_CLEAN3=$(echo "$CERT_CLEAN2" | awk '{gsub("-----BEGIN PUBLIC KEY-----",""); print}')
CERT_CLEAN4=$(echo "$CERT_CLEAN3" | awk '{gsub("-----END PUBLIC KEY-----",""); print}')
CERT_CLEAN=${CERT_CLEAN4//$' '/}
echo -e "\e[96mCertificate cleanup: \e[0m ${CERT_CLEAN/$'\n'/}"
sed "s/{{ publicKey }}/$CERT_CLEAN/g" ../your-financeje-banking/src/main/resources/config_copy.yml > ../your-financeje-banking/src/main/resources/config_cert.yml
sed "s/{{ issuer }}/$CERT_ISSUER/g" ../your-financeje-banking/src/main/resources/config_cert.yml > ../your-financeje-banking/src/main/resources/config.yml
rm ../your-financeje-banking/src/main/resources/config_cert.yml
rm ../your-financeje-banking/src/main/resources/config_copy.yml
echo -e "\e[93mSecurity elements completely generated!\e[0m"
echo -e "\e[93mGenerating tokens...\e[0m"
TOKEN_FOLDER=jwt-tokens
mkdir -p ${TOKEN_FOLDER}
#
CREATE_ACCOUNT_FILE=createAccount.sh
CREATE_USER_FILE=createUser.sh
SEND_MONEY_FILE=sendMoney.sh
ASK_CREDIT_FILE=askCredit.sh
TOKEN_NAME_VALUE=tokenNameValue.csv
echo "#!/usr/bin/env bash" > ${CREATE_ACCOUNT_FILE}
chmod +x ${CREATE_ACCOUNT_FILE}
echo "#!/usr/bin/env bash" > ${CREATE_USER_FILE}
chmod +x ${CREATE_USER_FILE}
echo "#!/usr/bin/env bash" > ${SEND_MONEY_FILE}
chmod +x ${SEND_MONEY_FILE}
echo "#!/usr/bin/env bash" > ${ASK_CREDIT_FILE}
chmod +x ${ASK_CREDIT_FILE}
for item in ../jwt-plain-tokens/jwt-token*.json; do
if [[ -f "$item" ]]; سپس
نام فایل=${مورد##*/}
در هر_توکن=${نام فایل/jwt-token-/}
token_name=${در هر_توکن/.json/}
cp "${مورد}" jwt-token.json جاوا -کوزه ../Your-Finance-jwt-generator/target/Your-Finance-jwt-generator.jar \
-ص jwt-token.json \
-کلید ../your-finance-files/privateKey.pem > token.jwt
cp token.jwt ${TOKEN_FOLDER}/توکن-"${token_name}".jwt
نشانه=$(گربه token.jwt)
اکو "# ایجاد حساب کاربری: ""${token_name}" >> ${CREATE_ACCOUNT_FILE}
اکو "پژواک -e \"\e[93mCreating account \e[96m${token_name}\e[0m\"" >> ${CREATE_ACCOUNT_FILE}
echo curl -i -H"'Authorization: Bearer ""${token}""'" http://localhost:8080/accounts -X POST >> ${CREATE_ACCOUNT_FILE}
echo "echo -e \"\e[93m\n---\e[0m\"" >> ${CREATE_ACCOUNT_FILE}
echo "# Create user: ""${token_name}" >> ${CREATE_USER_FILE}
echo "echo -e \"\e[93mCreating user \e[96m${token_name}\e[0m\"" >> ${CREATE_USER_FILE}
echo curl -i -H"'Authorization: Bearer ""${token}""'" http://localhost:8080/accounts/user -X POST >> ${CREATE_USER_FILE}
echo "echo -e \"\e[93m\n---\e[0m\"" >> ${CREATE_USER_FILE}
echo "# Send money to: "${token_name} >> ${SEND_MONEY_FILE}
echo "echo -e \"\e[93mSending money to \e[96m${token_name}\e[0m\"" >> ${SEND_MONEY_FILE}
echo curl -i -H"'Content-Type: application/json'" -H"'Authorization: Bearer ""${token}""'" http://localhost:8080/accounts -X PUT -d "'{ \"saldo\": "$((1 + RANDOM % 500))"}'" >> ${SEND_MONEY_FILE}
echo "echo -e \"\e[93m\n---\e[0m\"" >> ${SEND_MONEY_FILE}
echo "# Asking money credit to: "${token_name} >> ${ASK_CREDIT_FILE}
echo "echo -e \"\e[93mAsking credit from \e[96m${token_name}\e[0m\"" >> ${ASK_CREDIT_FILE}
echo curl -i -H"'Content-Type: application/json'" -H"'Authorization: Bearer ""${token}""'" http://localhost:8080/credit -X PUT -d "'{ \"saldo\": "$((1 + RANDOM % 500))"}'">> ${ASK_CREDIT_FILE}
echo "echo -e \"\e[93m\n---\e[0m\"" >> ${ASK_CREDIT_FILE}
echo "${token_name},${token}" >> ${TOKEN_NAME_VALUE}
fi
done
Environment generation
Please follow the file as I explain what it does. This is important so that we understand exactly what it is doing. We first create private and public keys in a PEM
format. We then use the private key with our runnable “your-finance-jwt-generator.jar” . This is our runnable jar which allows for the quick creation of tokens. The issuer cannot be changed later on. Finally, it creates a token. We will see how to read this token later on. This token contains 3 extra Header claims. These are “kid”, “typ”, and “alg”. It follows the following format:
{
"kid": "jwt.key",
"typ": "JWT",
"alg": "RS256"
}
The header of the JWT
Let’s look at these claims more closely:
- “kid” — Works as a hint claim. It indicates which sort of algorithm we are using.
- “typ” — It is used to declare
IANA
media types. There are three optionsJWT
(JSON Web token),JWE
(JSON Web Encryption), andJWA
(JSON Web Algorithms). These types aren’t relevant to our experiment. We will only see that our token isn’t really well encrypted and that it’s really easy to decrypt it. We will also see that although we can decrypt tokens, we cannot that easily tamper the to perform other actions. - “alg” — This is how we define the signature type we want to use. Signing can be considered as a cryptographic operation that will ensure that the original token has not been changed and is trusted. In our case, we are using RS256 otherwise known as RSA Signature with SHA-256.
With our public key, we can finally use it to change our template. The new config.yml file should look something like this:
kumuluzee:
name: your-financeje-banking
version: 1.0.0
jwt-auth:
public-key: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN
issuer: joaofilipesabinoesperancinha
healthy: true
config.yml
The second step is to create four files. For every single plain token in the directory “jwt-plain-tokens
“, we will create four commands. The first command is to create users that can effectively do things with their accounts. These are users with profiles “admin
“, “client
“, and “credit
“.
Let’s run the file “createAccount.sh
“, in order to create them. The second command will create the rest of the users which don’t possess any rights yet. This is the file “createUser.sh”. Let’s run it. Now we’ll see that all users are finally created. Let’s now look into details about transactions and look at the remaining two commands. One to “cashin” and another to ask for more credit. The first generated file is the “sendMoney.sh” bash script. Here we can find all requests to “cashin
“. In this file you’ll find a curl request to send random money quantities to users, per user. Let’s look at the admin case:
#!/usr/bin/env bash
# Send money to: admin
echo -e "\e[93mSending money to \e[96madmin\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer= FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 125}'
echo -e "\e[93m\n---\e[0m"
# Send money to: cindy
echo -e "\e[93mSending money to \e[96mcindy\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 360}'
echo -e "\e[93m\n---\e[0m"
# Send money to: faustina
echo -e "\e[93mSending money to \e[96mfaustina\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 50}'
echo -e "\e[93m\n---\e[0m"
# Send money to: jack
echo -e "\e[93mSending money to \e[96mjack\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 205}'
echo -e "\e[93m\n---\e[0m"
# Send money to: jitska
echo -e "\e[93mSending money to \e[96mjitska\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 332}'
echo -e "\e[93m\n---\e[0m"
# Send money to: judy
echo -e "\e[93mSending money to \e[96mjudy\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 295}'
echo -e "\e[93m\n---\e[0m"
# Send money to: lucy
echo -e "\e[93mSending money to \e[96mlucy\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 160}'
echo -e "\e[93m\n---\e[0m"
# Send money to: malory
echo -e "\e[93mSending money to \e[96mmalory\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 413}'
echo -e "\e[93m\n---\e[0m"
# Send money to: mara
echo -e "\e[93mSending money to \e[96mmara\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 464}'
echo -e "\e[93m\n---\e[0m"
# Send money to: namita
echo -e "\e[93mSending money to \e[96mnamita\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 51}'
echo -e "\e[93m\n---\e[0m"
# Send money to: pietro
echo -e "\e[93mSending money to \e[96mpietro\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 491}'
echo -e "\e[93m\n---\e[0m"
# Send money to: rachelle
echo -e "\e[93mSending money to \e[96mrachelle\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 474}'
echo -e "\e[93m\n---\e[0m"
# Send money to: sandra
echo -e "\e[93mSending money to \e[96msandra\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 417}'
echo -e "\e[93m\n---\e[0m"
# Send money to: shikka
echo -e "\e[93mSending money to \e[96mshikka\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/accounts -X PUT -d '{ "saldo": 64}'
echo -e "\e[93m\n---\e[0m"
sendMoney.sh extract
The same users have also their credit requests assigned to them:
#!/usr/bin/env bash
# Asking money credit to: admin
echo -e "\e[93mAsking credit from \e[96madmin\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 137}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: cindy
echo -e "\e[93mAsking credit from \e[96mcindy\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 117}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: faustina
echo -e "\e[93mAsking credit from \e[96mfaustina\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 217}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: jack
echo -e "\e[93mAsking credit from \e[96mjack\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 291}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: jitska
echo -e "\e[93mAsking credit from \e[96mjitska\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 184}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: judy
echo -e "\e[93mAsking credit from \e[96mjudy\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 388}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: lucy
echo -e "\e[93mAsking credit from \e[96mlucy\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 219}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: malory
echo -e "\e[93mAsking credit from \e[96mmalory\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 66}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: mara
echo -e "\e[93mAsking credit from \e[96mmara\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 441}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: namita
echo -e "\e[93mAsking credit from \e[96mnamita\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 358}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: pietro
echo -e "\e[93mAsking credit from \e[96mpietro\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 432}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: rachelle
echo -e "\e[93mAsking credit from \e[96mrachelle\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 485}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: sandra
echo -e "\e[93mAsking credit from \e[96msandra\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 500}'
echo -e "\e[93m\n---\e[0m"
# Asking money credit to: shikka
echo -e "\e[93mAsking credit from \e[96mshikka\e[0m"
curl -i -H'Content-Type: application/json' -H'Authorization: Bearer FAKE.FAKE.FAKE' http://localhost:8080/credit -X PUT -d '{ "saldo": 89}'
echo -e "\e[93m\n---\e[0m"
askCredit.sh extract
All our characters
are part of the League of Nature
. Essentially just some group of people to be part of this banking system. In this context, they are defending the environment. It’s not really relevant for the article what this group of people does or where in the story they fit in, but for context, they participate in actions to defend the environment and slow down the effects of climate change. Some of our characters
can do everything, others cannot do anything and others can only “cashin” or just “ask for credit”. Also notice that I am obfuscating sensitive information. These tokens normally should not be shared or be visible in on particular URL. They are yes always available via the browser developer console but anyway is to protect
some requests being made. This is a concept known as “security-per-obscurity” and
although it does not technically prevent the user to become aware of the token being used, it does work as a deterrent.
In both methods, when we make a deposit or when we ask for credit, notice that for each request, we are sending a random number between 1 to 500.
We are now almost ready to start our application, but first, let’s take a dive into a bit more theory.
10. How is a JWT
token made
Now that we have generated our tokens, let’s look into one of them. I am going to show you an obfuscated token, and we are going to use that to understand this.
Here is our token:FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE
.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO
.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN
What’s important here to notice is that our token is divided into three parts:
- Header — This is a Base64 encoded JSON configuration header, just as we discussed above.
- Payload — This is a Base64 encoded JSON payload. This is where we defined our Reserved and Custom claims. We can also define here Private and Public claims. Both of them fall under Custom claims. As a quick note, we can do what we want with both of these claims. However public claims are referred to as the ones defined in the IANA JSON Web Token Registry. It is important that we name our tokens in a certain way in order to avoid collisions with the registry. Public claims can also be defined as standard optional. Private claims follow no standard and it’s up to us to define them.
-
Signature — This is where we can be a bit creative. The signature is a cyphered combination of the
Header
and thePayload
. We decide the algorithm we want to use and this bit of the token will basically determine if the message we are sending is to be trusted. It is unique to that combination and our server will use the “public-key” we created to determine if we have a match. If you remember from the above we are usingRS256
in our example.
Before we continue, please note that both the Header
and the Payload
can be decyphered
in our example. We just “can’t” tamper with the payload or the header and still make it trusted. The protection against the potential effects of a malicious token can only be protected by the algorithm we choose. So choose wisely.
If you are working in an organization where top secret information is a concern, such as a bank, please DON’T do what we are about to do. This is only a way for us to check online the content of the tokens we’ve generated locally.
First, let’s go to https://jwt.io/ and fill out our JWT
token. Use the token you’ve just generated:
Using https://jwt.io/ to check the contents of our token
Let’s examine what we have here. This is our administrator token. That person is “Admin” in our example. We can see that our parameters are all available. In our list we see “sub”, “aud”, “upn”, “access”, “user_id”, “iss”, “name”, “groups” and finally the “jti”. We also have some extra claims. Let’s look at them:
“auth_time” — This is when the authentication has happened. Our token as been authenticated on Sunday, 17 July 2022 16:15:47 GMT+02:00 DST
“iat” — This is when the token has been created. In our case, this happens simultaneously as the auth_time.
“exp” — This is the expiry date of the token. It expires on Sunday, 17 July 2022 16:32:27 GMT+02:00 DST. We didn’t specify any expiry date in our token. This means that JWT
uses its default value of ~15 minutes.
Let’s now perform some tests.
11. Running the application
The code is ready to be used on GitHub. If we check the code out and open it with Intellij we need to be aware that we can’t run this application like a Spring Boot application. There is no “psvm” to make it run. Instead, we can just run the generated jar directly and make sure that we make an “mvn build” just before. Here is how I am using it at the moment:
[
حالا بیایید “setupCertificates.sh
“دوباره اسکریپت. نمیدانم چقدر زمان صرف کردهاید تا به اینجا برسید، اما به احتمال بسیار زیاد این 15 دقیقه در این مرحله تمام شده است. در هر صورت، فقط آنها را دوباره اجرا کنید.
بیایید برنامه خود را شروع کنیم!
می توانیم آن را اینگونه شروع کنیم:
mvn clean install
java -jar your-financeje-banking/target/your-financeje-banking.jar
یا فقط میتوانیم آن را از طریق پیکربندی آماده اجرا اجرا کنیم. اگر میخواهید همه چیز را بدانید، مخزن و Makefile را از قبل بررسی کنید:
make dcup-full-action
این اسکریپت 2 سرویس را اجرا خواهد کرد. یکی روی پورت 8080
و دیگری در بندر 8081
. روی پورت 8080
ما نسخهای از این نرمافزار را اجرا میکنیم که کدهای خودمان را تولید میکند JWT
توکن ها در پورت 8081، نسخه ای را با استفاده از jwtknizr
ژنراتور ایجاد شده توسط Adam Bien
. با این حال، ما این مقاله را بر روی سرویسی که در پورت اجرا می شود متمرکز خواهیم کرد 8080
. در صورت تمایل می توانید بدوید cypress
با:
make cypress-open
این خواهد شد open
را cypress
کنسول، و شما می توانید تست ها را با مرورگر مورد نظر خود اجرا کنید. با این حال، گزینه های مرورگر هنوز در این مرحله محدود هستند. اکثر درخواست ها در واقع درخواست های خط فرمان ارائه شده توسط خواهند بود cypress
.
فعلا وارد این موضوع نشویمcypress
لطفاً به مرورگر خود بروید و به این مکان بروید:
http://localhost:8080/accounts/all
باید نتیجه ای مثل این بگیریم:
همانطور که می بینیم، “Malory
“،”Jack Fallout
“، و”Jitska
“هیچ اعتبار یا پولی ندارید. این به این دلیل است که فقط گروه کاربری به آنها داده شده است. به این نکته نیز توجه کنید Shikka
اعتباری داده نشده است “Shikka
“، تنها مشتری ما است که اعتبار گروه را ندارد.
اگر به گزارش ها نگاه کنیم، می بینیم که عملیات موفقیت آمیز این فرمت را دارند:
Sending money to admin
HTTP/1.1 200 OK
Date: Sun, 17 Jul 2022 15:01:13 GMT
X-Powered-By: KumuluzEE/4.1.0
Content-Type: application/json
Content-Length: 32
Server: Jetty(10.0.9)
{"balance":212,"client":"Admin"}
200 به ما اطلاع می دهد که عملیات با موفقیت انجام شد.
در مورد «مالوری»، «جک فالآوت» و «جیتسکا»، هر دو عملیات شکست میخورند و پس از آن، این نوع پیام را دریافت خواهیم کرد:
Sending money to jitska
HTTP/1.1 403 Forbidden
X-Powered-By: KumuluzEE/4.1.0
Content-Length: 0
Server: Jetty(10.0.9)
یک 403 به ما اطلاع می دهد که ما JWT
رمز تأیید شده است و قابل اعتماد است. با این حال، کاربر از انجام آن عملیات منع شده است. به عبارت دیگر، آنها به روش تعیین شده دسترسی ندارند.
بیایید کمی توکن هایمان را دستکاری کنیم. اگر برخی از توکن های فایل sendMoney.sh را تغییر دهیم. ما باید این را دریافت کنیم:
Sending money to admin
HTTP/1.1 401 Unauthorized
X-Powered-By: KumuluzEE/4.1.0
WWW-Authenticate: Bearer realm="MP-JWT"
Content-Length: 0
Server: Jetty(10.0.9)
این 401
به این معنی است که توکن ما اعتبار سنجی نشده است. این بدان معناست که کلید عمومی که سرور برای بررسی اینکه آیا توکن ما قابل اعتماد است یا خیر استفاده میکند، مطابقت پیدا نکرده است. اگر کلید عمومی نتواند امضای توکن JWT را ارزیابی و تأیید کند، آن را رد می کند.
به عنوان خلاصه، سربرگ و “Payload” رمزگذاری نشده اند. آنها فقط پایه 64 “رمزگذاری شده” هستند. این بدان معنی است که “رمزگشایی” به ما امکان می دهد همیشه نگاهی به محموله واقعی داشته باشیم. اگر به دنبال محافظت از محموله خود در برابر استراق سمع هستیم، نباید از “Payload” توکن برای چیز دیگری به جز انتخاب پارامترهای شناسایی استفاده کنیم. مشکل واقعاً زمانی است که کسی دستش را می گیرد JWT
به عنوان مثال، زمانی که تونل TLS در معرض خطر قرار گرفته باشد و کسی بتواند محتوای پیام های مبادله شده را بخواند. وقتی این اتفاق بیفتد، باز هم حفاظت دیگری وجود دارد. و این امضا است. تنها سروری که میتواند یک پیام را تأیید کند، سروری است که حاوی کلید عمومی است. این کلید عمومی، اگرچه عمومی است، اما فقط با اجرا کردن در مقابل امضا و “Header + Payload” اجازه اعتبارسنجی پیام دریافتی را می دهد.
12. نتیجه گیری
ما به پایان جلسه خود رسیده ایم. از اینکه این را دنبال کردید متشکرم
ما می توانیم ببینیم که چگونه JWT
توکن ها جمع و جور هستند و بسیار کمتر از همتای XML خود، یعنی SAML
توکن ها ما دیدهایم که ایجاد و استفاده از توکنها برای دریافت مجوزهای خاص مورد نیاز برای روشهای خاص چقدر آسان است و چگونه از طریق یک توکن امضا شده به آنجا میرسیم.
با این حال، برای من بسیار مهم است که ایده ای در مورد چگونگی آن داشته باشم JWT
کار می کند. امیدوارم با این کار به شما معرفی خوبی در مورد نحوه انجام آن داده باشم JWT
توکن ها کار می کنند
برای دریافت ایده بهتر از نحوه عملکرد همه اینها، به شما توصیه می کنم با موارد اجرا شده بازی کنید cypress
تست ها این یک راه عالی برای دیدن اینکه درخواستها چگونه انجام میشوند و چه چیزی را آزمایش میکنیم و چه چیزی انتظار میرود است. سپس شما همچنین ایده بهتری در مورد اینکه چرا برخی از کاربران میتوانند عملیات خاصی را انجام دهند و برخی دیگر انجام نمیدهند، دریافت خواهید کرد.
من تمام کد منبع این برنامه را در GitHub قرار داده ام
امیدوارم از این مقاله به همان اندازه که من از نوشتن آن لذت بردم لذت برده باشید.
با تشکر از شما برای خواندن!
13. مراجع
https://www.youtube.com/watch?v=Mq3V3P0wdGE