336 читања
336 читања

Зошто JWT е клучот за обезбедување на вашата апликација за претпријатие - и зошто KumuluzEE може да биде вашиот нов најдобар пријател

од страна на João Esperancinha40m2025/01/17
Read on Terminal Reader

Премногу долго; Да чита

„JWT“ или JavaScript Object Notation Web Token е стандард дефиниран во [ RFC7519] Може да се дефинира на повеќе начини и може да се користи за пренос на информации помеѓу две страни. Во оваа статија, ќе погледнеме како да се интегрира „JWT“ во заедничка Java претпријатие апликација.
featured image - Зошто JWT е клучот за обезбедување на вашата апликација за претпријатие - и зошто KumuluzEE може да биде вашиот нов најдобар пријател
João Esperancinha HackerNoon profile picture
0-item

Во денешно време, се повеќе се грижиме за перформансите, а во исто време сакаме да знаеме како системите можат брзо и сигурно да комуницираат. Многу пати сакаме да испратиме информации и да ги чуваме доверливи и безбедни колку што е можно повеќе. Чувствителните податоци понекогаш мора и јавно да се движат низ мрежата и да активираат дејства на другиот крај на жицата. Најчесто, сакаме да генерираме дејства што ќе предизвикаат мутации на податоците. Во овие случаи, ние не гледаме само на заштита на нашите податоци. Сакаме да се увериме дека дејствата што се активираат со испраќање на нашите податоци се доверливи. Можеме да ги заштитиме нашите податоци на неколку начини. Најчесто, ние ги испраќаме податоците преку безбедна врска TLS (Transport Layer Security). Тоа ќе осигури дека нашите податоци се шифрираат преку жица. Ние користиме сертификати за да создадеме доверливи односи меѓу две страни и да го постигнеме тоа. Во оваа статија, сакам да разговарам за стандардот JWT и понатаму да видам како можеме да го интегрираме JWT во заедничка апликација Enterprise . Во овој случај, ќе погледнеме во KumuluzEE . Ајде да погледнеме неколку основни концепти. JWT или JSON Web Token, или уште подобро, JavaScript Object Notation Web Token, е стандард дефиниран во RFC7519 . Овој стандард е, како и сите RFC (Барање за коментари) стандарди, дефиниран, напишан и објавен од IETF (Internet Engineering Task Force). Може да се дефинира на повеќе начини. Општо земено, можеме да кажеме дека JWT е компактна, безбедна форма за пренос на побарувања помеѓу две страни. Еден начин да се поедностави што е тврдење, во основа е да се опише како пар име/вредност што содржи информации. Овие информации ни се потребни за да гарантираме неколку важни аспекти од нашата интернет комуникација. Треба да се погрижиме информациите што ги добиваме да бидат потврдени и доверливи во прв случај. Потоа треба да го потврдиме. Ова е во основа. За да го имплементираме овој стандард, можеме да користиме неколку рамки кои можат да ни помогнат да имплементираме Java претпријатие апликација. Пролетната чизми се користи нашироко. Многу пати, исто така, се завиткува под друго име во софтвер за соодветен софтвер од одредени организации како банки и други финансиски организации. За нашиот пример, решив да направам нешто поинакво. Наместо Spring Boot, ќе погледнеме пример со KumuluzEE . Поентата е да се идентификува точно што е JWT и како изгледа. Java Enterprise Applications се во основа апликации кои можат да се распоредат во апликациски сервер или едноставно да се извршуваат сами преку употреба на вграден сервер. Како пример, апликациите Spring Boot работат на вграден Tomcat сервер. Во оваа статија, нашиот фокус ќе биде поставен на KumuluzEE . Исто како Spring Boot, содржи и вграден сервер. Освен што во овој случај се вика Џети. Ова се користи во комбинација со Weld со цел да се обезбеди CDI (Context Dependency Injection). Сите стандарди за технологија на Java EE и Jakarta EE се компатибилни со оваа framework .

2. Пример на случај


За да покажам како функционира JWT во неговата основна форма, морав да смислам начин да го претставам. Класични примери каде безбедноста е грижа се банките. Сепак, правењето на цела банкарска апликација за да се покаже како функционира JWT би било губење време и можеби би биле вклучени премногу концепти. Наместо тоа, она што го направив е многу едноставен банкарски систем. Нашата главна грижа е да покажеме како податоците течат низ жицата и како корисниците добиваат пристап до одредени области на нашата апликација. Исто така, нема да разговарам за TLS или како можеме да испраќаме шифрирани информации преку жица. Ќе го задржиме нашиот фокус на JWT во неговата најчиста форма. Нашиот случај е банкарски систем што го користи група која ја брани природата и животната средина. Ова е само забавен начин да се покаже како функционира JWT . Главниот лик на оваа Лига на природата е Луси, која станува вообичаен лик во сите мои написи.

3. Архитектура

Пред да започнеме, само да ја скицираме нашата апликација која работи. Тоа е многу едноставна апликација, но сепак е добра работа да се нацрта:

Причината зошто ова е толку едноставно е што бидејќи JWT се проверува на секое барање и секое барање се потврдува со јавниот клуч, тогаш знаеме дека сè додека го испратиме точниот токен на секое барање ќе можеме да го добиеме. JWT може да се интегрира со OAuth2, Okta SSO или кој било друг механизам за авторизација. Во овој случај, она што го правиме е воспоставување автентикација и овластување. Во нашата апликација, ние ќе користиме JWT и со него ќе ја автентицираме нашата порака користејќи потпис. Сепак, нема да се најавиме во апликацијата. Наместо тоа, ќе ги овластиме корисниците да ја користат нашата апликација по успешната автентикација. Во овој момент, лесно е да се види дека JWT во неговото јадро е всушност многу мал дел од целосната апликација. Сепак, треба да се додаде некоја функционалност. Ова се ресурсите што ни се потребни:

  • Баланс систем
  • Кредитен систем


Само да кажеме дека нашиот основен систем ќе регистрира само барања за пари и кредит. Во суштина тоа само ќе акумулира вредности. Ајде, исто така, да претпоставиме дека некои луѓе ќе можат да добијат кредит, а други не. Некои луѓе ќе можат да складираат пари, а други ќе можат да добијат кредит.

4. Избор на технологии

Како што беше споменато во воведот, ќе користиме KumuluzEE како наша рамка за апликација за претпријатија и ќе имплементираме ултра-основна апликација на начин на кој можеме да ја разгледаме основната терминологија и концепти JWT . Погрижете се да ја имате точната верзија на Java. Во оваа фаза, ќе ни треба инсталиран минимален Java 17 SDK. Ќе ни требаат maven, git, Java-компатибилен IDE како IntelliJ и некаков вид школка.

5. Поставување

За да ја започнеме нашата апликација, имаме неколку зависности од KumuluzEE . Ова е главно затоа што KumuluzEE , исто како и Spring Boot има потреба од неколку зависности. Ајде накратко да ја погледнеме датотеката POM:

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

Ајде накратко да разговараме за неколку зависности. Додека го читате ова, ве молиме следете ја нашата датотека pom.xml од врвот до дното. Ова е важно за да се разбере следново објаснување. Потребен ни е пакет на зависности за да функционира нашата апликација. , За среќа, KumuluzEE , ни обезбедува библиотеки за микропрофил кои содржат основни стандардни пакети за да ја стартуваме оваа апликација. Сето ова е содржано во библиотеката KumuluzEE -Microprofile. За да можеме да ја конфигурираме нашата апликација со сите параметри JWT што ни се потребни, треба да додадеме библиотека MicroProfile на неа. Во исто време, потребна ни е библиотека за обработка на JSON. Ова ќе биде она што го прави Џонсон Кор. Се разбира, ни треба јадрото на KumuluzEE за да работи. Jetty е основниот сервер кој работи на рамката KumuluzEE . Затоа ни треба во нашите зависности. Имајќи предвид дека ни треба CDI , потребна ни е и библиотека што го поддржува. За да ги овозможиме нашите крајни точки REST, потребна ни е библиотеката за одмор на KumuluzEE . За да го добиеме нашиот API, тогаш ни треба библиотека Geronimo. Ова ќе осигури дека имаме на располагање имплементација на JSR-374 . Ние, исто така, треба да ги интерпретираме нашите JWT и неговите JSON-formatted содржини. Ломбок сам по себе не е навистина потребен. Тоа само прави сè убаво и сјајно! Исто така, важно е да се има најава за да можеме подобро да ги толкуваме дневниците и да ги разбереме нашите резултати. Ајде сега да ја погледнеме папката со нашите resources . За да започнеме, прво да разбереме што очекуваме да најдеме во оваа папка. Треба да ја конфигурираме нашата апликација со нешто поврзано со JWT , Logback и конечно, треба да кажеме нешто за гравот што ќе го создадеме. Да ја погледнеме наједноставната датотека таму. Beans.xml може да се најде во META-INF:

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

Ова е само типична и како што можеби мислите сега, малку стара датотека. Во овој момент, идејата е само да се активира KumuluzEE . Имаме акција за исклучување. Ова му кажува на Weld да не ги зема предвид class Accounts при неговото скенирање за дејство на грав. Ова е важно затоа што со имплементацијата што ја користиме, Weld во основа ќе ја смета секоја класа со празен конструктор како грав. Подоцна ќе видиме зошто не сакаме Сметките да се сметаат за грав. Во моментов, да имаме на ум дека поднесуваме барања во рамките на опсегот Барање. Ова е логично бидејќи секое барање може да има различен корисник. Сега да видиме како се имплементира „ logback “. Се наоѓа и во META-INF :

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

Ова е само многу јасна конфигурација за нашите logs . Конечно, можеби најважната датотека на нашата апликација. Ова е шаблонот за конфигурација. Во овој момент, важно е да се забележи дека некои од датотеките што ги создадов во овој проект се дел од структурата на шаблонот. Ќе објаснам повеќе за тоа подоцна. Оваа датотека со шаблон треба да се претвори во датотека config.yml која ќе ја чита MicroProfile. Оваа датотека се наоѓа во коренот на ресурсите:

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

Подоцна ќе видиме што точно значат сите овие својства. Сите тие се самообјаснети. Јавниот клуч и издавачот се сите параметри кои ќе бидат заменети. Ќе го истражиме тоа подоцна. Нашите баш скрипти ќе се погрижат да бидат заменети. Речиси сме подготвени да одиме на кодирање, но прво, ајде да ја погледнеме нашата структура на токени JWT .

6. Практичен код

Ајде да ја направиме нашата многу мала апликација. Овој дел ќе објасни како можеме да ја натераме нашата апликација да работи со JWT . Она што сакаме да го видиме е дали можеме да одредиме корисниците да пристапуваат до некои од нашите методи REST , а не до други. Еден од начините да започнете да го гледате овој код е прво да го погледнете нашиот обичен JWT токен. Еве го нашиот пример за администратор:

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

Секое од овие имиња во нашиот JSON се нарекува приговори. Во нашиот пример, гледаме неколку резервирани тврдења:

  • iss “ — Ова е издавачот на токенот. Можеме произволно да избереме вредност за ова. Вредноста на овој параметар мора да одговара на променливата на издавачот што треба да се замени во config.yml што ја видовме претходно.
  • " jti " — Ова е единствен идентификатор на токенот. На пример, можеме да го искористиме ова тврдење за да спречиме токен да се користи двапати или повеќе пати.
  • " sub " - Ова е предмет на токенот. Може да биде корисникот или нешто што ни се допаѓа. Важно е да се има предвид дека ова може да се користи и како идентификатор, клуч, именување или што било што сакаме.
  • upn “ — Главното име на корисникот. Ова се користи за да се идентификува главниот принцип што го користи корисникот.
  • " groups " — Ова е низа од групите на кои припаѓа тековниот корисник. Во суштина, ова ќе определи што може да направи барањето со овој токен. Во нашиот токен, потоа гледаме неколку прилагодени приговори. Можеме да го користиме ова исто како и резервираните побарувања
  • " user_id " — Ќе го користиме ова за да го поставиме корисничкиот id.
  • access “ — Ќе го одредиме нивото на пристап на корисникот.
  • name “ — Името на корисникот.

7. Практичен код

Ајде да направиме преглед на она што го знаеме досега. Знаеме дека ќе комуницираме со токени со структура што ја одредивме. Понатаму, ја поставивме конфигурацијата на нашата апликација, конфигурацијата за најава и конечно, поставивме приспособена конфигурација за пребарувањето на претпријатието грав. Да го погледнеме моделот на пакетот. Овде ќе најдеме 3 класи. Овие класи во основа само претставуваат агрегација на сметки и застапеност помеѓу client и account . На овој начин можеме да започнеме со гледање на kotlin датотеката Model.kt каде што Client се наоѓа на:

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

Оваа прва моделска класа е застапеност на нашиот клиент. Нашиот client за нашиот случај има само име. Ова е корисничкото име претставено со името на атрибутот „ jwt “. Понатаму, имаме Account :

 data class Account( @JsonProperty val accountNumber: String?, @JsonProperty val client: Client? = null, @JsonProperty var currentValue: BigDecimal = BigDecimal.ZERO, @JsonProperty var creditValue: BigDecimal = BigDecimal.ZERO ) { fun addCurrentValue(value: Long) = Account( accountNumber, client, currentValue .add(BigDecimal.valueOf(value)), creditValue ) fun addCreditValue(value: Long): Account = Account( accountNumber, client, currentValue, currentValue .add(BigDecimal.valueOf(value)) ) }


Во оваа класа, ние во основа поставивме број на сметка, клиент, тековна вредност и конечно кредитна вредност. Забележете дека ги поставуваме сите вредности на 0. Ние исто така користиме BigDecimal, чисто затоа што се занимаваме со пари. Парите мора да бидат точни и не можат да трпат системски заокружувања или заокружувања. Ова значи со други зборови и како пример дека број како што е 0. 0000000000000000000000000000000000000000000000000001 евра мора да остане тој број цело време. Исто така, сакаме да додадеме вредности на нашата сметка. Тука доаѓа до постоење на методот addCurrentValue. Од истите причини, Accounts исто така ќе го надополниме нашиот кредит со addCreditValue .

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

Ова во суштина е само агрегатор на сите наши сметки. Ќе ја користиме нејзината содржина на мапа за да го имитираме однесувањето на базата на податоци. Сега да го погледнеме пакетот на контролорот. Ова е местото каде што ја креираме нашата апликација која работи со нашиот модел на податоци. Прво, ајде да погледнеме во класата BankApplication :

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


Со ова кажуваме 3 важни работи. Со прибелешката LoginConfig, ја дефинираме да користи и разбира JWT токени според MicroProfile. ApplicationPath го дефинира коренот на апликацијата. Тука ќе започне URL-то на апликацијата. Во нашиот пример, тоа ќе биде HTTP://localhost:8080 . Конечно, DeclareRoles ги дефинира улогите што ќе се користат и прифаќаат од нашата апликација. Улогите и групите се заменливи термини во оваа ситуација. За да може инјектирањето да работи ефикасно, создаваме прибелешка специфична за да ја идентификуваме мапата на сметката:

 annotation class AccountsProduct

Влезете во режим на цел екран Излезете од режимот на цел екран

Следно, создаваме фабрика за кеш објекти AccountsFactory:

 class AccountsFactory : Serializable { @Produces @AccountsProduct @ApplicationScoped fun accounts(): Accounts = Accounts(mutableMapOf()) companion object { @Throws(JsonProcessingException::class) fun createResponse( currentAccount: Account, name: JsonString, accounts: Accounts, log: Logger, objectMapper: ObjectMapper, principal: Principal?, jsonWebToken: JsonWebToken? ): Response { val jsonObject = Json.createObjectBuilder() .add("balance", currentAccount.currentValue) .add("client", name) .build() accounts.accountMap[name.string] = currentAccount log.info("Principal: {}", objectMapper.writeValueAsString(principal)) log.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(jsonObject) .build() } } }


Оваа фабрика е причината зошто го оневозможивме пребарувањето специјално за Accounts . Наместо да дозволиме процесот на пребарување да создаде грав, ние самите го создаваме примерот на агрегаторот. Користењето на прибелешката Произведува, ни овозможува да го создадеме гравот. Користејќи ја нашата прилагодена прибелешка, AccountsProduct, ја правиме употребата на овој грав поконкретна. Конечно, со користење на ApplicationScoped , го дефинираме неговиот опсег како опсег Application . Со други зборови, збирката за агрегација на сметката ќе се однесува како единствен објект низ апликацијата. „ createResponse “ е само генерички метод за креирање JSON одговори. Она што сега ни треба се два „ресурси“. Ова е во основа исто како „ Controllers “ во пролет. Тоа е различно име, но има иста употреба. Да ја погледнеме класата AccountsResource :

 @Path("accounts") @RequestScoped @Produces(MediaType.APPLICATION_JSON) open class AccountResource { @Inject @AccountsProduct open var accounts: Accounts? = null @Inject open var principal: Principal? = null @Inject open var jsonWebToken: JsonWebToken? = null @Inject @Claim("access") open var access: JsonString? = null @Claim("iat") @Inject open var iat: JsonNumber? = null @Inject @Claim("name") open var name: JsonString? = null @Inject @Claim("user_id") open var userId: JsonNumber? = null @POST @RolesAllowed("admin", "client", "credit") @Throws(JsonProcessingException::class) open fun createAccount(): Response = createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account( client = Client(name = requireNotNull(name).string), accountNumber = UUID.randomUUID().toString() ) ) @POST @RolesAllowed("admin", "user") @Path("user") @Throws(JsonProcessingException::class) open fun createUser(): Response { return createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account( client = Client(name = requireNotNull(name).string), accountNumber = UUID.randomUUID().toString() ) ) } @GET @RolesAllowed("admin", "client") @Throws(JsonProcessingException::class) open fun getAccount(): Response? { return createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: return Response.serverError() .build() ) } @PUT @RolesAllowed("admin", "client") @Consumes(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun cashIn(transactionBody: TransactionBody): Response? { val userAccount = requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: return Response.serverError() .build() val currentAccount = userAccount.addCurrentValue(transactionBody.saldo?: 0) requireNotNull(accounts).accountMap[requireNotNull(name).string] = currentAccount return createResponse(currentAccount) } @GET @Path("all") @Produces(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun getAll(): Response? { val allAccounts = ArrayList( requireNotNull(accounts).accountMap .values ) logger.info("Principal: {}", objectMapper.writeValueAsString(principal)) logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(allAccounts) .build() } @GET @Path("summary") @Throws(JsonProcessingException::class) open fun getSummary(): Response? { val totalCredit = requireNotNull(accounts).accountMap .values .map(Account::currentValue) .stream() .reduce { result, u -> result.add(u) } .orElse(BigDecimal.ZERO) val jsonObject = Json.createObjectBuilder() .add("totalCurrent", totalCredit) .add("client", "Mother Nature Dream Team") .build() logger.info("Summary") logger.info("Principal: {}", objectMapper.writeValueAsString(principal)) logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(jsonObject) .build() } @GET @RolesAllowed("admin", "client") @Path("jwt") open fun getJWT(): Response? { val jsonObject = Json.createObjectBuilder() .add("jwt", requireNotNull(jsonWebToken).rawToken) .add("userId", requireNotNull(userId).doubleValue()) .add("access", requireNotNull(access).string) .add("iat", requireNotNull(iat).doubleValue()) .build() return Response.ok(jsonObject) .build() } @Throws(JsonProcessingException::class) private fun createResponse(currentAccount: Account): Response = AccountsFactory.createResponse( currentAccount, requireNotNull(name), requireNotNull(accounts), logger, objectMapper, principal, jsonWebToken ) companion object { val objectMapper: ObjectMapper = ObjectMapper() val logger: Logger = LoggerFactory.getLogger(AccountResource::class.java) } }

Одвојте момент за да ја разгледате оваа класа подетално. Прибелешката Path дефинира како да се стигне до овој ресурс од коренот. Запомнете дека користиме „/“ како корен. Во овој случај, „сметки“ е нашата root пристапна точка за овој ресурс. Сите наши ресурси, во нашиот случај само два работат со опсег RequestResource. Со прибелешка Произведува одредува дека сите одговори на сите барања без оглед aggregator нивниот тип ќе имаат форма на пораки форматирани JSON AccountsProduct

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


Ова се совпаѓа со она што го дефиниравме во фабриката. Понатаму, ние исто така вбризгуваме два важни елементи на безбедност. principal и jsonWebToken :

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


И JsonWebToken и Principal ќе бидат исти, и тоа ќе го видиме во нашите дневници. Во нашите ресурси, секогаш можеме да внесеме барања од барање со одреден токен:

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


Ова се постигнува со комбинацијата на прибелешките Inject и Claim . Името ставено под прибелешката Claim дефинира кое тврдење сакаме да го инјектираме. Мора да бидеме внимателни со типот со кој ги дефинираме нашите параметри. Во нашиот пример, ни требаат само типови JsonString и JsonNumber Прво, да погледнеме како создаваме сметки и корисници:

 @POST @RolesAllowed("admin", "client", "credit") @Throws(JsonProcessingException::class) open fun createAccount(): Response = createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account( client = Client(name = requireNotNull(name).string), accountNumber = UUID.randomUUID().toString() ) ) @POST @RolesAllowed("admin", "user") @Path("user") @Throws(JsonProcessingException::class) open fun createUser(): Response { return createResponse( requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: Account( client = Client(name = requireNotNull(name).string), accountNumber = UUID.randomUUID().toString() ) ) }

Креирање сметки и корисници


Целта овде е да може да се издвојат методите и да им се дадат различни дозволи. Во нашиот пример, обајцата само создаваат сметка, но важно е да се забележи дека само корисници со улоги, корисник може да го користат методот createUser. На ист начин, само корисници со улоги на клиент и кредит можат да пристапат до методот createAccount. Сега да го разгледаме детално методот на барање PUT на овој ресурс:

 @PUT @RolesAllowed("admin", "client") @Consumes(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun cashIn(transactionBody: TransactionBody): Response? { val userAccount = requireNotNull(accounts).accountMap[requireNotNull(name).string] ?: return Response.serverError() .build() val currentAccount = userAccount.addCurrentValue(transactionBody.saldo?: 0) requireNotNull(accounts).accountMap[requireNotNull(name).string] = currentAccount return createResponse(currentAccount) }

Кеширање


Знаеме дека прибелешката PUT покажува дека овој метод е достапен само со барања од типот PUT . Патеката за прибелешки потоа му кажува на Џети дека патеката до овој метод е вредност. Ова е познато и како PathParam . Конечно, можеме да го дефинираме овој метод да биде дозволен да се користи само од корисници со улоги администратор или клиент. Влезната вредност потоа се пренесува на нашата променлива Long value со користење на CreditResource . начин:

 @Path("credit") @RequestScoped @Produces(MediaType.APPLICATION_JSON) open class CreditResource { @Inject @AccountsProduct open var accounts: Accounts? = null @Inject open var principal: Principal? = null @Inject open var jsonWebToken: JsonWebToken? = null @Inject @Claim("access") open var access: JsonString? = null @Inject @Claim("iat") open var iat: JsonNumber? = null @Inject @Claim("name") open var name: JsonString? = null @Inject @Claim("user_id") open var userId: JsonNumber? = null @GET @RolesAllowed("admin", "credit") @Throws(JsonProcessingException::class) open fun getAccount(): Response = requireNotNull(accounts).let { accounts -> createResponse( accounts.accountMap[requireNotNull(name).string] ?: return Response.serverError().build() ) } @PUT @RolesAllowed("admin", "credit") @Consumes(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun cashIn(transactionBody: TransactionBody) = requireNotNull(accounts).let { accounts -> requireNotNull(name).let { name -> accounts.accountMap[name.string] = (accounts.accountMap[name.string] ?: return Response.serverError() .build()).addCreditValue(transactionBody.saldo?: 0L) createResponse( (accounts.accountMap[name.string] ?: return Response.serverError() .build()).addCreditValue(transactionBody.saldo?: 0L) ) } } @GET @Path("all") @Produces(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun getAll(): Response? { val allAccounts = ArrayList( requireNotNull(accounts).accountMap .values ) logger.info("Principal: {}", objectMapper.writeValueAsString(principal)) logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(allAccounts) .build() } @GET @Path("summary") @Produces(MediaType.APPLICATION_JSON) @Throws( JsonProcessingException::class ) open fun getSummary(): Response? { val totalCredit = requireNotNull(accounts).accountMap .values .map(Account::creditValue) .stream() .reduce { total, v -> total.add(v) } .orElse(BigDecimal.ZERO) val jsonObject = Json.createObjectBuilder() .add("totalCredit", totalCredit) .add("client", "Mother Nature Dream Team") .build() logger.info("Summary") logger.info("Principal: {}", objectMapper.writeValueAsString(principal)) logger.info("JSonWebToken: {}", objectMapper.writeValueAsString(jsonWebToken)) return Response.ok(jsonObject) .build() } @GET @RolesAllowed("admin", "client") @Path("jwt") open fun getJWT(): Response? { val jsonObject = Json.createObjectBuilder() .add("jwt", requireNotNull(jsonWebToken).rawToken) .add("userId", requireNotNull(userId).doubleValue()) .add("access", requireNotNull(access).string) .add("iat", requireNotNull(iat).doubleValue()) .build() return Response.ok(jsonObject) .build() } @Throws(JsonProcessingException::class) private fun createResponse(currentAccount: Account): Response { return AccountsFactory.createResponse( currentAccount, requireNotNull(name), requireNotNull(accounts), logger, objectMapper, principal, jsonWebToken ) } companion object { val objectMapper: ObjectMapper = ObjectMapper() val logger: Logger = LoggerFactory.getLogger(CreditResource::class.java) } }

Единствената разлика е во тоа што наместо да користиме улоги admin и client сега користиме admin и credit улоги. Исто така, забележете дека сметките за корисниците никогаш нема да се креираат во овој resource . Тоа е можно само преку resource на сметката. Сега кога знаеме како се имплементира кодот, ајде прво да направиме преглед кои методи сме ги ставиле на располагање во нашата услуга REST .

8. Употреба на апликација

Ајде да ја провериме листата на услуги што се користат:


Тип, URL, товар, резултат, дозволени улоги
ПОСТАВЕТЕ, http://localhost:8080/accounts,n/a,Created сметка, админ/клиент/кредит
ПОСТАВЕТЕ,
http://localhost:8080/accounts/user,n/a,Created корисник, администратор/корисник
ДОБИЈ,
http://localhost:8080/accounts,n/a,Соклопување сметка, админ/клиент
СТАВИ,
http://localhost:8080/accounts,{saldo: Long}, Тековно салдо, администратор/клиент
ДОБИЈ,
http://localhost:8080/accounts/all,n/a,All тековни сметки, Сите
ДОБИЈ,
http://localhost:8080/accounts/summary,n/a,Sum од сите салда, Сите
ДОБИЈ,
http://localhost:8080/credit,n/a,Соклопување сметка, админ/клиент
СТАВИ,
http://localhost:8080/credit,{saldo: Long}, Тековен кредит, администратор/клиент
ДОБИЈ,
http://localhost:8080/credit/all,n/a,All кредити, сите
ДОБИЈ,
http://localhost:8080/credit/summary,n/a,Sum
кредити, сите

9. Генерирање тест средина

Создадов bash датотека во root фолдерот. Оваа датотека се нарекува "setupCertificates.sh". Ајде да го разгледаме за да имаме идеја што прави:

 #!/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

Генерирање на животната средина

Ве молиме следете ја датотеката додека објаснувам што прави. Ова е важно за да разбереме што точно прави. Прво создаваме приватни и јавни клучеви во PEM формат. Потоа го користиме приватниот клуч со нашиот водлив „your-finance-jwt-generator.jar“ . Ова е нашата тегла што може да се движи што овозможува брзо создавање на токени. Издавачот не може да се промени подоцна. Конечно, создава токен. Ќе видиме како да го прочитаме овој токен подоцна. Овој токен содржи 3 дополнителни приговори за заглавие. Тоа се „дете“, „тип“ и „алг“. Го следи следниот формат:

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

Заглавјето на JWT

Да ги погледнеме овие тврдења подетално:

  • "дете" - Работи како навестување тврдење. Тоа покажува каков вид на алгоритам користиме.
  • „typ“ — Се користи за декларирање на типови медиуми IANA . Постојат три опции JWT (JSON Web token), JWE (JSON Web Encryption) и JWA (JSON Web Algorithms). Овие типови не се релевантни за нашиот експеримент. Ќе видиме само дека нашиот токен не е навистина добро шифриран и дека е навистина лесно да се дешифрира. Исто така, ќе видиме дека иако можеме да ги дешифрираме токените, не можеме така лесно да ги нарушиме за да извршиме други дејства.
  • "alg" — Вака го дефинираме типот на потпис што сакаме да го користиме. Потпишувањето може да се смета како криптографска операција која ќе осигури дека оригиналниот токен не е променет и дека му се верува. Во нашиот случај, ние користиме RS256 инаку познат како RSA Signature со SHA-256.

Со нашиот јавен клуч, конечно можеме да го користиме за да го промениме нашиот шаблон. Новата датотека config.yml треба да изгледа вака:

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

config.yml


Вториот чекор е да креирате четири датотеки. За секој обичен токен во директориумот „ jwt-plain-tokens “, ќе создадеме четири команди. Првата команда е да се создадат корисници кои можат ефективно да вршат работи со нивните сметки. Станува збор за корисници со профили „ admin “, „ client “ и „ credit “. Ајде да ја извршиме датотеката „ createAccount.sh “, за да ги создадеме. Втората команда ќе ги создаде останатите корисници кои сè уште немаат никакви права. Ова е датотеката „createUser.sh“. Ајде да го водиме. Сега ќе видиме дека сите корисници се конечно создадени. Ајде сега да погледнеме во детали за трансакциите и да ги разгледаме преостанатите две команди. Еден да „кашине“ и друг да бара повеќе кредит. Првата генерирана датотека е баш скриптата „sendMoney.sh“. Овде можеме да ги најдеме сите барања до „ cashin “. Во оваа датотека ќе најдете барање за завиткување за испраќање случајни количини пари до корисниците, по корисник. Ајде да го погледнеме случајот со администраторот:

 #!/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 екстракт

На истите корисници им се доделени и нивните барања за кредит:

 #!/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



Сите наши characters се дел од Лигата на Nature . Во суштина само некоја група на луѓе да биде дел од овој банкарски систем. Во овој контекст тие ја бранат животната средина. Не е навистина релевантно за написот што прави оваа група луѓе или каде се вклопуваат во приказната, туку за контекст, тие учествуваат во акции за одбрана на животната средина и забавување на ефектите од климатските промени . Некои од нашите characters можат да направат сè, други не можат ништо, а други можат само да „кашнат“ или само „да бараат кредит“. Забележете и дека прикривам чувствителни информации. Овие токени обично не треба да се споделуваат или да бидат видливи на одредена URL адреса. Тие се секогаш достапни преку програмерската конзола на прелистувачот, но сепак е да protect некои барања што се направени. Ова е концепт познат како „безбедност по опскурност“ and иако технички не го спречува корисникот да стане свесен за токенот што се користи, тој функционира како пречка. Во двата методи, кога правиме депозит или кога побарајте кредит, забележите дека за секое барање испраќаме случаен број од 1 до 500. Сега сме речиси подготвени да ја започнеме нашата апликација, но прво, ајде да се нурнеме во малку повеќе теорија.

10. Како се прави JWT токен




Сега кога ги генериравме нашите токени, ајде да погледнеме во еден од нив. Ќе ви покажам заматен токен и ќе го искористиме за да го разбереме ова. Еве го нашиот токен: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE . FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO . FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN Она што е важно овде да се забележи е дека нашиот токен е поделен на три дела:

  • Заглавие - Ова е заглавие за конфигурација на JSON кодирана од Base64, исто како што дискутиравме погоре.
  • Корисно оптоварување - ова е корисен товар со JSON кодиран од Base64. Ова е местото каде што ги дефиниравме нашите резервирани и приспособени побарувања. Овде можеме да дефинираме и Приватни и Јавни побарувања. И двете од нив спаѓаат под прилагодени барања. Како брза забелешка, можеме да правиме што сакаме со двете од овие тврдења. Сепак, јавните побарувања се нарекуваат оние дефинирани во регистарот на веб-токени на IANA JSON. Важно е да ги именуваме нашите токени на одреден начин за да избегнеме судири со регистарот. Јавните барања може да се дефинираат и како стандардни опционални. Приватните барања не следат никакви стандарди и наше е да ги дефинираме.
  • Потпис - Ова е местото каде што можеме да бидеме малку креативни. Потписот е шифрирана комбинација на Header и Payload . Ние одлучуваме за алгоритмот што сакаме да го користиме и овој дел од токенот во основа ќе определи дали треба да и се верува на пораката што ја испраќаме. Таа е единствена за таа комбинација и нашиот сервер ќе го користи „јавниот клуч“ што го создадовме за да утврди дали имаме совпаѓање. Ако се сеќавате од горенаведеното, ние користиме RS256 во нашиот пример.


Пред да продолжиме, имајте предвид дека и Header и Payload може да се decyphered во нашиот пример. Едноставно „не можеме“ да го манипулираме товарот или заглавието и сепак да го направиме доверлив. Заштитата од потенцијалните ефекти на злонамерниот токен може да биде заштитена само со алгоритмот што го избираме. Затоа, изберете мудро. Ако работите во организација каде што се загрижуваат строго доверливите информации, како што е банката, ве молиме НЕ го правете тоа што ќе го направиме. Ова е само начин да ја провериме онлајн содржината на токените што сме ги генерирале локално. Прво, да одиме на https://jwt.io/ и да го пополниме нашиот JWT токен. Користете го токенот што штотуку го генериравте:


Користење на https://jwt.io/ за проверка на содржината на нашиот токен Ајде да испитаме што имаме овде. Ова е нашиот администраторски токен. Таа личност е „Админ“ во нашиот пример. Можеме да видиме дека нашите параметри се сите достапни. Во нашата листа гледаме „sub“, „aud“, „upn“, „access“, „user_id“, „iss“, „name“, „groups“ и на крајот „jti“. Имаме и некои дополнителни побарувања. Ајде да ги погледнеме:



" auth_time " — Ова е моментот кога ќе се случи автентикацијата. Нашиот токен како што е автентификуван во недела, 17 јули 2022 година, 16:15:47 GMT + 02:00 DST" и тоа " - Ова е моментот кога е креиран токенот. Во нашиот случај, ова се случува истовремено како auth_time." exp " — Ова е датумот на истекување на токенот. Истекува во недела, 17 јули 2022 година во 16:32:27 GMT + 02:00 DST. Не наведовме датум на истекување во нашиот токен. Ова значи дека JWT ја користи својата стандардна вредност од ~ 15 минути.

Ајде сега да извршиме неколку тестови.

11. Вклучување на апликацијата

Кодот е подготвен за употреба на GitHub . Ако го провериме кодот и го отвориме со Intellij, треба да бидеме свесни дека не можеме да ја извршиме оваа апликација како апликација Spring Boot. Нема „psvm“ за да може да работи. Наместо тоа, можеме само да ја извршиме генерираната тегла директно и да се погрижиме да направиме „mvn build“ непосредно пред тоа. Еве како го користам во моментов:

[ ] https://github.com/jesperancinha/your-finance-je „Поставување на животната средина за извршување на апликацијата“)



Ајде сега повторно да ја извршиме скриптата „ setupCertificates.sh “. Не знам колку време ви требаше за да стигнете овде, но многу е веројатно дека 15-те минути веќе поминале во овој момент. За секој случај, само стартувајте ги повторно. Ајде да ја стартуваме нашата апликација! Можеме да ја стартуваме вака:

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

Или можеме само да го извршиме преку нашата подготвена конфигурација за трчање. Претходно проверете го репото и Makefile ако сакате да разберете што прави:

 make dcup-full-action

Оваа скрипта ќе работи 2 услуги. Едниот на портата 8080 , а другиот на портата 8081 . На портата 8080 ќе извршиме верзија на овој софтвер со наш сопствен код за генерирање на JWT токени. На портата 8081, ќе извршиме верзија користејќи го генераторот jwtknizr создаден од Adam Bien . Ние ќе го фокусираме овој напис, сепак на услугата што работи на портата 8080 . Ако сакате, можете да трчате и cypress со:

 make cypress-open

Ова ќе open конзолата cypress и ќе можете да ги извршите тестовите со прелистувачот по ваш избор. Сепак, опциите на прелистувачот сè уште се ограничени во оваа фаза. Повеќето од барањата всушност ќе бидат барања за командна линија обезбедени од cypress . Засега, да не навлегуваме во „ cypress “. Ве молиме одете на вашиот прелистувач и одете на оваа локација:

http://localhost:8080/accounts/all

Треба да добиеме ваков резултат:


Како што можеме да видиме, „ Malory “, „ Jack Fallout “ и „ Jitska “ немаат никаков кредит или пари. Ова е затоа што им е дадена само корисничката група. Забележете, исто така, дека Shikka не му е дадена никаков кредит. " Shikka ", е нашиот единствен клиент кој нема групен кредит. Ако ги погледнеме дневниците, можеме да видиме дека успешните операции го имаат овој формат:

 Sending money to admin HTTP/1.1 200 OK Date: Sun, 17 Jul 2022 15:01:13 GMT X-Powered-By: KumuluzEE/4.1.0 Content-Type: application/json Content-Length: 32 Server: Jetty(10.0.9) {"balance":212,"client":"Admin"}


А 200 ни дава до знаење дека операцијата поминала успешно. Во случајот „Малори“, „Џек Фалоут“ и „Јицка“, двете операции не успеваат и тогаш ќе добиеме ваква порака:

 Sending money to jitska HTTP/1.1 403 Forbidden X-Powered-By: KumuluzEE/4.1.0 Content-Length: 0 Server: Jetty(10.0.9)

А 403 ни дава до знаење дека нашиот JWT токен е потврден и дека му е доверлив. Меѓутоа, на корисникот му е забрането да ја изврши таа операција. Со други зборови, тие немаат пристап до назначениот метод.

Ајде малку да ги чепкаме нашите токени. Ако смениме некои од токените на датотеката sendMoney.sh. Треба да го добиеме ова:

 Sending money to admin HTTP/1.1 401 Unauthorized X-Powered-By: KumuluzEE/4.1.0 WWW-Authenticate: Bearer realm="MP-JWT" Content-Length: 0 Server: Jetty(10.0.9)

Влезете во режим на цел екран Излезете од режимот на цел екран

Овој 401 значи дека нашиот токен не е потврден. Тоа значи дека јавниот клуч што го користи серверот за да провери дали треба да му се верува на нашиот токен, не најде совпаѓање. Ако јавниот клуч не може да го оцени и потврди потписот на токенот JWT, тогаш ќе го одбие.

Како резиме, Заглавието и „Патоварот“ не се шифрирани. Тие се само база 64 „кодирани“. Ова значи дека „Декодирањето“ ни овозможува секогаш да ѕирнеме во она што всушност е товарот. Ако сакаме да го заштитиме нашиот товар од прислушување, не треба да го користиме „Payload“ на токенот за ништо друго освен да избереме параметри за идентификација. Проблемот лежи навистина кога некој ќе го добие токенот JWT , на пример, кога тунелот TLS е компромитиран и некој може да ја прочита содржината на разменетите пораки. Кога тоа ќе се случи, има уште една заштита. И ова е потписот. Единствениот што може да потврди порака што влегува е серверот што го содржи јавниот клуч. Овој јавен клуч, иако јавен, дозволува само валидација на дојдовната порака со трчање против потписот и „Header + Payload“.

12. Заклучок

Стигнавме до крајот на нашата сесија. Ви благодариме што го следевте ова. Можеме да видиме како JWT токените се компактни и многу помалку опширни од нивниот XML колега, SAML токените. Видовме колку е лесно да се креираат и користат токени за да се добијат одредени овластувања потребни за одредени методи и како доаѓаме таму преку потпишан токен. Сепак, сметам дека е многу важно да се добие идеја за тоа како функционира JWT . Се надеваме, со ова, ви дадов добар вовед во тоа како функционираат токените JWT . За да добиете подобра идеја за тоа како функционира сето ова, ве советувам да си поиграте со имплементираните тестови cypress . Ова е одличен начин да се види како се поднесуваат барањата и што тестираме и што се очекува. Потоа, исто така, ќе добиете подобра идеја за тоа зошто некои корисници можат да извршуваат одредени операции, а други не. Го ставив целиот изворен код на оваа апликација на GitHub Се надевам дека уживавте во оваа статија исто како што јас уживав во пишувањето тоа. Ви благодариме што прочитавте!

13. Користена литература


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

About Author

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

ВИСЕТЕ ТАГОВИ

ОВОЈ СТАТИЈА БЕШЕ ПРЕТСТАВЕН ВО...

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks