Šiais laikais mes vis labiau susirūpinę dėl našumo ir tuo pat metu norime žinoti, kaip sistemos gali greitai ir patikimai bendrauti. Daug kartų norime siųsti informaciją ir išlaikyti ją kiek įmanoma konfidencialią ir saugią. Neskelbtini duomenys kartais taip pat turi viešai judėti žiniatinklyje ir suaktyvinti veiksmus kitame laido gale. Dažniausiai norime generuoti veiksmus, kurie sukels duomenų mutacijas. Tokiais atvejais rūpinamės ne tik savo duomenų apsauga. Norime įsitikinti, kad veiksmai, suaktyvinti siunčiant mūsų duomenis, yra patikimi. Savo duomenis galime apsaugoti keliais būdais. Dažniausiai duomenis siunčiame saugiu TLS
(Transport Layer Security) ryšiu. Tai užtikrins, kad mūsų duomenys bus užšifruoti per laidą. Mes naudojame sertifikatus, kad sukurtume patikimus santykius tarp dviejų šalių ir tai pasiektume.Šiame straipsnyje noriu aptarti JWT
standartą ir toliau pažiūrėti, kaip galime integruoti JWT
į bendrą Enterprise
programą. Šiuo atveju pažvelgsime į KumuluzEE
.Pažvelkime į keletą pagrindinių sąvokų. JWT
arba JSON žiniatinklio prieigos raktas arba dar geriau JavaScript Object Notation Web Token yra standartas, apibrėžtas RFC7519 . Šį standartą, kaip ir visus RFC
(Request For Comments) standartus, apibrėžė, parašė ir paskelbė IETF
(Internet Engineering Task Force). Jį galima apibrėžti keliais būdais. Apskritai galime sakyti, kad JWT
yra kompaktiška ir saugi pretenzijų tarp dviejų šalių perdavimo forma. Vienas iš būdų supaprastinti, kas yra teiginys, iš esmės yra apibūdinti jį kaip pavadinimo / reikšmės porą, kurioje yra informacijos. Mums reikia šios informacijos, kad galėtume užtikrinti keletą svarbių interneto komunikacijos aspektų. Turime užtikrinti, kad gauta informacija visų pirma būtų patvirtinta ir patikima. Tada turime tai patvirtinti. Tai iš esmės yra tai.Siekdami įgyvendinti šį standartą, galime naudoti kelias sistemas, kurios gali padėti mums įdiegti „Java“ įmonės programą. Spring Boot yra plačiai naudojamas. Daugybę kartų jis taip pat yra supakuotas kitu pavadinimu tam tikrų organizacijų, pvz., bankų ir kitų finansinių organizacijų, programinėje įrangoje. Pavyzdžiui, aš nusprendžiau padaryti kažką kitokio. Vietoj Spring Boot pažvelgsime į KumuluzEE
pavyzdį. Esmė yra tiksliai nustatyti, kas yra JWT
ir kaip jis atrodo. „Java Enterprise Applications“ iš esmės yra programos, kurias galima įdiegti programų serveryje arba tiesiog paleisti pačios, naudojant įterptąjį serverį. Pavyzdžiui, „Spring Boot“ programos veikia įterptajame „Tomcat“ serveryje. Šiame straipsnyje daugiausia dėmesio skirsime KumuluzEE
. Kaip ir „Spring Boot“, jame taip pat yra įterptinis serveris. Išskyrus tai, kad šiuo atveju jis vadinamas prieplauka. Tai naudojama kartu su Weld, siekiant užtikrinti CDI (kontekstinės priklausomybės įpurškimą). Visi Java EE
ir Jakarta EE
technologijų standartai yra suderinami su šia framework
.
Norėdamas parodyti, kaip JWT
veikia pagrindinėje formoje, turėjau sugalvoti būdą, kaip jį pristatyti. Klasikiniai pavyzdžiai, kai susirūpinimą kelia saugumas, yra bankai. Tačiau pateikti visą banko paraišką, kad parodytų, kaip veikia JWT
, būtų gaištamas laikas ir galbūt būtų įtraukta per daug koncepcijų. Vietoj to, aš sukūriau labai paprastą bankų sistemą. Mūsų pagrindinis rūpestis yra parodyti, kaip duomenys teka per laidą ir kaip vartotojai gauna prieigą prie tam tikrų mūsų programos sričių. Taip pat neketinu aptarinėti TLS ar kaip galime laidu siųsti šifruotą informaciją. Daugiausia dėmesio skirsime JWT
gryniausia forma. Mūsų atvejis yra bankų sistema, kurią naudoja gamtą ir aplinką ginanti grupė. Tai tik įdomus būdas parodyti, kaip veikia JWT
. Pagrindinė šios gamtos lygos veikėja yra Liusė, kuri tampa įprastu visų mano straipsnių veikėju.
Prieš pradėdami, tiesiog nubraižykite veikiančią programą. Tai labai paprasta programa, bet vis tiek verta ją nupiešti:
Priežastis, kodėl tai taip paprasta, yra ta, kad JWT
patikrinama kiekviena užklausa ir kiekviena užklausa patikrinama pagal viešąjį raktą, todėl žinome, kad tol, kol išsiųsime teisingą prieigos raktą kiekvieną užklausą, galėsime ją įvykdyti. JWT
gali būti integruotas su OAuth2, Okta SSO ar bet kokiu kitu autorizacijos mechanizmu. Šiuo atveju tai, ką darome, yra autentifikavimo ir autorizacijos nustatymas. Savo programoje naudosime JWT
ir kartu su juo patvirtinsime savo pranešimą naudodami parašą. Tačiau mes neprisijungsime prie programos. Vietoj to mes leisime naudotojams naudoti mūsų programą po sėkmingo autentifikavimo. Šiuo metu nesunku suprasti, kad JWT
iš tikrųjų yra labai maža visos programos dalis. Nepaisant to, reikia pridėti tam tikrų funkcijų. Tai yra ištekliai, kurių mums reikia:
Tarkime, mūsų pagrindinė sistema registruos tik pinigų ir kredito užklausas. Iš esmės tai tik kaups vertybes. Taip pat manykime, kad vieni žmonės galės gauti kreditą, o kiti – ne. Vieni žmonės galės laikyti pinigus, o kiti – gauti kreditą.
Kaip minėta įžangoje, kaip įmonės taikomųjų programų sistemą naudosime KumuluzEE
ir įdiegsime itin paprastą programą taip, kad galėtume pažvelgti į pagrindinę JWT
terminologiją ir sąvokas. Įsitikinkite, kad turite tinkamą Java versiją. Šiame etape mums reikės įdiegti minimalų Java 17 SDK. Mums reikės maven, git, su Java suderinamo IDE, pvz., IntelliJ, ir tam tikro apvalkalo.
Kad galėtume pradėti taikyti, turime keletą KumuluzEE
priklausomybių. Taip yra daugiausia todėl, kad KumuluzEE
, kaip ir „Spring Boot“, reikia kelių priklausomybių. Trumpai pažvelkime į POM failą:
<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>
Trumpai aptarkime keletą priklausomybių. Skaitydami tai sekite pom.xml
failą iš viršaus į apačią. Tai svarbu norint suprasti šį paaiškinimą. Mums reikia priklausomybių paketo, kad programa veiktų. Laimei, KumuluzEE
“ teikia mums mikroprofilių bibliotekas, kuriose yra pagrindiniai standartiniai paketai, leidžiantys paleisti šią programą. Visa tai yra KumuluzEE
mikroprofilio bibliotekoje. Kad galėtume sukonfigūruoti savo programą su visais mums reikalingais JWT
parametrais, turime prie jos pridėti MicroProfile biblioteką. Tuo pačiu metu mums reikia JSON apdorojimo bibliotekos. Tai bus tai, ką padarys Johnson Core. Žinoma, mums reikia KumuluzEE
branduolio, kad veiktų. Jetty yra pagrindinis serveris, kuriame veikia KumuluzEE
sistema. Štai kodėl mums to reikia mūsų priklausomybėse. Atsižvelgiant į tai, kad mums reikia CDI
, mums taip pat reikia ją palaikančios bibliotekos. Kad įgalintume REST galinius taškus, mums reikia likusios KumuluzEE
bibliotekos. Kad gautume API, mums reikia Geronimo bibliotekos. Tai užtikrins, kad turėsime JSR-374
diegimą. Taip pat turime interpretuoti savo JWT
ir jo JSON-formatted
turinį. Lombok per se tikrai nereikalingas. Tai tiesiog daro viską gražu ir blizga! Taip pat svarbu turėti prisijungimo funkciją, kad galėtume geriau interpretuoti žurnalus ir suprasti rezultatus. Dabar pažvelkime į resources
aplanką. Norėdami pradėti, pirmiausia supraskime, ką tikimės rasti šiame aplanke. Turime sukonfigūruoti savo programą su kažkuo susijusiu su JWT
, Logback ir galiausiai turime pasakyti ką nors apie pupeles, kurias ketiname sukurti. Pažvelkime į paprasčiausią failą. Beans.xml galima rasti 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>
Tai tik įprastas ir, kaip galbūt dabar galvojate, šiek tiek senas failas. Šiuo metu idėja yra tik pradėti KumuluzEE
paleisti. Turime pašalinimo veiksmą. Tai įpareigoja Weld neatsižvelgti į klasės paskyras, kai ieškoma beans veiksmų. Tai svarbu, nes naudojant mūsų naudojamą įgyvendinimą, Weld
iš esmės kiekvieną klasę su tuščiu konstruktoriumi laikys pupele. Vėliau pamatysime, kodėl nenorime, kad paskyros būtų laikomos pupelėmis. Šiuo metu turėkime omenyje, kad teikiame užklausas pagal užklausos sritį. Tai logiška, nes kiekviena užklausa gali turėti skirtingą vartotoją. Dabar pažiūrėkime, kaip įgyvendinamas „ logback
“. Jis taip pat randamas 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>
Tai tik labai paprasta mūsų logs
konfigūracija. Galiausiai, galbūt svarbiausias mūsų programos failas. Tai yra konfigūracijos šablonas. Šiuo metu svarbu pažymėti, kad kai kurie failai, kuriuos sukūriau šiame projekte, yra šablono struktūros dalis. Daugiau apie tai paaiškinsiu vėliau. Šis šablono failas turėtų būti paverstas config.yml failu, kurį skaitys MicroProfile. Šis failas yra išteklių šaknyje:
kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: {{ publicKey }} issuer: {{ issuer }} healthy: true
Vėliau pamatysime, ką iš tikrųjų reiškia visos šios savybės. Visi jie yra savaime suprantami. PublicKey ir emitentas yra visi parametrai, kurie bus pakeisti. Tai išnagrinėsime vėliau. Mūsų bash scenarijai užtikrins, kad jie būtų pakeisti. Mes beveik pasiruošę pradėti kodavimą, bet pirmiausia pažvelkime į mūsų JWT
prieigos rakto struktūrą.
Padarykime savo labai mažą programą. Šiame skyriuje bus paaiškinta, kaip galime pritaikyti programą, kad ji veiktų su JWT
. Mes norime pamatyti, ar galime nurodyti, kad vartotojai galėtų pasiekti kai kuriuos REST
metodus, o ne kitus. Vienas iš būdų pradėti žiūrėti šį kodą yra pirmiausia pažvelgti į mūsų paprastą JWT
prieigos raktą. Štai mūsų administratoriaus pavyzdys:
{ "iss": "joaofilipesabinoesperancinha", "jti": "01MASTERFINANCE", "sub": "admin", "aud": "nature", "upn": "admin", "groups": [ "user", "admin", "client", "credit" ], "user_id": 1, "access": "TOP", "name": "Admin" }
Kiekvienas iš šių pavadinimų mūsų JSON
yra vadinamas pretenzijomis. Mūsų pavyzdyje matome keletą rezervuotų pretenzijų:
iss
“ – tai žetono išdavėjas. Tam galime savavališkai pasirinkti vertę. Šio parametro reikšmė turi atitikti leidėjo kintamąjį, kuris turi būti pakeistas faile config.yml, kurį matėme anksčiau.jti
“ – tai unikalus prieigos rakto identifikatorius. Pavyzdžiui, galime naudoti šį teiginį, kad išvengtume prieigos rakto naudojimo du kartus ar daugiau kartų.sub
“ – tai žetono tema. Tai gali būti vartotojas arba bet kas, kas mums patinka. Svarbu nepamiršti, kad tai taip pat gali būti naudojama kaip identifikatorius, raktas, įvardijimas ar bet kas, ko norime.upn
“ – pagrindinis vartotojo vardas. Tai naudojama vartotojo naudojamam principui nustatyti.groups
" – tai grupių, kurioms priklauso dabartinis vartotojas, masyvas. Iš esmės tai lems, ką gali padaryti užklausa su šiuo prieigos raktu. Savo žetonu matome keletą pasirinktinių reikalavimų. Galime tai naudoti taip pat gerai, kaip ir Reserved teiginiususer_id
" - naudosime tai norėdami nustatyti vartotojo ID.access
“ – nustatysime vartotojo prieigos lygį.name
“ – vartotojo vardas. Apibendrinkime tai, ką žinome iki šiol. Žinome, kad bendrausime su mūsų nustatytos struktūros žetonais. Be to, nustatėme savo programos konfigūraciją, prisijungimo konfigūraciją ir galiausiai nustatėme tinkintą įmonės pupelių paieškos konfigūraciją. Pažvelkime į paketo modelį. Čia rasime 3 klases. Šios klasės iš esmės yra tik sąskaitų apibendrinimas ir atstovavimas tarp client
ir account
. Tokiu būdu galime pradėti žiūrėdami kotlin failą Model.kt, kuriame yra Client
:
data class Client constructor( @JsonProperty var name: String ?= null )
Ši pirmoji modelių klasė yra mūsų kliento reprezentacija. Mūsų client
mūsų atveju turi tik vardą. Tai naudotojo vardas, atstovaujamas „ jwt
“ atributo pavadinimu. Be to, turime 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)) ) }
Šioje klasėje iš esmės nustatome paskyros numerį, klientą, dabartinę vertę ir galiausiai kredito vertę. Atkreipkite dėmesį, kad pagal numatytuosius nustatymus visos reikšmės yra 0. Taip pat naudojame BigDecimal vien todėl, kad turime reikalų su pinigais. Pinigai turi būti tikslūs ir negali patirti sistemos apvalinimo ar apvalinimo. Kitaip tariant, tai reiškia, kad toks skaičius kaip 0. 0000000000000000000000000000000000000000000000000001
euras turi išlikti tuo skaičiumi visą laiką. Be to, norime pridėti verčių į savo paskyrą. Čia atsiranda metodas addCurrentValue. Dėl tų pačių priežasčių mes taip pat papildysime savo kreditą naudodami addCreditValue
. Galiausiai paskutinėje duomenų sąrankos dalyje susiduriame su klase Accounts
:
open class Accounts constructor( open val accountMap: MutableMap<String, Account> = mutableMapOf() )
Tai iš esmės tik visų mūsų paskyrų kaupiklis. Mes naudosime jo žemėlapio turinį, kad imituotume duomenų bazės elgesį. Dabar pažiūrėkime į valdiklio paketą. Čia sukuriame programą, veikiančią su mūsų duomenų modeliu. Pirmiausia pažvelkime į klasę BankApplication
:
@LoginConfig(authMethod = "MP-JWT") @ApplicationPath("/") @DeclareRoles("admin", "creditor", "client", "user") class BankApplication : Application()
Tuo mes sakome 3 svarbius dalykus. Naudodami „LoginConfig“ anotaciją apibrėžiame, kad ji naudotų ir suprastų JWT
žetonus pagal „MicroProfile“. ApplicationPath apibrėžia programos šaknį. Čia prasidės programos URL. Mūsų pavyzdyje tai bus HTTP://localhost:8080 . Galiausiai, „DeclareRoles“ apibrėžia vaidmenis, kuriuos ketina naudoti ir priimti mūsų programa. Šioje situacijoje vaidmenys ir grupės yra keičiami terminai. Kad injekcija veiktų efektyviai, sukuriame anotaciją, skirtą paskyros žemėlapiui identifikuoti:
annotation class AccountsProduct
Įjungti viso ekrano režimą Išjungti viso ekrano režimą
Tada sukuriame talpyklos objektų gamyklą 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() } } }
Dėl šios gamyklos išjungėme specialiai Accounts
paiešką. Užuot leidę paieškos procesui sukurti komponentą, mes patys sukuriame agregatoriaus egzempliorių. Naudodami anotaciją Gamina, galime sukurti pupelę. Naudodami savo tinkintą anotaciją „AccountsProduct“ konkretizuojame šios pupelės naudojimą. Galiausiai, naudodami ApplicationScoped
, apibrėžiame jo taikymo sritį kaip Application
sritį. Kitaip tariant, paskyros kaupimo komponentas veiks kaip vienas objektas visoje programoje. „ createResponse
“ yra tik bendras metodas JSON atsakymams kurti. Dabar mums reikia dviejų „išteklių“. Tai iš esmės tas pats, kas pavasarį „ Controllers
“. Tai yra kitoks pavadinimas, bet jo paskirtis lygiai tokia pati. Pažvelkime į AccountsResource
klasę:
@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) } }
Skirkite šiek tiek laiko ir pažvelkite į šią klasę išsamiau. Path
anotacija apibrėžia, kaip pasiekti šį šaltinį iš šaknies. Atminkite, kad kaip šaknį naudojame „/“. Šiuo atveju „paskyros“ yra mūsų šakninis šio šaltinio prieigos taškas. Visi mūsų ištekliai, mūsų atveju tik du, veikia su apimtimi RequestResource. Naudodamas anotaciją „Produces“ nustato, kad visi atsakymai į visas užklausas, neatsižvelgiant į jų tipą, bus JSON formato pranešimai. Norėdami įterpti savo aggregator
tiesiog naudojame Inject anotation ir AccountsProduct
anotacijos derinį:
@Inject @AccountsProduct open var accounts: Accounts? = null
Tai atitinka tai, ką apibrėžėme gamykloje. Be to, mes taip pat įtraukiame du svarbius saugumo elementus. principal
ir jsonWebToken
:
@Inject open var principal: Principal? = null @Inject open var jsonWebToken: JsonWebToken? = null
JsonWebToken
ir Principal
bus vienodi, ir tai matysime savo žurnaluose. Savo ištekliais visada galime įvesti paraiškas iš užklausos su tam tikru prieigos raktu:
@Inject @Claim("name") open var name: JsonString? = null @Inject @Claim("user_id") open var userId: JsonNumber? = null
Tai pasiekiama derinant „ Inject
“ ir Claim
anotacijas. Pavadinimas, pateiktas po Claim
anotacija, apibrėžia, kurią pretenziją norime pateikti. Turime būti atsargūs su tipu, kuriuo apibrėžiame savo parametrus. Mūsų pavyzdyje r mums reikia tik JsonString
ir JsonNumber
tipų. Pirmiausia pažiūrėkime, kaip kuriame paskyras ir vartotojus:
@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() ) ) }
Paskyrų ir vartotojų kūrimas
Siekiama, kad būtų galima atskirti metodus ir suteikti jiems skirtingus leidimus. Mūsų pavyzdyje jie abu tiesiog sukuria paskyrą, tačiau svarbu pastebėti, kad tik vartotojai, turintys vartotojo vaidmenis, gali naudoti „createUser“ metodą. Taip pat tik vartotojai, turintys kliento ir kredito vaidmenis, gali pasiekti metodą createAccount. Dabar išsamiai pažvelkime į šio šaltinio PUT užklausos metodą:
@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) }
Išgryninimas
Žinome, kad komentaras PUT
rodo, kad šis metodas pasiekiamas tik su PUT
tipo užklausomis. Tada anotacijos kelias nurodo Jetty, kad kelias į šį metodą yra vertė. Tai taip pat žinoma kaip PathParam
. Galiausiai galime apibrėžti, kad šį metodą būtų leidžiama naudoti tik vartotojams, turintiems administratoriaus arba kliento vaidmenis. Tada įvesties reikšmė perduodama į mūsų ilgosios reikšmės kintamąjį naudojant „PathParam“. Jei neapibrėžsime jokių vaidmenų, bet kuris vartotojas, turintis tinkamą prieigos raktą, galės pasiekti šiuos metodus. CreditResource
yra įdiegtas tuo pačiu būdu. būdas:
@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) } }
Vienintelis skirtumas yra tas, kad vietoj admin
ir client
vaidmenų dabar naudojame admin
ir credit
vaidmenis. Be to, atkreipkite dėmesį, kad naudotojų paskyros niekada nebus kuriamos šiame resource
. Tai įmanoma tik naudojant paskyros resource
. Dabar, kai žinome, kaip kodas yra įdiegtas, pirmiausia apibendrinkite, kokius metodus suteikėme savo REST
paslaugoje.
Pažiūrėkime naudojamų paslaugų sąrašą:
Tipas, URL, naudingoji apkrova, rezultatas, vaidmenys leidžiami
POST,
POST,
GAUTI,
PUT,
GAUTI,
GAUTI,
GAUTI,
PUT,
GAUTI,
GAUTI,
Sukūriau bash
failą šakniniame aplanke. Šis failas vadinamas „setupCertificates.sh“. Pažvelkime į tai, kad suprastume, ką jis daro:
#!/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
Aplinkos generavimas
Sekite failą, kai paaiškinsiu, ką jis daro. Tai svarbu, kad mes tiksliai suprastume, ką ji daro. Pirmiausia sukuriame privačius ir viešuosius raktus PEM
formatu. Tada naudojame privatų raktą su vykdomu „your-finance-jwt-generator.jar“ . Tai yra mūsų veikiamas stiklainis, leidžiantis greitai sukurti žetonus. Išdavėjas vėliau negali būti pakeistas. Galiausiai sukuriamas ženklas. Kaip perskaityti šį žetoną, pamatysime vėliau. Šiame prieigos rakte yra 3 papildomi antraštės reikalavimai. Tai yra „vaikas“, „tipas“ ir „alg“. Jis pateikiamas tokiu formatu:
{ "kid": "jwt.key", "typ": "JWT", "alg": "RS256" }
JWT
antraštė
Pažvelkime į šiuos teiginius atidžiau:
IANA
laikmenų tipams deklaruoti. Yra trys parinktys JWT
(JSON žiniatinklio prieigos raktas), JWE
(JSON žiniatinklio šifravimas) ir JWA
(JSON žiniatinklio algoritmai). Šie tipai nėra susiję su mūsų eksperimentu. Pamatysime tik tai, kad mūsų prieigos raktas nėra tikrai gerai užšifruotas ir kad jį tikrai lengva iššifruoti. Taip pat pamatysime, kad nors galime iššifruoti žetonus, negalime taip lengvai sugadinti jų atlikti kitus veiksmus.Naudodami viešąjį raktą, pagaliau galime jį naudoti norėdami pakeisti savo šabloną. Naujasis config.yml failas turėtų atrodyti maždaug taip:
kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN issuer: joaofilipesabinoesperancinha healthy: true
config.yml
Antrasis žingsnis yra sukurti keturis failus. Kiekvienam paprastam žetonui kataloge „ jwt-plain-tokens
“ sukursime keturias komandas. Pirmoji komanda yra sukurti vartotojus, kurie galėtų efektyviai atlikti veiksmus su savo paskyromis. Tai vartotojai, turintys profilius „ admin
“, „ client
“ ir „ credit
“. Paleiskite failą „ createAccount.sh
“, kad juos sukurtume. Antroji komanda sukurs likusius vartotojus, kurie dar neturi jokių teisių. Tai failas "createUser.sh". Paleiskime. Dabar pamatysime, kad visi vartotojai pagaliau sukurti. Dabar panagrinėkime išsamią informaciją apie operacijas ir pažvelkime į likusias dvi komandas. Vieną „išgryninti“, kitą – prašyti daugiau kredito. Pirmasis sugeneruotas failas yra „sendMoney.sh“ bash scenarijus. Čia galime rasti visus prašymus „ cashin
“. Šiame faile rasite užklausą siųsti atsitiktinius pinigų kiekius naudotojams kiekvienam vartotojui. Pažiūrėkime į administratoriaus atvejį:
#!/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 ekstraktas
Tie patys vartotojai taip pat turi jiems priskirtas kredito užklausas:
#!/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 ištrauka
Visi mūsų characters
priklauso Nature
lygai. Iš esmės tik tam tikra žmonių grupė yra šios bankų sistemos dalis. Šiame kontekste jie gina aplinką. Straipsniui nelabai svarbu, ką ši žmonių grupė veikia ar kur jie patenka į istoriją, bet kontekstui jie dalyvauja veiksmuose, skirtuose apsaugoti aplinką ir sulėtinti klimato kaitos poveikį. Vieni mūsų characters
gali viską, kiti nieko negali, o kiti gali tik „išsipirkti“ ar tiesiog „prašyti kredito“. Taip pat atkreipkite dėmesį, kad užtemdu neskelbtiną informaciją. Šie prieigos raktai paprastai neturėtų būti bendrinami arba matomi konkrečiame URL. Taip, jie visada pasiekiami naršyklės kūrėjo pulte, bet vis tiek yra protect
kai kurios pateiktos užklausos. Tai sąvoka, žinoma kaip „saugumas už tamsumą“ and
nors ji techniškai netrukdo vartotojui sužinoti apie naudojamą prieigos raktą, ji veikia kaip atgrasymo priemonė. Abiem būdais, kai atliekame indėlį arba kai atliekame paprašykite kredito, atkreipkite dėmesį, kad kiekvienai užklausai mes siunčiame atsitiktinį skaičių nuo 1 iki 500. Dabar esame beveik pasirengę pradėti savo paraišką, bet pirmiausia pasinerkime į šiek tiek daugiau teorijos.
JWT
žetonas
Dabar, kai sugeneravome savo žetonus, pažvelkime į vieną iš jų. Aš parodysiu jums užmaskuotą prieigos raktą ir naudosime jį, kad tai suprastume. Štai mūsų prieigos raktas: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE
. FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO
. FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN
Svarbu pastebėti, kad mūsų prieigos raktas yra padalintas į tris dalis:
Header
ir Payload
derinys. Mes nusprendžiame, kokį algoritmą norime naudoti, ir ši žetono dalis iš esmės nustatys, ar mūsų siunčiama žinutė yra patikima. Jis yra unikalus šiam deriniui ir mūsų serveris naudos mūsų sukurtą „viešąjį raktą“, kad nustatytų, ar turime atitiktį. Jei prisimenate iš to, kas išdėstyta aukščiau, pavyzdyje naudojame RS256
.
Prieš tęsdami, atkreipkite dėmesį, kad mūsų pavyzdyje galima decyphered
ir Header
, ir Payload
. Mes tiesiog „negalime“ sugadinti naudingojo krovinio ar antraštės ir vis tiek padaryti jais patikimus. Apsaugą nuo galimo kenkėjiško žetono poveikio gali apsaugoti tik mūsų pasirinktas algoritmas. Taigi rinkitės išmintingai. Jei dirbate organizacijoje, kurioje rūpi itin slapta informacija, pvz., banke, NEDARYKITE to, ką ketiname daryti. Tai tik būdas internete patikrinti žetonų, kuriuos sukūrėme vietoje, turinį. Pirmiausia eikime į https://jwt.io/ ir užpildykime JWT
prieigos raktą. Naudokite ką tik sugeneruotą prieigos raktą:
Naudodami https://jwt.io/ norėdami patikrinti žetono turinį Panagrinėkime, ką čia turime. Tai mūsų administratoriaus prieigos raktas. Mūsų pavyzdyje tas asmuo yra „administratorius“. Matome, kad visi mūsų parametrai yra prieinami. Mūsų sąraše matome „sub“, „aud“, „upn“, „access“, „user_id“, „iss“, „name“, „groups“ ir galiausiai „jti“. Taip pat turime keletą papildomų pretenzijų. Pažvelkime į juos:
„ auth_time “ – tai yra tada, kai įvyko autentifikavimas. Mūsų prieigos raktas buvo patvirtintas sekmadienį, 2022 m. liepos 17 d. 16:15:47 GMT+02:00 DST" iat " — tada buvo sukurtas prieigos raktas. Mūsų atveju tai vyksta tuo pačiu metu kaip ir auth_time." exp " — Tai prieigos rakto galiojimo pabaigos data. Galiojimas baigiasi sekmadienį, 2022 m. liepos 17 d., 16:32:27 GMT+02:00 DST. Savo žetonuose nenurodėme jokios galiojimo pabaigos datos. Tai reiškia, kad JWT
naudoja numatytąją ~15 minučių reikšmę.
Dabar atlikime keletą testų.
Kodas paruoštas naudoti GitHub . Jei patikrinsime kodą ir atidarysime jį naudodami „Intellij“, turime žinoti, kad negalime paleisti šios programos kaip „Spring Boot“ programos. Nėra „psvm“, kad jis veiktų. Vietoj to galime tiesiog paleisti sugeneruotą jar tiesiogiai ir įsitikinti, kad prieš pat sukursime „mvn build“. Štai kaip aš jį naudoju šiuo metu:
[ ] https://github.com/jesperancinha/your-finance-je „Aplinkos sąranka programai paleisti“)
Dabar dar kartą paleiskime scenarijų " setupCertificates.sh
". Nežinau, kiek jums prireikė laiko čia patekti, bet labai tikėtina, kad 15 minučių šiuo metu jau praėjo. Tik tuo atveju, tiesiog paleiskite juos dar kartą. Paleiskite savo programą!Galime ją paleisti taip:
mvn clean install java -jar your-financeje-banking/target/your-financeje-banking.jar
Arba galime tiesiog paleisti jį naudodami paruoštą naudoti konfigūraciją. Iš anksto patikrinkite repo ir Makefile, jei norite suprasti viską, ką jis daro:
make dcup-full-action
Šis scenarijus vykdys 2 paslaugas. Vienas 8080
prievade, o kitas 8081
prievade. 8080
prievade paleisime šios programinės įrangos versiją, kurioje bus sukurtas mūsų pačių kodas JWT
žetonams generuoti. 8081 prievade paleisime versiją naudodami jwtknizr
generatorių, kurį sukūrė Adam Bien
. Tačiau šiame straipsnyje mes sutelksime dėmesį į paslaugą, veikiančią 8080
prievade. Jei norite, cypress
taip pat galite paleisti su:
make cypress-open
Taip bus open
cypress
pultas ir galėsite vykdyti testus pasirinkta naršykle. Tačiau naršyklės parinktys šiame etape vis dar ribotos. Dauguma užklausų iš tikrųjų bus komandinės eilutės užklausos, kurias pateikia cypress
. Kol kas nesigilinkime į " cypress
". Eikite į savo naršyklę ir eikite į šią vietą:
http://localhost:8080/accounts/all
Turėtume gauti tokį rezultatą:
Kaip matome, „ Malory
“, „ Jack Fallout
“ ir „ Jitska
“ neturi nei kredito, nei pinigų. Taip yra todėl, kad jiems buvo suteikta tik vartotojų grupė. Taip pat atkreipkite dėmesį, kad Shikka
nebuvo suteiktas kreditas. „ Shikka
“ yra vienintelis mūsų klientas, neturintis grupės kredito. Jei pažvelgsime į žurnalus, pamatysime, kad sėkmingos operacijos vyksta tokiu formatu:
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 praneša, kad operacija pavyko sėkmingai. „Malory“, „Jack Fallout“ ir „Jitska“ atveju abi operacijos nepavyksta ir tada gausime tokį pranešimą:
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 praneša mums, kad mūsų JWT
prieigos raktas buvo patvirtintas ir patikimas. Tačiau vartotojui tą operaciją atlikti draudžiama. Kitaip tariant, jie neturi prieigos prie nurodyto metodo.
Šiek tiek sugadinkime savo žetonus. Jei pakeisime kai kuriuos failo sendMoney.sh žetonus. Turėtume gauti tai:
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)
Įjungti viso ekrano režimą Išjungti viso ekrano režimą
Šis 401
reiškia, kad mūsų prieigos raktas nebuvo patvirtintas. Tai reiškia, kad viešasis raktas, kurį serveris naudoja tikrindamas, ar mūsų prieigos raktas yra patikimas, nerado atitikmens. Jei viešasis raktas negali įvertinti ir patvirtinti JWT prieigos rakto parašo, jis jį atmes.
Apibendrinant galima pasakyti, kad antraštė ir „naudingoji apkrova“ nėra užšifruoti. Jie tiesiog „užkoduoti“ 64 baze. Tai reiškia, kad „Dekodavimas“ leidžia mums visada pažvelgti į tai, kas iš tikrųjų yra naudingoji apkrova. Jei norime apsaugoti savo naudingą apkrovą nuo pasiklausymo, neturėtume naudoti prieigos rakto „naudingos apkrovos“ niekam kitam, tik pasirinkti identifikavimo parametrus. Problema iš tikrųjų kyla tada, kai kas nors paima į rankas JWT
prieigos raktą, pavyzdžiui, kai TLS tunelis buvo pažeistas ir kas nors gali perskaityti apsikeistų pranešimų turinį. Kai taip nutinka, vis dar yra kita apsaugos priemonė. Ir tai yra parašas. Vienintelis, kuris gali patvirtinti gaunamą pranešimą, yra serveris, kuriame yra viešasis raktas. Šis viešasis raktas, nors ir viešas, leidžia tik patvirtinti gaunamą pranešimą, paleidžiant prieš parašą ir „Antraštė + naudingoji apkrova“.
Pasiekėme sesijos pabaigą. Dėkojame, kad tai sekate. Matome, kad JWT
žetonai yra kompaktiški ir daug mažiau išsamūs nei jų XML atitikmenys SAML
prieigos raktai. Matėme, kaip lengva sukurti ir naudoti žetonus, kad gautume tam tikrus įgaliojimus, reikalingus tam tikriems metodams, ir kaip mes juos gauname naudodami pasirašytą prieigos raktą. Tačiau manau, kad labai svarbu suprasti, kaip veikia JWT
. Tikimės, kad tai gerai supažindinsiu su JWT
žetonų veikimu. Kad geriau suprastumėte, kaip visa tai veikia, patariu pažaisti su įdiegtais cypress
testais. Tai puikus būdas pamatyti, kaip teikiamos užklausos, ką mes testuojame ir ko tikimasi. Tada taip pat geriau suprasite, kodėl vieni vartotojai gali atlikti tam tikras operacijas, o kiti ne. Visą šios programos šaltinio kodą įdėjau į GitHub. Tikiuosi, kad jums šis straipsnis patiko tiek pat, kiek man patiko rašyti. tai. Ačiū, kad skaitėte!