برنامه نویسی

یادگیری امنیت 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
Enter fullscreen mode

Exit fullscreen mode

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"
}
Enter fullscreen mode

Exit fullscreen mode

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 options JWT(JSON Web token), JWE(JSON Web Encryption), and JWA(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
Enter fullscreen mode

Exit fullscreen mode

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"
Enter fullscreen mode

Exit fullscreen mode

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"
Enter fullscreen mode

Exit fullscreen mode

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

blogcenter

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 the Payload. 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 using RS256 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:

blogcenter

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:

[blogcenter]https://github.com/jesperancinha/your-finance-je “تنظیم محیط برای اجرای برنامه”)

حالا بیایید “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

https://www.youtube.com/watch?v=lELDF1t-sJ8

https://www.youtube.com/watch?v=z3Y4NQgjGLE

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا