Hozirgi kunda bizda ishlash haqida ko'proq tashvishlanmoqdamiz va shu bilan birga, biz tizimlar qanday tez va ishonchli aloqa qilishini bilishni xohlaymiz. Ko'p marta biz ma'lumot yuborishni va uni iloji boricha maxfiy va xavfsiz saqlashni xohlaymiz. Nozik ma'lumotlar ham ba'zan Internetda ommaviy ravishda harakatlanishi va simning boshqa uchida harakatlarni boshlashi kerak. Ko'pincha biz ma'lumotlar mutatsiyalariga olib keladigan harakatlarni yaratmoqchimiz. Bunday hollarda biz nafaqat ma'lumotlarimizni himoya qilamiz. Maʼlumotlarimizni yuborish orqali boshlangan harakatlar ishonchli ekanligiga ishonch hosil qilishni xohlaymiz. Biz ma'lumotlarimizni bir necha usul bilan himoya qilishimiz mumkin. Odatda, biz ma'lumotlarni TLS
(Transport Layer Security) xavfsiz ulanishi orqali yuboramiz. Bu bizning ma'lumotlarimiz sim orqali shifrlangan bo'lishini ta'minlaydi. Ikki tomon oʻrtasida ishonchli munosabatlar oʻrnatish va bunga erishish uchun biz sertifikatlardan foydalanamiz.Ushbu maqolada men JWT
standartini muhokama qilmoqchiman va JWT
qanday qilib umumiy Enterprise
ilovasiga integratsiyalash mumkinligini koʻrib chiqmoqchiman. Bu holda, biz KumuluzEE
ni ko'rib chiqamiz.Keling, ba'zi asosiy tushunchalarni ko'rib chiqaylik. JWT
yoki JSON Web Token, yoki yaxshiroq, JavaScript Object Notation Web Token, RFC7519 da belgilangan standartdir. Ushbu standart, barcha RFC
(Izohlar so'rovi) standartlari singari, IETF
(Internet Engineering Task Force) tomonidan aniqlangan, yozilgan va nashr etilgan. Uni bir necha usul bilan aniqlash mumkin. Umuman olganda, JWT
ikki tomon o'rtasida da'volarni uzatishning ixcham, xavfsiz shakli deb aytishimiz mumkin. Da'vo nima ekanligini soddalashtirishning usullaridan biri uni asosan ma'lumotni o'z ichiga olgan nom/qiymat juftligi sifatida tasvirlashdir. Bizning internet aloqamizning bir necha muhim jihatlarini kafolatlash uchun bizga bu ma'lumot kerak. Biz olgan ma'lumotlar birinchi navbatda tasdiqlangan va ishonchli ekanligiga ishonch hosil qilishimiz kerak. Keyin biz buni tasdiqlashimiz kerak. Bu asosan shunday. Ushbu standartni amalga oshirish uchun biz Java korporativ ilovasini amalga oshirishga yordam beradigan bir nechta ramkalardan foydalanishimiz mumkin. Spring Boot keng qo'llaniladi. Ko'pincha u banklar va boshqa moliyaviy tashkilotlar kabi ba'zi tashkilotlarning tegishli dasturiy ta'minotida boshqa nom ostida o'ralgan. Bizning misolimiz uchun men boshqa narsa qilishga qaror qildim. Spring Boot o'rniga biz KumuluzEE
bilan misolni ko'rib chiqamiz. Gap JWT
nima ekanligini va uning qanday ko'rinishini aniq aniqlashdir. Java Enterprise ilovalari asosan dastur serverida joylashtiriladigan yoki o'rnatilgan server yordamida o'z-o'zidan ishlashi mumkin bo'lgan ilovalardir. Misol tariqasida, Spring Boot ilovalari o'rnatilgan Tomcat serverida ishlaydi. Ushbu maqolada bizning e'tiborimiz KumuluzEE
ga qaratiladi. Spring Boot singari, u ham o'rnatilgan serverni o'z ichiga oladi. Bundan tashqari, bu holda u Jetty deb ataladi. Bu CDI (Context Dependency Injection) ni ta'minlash uchun Weld bilan birgalikda ishlatiladi. Barcha Java EE
va Jakarta EE
texnologiya standartlari ushbu framework
bilan mos keladi.
JWT
asosiy shaklida qanday ishlashini misol qilish uchun men uni taqdim etish usulini o'ylab ko'rishim kerak edi. Xavfsizlik muammosi bo'lgan klassik misollar banklardir. Biroq, JWT
qanday ishlashini ko'rsatish uchun to'liq bank arizasini tayyorlash vaqtni behuda sarflash va ehtimol juda ko'p tushunchalarni o'z ichiga olishi mumkin. Buning o'rniga, men qilgan narsa juda oddiy bank tizimi. Bizning asosiy tashvishimiz ma'lumotlar sim orqali qanday oqishini va foydalanuvchilar ilovamizning muayyan sohalariga qanday kirishlarini ko'rsatishdir. Men shuningdek, TLS yoki shifrlangan ma'lumotni sim orqali qanday yuborishimiz mumkinligini muhokama qilmoqchi emasman. Biz JWT
e'tiborimizni sof shaklda beramiz. Bizning ishimiz tabiat va atrof-muhitni himoya qiluvchi guruh tomonidan foydalaniladigan bank tizimidir. Bu JWT
qanday ishlashini ko'rsatishning qiziqarli usuli. Ushbu Tabiat Ligasining bosh qahramoni Lyusi bo'lib, u mening barcha maqolalarimda umumiy xarakterga aylanib bormoqda.
Ishni boshlashdan oldin, keling, ishlayotgan dasturimizni chizamiz. Bu juda oddiy dastur, lekin uni chizish hali ham yaxshi narsa:
Bu juda oddiy bo'lishining sababi shundaki, JWT
har bir so'rov bo'yicha tekshiriladi va har bir so'rov ochiq kalit bilan tekshiriladi, shuning uchun biz bilamizki, har bir so'rov bo'yicha to'g'ri tokenni yuborsak, biz buni amalga oshirishimiz mumkin. JWT
OAuth2, Okta SSO yoki boshqa avtorizatsiya mexanizmi bilan birlashtirilishi mumkin. Bunday holda, biz qilayotgan narsa autentifikatsiya va avtorizatsiyani o'rnatishdir. Ilovamizda biz JWT
foydalanamiz va u bilan imzo yordamida xabarimizni autentifikatsiya qilamiz. Ammo biz ilovaga kirmaymiz. Buning o'rniga, muvaffaqiyatli autentifikatsiyadan so'ng foydalanuvchilarga ilovamizdan foydalanishga ruxsat beramiz. Shu nuqtada, JWT
aslida to'liq dasturning juda kichik qismi ekanligini ko'rish oson. Shunga qaramay, ba'zi funksiyalarni qo'shish kerak. Bular bizga kerak bo'lgan manbalar:
Aytaylik, bizning asosiy tizimimiz faqat pul va kredit so'rovlarini ro'yxatga oladi. Aslida, u faqat qiymatlarni to'playdi. Aytaylik, ba'zi odamlar kredit olishlari mumkin, boshqalari esa yo'q. Ba'zi odamlar pul saqlash imkoniyatiga ega bo'ladi va boshqalar kredit olish imkoniyatiga ega bo'ladi.
Kirish qismida aytib o'tilganidek, biz KumuluzEE
korporativ dastur tizimi sifatida foydalanamiz va JWT
asosiy terminologiyasi va tushunchalarini ko'rib chiqishimiz mumkin bo'lgan tarzda ultra-asosiy dasturni amalga oshiramiz. Java versiyasining to'g'ri ekanligiga ishonch hosil qiling. Ushbu bosqichda bizga minimal Java 17 SDK o'rnatilgan bo'lishi kerak. Bizga maven, git, IntelliJ kabi Java-mos keluvchi IDE va qandaydir qobiq kerak bo'ladi.
Ilovamizni boshlash uchun bizda bir nechta KumuluzEE
bog'liqliklari mavjud. Buning sababi, KumuluzEE
xuddi Spring Boot kabi bir nechta qaramlikka muhtoj. Keling, POM faylini qisqacha ko'rib chiqaylik:
<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>
Keling, bir nechta bog'liqliklarni qisqacha muhokama qilaylik. Buni o'qiyotganingizda, iltimos, pom.xml
faylimizni yuqoridan pastgacha kuzatib boring. Bu quyidagi tushuntirishni tushunish uchun muhim. Ilovamiz ishlashi uchun bizga bog'liqliklar to'plami kerak. , Yaxshiyamki, KumuluzEE
bizga ushbu ilovani ishga tushirish uchun asosiy standart to'plamlarni o'z ichiga olgan Mikroprofil kutubxonalarini taqdim etadi. Bularning barchasi KumuluzEE
-Microprofile kutubxonasida mavjud. Ilovamizni barcha kerakli JWT
parametrlari bilan sozlash uchun biz unga MicroProfile kutubxonasini qo'shishimiz kerak. Shu bilan birga, bizga JSON ishlov berish kutubxonasi kerak. Bu Jonson Core qiladigan narsa bo'ladi. Bizga, albatta, ishlash uchun KumuluzEE
yadrosi kerak. Jetty - bu KumuluzEE
ramkasini boshqaradigan asosiy server. Shuning uchun bu bizning qaramligimizda kerak. Bizga CDI
kerakligini hisobga olsak, uni qo'llab-quvvatlaydigan kutubxona ham kerak. REST so'nggi nuqtalarimizni yoqish uchun bizga KumuluzEE
ning qolgan kutubxonasi kerak. API-ni olish uchun bizga Geronimo kutubxonasi kerak bo'ladi. Bu bizda JSR-374
ilovasi mavjudligini ta'minlaydi. Biz JWT
va uning JSON-formatted
mazmunini ham izohlashimiz kerak. Lombok o'z-o'zidan kerak emas. Bu shunchaki hamma narsani chiroyli va yorqin qiladi! Jurnallarni yaxshiroq talqin qilish va natijalarimizni tushunishimiz uchun tizimga qaytish ham muhim. Endi resources
papkamizni ko'rib chiqamiz. Boshlash uchun avval ushbu jildda nimani topishimiz kerakligini tushunib olaylik. Biz ilovamizni JWT
, Logback bilan bog'liq bo'lgan narsa bilan sozlashimiz kerak va nihoyat, biz yaratmoqchi bo'lgan fasol haqida nimadir aytishimiz kerak. U erda eng oddiy faylni ko'rib chiqamiz. beans.xml faylini META-INF da topish mumkin:
<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 oddiy va siz hozir o'ylayotganingizdek, biroz eski fayl. Shu nuqtada, g'oya faqat KumuluzEE
ishga tushirishdir. Bizda istisno amali bor. Bu Weldga loviya harakatini skanerlashda hisoblar sinfini hisobga olmaslikni aytadi. Bu juda muhim, chunki biz foydalanayotgan dastur bilan Weld
asosan bo'sh konstruktorli har bir sinfni loviya sifatida ko'rib chiqadi. Nima uchun Hisoblar loviya hisoblanishini istamasligimizni keyinroq bilib olamiz. Hozircha shuni yodda tutaylikki, biz so'rovlar doirasida so'rovlar yubormoqdamiz. Bu mantiqan to'g'ri, chunki har bir so'rov boshqa foydalanuvchiga ega bo'lishi mumkin. Endi " logback
" qanday amalga oshirilishini ko'rib chiqamiz. U META-INF
da topilgan:
<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 bizning logs
uchun juda oddiy konfiguratsiya. Nihoyat, ilovamizning eng muhim fayli. Bu konfiguratsiya shablonidir. Shu o‘rinda shuni ta’kidlash kerakki, men ushbu loyihada yaratgan ba’zi fayllar shablon tuzilishining bir qismidir. Bu haqda keyinroq batafsil tushuntiraman. Ushbu shablon fayli MicroProfile tomonidan o'qiladigan config.yml fayliga aylantirilishi kerak. Ushbu fayl resurslarning ildizida joylashgan:
kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: {{ publicKey }} issuer: {{ issuer }} healthy: true
Bu xususiyatlarning barchasi aslida nimani anglatishini keyinroq ko'rib chiqamiz. Ularning barchasi o'z-o'zidan tushunarli. PublicKey va emitent almashtiriladigan barcha parametrlardir. Biz buni keyinroq o'rganamiz. Bizning bash skriptlarimiz ularning almashtirilishiga ishonch hosil qiladi. Biz kodlashga deyarli tayyormiz, lekin avval JWT
token tuzilishini ko'rib chiqamiz.
Keling, juda kichik dasturimizni yarataylik. Ushbu bo'limda ilovamizni JWT
bilan ishlash uchun qanday qilib olishimiz mumkinligi tushuntiriladi. Biz ko'rmoqchi bo'lgan narsa, foydalanuvchilarga boshqalarga emas, balki ba'zi REST
usullarimizga kirishni belgilashimiz mumkin. Ushbu kodni ko'rib chiqishni boshlash usullaridan biri, avvalo oddiy JWT
tokenimizni ko'rib chiqishdir. Mana bizning administrator misolimiz:
{ "iss": "joaofilipesabinoesperancinha", "jti": "01MASTERFINANCE", "sub": "admin", "aud": "nature", "upn": "admin", "groups": [ "user", "admin", "client", "credit" ], "user_id": 1, "access": "TOP", "name": "Admin" }
JSON
ushbu nomlarning har biri da'volar deb ataladi. Bizning misolimizda biz bir nechta himoyalangan da'volarni ko'ramiz:
iss
" - bu tokenning emitenti. Buning uchun o'zboshimchalik bilan qiymat tanlashimiz mumkin. Ushbu parametrning qiymati biz ilgari ko'rgan config.yml da almashtiriladigan emitent o'zgaruvchisiga mos kelishi kerak.jti
" - Bu tokenning noyob identifikatori. Masalan, biz ushbu da'voni token ikki yoki undan ortiq marta ishlatilishini oldini olish uchun ishlatishimiz mumkin.sub
" - Bu token mavzusi. Bu foydalanuvchi yoki bizga yoqadigan narsa bo'lishi mumkin. Shuni yodda tutish kerakki, bu identifikator, kalit, nomlash yoki biz xohlagan narsa sifatida ham ishlatilishi mumkin.upn
" — foydalanuvchining asosiy ismi. Bu foydalanuvchi foydalanadigan asosiyni aniqlash uchun ishlatiladi.groups
" - Bu joriy foydalanuvchi tegishli bo'lgan guruhlar massivi. Asosan, bu token bilan so'rov nima qilishi mumkinligini aniqlaydi. Tokenimizda biz bir nechta Maxsus daʼvolarni koʻramiz. Biz bundan xuddi Zaxiralangan daʼvolar kabi foydalanishimiz mumkinuser_id
" - Biz bundan foydalanuvchi identifikatorini o'rnatish uchun foydalanamiz.access
" — Biz foydalanuvchining kirish darajasini aniqlaymiz.name
" - foydalanuvchi nomi. Keling, hozirgacha bilganlarimizni takrorlaylik. Biz belgilagan tuzilishga ega bo'lgan tokenlar bilan aloqa qilishimizni bilamiz. Bundan tashqari, biz ilovamiz konfiguratsiyasini, logback konfiguratsiyasini o'rnatdik va nihoyat, korporativ loviya qidirish uchun maxsus konfiguratsiyani o'rnatdik. Keling, paket modelini ko'rib chiqaylik. Bu erda biz 3 ta sinfni topamiz. Bu sinflar asosan hisoblar yig'indisini va client
va account
o'rtasidagi vakillikni ifodalaydi. Shunday qilib, Client
joylashgan Model.kt kotlin fayliga qarashdan boshlashimiz mumkin:
data class Client constructor( @JsonProperty var name: String ?= null )
Ushbu birinchi model klassi bizning mijozimiz vakilidir. Bizning ishimiz uchun client
faqat ismga ega. Bu " jwt
" atribut nomi bilan ifodalangan foydalanuvchi nomi. Bundan tashqari, bizda Account
bor:
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)) ) }
Ushbu sinfda biz asosan hisob raqamini, mijozni, joriy qiymatni va nihoyat kredit qiymatini o'rnatamiz. E'tibor bering, biz barcha qiymatlarni sukut bo'yicha 0 ga qo'yamiz. Biz BigDecimaldan ham foydalanamiz, chunki biz pul bilan ishlaymiz. Pul aniq bo'lishi kerak va tizimning o'zgarishi yoki pastga tushishiga duch kelmasligi kerak. Bu boshqa so'zlar bilan aytganda va misol sifatida 0. 0000000000000000000000000000000000000000000000000001
evro kabi raqam har doim shu raqam bo'lib qolishi kerakligini anglatadi. Shuningdek, biz hisobimizga qiymatlar qo'shmoqchimiz. Bu erda addCurrentValue usuli paydo bo'ladi. Xuddi shu sabablarga ko'ra, biz ham kreditimizni addCreditValue
bilan to'ldiramiz.Nihoyat, ma'lumotlar sozlamalarimizning oxirgi qismida biz Accounts
sinfiga duch kelamiz:
open class Accounts constructor( open val accountMap: MutableMap<String, Account> = mutableMapOf() )
Bu aslida barcha hisoblarimizning yig'uvchisidir. Biz uning xarita mazmunidan maʼlumotlar bazasining harakatini taqlid qilish uchun foydalanamiz. Endi kontroller paketini koʻrib chiqamiz. Bu erda biz ma'lumotlar modelimiz bilan ishlaydigan ilovamizni yaratamiz. Birinchidan, BankApplication
sinfini ko'rib chiqaylik:
@LoginConfig(authMethod = "MP-JWT") @ApplicationPath("/") @DeclareRoles("admin", "creditor", "client", "user") class BankApplication : Application()
Bu bilan biz 3 ta muhim narsani aytamiz. LoginConfig izohi bilan biz uni MicroProfile-ga muvofiq JWT
tokenlaridan foydalanish va tushunish uchun belgilaymiz. ApplicationPath ilova ildizini belgilaydi. Bu erda ilovaning URL manzili boshlanadi. Bizning misolimizda u HTTP://localhost:8080 bo'ladi. Va nihoyat, DeclareRoles ilovamiz tomonidan ishlatiladigan va qabul qilinadigan rollarni belgilaydi. Rollar va guruhlar bu vaziyatda bir-birini almashtiradigan atamalardir. Inyeksiya samarali ishlashi uchun biz hisob xaritasini aniqlash uchun maxsus izoh yaratamiz:
annotation class AccountsProduct
To'liq ekran rejimiga kirish To'liq ekran rejimidan chiqish
Keyinchalik, AccountsFactory kesh ob'ektini yaratamiz:
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() } } }
Aynan shu zavod Accounts
uchun qidiruvni o'chirib qo'yganimiz sababidir. Qidiruv jarayoniga fasol yaratishga ruxsat berish o'rniga, biz o'zimiz agregator misolini yaratamiz. Produces izohidan foydalanish bizga loviya yaratish imkonini beradi. Bizning shaxsiy izohimiz, AccountsProduct yordamida biz ushbu loviyadan foydalanishni yanada aniqroq qilamiz. Nihoyat, ApplicationScoped
foydalanib, biz uning doirasini Application
doirasi sifatida aniqlaymiz. Boshqacha qilib aytadigan bo'lsak, hisobni yig'ish loviya ilova bo'ylab yagona ob'ekt sifatida ishlaydi. " createResponse
" JSON javoblarini yaratishning oddiy usulidir. Hozir bizga ikkita "Resurs" kerak. Bu asosan bahorda " Controllers
" bilan bir xil. Bu boshqa nom, lekin aynan bir xil foydalanishga ega. Keling, AccountsResource
sinfini ko'rib chiqaylik:
@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) } }
Ushbu sinfni batafsil ko'rib chiqish uchun bir lahzani ajrating. Path
izohi ushbu manbaga ildizdan qanday erishish mumkinligini belgilaydi. Esda tutingki, biz "/" dan ildiz sifatida foydalanamiz. Bunday holda, "hisoblar" bu resurs uchun bizning asosiy kirish nuqtamizdir. Bizning barcha resurslarimiz, bizning holatlarimizda faqat ikkitasi RequestResource bilan ishlaydi. Annotatsiya bilan Produces barcha so'rovlarga javoblar turidan qat'iy nazar JSON formatidagi xabarlar ko'rinishida bo'lishini aniqlaydi. aggregator
kiritish uchun biz faqat Inject annotatsiyasi va AccountsProduct
izohi birikmasidan foydalanamiz:
@Inject @AccountsProduct open var accounts: Accounts? = null
Bu biz zavodda belgilagan narsaga mos keladi. Bundan tashqari, biz xavfsizlikning ikkita muhim elementini ham kiritamiz. principal
va jsonWebToken
:
@Inject open var principal: Principal? = null @Inject open var jsonWebToken: JsonWebToken? = null
JsonWebToken
ham, Principal
ham bir xil bo'ladi va biz buni jurnallarimizda ko'ramiz. Resurslarimizda biz har doim ma'lum bir token bilan so'rovdan da'volarni kiritishimiz mumkin:
@Inject @Claim("name") open var name: JsonString? = null @Inject @Claim("user_id") open var userId: JsonNumber? = null
Bu Inject
va Claim
izohlarining kombinatsiyasi bilan amalga oshiriladi. Claim
izohi ostidagi nom qaysi daʼvo kiritmoqchi ekanligimizni belgilaydi. Biz parametrlarimizni belgilagan turga ehtiyot bo'lishimiz kerak. Bizning misolimizda, r bizga faqat JsonString
va JsonNumber
turlari kerak. Birinchidan, hisoblar va foydalanuvchilarni qanday yaratayotganimizni ko'rib chiqamiz:
@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() ) ) }
Hisob qaydnomalari va foydalanuvchilarni yaratish
Bu erda maqsad usullarni ajratish va ularga turli ruxsatnomalar berishdir. Bizning misolimizda, ularning ikkalasi ham shunchaki hisob yaratadi, lekin shuni ta'kidlash kerakki, faqat foydalanuvchi roli bo'lgan foydalanuvchilar createUser usulidan foydalanishi mumkin. Xuddi shu tarzda, faqat mijoz va kredit roliga ega foydalanuvchilar createAccount usulidan foydalanishlari mumkin. Endi ushbu resursning PUT so'rov usulini batafsil ko'rib chiqamiz:
@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) }
Naqd pul olish
Biz bilamizki, PUT
izohi ushbu usulga faqat PUT
tipidagi so'rovlar bilan kirish mumkinligini bildiradi. Annotatsiya yo'li keyin Jettyga ushbu usulga yo'l qiymat ekanligini aytadi. Bu PathParam
sifatida ham tanilgan. Nihoyat, biz ushbu usulni faqat administrator yoki mijoz roliga ega foydalanuvchilar ishlatishiga ruxsat berishimiz mumkin. Kirish qiymati PathParamdan foydalanish orqali bizning Long qiymat o'zgaruvchimizga o'tkaziladi. Agar biz hech qanday rollarni belgilamasak, to'g'ri tokenga ega har qanday foydalanuvchi ushbu usullardan foydalana oladi. CreditResource
xuddi shu tarzda amalga oshiriladi. yo'l:
@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) } }
Yagona farq shundaki, biz admin
va client
rollarini ishlatish o'rniga endi admin
va credit
rollaridan foydalanmoqdamiz. Bundan tashqari, ushbu resource
foydalanuvchilar hisoblari hech qachon yaratilmasligiga e'tibor bering. Bu faqat hisob resource
orqali mumkin. Endi kod qanday amalga oshirilganligini bilganimizdan so'ng, avval REST
xizmatimizda qaysi usullarni taqdim etganimizni takrorlaymiz.
Keling, foydalaniladigan xizmatlar ro'yxatini ko'rib chiqaylik:
Tur, URL, Yuk, Natija, Ruxsat etilgan rollar
POST,
POST,
OLISH,
QO'YISH,
OLISH,
OLISH,
OLISH,
QO'YISH,
OLISH,
OLISH,
Men ildiz papkasida bash
faylini yaratdim. Ushbu fayl "setupCertificates.sh" deb ataladi. Keling, nima qilishini tushunish uchun uni ko'rib chiqaylik:
#!/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
Atrof muhitni yaratish
Iltimos, faylga amal qiling, chunki men nima qilishini tushuntiraman. Bu nima qilayotganini aniq tushunishimiz uchun muhim. Biz birinchi navbatda PEM
formatida shaxsiy va ochiq kalitlarni yaratamiz. Keyin biz shaxsiy kalitni ishga tushiriladigan "your-finance-jwt-generator.jar" bilan ishlatamiz. Bu tokenlarni tezda yaratishga imkon beruvchi bizning ishlaydigan idishimiz. Emitentni keyinchalik o'zgartirib bo'lmaydi. Nihoyat, u token yaratadi. Ushbu tokenni qanday o'qishni keyinroq ko'rib chiqamiz. Bu tokenda 3 ta qoʻshimcha sarlavha daʼvolari mavjud. Bular "bolalar", "typ" va "alg". U quyidagi formatga amal qiladi:
{ "kid": "jwt.key", "typ": "JWT", "alg": "RS256" }
JWT
sarlavhasi
Keling, ushbu da'volarni batafsil ko'rib chiqaylik:
IANA
media turlarini e'lon qilish uchun ishlatiladi. Uchta variant mavjud: JWT
(JSON Web token), JWE
(JSON Web Encryption) va JWA
(JSON Web Algoritms). Bu turlar bizning tajribamizga tegishli emas. Biz faqat bizning tokenimiz juda yaxshi shifrlanmaganligini va uni parolini hal qilish juda oson ekanligini ko'ramiz. Shuningdek, biz tokenlarning shifrini ochishimiz mumkin bo'lsa-da, boshqa amallarni bajarish uchun ularni osonlikcha o'zgartira olmasligimizni ko'ramiz.Ochiq kalitimiz yordamida biz nihoyat shablonimizni o'zgartirish uchun foydalanishimiz mumkin. Yangi config.yml fayli quyidagicha ko'rinishi kerak:
kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN issuer: joaofilipesabinoesperancinha healthy: true
config.yml
Ikkinchi qadam to'rtta fayl yaratishdir. " jwt-plain-tokens
" katalogidagi har bir oddiy token uchun biz to'rtta buyruq yaratamiz. Birinchi buyruq o'z hisoblari bilan samarali ishlarni qila oladigan foydalanuvchilarni yaratishdir. Bular " admin
", " client
" va " credit
" profillariga ega foydalanuvchilar. Ularni yaratish uchun " createAccount.sh
" faylini ishga tushiramiz. Ikkinchi buyruq hali hech qanday huquqlarga ega bo'lmagan qolgan foydalanuvchilarni yaratadi. Bu "createUser.sh" fayli. Keling, ishga tushiraylik. Endi biz barcha foydalanuvchilar nihoyat yaratilganligini ko'ramiz. Endi tranzaktsiyalar tafsilotlarini ko'rib chiqamiz va qolgan ikkita buyruqni ko'rib chiqamiz. Biri "kashin" qilish uchun, ikkinchisi esa ko'proq kredit so'rash uchun. Birinchi yaratilgan fayl "sendMoney.sh" bosh skriptidir. Bu erda biz " cashin
" bo'yicha barcha so'rovlarni topishingiz mumkin. Ushbu faylda siz har bir foydalanuvchiga tasodifiy pul miqdorini yuborish uchun jingalak so'rovini topasiz. Keling, administrator ishini ko'rib chiqaylik:
#!/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 ekstrakti
Xuddi shu foydalanuvchilarning kredit so'rovlari ham ularga tayinlangan:
#!/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 ekstrakti
Bizning barcha characters
Nature
Ligasining bir qismidir. Aslida, bu bank tizimining bir qismi bo'lishi uchun faqat bir guruh odamlar. Shu nuqtai nazardan, ular atrof-muhitni himoya qilmoqdalar. Bu odamlar guruhi nima qilayotgani yoki hikoyaning qayeriga mos kelishi maqola uchun unchalik muhim emas, lekin kontekst uchun ular atrof-muhitni himoya qilish va iqlim o'zgarishi oqibatlarini sekinlashtirish bo'yicha harakatlarda qatnashadilar. Ba'zi characters
hamma narsani qila oladi, boshqalari hech narsa qila olmaydi, boshqalari esa faqat "kashin" yoki "kredit so'rashi" mumkin. Bundan tashqari, men nozik ma'lumotlarni chalkashtirib yuborayotganimga e'tibor bering. Ushbu tokenlar odatda baham ko'rilmasligi yoki ma'lum bir URL manzilida ko'rinmasligi kerak. Ha, ular har doim brauzerni ishlab chiquvchi konsoli orqali mavjud, ammo baribir ba'zi so'rovlarni protect
uchun. Bu "xavfsizlik-noaniqlik" deb nomlanuvchi tushuncha bo'lib and
foydalanuvchining foydalanilayotgan tokendan xabardor bo'lishiga texnik jihatdan to'sqinlik qilmasa ham, u to'xtatuvchi vosita sifatida ishlaydi. Ikkala usulda ham, biz depozit qo'yganimizda yoki biz kredit so'rang, e'tibor bering, har bir so'rov uchun biz 1 dan 500 gacha bo'lgan tasodifiy raqamni yubormoqdamiz.
JWT
tokeni qanday yasaladi
Endi biz tokenlarimizni yaratdik, keling, ulardan birini ko'rib chiqaylik. Men sizga tushunarsiz tokenni ko'rsatmoqchiman va biz buni tushunish uchun undan foydalanamiz. Mana bizning tokenimiz: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE
. FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO
. FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN
Shuni ta'kidlash kerakki, bizning tokenimiz uch qismga bo'lingan:
Header
va Payload
shifrlangan birikmasidir. Biz foydalanmoqchi bo'lgan algoritmni hal qilamiz va tokenning bu qismi asosan biz yuborayotgan xabarga ishonish yoki yo'qligini aniqlaydi. Bu o'sha kombinatsiyaga xosdir va serverimiz mos keladigan yoki yo'qligini aniqlash uchun biz yaratgan "ommaviy kalit" dan foydalanadi. Yuqoridagilarni eslasangiz, biz misolimizda RS256
foydalanamiz.
Davom etishdan oldin, iltimos, bizning misolimizda Header
va Payload
decyphered
mumkinligini unutmang. Biz shunchaki foydali yuk yoki sarlavhani o'zgartira olmaymiz va baribir uni ishonchli qilamiz. Zararli tokenning potentsial ta'siridan himoya faqat biz tanlagan algoritm bilan himoyalanishi mumkin. Shuning uchun oqilona tanlang. Agar siz bank kabi o'ta maxfiy ma'lumotlar tashvishga soladigan tashkilotda ishlayotgan bo'lsangiz, iltimos biz qilmoqchi bo'lgan ishni QILMANG. Bu biz uchun mahalliy ishlab chiqarilgan tokenlar tarkibini onlayn tekshirishning bir usuli. Birinchidan, https://jwt.io/ saytiga o‘tamiz va JWT
tokenimizni to‘ldiramiz. O'zingiz yaratgan tokendan foydalaning:
Tokenimiz tarkibini tekshirish uchun https://jwt.io/ dan foydalaning, keling, bizda nima borligini ko'rib chiqaylik. Bu bizning administrator tokenimiz. Bu odam bizning misolimizda "Admin". Bizning barcha parametrlarimiz mavjudligini ko'rishimiz mumkin. Ro'yxatimizda "sub", "aud", "upn", "access", "user_id", "iss", "name", "groups" va nihoyat "jti" ni ko'ramiz. Bizda qo'shimcha da'volar ham bor. Keling, ularni ko'rib chiqaylik:
" auth_time " - Bu autentifikatsiya sodir bo'lganda. Bizning tokenimiz 2022-yil 17-iyul, yakshanba kuni 16:15:47 GMT+02:00 DST" iat "da autentifikatsiya qilingan - Bu token yaratilgan. Bizning holatda, bu auth_time bilan bir vaqtda sodir bo'ladi." exp " — Bu tokenning amal qilish muddati. 2022-yil 17-iyul, yakshanba kuni 16:32:27 GMT+02:00 DST tugaydi. Tokenimizda hech qanday amal qilish muddati ko‘rsatilmagan. Bu shuni anglatadiki, JWT
o'zining ~15 daqiqalik standart qiymatidan foydalanadi.
Keling, ba'zi testlarni bajaramiz.
Kod GitHub da foydalanishga tayyor. Agar biz kodni tekshirib chiqsak va uni Intellij bilan ochsak, bu dasturni Spring Boot ilovasi kabi ishga tushira olmasligimizni bilishimiz kerak. Uni ishga tushirish uchun "psvm" yo'q. Buning o'rniga, biz yaratilgan kavanozni to'g'ridan-to'g'ri ishga tushirishimiz va bundan oldin "mvn qurish" ni yaratishimizga ishonch hosil qilishimiz mumkin. Hozir men uni qanday ishlataman:
[ ] https://github.com/jesperancinha/your-finance-je "Ilovani ishga tushirish uchun muhitni sozlash")
Endi “ setupCertificates.sh
” skriptini qayta ishga tushiramiz. Bu yerga kelish uchun qancha vaqt ketganingizni bilmayman, lekin 15 daqiqa allaqachon o'tib ketgan bo'lishi mumkin. Har holda, ularni qayta ishga tushiring. Keling, ilovamizni ishga tushiramiz! Uni quyidagicha boshlashimiz mumkin:
mvn clean install java -jar your-financeje-banking/target/your-financeje-banking.jar
Yoki biz uni ishga tushirishga tayyor konfiguratsiyamiz orqali ishga tushirishimiz mumkin. Agar hamma narsani tushunmoqchi bo'lsangiz, repo va Makefile-ni oldindan tekshiring:
make dcup-full-action
Ushbu skript 2 ta xizmatni ishga tushiradi. Biri 8080
portida, ikkinchisi 8081
portida. 8080
portida biz JWT
tokenlarini yaratish uchun o'z kodimiz bilan ishlaydigan ushbu dasturning versiyasini ishga tushiramiz. 8081 portida Adam Bien
tomonidan yaratilgan jwtknizr
generatoridan foydalangan holda versiyani ishga tushiramiz. Biz ushbu maqolani 8080
portida ishlaydigan xizmatga qaratamiz. Agar xohlasangiz, cypress
bilan ham ishlatishingiz mumkin:
make cypress-open
Bu cypress
konsolini open
va siz tanlagan brauzer yordamida testlarni o'tkazishingiz mumkin bo'ladi. Biroq, ushbu bosqichda brauzer imkoniyatlari hali ham cheklangan. So'rovlarning aksariyati aslida cypress
tomonidan taqdim etilgan buyruq qatori so'rovlari bo'ladi. Hozircha " cypress
" ga kirmaylik. Iltimos, brauzeringizga o'ting va ushbu manzilga o'ting:
http://localhost:8080/accounts/all
Biz shunday natijaga erishishimiz kerak:
Ko'rib turganimizdek, " Malory
", " Jack Fallout
" va " Jitska
" ning krediti yoki puli yo'q. Buning sababi, ularga faqat foydalanuvchi guruhi berilgan. Shuningdek, Shikka
kredit berilmaganiga e'tibor bering. " Shikka
", bizning guruh kreditiga ega bo'lmagan yagona mijozimiz. Agar jurnallarga qarasak, muvaffaqiyatli operatsiyalar quyidagi formatga ega ekanligini ko'rishimiz mumkin:
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 bizga operatsiya muvaffaqiyatli o'tganligini bildiradi. "Malory", "Jek Fallout" va "Jitska" holatlarida ikkala operatsiya ham muvaffaqiyatsiz tugadi va keyin biz bunday xabarni olamiz:
Sending money to jitska HTTP/1.1 403 Forbidden X-Powered-By: KumuluzEE/4.1.0 Content-Length: 0 Server: Jetty(10.0.9)
403 JWT
tokenimiz tasdiqlanganligini va ishonchli ekanligini bizga bildiradi. Biroq, foydalanuvchiga ushbu operatsiyani bajarish taqiqlanadi. Boshqacha qilib aytganda, ular belgilangan usulga kirish imkoniga ega emaslar.
Keling, tokenlarimizni biroz o'zgartiraylik. Agar sendMoney.sh faylining ba'zi belgilarini o'zgartirsak. Biz buni olishimiz kerak:
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)
To'liq ekran rejimiga kirish To'liq ekran rejimidan chiqish
Bu 401
bizning tokenimiz tasdiqlanmaganligini anglatadi. Bu shuni anglatadiki, server bizning tokenimiz ishonchli yoki yo'qligini tekshirish uchun foydalanadigan ochiq kalit mos kelmadi. Agar ochiq kalit JWT tokenining imzosini baholay olmasa va tasdiqlay olmasa, u keyin uni rad etadi.
Xulosa qilib aytganda, Sarlavha va "Payload" shifrlanmagan. Ular faqat 64 ta "kodlangan". Bu shuni anglatadiki, "Dekodlash" har doim foydali yuk nima ekanligini ko'rishga imkon beradi. Agar biz foydali yukimizni tinglashdan himoya qilmoqchi bo'lsak, tokenning "Yuqori" dan identifikatsiya parametrlarini tanlashdan boshqa narsa uchun foydalanmasligimiz kerak. Muammo haqiqatan ham kimdir JWT
tokenini qo'liga olganida, masalan, TLS tunneli buzilganida va kimdir almashilgan xabarlar mazmunini o'qiy olsa. Bu sodir bo'lganda, yana bir himoya mavjud. Va bu imzo. Kirayotgan xabarni tekshirishga qodir bo'lgan yagona narsa ochiq kalitni o'z ichiga olgan serverdir. Ushbu ochiq kalit, garchi ochiq bo'lsa-da, faqat imzo va "Sarlavha + Payload" bilan ishlash orqali kiruvchi xabarni tasdiqlash imkonini beradi.
Biz sessiyamizning oxiriga yetdik. Bunga rioya qilganingiz uchun tashakkur. Biz JWT
tokenlarining XML hamkasbi SAML
tokenlariga qaraganda qanchalik ixcham va juda kam batafsil ekanligini ko'rishimiz mumkin. Biz ma'lum usullar uchun zarur bo'lgan ruxsatlarni olish uchun tokenlarni yaratish va ulardan foydalanish qanchalik oson ekanligini va imzolangan token orqali u erga qanday etib borishimizni ko'rdik. Ammo JWT
qanday ishlashi haqida tasavvurga ega bo'lish juda muhim deb hisoblayman. Umid qilamanki, bu bilan men sizga JWT
tokenlari qanday ishlashi haqida yaxshi ma'lumot berdim. Bularning barchasi qanday ishlashi haqida yaxshiroq tasavvurga ega bo'lish uchun sizga amalga oshirilgan cypress
sinovlari bilan o'ynashni maslahat beraman. Bu so'rovlar qanday amalga oshirilayotganini, biz nimani sinab ko'rayotganimizni va nima kutilayotganini ko'rishning ajoyib usuli. Shunda siz nima uchun ba'zi foydalanuvchilar ma'lum operatsiyalarni bajarishi, boshqalari esa bajarmayotgani haqida ham yaxshiroq tasavvurga ega bo'lasiz. Men ushbu ilovaning barcha manba kodini GitHub'da joylashtirdim. Umid qilamanki, bu maqolani yozish men kabi sizga ham yoqdi. o'qiganingiz uchun tashakkur!