paint-brush
Niyə JWT Müəssisə Tətbiqinizi Təhlükəsizliyin Açarıdır – Və Niyə KumuluzEE Sizin Yeni Ən Yaxşı Dostunuz Ola bilərtərəfindən@jesperancinha
Yeni tarix

Niyə JWT Müəssisə Tətbiqinizi Təhlükəsizliyin Açarıdır – Və Niyə KumuluzEE Sizin Yeni Ən Yaxşı Dostunuz Ola bilər

tərəfindən João Esperancinha40m2025/01/17
Read on Terminal Reader

Çox uzun; Oxumaq

'JWT' və ya JavaScript Object Notation Web Token [RFC7519]-da müəyyən edilmiş standartdır. O, bir neçə yolla müəyyən edilə bilər və iki tərəf arasında məlumat ötürmək üçün istifadə edilə bilər. Bu yazıda biz `JWT`-nin ümumi Java müəssisə proqramına necə inteqrasiya olunacağına baxacağıq.
featured image - Niyə JWT Müəssisə Tətbiqinizi Təhlükəsizliyin Açarıdır – Və Niyə KumuluzEE Sizin Yeni Ən Yaxşı Dostunuz Ola bilər
João Esperancinha HackerNoon profile picture
0-item

İndiki vaxtda biz performansla bağlı getdikcə daha çox narahat oluruq və eyni zamanda, sistemlərin necə sürətli və etibarlı əlaqə qura biləcəyini bilmək istəyirik. Çox vaxt biz məlumat göndərmək və mümkün qədər məxfi və təhlükəsiz saxlamaq istəyirik. Həssas məlumatlar bəzən internetdə hamıya açıq şəkildə hərəkət etməli və telin digər ucunda hərəkətləri tetiklemelidir. Əsasən, biz məlumat mutasiyalarına səbəb olacaq hərəkətlər yaratmaq istəyirik. Bu hallarda biz təkcə məlumatlarımızı qorumaq fikrində deyilik. Məlumatlarımızın göndərilməsi ilə törədilən əməliyyatların etibarlı olduğuna əmin olmaq istəyirik. Məlumatlarımızı bir neçə yolla qoruya bilərik. Çox vaxt biz məlumatları TLS (Nəqliyyat Layeri Təhlükəsizliyi) təhlükəsiz bağlantısı vasitəsilə göndəririk. Bu, məlumatlarımızın tel vasitəsilə şifrələnməsini təmin edəcək. Biz iki tərəf arasında etibarlı əlaqələr yaratmaq və buna nail olmaq üçün Sertifikatlardan istifadə edirik. Bu məqalədə mən JWT standartını müzakirə etmək və daha sonra JWT ümumi Enterprise tətbiqinə necə inteqrasiya edə biləcəyimizi görmək istəyirəm. Bu halda biz KumuluzEE -yə nəzər salacağıq.Gəlin bəzi əsas anlayışlara nəzər salaq. JWT və ya JSON Veb Tokeni və ya daha yaxşısı, JavaScript Obyekt Notasiyası Veb Tokeni RFC7519 -da müəyyən edilmiş standartdır. Bu standart, bütün RFC (Şərh tələbi) standartları kimi, IETF (İnternet Mühəndisliyi İş Qrupu) tərəfindən müəyyən edilmiş, yazılmış və dərc edilmişdir. Bir neçə yolla müəyyən edilə bilər. Ümumiyyətlə, deyə bilərik ki, JWT iki tərəf arasında iddiaların ötürülməsinin yığcam, təhlükəsiz formasıdır. İddianın nə olduğunu sadələşdirməyin bir yolu, onu məlumatı ehtiva edən ad/dəyər cütü kimi təsvir etməkdir. İnternet əlaqəmizin bir neçə vacib aspektini təmin etmək üçün bizə bu məlumat lazımdır. Aldığımız məlumatların ilk növbədə təsdiqləndiyinə və etibar edildiyinə əmin olmalıyıq. Sonra onu təsdiqləməliyik. Bu, əsasən budur. Bu standartı həyata keçirmək üçün biz Java korporativ tətbiqini həyata keçirməyə kömək edə biləcək bir neçə çərçivədən istifadə edə bilərik. Spring Boot geniş istifadə olunur. Çox vaxt o, banklar və digər maliyyə təşkilatları kimi müəyyən təşkilatların xüsusi proqram təminatında başqa ad altında bükülür. Məsələn, mən fərqli bir şey etmək qərarına gəldim. Spring Boot əvəzinə KumuluzEE ilə bir nümunəyə nəzər salacağıq. Məsələ JWT nə olduğunu və necə göründüyünü dəqiq müəyyən etməkdir. Java Müəssisə Tətbiqləri əsasən proqram serverində yerləşdirilə bilən və ya quraşdırılmış serverdən istifadə etməklə öz-özünə işləyə bilən proqramlardır. Nümunə olaraq, Spring Boot proqramları quraşdırılmış Tomcat serverində işləyir. Bu yazıda diqqətimiz KumuluzEE üzərində qurulacaq. Spring Boot kimi o da quraşdırılmış serveri ehtiva edir. Bundan başqa, bu halda o, Jetty adlanır. Bu, CDI (Context Dependency Injection) təmin etmək üçün Weld ilə birlikdə istifadə olunur. Bütün Java EEJakarta EE texnologiya standartları bu framework uyğundur.

2. İş nümunəsi


