İ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 EE
və Jakarta EE
texnologiya standartları bu framework
uyğundur.
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.
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:
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.
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.
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.
Ç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ərikuser_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ı. İ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. principal
və jsonWebToken
:
@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 JsonString
və JsonNumber
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, admin
və client
rollarından istifadə etmək əvəzinə indi admin
və credit
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.
İstifadə olunan xidmətlərin siyahısını yoxlayaq:
Növ,URL,Yük,Nəticə,İcazə verilən rollar
POST,
POST,
GET,
QOYUN,
GET,
GET,
GET,
QOYUN,
GET,
GET,
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:
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.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.
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:
Header
və Payload
ş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.
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.
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!