JWT əsas formada necə işlədiyini nümunə göstərmək üçün onu təqdim etmək üçün bir yol düşünməli oldum. Təhlükəsizliyin narahatlıq doğurduğu klassik nümunələr banklardır. Bununla belə, JWT necə işlədiyini göstərmək üçün bütöv bir bank ərizəsi hazırlamaq vaxt itkisi olardı və ola bilsin ki, çoxlu konsepsiyalar iştirak edə bilər. Əvəzində etdiyim şey çox sadə bir bank sistemidir. Əsas narahatlığımız məlumatların tel vasitəsilə necə axdığını və istifadəçilərin tətbiqimizin müəyyən sahələrinə necə daxil olduğunu göstərməkdir. Mən həmçinin TLS və ya tel vasitəsilə şifrələnmiş məlumatı necə göndərə biləcəyimizi müzakirə etmək fikrində deyiləm. Biz JWT yə diqqətimizi ən təmiz formada saxlayacağıq. Bizim işimiz təbiəti və ətraf mühiti müdafiə edən bir qrup tərəfindən istifadə edilən bank sistemidir. Bu, JWT necə işlədiyini göstərmək üçün sadəcə əyləncəli bir yoldur. Bu Təbiət Liqasının əsas personajı bütün məqalələrimdə ümumi xarakterə çevrilən Lüsidir.

3. Memarlıq

Başlamazdan əvvəl, yalnız çalışan tətbiqimizin eskizini çəkək. Bu, çox sadə proqramdır, lakin onu çəkmək hələ də yaxşı bir şeydir:

Bunun bu qədər sadə olmasının səbəbi odur ki, JWT hər sorğuda yoxlanıldığından və hər sorğu açıq açarla yoxlanıldığından, biz bilirik ki, hər sorğuya düzgün işarə göndərdiyimiz müddətcə keçə biləcəyik. JWT OAuth2, Okta SSO və ya hər hansı digər avtorizasiya mexanizmi ilə inteqrasiya oluna bilər. Bu halda, etdiyimiz iş autentifikasiya və avtorizasiya yaratmaqdır. Tətbiqimizdə JWT istifadə edəcəyik və bununla da mesajımızı imza ilə təsdiqləyəcəyik. Bununla belə, proqrama daxil olmayacağıq. Bunun əvəzinə, uğurlu autentifikasiyadan sonra istifadəçilərə tətbiqimizdən istifadə etməyə icazə verəcəyik. Bu nöqtədə, JWT əslində tam tətbiqin çox kiçik bir hissəsi olduğunu görmək asandır. Bununla belə, bəzi funksiyalar əlavə edilməlidir. Bunlar bizə lazım olan Resurslardır:

  • Balans sistemi
  • Kredit sistemi


Deyək ki, bizim əsas sistemimiz yalnız pul və kredit sorğularını qeydiyyata alacaq. Əslində o, sadəcə dəyərləri toplayacaq. Həmçinin fərz edək ki, bəzi insanlar kredit ala biləcək, digərləri isə olmayacaq. Bəzi insanlar pul saxlaya biləcək, bəziləri isə kredit ala biləcəklər.

4. Texnologiyaların seçilməsi

Girişdə qeyd olunduğu kimi, biz KumuluzEE dən müəssisə proqram çərçivəmiz kimi istifadə edəcəyik və əsas JWT terminologiyasına və konsepsiyalarına baxa bildiyimiz şəkildə ultra əsas tətbiqetməni həyata keçirəcəyik. Düzgün Java versiyasına sahib olduğunuzdan əmin olun. Bu mərhələdə bizə minimum Java 17 SDK quraşdırılmalıdır. Bizə maven, git, IntelliJ kimi Java uyğun IDE və bir növ qabıq lazımdır.

5. Quraşdırma

Tətbiqimizə başlamaq üçün bir neçə KumuluzEE asılılığımız var. Bunun səbəbi, KumuluzEE , Spring Boot kimi bir neçə asılılığa ehtiyac duymasıdır. POM faylına qısaca nəzər salaq:

 <dependencies> <dependency> <groupId>com.kumuluz.ee.openapi</groupId> <artifactId>kumuluzee-openapi-mp</artifactId> </dependency> <dependency> <groupId>com.kumuluz.ee.openapi</groupId> <artifactId>kumuluzee-openapi-mp-ui</artifactId> </dependency> <dependency> <groupId>com.kumuluz.ee</groupId> <artifactId>kumuluzee-microProfile-3.3</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.mockk</groupId> <artifactId>mockk-jvm</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.ninja-squad</groupId> <artifactId>springmockk</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.kotest</groupId> <artifactId>kotest-assertions-core-jvm</artifactId> <scope>test</scope> </dependency> </dependencies>

Bir neçə asılılığı qısaca müzakirə edək. Bunu oxuduqca pom.xml faylımızı yuxarıdan aşağıya izləyin. Bu, aşağıdakı izahatı başa düşmək üçün vacibdir. Tətbiqimizin işləməsi üçün bizə asılılıqlar paketi lazımdır. , Xoşbəxtlikdən, KumuluzEE bizə bu proqramı işə salmaq üçün əsas standart paketləri ehtiva edən Mikroprofil kitabxanaları təqdim edir. Bütün bunlar KumuluzEE -Microprofile kitabxanasında var. Proqramımızı bizə lazım olan bütün JWT parametrləri ilə konfiqurasiya edə bilmək üçün ona MicroProfile kitabxanası əlavə etməliyik. Eyni zamanda, bizə JSON emal kitabxanası lazımdır. Bu, Johnson Core'un etdiyi şey olacaq. KumuluzEE -nin işləməsi üçün təbii ki nüvəyə ehtiyacımız var. Jetty, KumuluzEE çərçivəsini idarə edən əsas serverdir. Buna görə bizim asılılıqlarımızda buna ehtiyacımız var. CDI ehtiyacımız olduğunu nəzərə alsaq, onu dəstəkləyən bir kitabxanaya da ehtiyacımız var. REST son nöqtələrimizi aktivləşdirmək üçün bizə KumuluzEE -nin istirahət kitabxanasına ehtiyacımız var. API-mizi əldə etmək üçün bizə Geronimo kitabxanası lazımdır. Bu, bizdə JSR-374 tətbiqinin mövcud olmasını təmin edəcək. Biz həmçinin JWT mizi və onun JSON-formatted məzmununu şərh etməliyik. Lombok əslində öz başına lazım deyil. Sadəcə hər şeyi gözəl və parlaq edir! Qeydləri daha yaxşı şərh etmək və nəticələrimizi başa düşmək üçün logback də vacibdir. Gəlin indi resources qovluğumuza nəzər salaq. Başlamaq üçün əvvəlcə bu qovluqda nə tapacağımızı anlayaq. Biz tətbiqimizi JWT , Logback ilə əlaqəli bir şeylə konfiqurasiya etməliyik və nəhayət, yaradacağımız lobya haqqında bir şey söyləməliyik. Oradakı ən sadə fayla baxaq. beans.xml META-INF-də tapıla bilər:

 <beans 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"> <weld:scan> <weld:exclude name="org.jesperancinha.fintech.model.Accounts"/> </weld:scan> </beans>

Bu, sadəcə tipikdir və indi düşündüyünüz kimi, bir az köhnə fayldır. Bu nöqtədə, fikir yalnız KumuluzEE işə salmaqdır. İstisna aksiyamız var. Bu, Weld-ə deyir ki, paxla əməliyyatı üçün skan edərkən sinif Hesablarını nəzərə almasın. Bu vacibdir, çünki istifadə etdiyimiz tətbiq ilə Weld əsasən boş konstruktoru olan hər bir sinfi lobya kimi nəzərdən keçirəcəkdir. Hesabların nə üçün paxla hesab edilməsini istəmədiyimizi daha sonra görəcəyik. Hələlik nəzərə alaq ki, biz Sorğu çərçivəsində sorğular edirik. Bu, məntiqlidir, çünki hər sorğunun fərqli istifadəçisi ola bilər. İndi görək “ logback ” necə həyata keçirilir. O, həmçinin META-INF də tapılır:

 <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> </configuration>

Bu, bizim logs üçün çox sadə konfiqurasiyadır. Nəhayət, proqramımızın bəlkə də ən vacib faylı. Bu konfiqurasiya şablonudur. Bu nöqtədə qeyd etmək vacibdir ki, bu layihədə yaratdığım bəzi fayllar şablon strukturunun bir hissəsidir. Bu barədə daha sonra daha ətraflı izah edəcəyəm. Bu şablon faylının MicroProfile tərəfindən oxunacaq config.yml faylına çevrilməsi nəzərdə tutulur. Bu fayl resursların kökündə yerləşir:

 kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: {{ publicKey }} issuer: {{ issuer }} healthy: true

Bütün bu xüsusiyyətlərin əslində nə demək olduğunu daha sonra görəcəyik. Onların hamısı öz-özünə izah olunur. PublicKey və emitent dəyişdiriləcək bütün parametrlərdir. Bunu daha sonra araşdıracağıq. Bizim bash skriptlərimiz onların dəyişdirilməsinə əmin olacaq. Biz kodlaşdırmaya demək olar ki, hazırıq, lakin əvvəlcə JWT token strukturumuza nəzər salaq.

6. Təcrübəli kod

Çox kiçik tətbiqimizi edək. Bu bölmə bizim tətbiqimizi JWT ilə işləmək üçün necə əldə edə biləcəyimizi izah edəcək. Görmək istədiyimiz, istifadəçilərin digərlərinə deyil, bəzi REST metodlarımıza daxil olmasını təyin edə bildiyimizdir. Bu koda baxmağa başlamağın yollarından biri əvvəlcə bizim sadə JWT nişanımıza nəzər salmaqdır. Budur admin nümunəmiz:

 { "iss": "joaofilipesabinoesperancinha", "jti": "01MASTERFINANCE", "sub": "admin", "aud": "nature", "upn": "admin", "groups": [ "user", "admin", "client", "credit" ], "user_id": 1, "access": "TOP", "name": "Admin" }

JSON -da bu adların hər biri iddialar kimi istinad edilir. Nümunəmizdə bir neçə Qorunan iddiaları görürük:

  • " iss " — Bu, tokenin emitentidir. Bunun üçün özbaşına bir dəyər seçə bilərik. Bu parametrin dəyəri əvvəllər gördüyümüz config.yml-də əvəz olunacaq emitent dəyişəninə uyğun olmalıdır.
  • " jti " — Bu işarənin unikal identifikatorudur. Məsələn, bir tokenin iki və ya daha çox dəfə istifadə edilməsinin qarşısını almaq üçün bu iddiadan istifadə edə bilərik.
  • " sub " — Bu işarənin mövzusudur. Bu istifadəçi və ya bəyəndiyimiz hər hansı bir şey ola bilər. Nəzərə almaq lazımdır ki, bu həm də identifikator, açar, adlandırma və ya istədiyimiz hər şey kimi istifadə edilə bilər.
  • " upn " — İstifadəçinin əsas adı. Bu istifadəçinin istifadə etdiyi prinsipi müəyyən etmək üçün istifadə olunur.
  • " groups " — Bu, cari istifadəçinin aid olduğu qruplar massividir. Əsasən bu, bu işarə ilə sorğunun nə edə biləcəyini müəyyən edəcək. Tokenimizdə biz bir neçə Xüsusi iddia görürük. Biz bunu Qorunan iddialar kimi istifadə edə bilərik
  • " user_id " — Biz bundan istifadəçi identifikatorunu təyin etmək üçün istifadə edəcəyik.
  • " access " — İstifadəçinin giriş səviyyəsini müəyyən edəcəyik.
  • " name " — İstifadəçinin adı.

7. Təcrübəli kod

İndiyə qədər bildiklərimizi təkrarlayaq. Bilirik ki, müəyyən etdiyimiz strukturla tokenlərlə ünsiyyət quracağıq. Bundan əlavə, biz tətbiqimizin konfiqurasiyasını, logback konfiqurasiyasını qurduq və nəhayət, müəssisə lobya axtarışı üçün fərdi konfiqurasiya qurduq. Gəlin paket modelinə baxaq. Burada 3 sinif tapacağıq. Bu siniflər əsasən hesabların məcmusunu və client ilə account arasındakı təmsili təmsil edir. Bu yolla biz Client yerləşdiyi Model.kt kotlin faylına baxaraq başlaya bilərik:

 data class Client constructor( @JsonProperty var name: String ?= null )

Bu birinci model sinfi müştərimizin təmsilidir. Bizim işimiz üçün client yalnız adı var. Bu, " jwt " atribut adı ilə təmsil olunan istifadəçi adıdır. Bundan əlavə, Account var:

 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)) ) }


Bu sinifdə biz əsasən hesab nömrəsini, müştərini, cari Dəyəri və nəhayət kredit Dəyərini təyin etdik. Diqqət yetirin ki, biz bütün dəyərləri 0-a defolt edirik. Biz BigDecimal-dan da istifadə edirik, çünki biz pulla məşğul oluruq. Pul dəqiq olmalıdır və sistem dövriyyəsi və ya yuvarlanmasına məruz qalmamalıdır. Bu, başqa sözlə və misal olaraq o deməkdir ki, 0. 0000000000000000000000000000000000000000000000000001 avro kimi bir rəqəm hər zaman bu rəqəm olaraq qalmalıdır. Həmçinin, hesabımıza dəyərlər əlavə etmək istəyirik. AddCurrentValue metodunun mövcud olduğu yer budur. Eyni səbəblərə görə, biz də kreditimizi addCreditValue ilə artıracağıq. Nəhayət, məlumat quraşdırmamızın sonuncu hissəsində Accounts sinfi ilə rastlaşırıq:

 open class Accounts constructor( open val accountMap: MutableMap<String, Account> = mutableMapOf() )

Bu, əslində bütün hesablarımızın cəmidir. Biz verilənlər bazasının davranışını təqlid etmək üçün onun xəritə məzmunundan istifadə edəcəyik. İndi nəzarətçi paketinə baxaq. Burada məlumat modelimizlə işləyən tətbiqimizi yaradırıq. Əvvəlcə BankApplication sinfinə nəzər salaq:

 @LoginConfig(authMethod = "MP-JWT") @ApplicationPath("/") @DeclareRoles("admin", "creditor", "client", "user") class BankApplication : Application()


Bununla biz 3 vacib şeyi deyirik. LoginConfig annotasiyası ilə biz onu MicroProfile-ə uyğun olaraq JWT tokenlərindən istifadə etmək və başa düşmək üçün müəyyən edirik. ApplicationPath proqram kökünü müəyyən edir. Tətbiqin URL-i burada başlayacaq. Bizim nümunəmizdə HTTP://localhost:8080 olacaq. Nəhayət, DeclareRoles tətbiqimiz tərəfindən istifadə ediləcək və qəbul ediləcək rolları müəyyənləşdirir. Rollar və Qruplar bu vəziyyətdə bir-birini əvəz edə bilən terminlərdir. İnyeksiyanın səmərəli işləməsi üçün hesab xəritəsini müəyyən etmək üçün xüsusi annotasiya yaradırıq:

 annotation class AccountsProduct

Tam ekran rejiminə daxil olun Tam ekran rejimindən çıxın

Sonra, AccountsFactory keş obyekti fabriki yaradırıq:

 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() } } }


Xüsusilə Accounts üçün axtarışı söndürməmizin səbəbi bu zavoddur. Axtarış prosesinə paxla yaratmağa icazə vermək əvəzinə, aqreqator nümunəsini özümüz yaradırıq. İstehsal annotasiyasından istifadə edərək, paxla yaratmağa imkan verir. Xüsusi annotasiyamız olan AccountsProduct istifadə edərək, biz bu paxlanın istifadəsini daha konkret edirik. Nəhayət, ApplicationScoped istifadə edərək, onun əhatə dairəsini Application sahəsi kimi müəyyən edirik. Başqa sözlə, hesabın aqreqasiyası lobya tətbiqi üzrə təkton obyekti kimi davranacaq. " createResponse " JSON cavabları yaratmaq üçün sadəcə ümumi metoddur. İndi bizə iki "Resurs" lazımdır. Bu, əsasən, Baharda " Controllers " ilə eynidir. Bu fərqli addır, lakin eyni istifadəyə malikdir. Gəlin AccountsResource sinfinə baxaq:

 @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) } }

Bu sinfə daha ətraflı baxmaq üçün bir dəqiqənizi ayırın. Path annotasiyası bu mənbəyə kökdən necə çatmağı müəyyənləşdirir. Kök kimi "/" istifadə etdiyimizi unutmayın. Bu halda, "hesablar" bu resurs üçün əsas giriş nöqtəmizdir. Bütün resurslarımız, bizim vəziyyətimizdə yalnız ikisi RequestResource əhatə dairəsi ilə işləyir. Annotasiya ilə Produces növündən asılı olmayaraq bütün sorğulara cavabların JSON formatlı mesajlar formasını alacağını müəyyən edir. aggregator inyeksiya etmək üçün biz sadəcə Inject annotasiyası və AccountsProduct annotasiyasının birləşməsindən istifadə edirik:

 @Inject @AccountsProduct open var accounts: Accounts? = null


Bu, fabrikdə müəyyən etdiyimizə uyğun gəlir. Bundan əlavə, biz iki mühüm təhlükəsizlik elementini də tətbiq edirik. principaljsonWebToken :

 @Inject open var principal: Principal? = null @Inject open var jsonWebToken: JsonWebToken? = null


Həm JsonWebToken , həm də Principal eyni olacaq və biz qeydlərimizdə görəcəyik. Resurslarımızda biz həmişə müəyyən işarə ilə sorğudan iddialar daxil edə bilərik:

 @Inject @Claim("name") open var name: JsonString? = null @Inject @Claim("user_id") open var userId: JsonNumber? = null


Bu, Inject and Claim annotasiyalarının birləşməsi ilə həyata keçirilir. Claim annotasiyasının altında yerləşdirilmiş ad bizim hansı iddianı tətbiq etmək istədiyimizi müəyyən edir. Parametrlərimizi təyin etdiyimiz tiplə diqqətli olmalıyıq. Nümunəmizdə, bizə yalnız JsonStringJsonNumber növləri lazımdır. Əvvəlcə hesabları və istifadəçiləri necə yaratdığımıza baxaq:

 @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() ) ) }

Hesabların və istifadəçilərin yaradılması


Burada məqsəd metodları ayıra bilmək və onlara müxtəlif icazələr verməkdir. Bizim nümunəmizdə onların hər ikisi sadəcə hesab yaradır, lakin nəzərə almaq lazımdır ki, yalnız istifadəçi rolu olan istifadəçilər createUser metodundan istifadə edə bilər. Eyni şəkildə, yalnız müştəri və kredit rolu olan istifadəçilər createAccount metoduna daxil ola bilər. İndi bu resursun PUT sorğu metoduna ətraflı nəzər salaq:

 @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) }

Nağdlaşdırma


Bilirik ki, PUT annotasiyası bu metodun yalnız PUT tipli sorğularla əlçatan olduğunu göstərir. Annotasiya Yolu daha sonra Jetty-yə bu metodun yolunun bir dəyər olduğunu bildirir. Bu həm də PathParam kimi tanınır. Nəhayət, bu metodun yalnız admin və ya müştəri rolu olan istifadəçilər tərəfindən istifadə edilməsinə icazə veriləcəyini müəyyən edə bilərik. Daxil edilən dəyər daha sonra PathParam-ın istifadəsi ilə Uzun dəyər dəyişənimizə ötürülür. Əgər biz heç bir rol təyin etməsək, onda düzgün işarəyə malik istənilən istifadəçi bu metodlara daxil ola biləcək. CreditResource eyni şəkildə həyata keçirilir. yol:

 @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) } }

Yeganə fərq odur ki, adminclient rollarından istifadə etmək əvəzinə indi admincredit rollarından istifadə edirik. Həmçinin, istifadəçilər üçün hesabların heç vaxt bu resource yaradılmayacağına diqqət yetirin. Bu, yalnız hesabın resource vasitəsilə mümkündür. İndi kodun necə tətbiq olunduğunu bildiyimizə görə gəlin əvvəlcə REST xidmətimizdə hansı üsulları əlçatan etdiyimizi təkrarlayaq.

8. Tətbiqdən istifadə

İstifadə olunan xidmətlərin siyahısını yoxlayaq:


Növ,URL,Yük,Nəticə,İcazə verilən rollar
POST, http://localhost:8080/accounts,n/a,Yaradıldı hesab, admin/müştəri/kredit
POST,
http://localhost:8080/accounts/user,n/a,Yaradıldı istifadəçi, admin/istifadəçi
GET,
http://localhost:8080/accounts,n/a,Uyğunluq hesab, admin/müştəri
QOYUN,
http://localhost:8080/accounts,{saldo: Long}, Cari Balans, admin/müştəri
GET,
http://localhost:8080/accounts/all,n/a,Hamısı cari hesablar, Hamısı
GET,
http://localhost:8080/accounts/summary,n/a,Sum bütün balanslar, Hamısı
GET,
http://localhost:8080/credit,n/a,Uyğunluq hesab, admin/müştəri
QOYUN,
http://localhost:8080/credit,{saldo: Long}, Cari Kredit, admin/müştəri
GET,
http://localhost:8080/credit/all,n/a,Hamısı kredit, Hamısı
GET,
http://localhost:8080/credit/summary,n/a,Sum
kredit, Hamısı

9. Test mühitinin yaradılması

Kök qovluğunda bir bash faylı yaratdım. Bu fayl "setupCertificates.sh" adlanır. Nə etdiyinə dair fikir sahibi olmaq üçün gəlin ona nəzər salaq:

 #!/bin/bash mkdir -p your-finance-files cd your-finance-files || exit openssl genrsa -out baseKey.pem openssl pkcs8 -topk8 -inform PEM -in baseKey.pem -out privateKey.pem -nocrypt openssl rsa -in baseKey.pem -pubout -outform PEM -out publicKey.pem echo -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//"/"/"\/"} 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" ]]; then filename=${item##*/} per_token=${filename/jwt-token-/} token_name=${per_token/.json/} cp "${item}" jwt-token.json java -jar ../your-finance-jwt-generator/target/your-finance-jwt-generator.jar \ -p jwt-token.json \ -key ../your-finance-files/privateKey.pem > token.jwt cp token.jwt ${TOKEN_FOLDER}/token-"${token_name}".jwt token=$(cat token.jwt) echo "# Create account: ""${token_name}" >> ${CREATE_ACCOUNT_FILE} echo "echo -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

Ətraf mühitin yaranması

Zəhmət olmasa faylı izləyin, çünki mən onun nə etdiyini izah edirəm. Bu, onun nə etdiyini dəqiq başa düşməyimiz üçün vacibdir. Əvvəlcə PEM formatında şəxsi və açıq açarlar yaradırıq. Sonra şəxsi açarı işlək "your-finance-jwt-generator.jar" ilə istifadə edirik. Bu, tokenləri tez yaratmağa imkan verən işlək qabımızdır. Emitent sonradan dəyişdirilə bilməz. Nəhayət, bir işarə yaradır. Bu nişanı necə oxuyacağımızı daha sonra görəcəyik. Bu işarə 3 əlavə Başlıq iddiasını ehtiva edir. Bunlar "uşaq", "tip" və "alq"dır. Aşağıdakı formatı izləyir:

 { "kid": "jwt.key", "typ": "JWT", "alg": "RS256" }

JWT -nin başlığı

Bu iddialara daha yaxından nəzər salaq:

  • "uşaq" — İşarə iddiası kimi işləyir. Hansı növ alqoritmdən istifadə etdiyimizi göstərir.
  • "typ" — IANA media növlərini elan etmək üçün istifadə olunur. Üç seçim var JWT (JSON Web token), JWE (JSON Web Encryption) və JWA (JSON Web Alqoritmləri). Bu növlər təcrübəmizə uyğun deyil. Biz yalnız tokenimizin həqiqətən yaxşı şifrələnmədiyini və onun şifrəsini açmağın həqiqətən asan olduğunu görəcəyik. Həm də görəcəyik ki, biz tokenləri deşifrə edə bilsək də, digər hərəkətləri yerinə yetirmək üçün onları asanlıqla dəyişdirə bilmərik.
  • "alg" — Biz istifadə etmək istədiyimiz imza növünü belə müəyyən edirik. İmzalanma, orijinal işarənin dəyişdirilməməsini və etibarlı olmasını təmin edəcək bir kriptoqrafik əməliyyat kimi qəbul edilə bilər. Bizim vəziyyətimizdə SHA-256 ilə RSA İmza kimi tanınan RS256-dan istifadə edirik.

Açıq açarımızla nəhayət şablonumuzu dəyişmək üçün ondan istifadə edə bilərik. Yeni config.yml faylı belə görünməlidir:

 kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN issuer: joaofilipesabinoesperancinha healthy: true

config.yml


İkinci addım dörd fayl yaratmaqdır. " jwt-plain-tokens " kataloqundakı hər bir sadə işarə üçün dörd əmr yaradacağıq. Birinci əmr, hesabları ilə hər şeyi effektiv şəkildə edə biləcək istifadəçilər yaratmaqdır. Bunlar " admin ", " client " və " credit " profilli istifadəçilərdir. Onları yaratmaq üçün " createAccount.sh " faylını işə salaq. İkinci komanda hələ heç bir hüquqa malik olmayan qalan istifadəçiləri yaradacaq. Bu "createUser.sh" faylıdır. Gəlin onu idarə edək. İndi bütün istifadəçilərin nəhayət yaradıldığını görəcəyik. İndi əməliyyatlar haqqında təfərrüatları nəzərdən keçirək və qalan iki əmrə baxaq. Biri "cashin", digəri isə daha çox kredit istəmək üçün. İlk yaradılan fayl "sendMoney.sh" bash skriptidir. Burada biz " cashin " üçün bütün sorğuları tapa bilərik. Bu faylda hər bir istifadəçiyə təsadüfi pul miqdarı göndərmək üçün qıvrım sorğusu tapa bilərsiniz. Gəlin admin işinə baxaq:

 #!/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 çıxarış

Eyni istifadəçilərin kredit sorğuları da onlara təyin edilmişdir:

 #!/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 çıxarış



Bütün characters Nature Liqasının bir hissəsidir. Əslində bu bank sisteminin bir hissəsi olmaq üçün bir qrup insan. Bu kontekstdə ətraf mühiti müdafiə edirlər. Məqalə üçün bu insanların nə ilə məşğul olması və ya hekayənin harasına uyğun olması həqiqətən də aktual deyil, kontekst üçün onlar ətraf mühiti qorumaq və iqlim dəyişikliyinin təsirlərini yavaşlatmaq üçün tədbirlərdə iştirak edirlər. Bəzi characters hər şeyi edə bilər, digərləri heç nə edə bilməz və digərləri yalnız "kaşin" və ya sadəcə "kredit istəyə bilər". Həmçinin diqqət yetirin ki, mən həssas məlumatları çaşdırıram. Bu tokenlər adətən paylaşılmamalı və ya xüsusi URL-də görünməməlidir. Bəli, onlar həmişə brauzerin inkişaf etdirici konsolu vasitəsilə mövcuddur, lakin hər halda edilən bəzi sorğuları protect üçündür. Bu, "müəyyənlik başına təhlükəsizlik" kimi tanınan bir anlayışdır and istifadəçinin istifadə olunan tokendən xəbərdar olmasına texniki cəhətdən mane olmasa da, çəkindirici rol oynayır. Hər iki üsulda, biz depozit etdikdə və ya biz kredit istəyin, diqqət yetirin ki, hər sorğu üçün biz 1-dən 500-ə qədər təsadüfi bir nömrə göndəririk. İndi demək olar ki, tətbiqimizə başlamağa hazırıq, amma əvvəlcə bir az daha nəzəriyyəyə nəzər salaq.

10. JWT tokeni necə hazırlanır




İndi tokenlərimizi yaratdıq, gəlin onlardan birinə baxaq. Mən sizə anlaşılmaz bir işarə göstərəcəyəm və biz bunu anlamaq üçün ondan istifadə edəcəyik. İşdə nişanımız: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE . FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO . FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN Diqqət yetirmək üçün burada vacib olan odur ki, nişanımız üç hissəyə bölünür:

  • Başlıq — Bu, yuxarıda müzakirə etdiyimiz kimi, Base64 kodlu JSON konfiqurasiya başlığıdır.
  • Faydalı yük — Bu, Base64 kodlu JSON yüküdür. Ehtiyat və Fərdi iddialarımızı burada müəyyən etdik. Burada Şəxsi və İctimai iddiaları da müəyyən edə bilərik. Onların hər ikisi Xüsusi iddialar altındadır. Qısa bir qeyd olaraq, bu iddiaların hər ikisi ilə istədiyimizi edə bilərik. Bununla belə, ictimai iddialara IANA JSON Veb Token Reyestrində müəyyən edilmiş iddialar deyilir. Qeydiyyatla toqquşmamaq üçün tokenlərimizi müəyyən bir şəkildə adlandırmağımız vacibdir. İctimai iddialar standart isteğe bağlı olaraq da müəyyən edilə bilər. Şəxsi iddialar heç bir standarta uyğun gəlmir və onları müəyyən etmək bizdən asılıdır.
  • İmza - Bir az yaradıcı ola biləcəyimiz yer budur. İmza HeaderPayload şifrələnmiş birləşməsidir. İstifadə etmək istədiyimiz alqoritmə qərar veririk və işarənin bu biti əsasən göndərdiyimiz mesajın etibarlı olub olmadığını müəyyən edəcək. Bu, həmin kombinasiya üçün unikaldır və serverimiz uyğunluğumuz olub-olmadığını müəyyən etmək üçün yaratdığımız "ictimai açardan" istifadə edəcək. Yuxarıdakıları xatırlayırsınızsa, nümunəmizdə RS256 istifadə edirik.


Davam etməzdən əvvəl, nümunəmizdə həm Header , həm də Payload decyphered edilə biləcəyini nəzərə alın. Biz sadəcə olaraq faydalı yükə və ya başlığa müdaxilə edə bilmirik və yenə də onu etibarlı edirik. Zərərli tokenin potensial təsirlərindən qorunma yalnız bizim seçdiyimiz alqoritmlə qoruna bilər. Odur ki, ağıllı seçim edin. Əgər siz bank kimi çox məxfi məlumatların narahatlıq doğurduğu təşkilatda işləyirsinizsə, lütfən, edəcəyimiz şeyi ETMEYİN. Bu, bizim yerli olaraq yaratdığımız tokenlərin məzmununu onlayn yoxlamaq üçün yalnız bir yoldur. Əvvəlcə https://jwt.io/ saytına daxil olaq və JWT nişanımızı dolduraq. Yaratdığınız tokendən istifadə edin:


Tokenimizin məzmununu yoxlamaq üçün https://jwt.io/ istifadə edərək, burada nələrə sahib olduğumuzu araşdıraq. Bu bizim idarəçi nişanımızdır. Həmin şəxs bizim nümunəmizdə “Admin”dir. Parametrlərimizin hamısının mövcud olduğunu görə bilərik. Siyahımızda "sub", "aud", "upn", "access", "user_id", "iss", "name", "groups" və nəhayət "jti" görürük. Bizim bəzi əlavə iddialarımız da var. Gəlin onlara baxaq:



" auth_time " — Bu, identifikasiyanın baş verdiyi zamandır. Tokenimiz 17 İyul 2022-ci il, Bazar günü 16:15:47 GMT+02:00 DST " iat " identifikasiyası ilə təsdiqləndi - Bu, token yaradıldığı vaxtdır. Bizim vəziyyətimizdə bu, auth_time ilə eyni vaxtda baş verir." exp " — Bu, tokenin bitmə tarixidir. 17 iyul 2022-ci il, bazar günü 16:32:27 GMT+02:00 DST vaxtı bitir. Tokenimizdə hər hansı son istifadə tarixini qeyd etməmişik. Bu o deməkdir ki, JWT ~15 dəqiqəlik standart dəyərindən istifadə edir.

İndi bəzi testləri yerinə yetirək.

11. Proqramın icrası

Kod GitHub -da istifadə olunmağa hazırdır. Kodu yoxlayıb Intellij ilə açsaq, bu proqramı Spring Boot proqramı kimi işlədə bilməyəcəyimizi bilməliyik. Onu işə salmaq üçün "psvm" yoxdur. Əvəzində biz sadəcə olaraq yaradılan kavanozu işə sala bilərik və bundan əvvəl "mvn quruluşu" hazırladığımızdan əmin ola bilərik. Hazırda ondan necə istifadə edirəm:

[ ] https://github.com/jesperancinha/your-finance-je "Tətbiqi işə salmaq üçün ətraf mühitin qurulması")



İndi " setupCertificates.sh " skriptini yenidən işə salaq. Buraya nə qədər vaxt sərf etdiyinizi bilmirəm, amma çox güman ki, 15 dəqiqə artıq bu nöqtədə keçib. Hər halda, onları yenidən işə salın. Tətbiqimizi işə salaq! Biz bunu belə başlaya bilərik:

 mvn clean install java -jar your-financeje-banking/target/your-financeje-banking.jar

Yaxud biz onu sadəcə olaraq hazır işləmə konfiqurasiyamızdan keçirə bilərik. Repo və Makefile-ni əvvəlcədən yoxlayın, əgər hər şeyi başa düşmək istəyirsinizsə:

 make dcup-full-action

Bu skript 2 xidməti işlədəcək. Biri 8080 portunda, digəri isə 8081 portunda. 8080 portunda biz JWT tokenlərini yaratmaq üçün öz kodumuzla işləyən bu proqram təminatının versiyasını işlədəcəyik. 8081 portunda Adam Bien tərəfindən yaradılmış jwtknizr generatorundan istifadə edərək versiyanı işlədəcəyik. Biz bu məqaləni 8080 portunda işləyən xidmətə yönəldəcəyik. İstəyirsinizsə, cypress də işlədə bilərsiniz:

 make cypress-open

Bu, cypress konsolunu open və siz seçdiyiniz brauzerlə testləri keçirə biləcəksiniz. Bununla belə, bu mərhələdə brauzer seçimləri hələ də məhduddur. Sorğuların əksəriyyəti əslində cypress tərəfindən təmin edilən əmr xətti sorğuları olacaq. Hələlik " cypress "ə girməyək. Zəhmət olmasa brauzerinizə gedin və bu yerə gedin:

http://localhost:8080/accounts/all

Belə bir nəticə əldə etməliyik:


Gördüyümüz kimi, " Malory ", " Jack Fallout " və " Jitska "nın heç bir krediti və pulu yoxdur. Bunun səbəbi onlara yalnız istifadəçi qrupu verilmişdir. Shikka heç bir kredit verilmədiyinə də diqqət yetirin. " Shikka ", qrup krediti olmayan yeganə müştərimizdir. Qeydlərə baxsaq, uğurlu əməliyyatların bu formatda olduğunu görə bilərik:

 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 bizə əməliyyatın uğurla keçdiyini bildirir. "Malory", "Jack Fallout" və "Jitska" vəziyyətində hər iki əməliyyat uğursuz olur və sonra belə bir mesaj alacağıq:

 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 bizə JWT tokenimizin təsdiq edildiyini və ona etibar edildiyini bildirir. Lakin istifadəçiyə həmin əməliyyatı yerinə yetirmək qadağandır. Başqa sözlə, onların təyin olunmuş metoda çıxışı yoxdur.

Tokenlərimizi bir az dəyişdirək. sendMoney.sh faylının bəzi işarələrini dəyişdirsək. Bunu almalıyıq:

 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)

Tam ekran rejiminə daxil olun Tam ekran rejimindən çıxın

Bu 401 o deməkdir ki, nişanımız təsdiqlənməyib. Bu o deməkdir ki, serverin tokenimizin etibarlı olub olmadığını yoxlamaq üçün istifadə etdiyi açıq açar heç bir uyğunluq tapmayıb. Əgər açıq açar JWT işarəsinin imzasını qiymətləndirə və təsdiq edə bilmirsə, o, onu rədd edəcək.

Xülasə olaraq, Başlıq və "Yük" şifrələnmir. Onlar yalnız baza 64 "kodlaşdırılmışdır". Bu o deməkdir ki, "Deşifrələmə" bizə həmişə faydalı yükün əslində nə olduğuna nəzər salmağa imkan verir. Əgər yükümüzü qulaq asmaqdan qorumaq istəyiriksə, identifikasiya parametrlərini seçməkdən başqa, işarənin "Yük" funksiyasını başqa bir şey üçün istifadə etməməliyik. Problem həqiqətən də kiminsə JWT tokeninə əl atması, məsələn, TLS tunelinin dağıdılması və kimsə mübadilə edilən mesajların məzmununu oxuya bildikdə olur. Bu baş verdikdə, başqa bir qorunma var. Və bu imzadır. Daxil olan mesajı təsdiq edə bilən yeganə şəxs ictimai açarı ehtiva edən serverdir. Bu açıq açar, açıq olsa da, yalnız imza və "Başlıq + Yük Yükü" ilə işləyərək gələn mesajı təsdiq etməyə imkan verir.

12. Nəticə

Sessiyamızın sonuna gəldik. Bunu izlədiyiniz üçün təşəkkür edirik. Biz JWT tokenlərinin XML analoqu olan SAML tokenlərindən necə yığcam və çox daha az ətraflı olduğunu görə bilərik. Müəyyən üsullar üçün lazım olan müəyyən icazələri əldə etmək üçün tokenləri yaratmağın və istifadə etməyin nə qədər asan olduğunu və imzalanmış token vasitəsilə oraya necə çatdığımızı gördük. Bununla belə, JWT necə işlədiyi barədə fikir əldə etmək mənə çox vacibdir. Ümid edirəm ki, bununla mən sizə JWT tokenlərinin necə işlədiyi barədə yaxşı bir giriş verdim. Bütün bunların necə işlədiyi barədə daha yaxşı fikir əldə etmək üçün sizə həyata keçirilən cypress testləri ilə oynamağı məsləhət görürəm. Bu, sorğuların necə edildiyini, nəyi sınaqdan keçirdiyimizi və gözlənilənləri görmək üçün əla yoldur. Onda siz həm də bəzi istifadəçilərin nə üçün müəyyən əməliyyatları yerinə yetirməsi, digərlərinin isə etməməsi haqqında daha yaxşı fikir əldə edəcəksiniz. Mən bu proqramın bütün mənbə kodunu GitHub -da yerləşdirmişəm. Ümid edirəm ki, bu məqaləni yazmaq mənim həzz aldığım qədər siz də bəyəndiniz. Oxuduğunuz üçün təşəkkür edirik!

13. İstinadlar


L O A D I N G
. . . comments & more!

About Author

João Esperancinha HackerNoon profile picture
João Esperancinha@jesperancinha
Software Engineer for 10+ Years, OCP11, Spring Professional 2020 and a Kong Champion

ETİKET ASIN

BU MƏQALƏ TƏQDİM EDİLMİŞDİR...