Deesdae het ons meer en meer kommer oor prestasie, en terselfdertyd wil ons weet hoe stelsels vinnig en betroubaar kan kommunikeer. Ons wil baie keer inligting stuur en dit sover moontlik vertroulik en veilig hou. Sensitiewe data moet soms ook publiek deur die web beweeg en aksies aan die ander kant van die draad veroorsaak. Meestal wil ons aksies genereer wat datamutasies sal veroorsaak. In hierdie gevalle kyk ons nie net na die beskerming van ons data nie. Ons wil seker maak dat die aksies wat veroorsaak word deur die stuur van ons data, vertrou word. Ons kan ons data op verskeie maniere beskerm. Meestal stuur ons die data via 'n TLS
(Transport Layer Security) veilige verbinding. Dit sal verseker dat ons data deur die draad geïnkripteer word. Ons gebruik sertifikate om vertrouensverhoudings tussen twee partye te skep en dit te bereik. In hierdie artikel wil ek die JWT
standaard bespreek en verder sien hoe ons JWT
in 'n algemene Enterprise
toepassing kan integreer. In hierdie geval sal ons na KumuluzEE
kyk. Kom ons kyk na 'n paar basiese konsepte. JWT
of JSON Web Token, of nog beter, JavaScript Object Notation Web Token, is 'n standaard wat in RFC7519 gedefinieer word. Hierdie standaard is, soos alle RFC
(Request For Comments) standaarde, gedefinieer, geskryf en gepubliseer deur die IETF
(Internet Engineering Task Force). Dit kan op verskeie maniere gedefinieer word. Oor die algemeen kan ons sê dat JWT
'n kompakte, veilige vorm is om eise tussen twee partye oor te dra. Een manier om te vereenvoudig wat 'n eis is, is basies om dit te beskryf as 'n naam/waarde-paar wat inligting bevat. Ons het hierdie inligting nodig om 'n paar belangrike aspekte van ons internetkommunikasie te waarborg. Ons moet seker maak dat die inligting wat ons ontvang in die eerste instansie bekragtig en vertrou word. Dan moet ons dit bekragtig. Dit is basies dit.Om hierdie standaard te implementeer, kan ons verskeie raamwerke gebruik wat ons kan help om 'n Java-ondernemingstoepassing te implementeer. Spring Boot word wyd gebruik. Baie keer word dit ook onder 'n ander naam toegedraai in behoorlike sagteware van sekere organisasies soos banke en ander finansiële organisasies. Vir ons voorbeeld het ek besluit om iets anders te doen. In plaas van Spring Boot, gaan ons na 'n voorbeeld met KumuluzEE
kyk. Die punt is om presies te identifiseer wat JWT
is en hoe dit lyk. Java Enterprise Applications is basies toepassings wat in 'n toepassingsbediener ontplooi kan word of net op hul eie kan hardloop deur die gebruik van 'n ingebedde bediener. As 'n voorbeeld, Spring Boot-toepassings loop op 'n ingebedde Tomcat-bediener. In hierdie artikel sal ons fokus op KumuluzEE
gestel word. Net soos Spring Boot bevat dit ook 'n ingebedde bediener. Behalwe dat dit in hierdie geval Jetty genoem word. Dit word in kombinasie met Weld gebruik om CDI (Context Dependency Injection) te verskaf. Alle Java EE
en Jakarta EE
tegnologie standaarde is versoenbaar met hierdie framework
.
Om 'n voorbeeld te gee van hoe JWT
in sy basiese vorm werk, moes ek aan 'n manier dink om dit aan te bied. Klassieke voorbeelde waar sekuriteit 'n bekommernis is, is banke. Om egter 'n hele bankaansoek te maak om te wys hoe JWT
werk, sal 'n mors van tyd wees en miskien sal te veel konsepte betrokke wees. In plaas daarvan, wat ek gemaak het, is 'n baie eenvoudige bankstelsel. Ons grootste bekommernis is om te wys hoe data deur die draad vloei en hoe gebruikers toegang tot sekere areas van ons toepassing kry. Ek gaan ook nie TLS bespreek of hoe ons geïnkripteer inligting deur die draad kan stuur nie. Ons sal ons fokus op JWT
in sy suiwerste vorm behou. Ons saak is 'n bankstelsel wat gebruik word deur 'n groep wat die natuur en die omgewing verdedig. Dit is net 'n prettige manier om te wys hoe JWT
werk. Die hoofkarakter van hierdie Liga van die Natuur is Lucy, wat 'n algemene karakter in al my artikels word.
Voordat ons begin, kom ons skets net ons lopende toepassing. Dit is 'n baie eenvoudige toepassing, maar dit is steeds 'n goeie ding om dit te teken:
Die rede waarom dit so eenvoudig is, is dat aangesien JWT
op elke versoek nagegaan word en elke versoek teen die publieke sleutel geverifieer word, ons weet dat solank ons die korrekte teken op elke versoek stuur, ons sal kan deurkom. JWT
kan geïntegreer word met OAuth2, Okta SSO of enige ander magtigingsmeganisme. In hierdie geval is wat ons doen om verifikasie en magtiging te vestig. In ons toepassing gaan ons JWT
gebruik en daarmee ons boodskap verifieer met 'n handtekening. Ons sal egter nie by die toepassing aanmeld nie. In plaas daarvan sal ons gebruikers magtig om ons toepassing te gebruik na suksesvolle verifikasie. Op hierdie stadium is dit maklik om te sien dat JWT
in sy kern eintlik 'n baie klein deel van 'n volledige toepassing is. Nietemin moet sekere funksies bygevoeg word. Dit is die hulpbronne wat ons benodig:
Kom ons sê net dat ons basiese stelsel net geld- en kredietversoeke sal registreer. In wese sal dit net waardes ophoop. Kom ons neem ook aan dat sommige mense krediet sal kan kry en ander nie. Sommige mense sal geld kan stoor en ander mense sal krediet kan kry.
Soos in die inleiding genoem, sal ons KumuluzEE
as ons ondernemingstoepassingsraamwerk gebruik, en ons sal 'n ultra-basiese toepassing implementeer op 'n manier dat ons na basiese JWT
terminologie en -konsepte kan kyk. Maak seker dat u die korrekte Java-weergawe het. Op hierdie stadium sal ons 'n minimum Java 17 SDK moet installeer. Ons benodig maven, git, 'n Java-versoenbare IDE soos IntelliJ, en 'n soort dop.
Om ons toepassing te begin, het ons 'n paar KumuluzEE
afhanklikhede. Dit is hoofsaaklik omdat KumuluzEE
, net soos Spring Boot, 'n paar afhanklikhede benodig. Kom ons kyk kortliks na die POM-lêer:
<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>
Kom ons bespreek 'n paar afhanklikhede kortliks. Soos jy dit lees, volg asseblief ons pom.xml
-lêer van bo na onder. Dit is belangrik om die volgende verduideliking te verstaan. Ons benodig 'n pakket van afhanklikhede om ons toepassing te laat werk. , Gelukkig bied KumuluzEE
aan ons mikroprofielbiblioteke wat basiese standaardbundels bevat om hierdie toepassing te begin. Dit is alles vervat in die KumuluzEE
-Mikroprofiel-biblioteek. Om ons toepassing te kan opstel met al die JWT
-parameters wat ons benodig, moet ons 'n MicroProfile-biblioteek daarby voeg. Terselfdertyd benodig ons 'n JSON-verwerkingsbiblioteek. Dit sal wees wat Johnson Core doen. Ons het natuurlik die kern van KumuluzEE
nodig om te werk. Jetty is die onderliggende bediener wat die KumuluzEE
raamwerk bestuur. Dit is hoekom ons dit nodig het in ons afhanklikhede. Aangesien ons CDI
benodig, benodig ons ook 'n biblioteek wat dit ondersteun. Om ons REST-eindpunte te aktiveer, het ons die resbiblioteek van KumuluzEE
nodig. Om ons API te kry, benodig ons dan 'n Geronimo-biblioteek. Dit sal verseker dat ons 'n implementering van JSR-374
beskikbaar het. Ons moet ook ons JWT
en sy JSON-formatted
inhoud interpreteer. Lombok is nie regtig per se nodig nie. Dit maak alles net mooi en blink! Logback is ook belangrik om te hê sodat ons logboeke beter kan interpreteer en ons resultate kan verstaan. Kom ons kyk nou na ons resources
lêergids. Om te begin, kom ons verstaan eers wat ons verwag om in hierdie gids te vind. Ons moet ons toepassing opstel met iets wat verband hou met JWT
, Logback en laastens moet ons iets sê oor die bone wat ons gaan skep. Kom ons kyk na die eenvoudigste lêer daar. Die boontjies.xml kan gevind word in 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>
Dit is net 'n tipiese en soos jy dalk nou dink, 'n bietjie van 'n ou lêer. Op hierdie stadium is die idee net om KumuluzEE
aan die gang te kry. Ons het wel 'n uitsluit-aksie. Dit sê vir Weld om nie klasrekeninge in sy skandering vir boontjies-aksie in ag te neem nie. Dit is belangrik, want met die implementering wat ons gebruik, sal Weld
basies elke klas met 'n leë konstruktor as 'n boontjie beskou. Ons sal later sien hoekom ons nie wil hê dat rekeninge as 'n boontjie beskou moet word nie. Laat ons vir die oomblik in gedagte hou dat ons versoeke onder die Versoek-omvang rig. Dit is logies omdat elke versoek 'n ander gebruiker kan hê. Kom ons kyk nou hoe " logback
" geïmplementeer word. Dit word ook gevind in 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>
Dit is net 'n baie eenvoudige konfigurasie vir ons logs
. Ten slotte, miskien die belangrikste lêer van ons toepassing. Dit is die config-sjabloon. Op hierdie stadium is dit belangrik om daarop te let dat sommige van die lêers wat ek in hierdie projek geskep het, deel is van 'n sjabloonstruktuur. Ek sal later meer daaroor verduidelik. Hierdie sjabloonlêer is veronderstel om in 'n config.yml-lêer verander te word wat deur MicroProfile gelees sal word. Hierdie lêer is by die wortel van hulpbronne geleë:
kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: {{ publicKey }} issuer: {{ issuer }} healthy: true
Ons sal later sien wat presies al hierdie eiendomme eintlik beteken. Almal van hulle is selfverduidelikend. Die publicKey en uitreiker is almal parameters wat vervang sal word. Ons sal dit later ondersoek. Ons bash-skrifte sal seker maak dat hulle vervang word. Ons is amper gereed om te gaan kodeer, maar kom ons kyk eers na ons JWT
tokenstruktuur.
Kom ons maak ons baie klein aansoek. Hierdie afdeling sal verduidelik hoe ons ons aansoek kan kry om met JWT
te werk. Wat ons wil sien, is of ons gebruikers kan spesifiseer om toegang te verkry tot sommige van ons REST
metodes en nie ander nie. Een van die maniere om na hierdie kode te begin kyk, is om eers na ons gewone JWT
token te kyk. Hier is ons admin voorbeeld:
{ "iss": "joaofilipesabinoesperancinha", "jti": "01MASTERFINANCE", "sub": "admin", "aud": "nature", "upn": "admin", "groups": [ "user", "admin", "client", "credit" ], "user_id": 1, "access": "TOP", "name": "Admin" }
Daar word na elkeen van hierdie name in ons JSON
verwys as eise. In ons voorbeeld sien ons 'n paar Voorbehou-aansprake:
iss
" - Dit is die uitreiker van die teken. Ons kan arbitrêr 'n waarde hiervoor kies. Die waarde van hierdie parameter moet ooreenstem met die uitreiker veranderlike wat vervang moet word in die config.yml wat ons voorheen gesien het.jti
" - Dit is 'n unieke identifiseerder van die teken. Ons kan byvoorbeeld hierdie eis gebruik om te verhoed dat 'n teken twee of meer keer gebruik word.sub
" - Dit is die onderwerp van die teken. Dit kan die gebruiker wees of enigiets waarvan ons hou. Dit is belangrik om in gedagte te hou dat dit ook gebruik kan word as 'n identifiseerder, sleutel, benaming of enigiets wat ons wil hê.upn
" — Gebruikershoofnaam. Dit word gebruik om die skoolhoof te identifiseer wat die gebruiker gebruik.groups
" — Dit is 'n reeks van die groepe waaraan die huidige gebruiker behoort. Dit sal in wese bepaal wat 'n versoek met hierdie teken kan doen. In ons teken sien ons dan 'n paar persoonlike eise. Ons kan dit net so goed gebruik as die Reserve-eiseuser_id
" — Ons sal dit gebruik om die gebruiker-ID te stel.access
" — Ons sal die toegangsvlak van die gebruiker bepaal.name
" — Die naam van die gebruiker. Kom ons maak 'n opsomming van wat ons tot dusver weet. Ons weet ons sal met tekens kommunikeer met 'n struktuur wat ons bepaal het. Verder het ons die konfigurasie van ons toepassing, die logback-konfigurasie opgestel en laastens het ons 'n pasgemaakte konfigurasie vir die ondernemingsboonopsoek opgestel. Kom ons kyk na die pakketmodel. Hier sal ons 3 klasse vind. Hierdie klasse verteenwoordig basies net 'n samevoeging van rekeninge en die verteenwoordiging tussen client
en account
. Op hierdie manier kan ons begin deur na kotlin-lêer Model.kt te kyk waar Client
geleë is by:
data class Client constructor( @JsonProperty var name: String ?= null )
Hierdie eerste modelklas is die voorstelling van ons kliënt. Ons client
vir ons saak het net 'n naam. Dit is die gebruikersnaam wat deur die " jwt
" kenmerknaam verteenwoordig word. Verder het ons 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)) ) }
In hierdie klas stel ons basies 'n rekeningnommer, 'n kliënt, 'n huidige waarde en laastens 'n kredietwaarde op. Let daarop dat ons alle waardes na 0 stel. Ons gebruik ook BigDecimal, bloot omdat ons met geld te doen het. Geld moet presies wees en kan nie stelsel-afrondings of -afrondings ondervind nie. Dit beteken met ander woorde en as 'n voorbeeld dat 'n getal soos 0. 0000000000000000000000000000000000000000000000000001
euro die hele tyd daardie getal moet bly. Ons wil ook waardes by ons rekening voeg. Dit is waar die metode addCurrentValue ontstaan. Om dieselfde redes sal ons ook ons krediet aanvul met die addCreditValue
. Ten slotte, in die laaste stuk van ons data-opstelling kom ons Accounts
teë:
open class Accounts constructor( open val accountMap: MutableMap<String, Account> = mutableMapOf() )
Dit is in wese net 'n versamelaar van al ons rekeninge. Ons sal sy kaartinhoud gebruik om die gedrag van 'n databasis na te boots. Kom ons kyk nou na die kontroleerderpakket. Dit is waar ons ons toepassing skep wat met ons datamodel loop. Kom ons kyk eers na klas BankApplication
:
@LoginConfig(authMethod = "MP-JWT") @ApplicationPath("/") @DeclareRoles("admin", "creditor", "client", "user") class BankApplication : Application()
Hiermee sê ons 3 belangrike dinge. Met die LoginConfig-aantekening definieer ons dit om JWT
-tokens volgens MicroProfile te gebruik en te verstaan. Die ApplicationPath definieer die toepassingswortel. Dit is waar die URL van die toepassing sal begin. In ons voorbeeld sal dit HTTP://localhost:8080 wees. Laastens definieer die DeclareRoles die rolle wat deur ons aansoek gebruik en aanvaar gaan word. Rolle en Groepe is uitruilbare terme in hierdie situasie. Om inspuiting doeltreffend te laat werk, skep ons 'n aantekening spesifiek om die rekeningkaart te identifiseer:
annotation class AccountsProduct
Gaan in volskermmodus Verlaat volskermmodus
Vervolgens skep ons 'n kasvoorwerpfabriek 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() } } }
Hierdie fabriek is die rede waarom ons opsoek spesifiek vir Accounts
gedeaktiveer het. In plaas daarvan om die opsoekproses toe te laat om 'n boontjie te skep, skep ons self die aggregator-instansie. Deur die Produseer-aantekening te gebruik, kan ons die boontjie skep. Met behulp van ons pasgemaakte aantekening, AccountsProduct, maak ons die gebruik van hierdie boontjie meer spesifiek. Ten slotte, deur ApplicationScoped
te gebruik, definieer ons die omvang daarvan as die Application
. Met ander woorde, die rekeningaggregasieboon sal optree as 'n enkelton-objek regoor die toepassing. Die " createResponse
" is net 'n generiese metode om JSON-antwoorde te skep. Wat ons nou nodig het, is twee "Hulpbronne". Dit is basies dieselfde as " Controllers
" in die lente. Dit is 'n ander naam, maar dit het presies dieselfde gebruik. Kom ons kyk na die 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) } }
Neem 'n oomblik om meer in detail na hierdie klas te kyk. Die Path
-aantekening definieer hoe om hierdie hulpbron vanaf die wortel te bereik. Onthou dat ons "/" as die wortel gebruik. In hierdie geval is "rekeninge" ons worteltoegangspunt vir hierdie hulpbron. Al ons bronne, in ons geval is net twee wat met omvang RequestResource loop. Met annotasie bepaal Produces dat alle antwoorde op alle versoeke, ongeag hul tipe, die vorm van JSON-geformateerde boodskappe sal aanneem. Om ons aggregator
in te spuit, gebruik ons net die kombinasie van die Inject annotation en AccountsProduct
annotasie:
@Inject @AccountsProduct open var accounts: Accounts? = null
Dit pas by wat ons in die fabriek gedefinieer het. Verder spuit ons ook twee belangrike elemente van sekuriteit in. 'n principal
en die jsonWebToken
:
@Inject open var principal: Principal? = null @Inject open var jsonWebToken: JsonWebToken? = null
Beide JsonWebToken
en Principal
sal dieselfde wees, en ons sal dit in ons logboeke sien. In ons hulpbronne kan ons altyd eise van 'n versoek met 'n sekere teken inspuit:
@Inject @Claim("name") open var name: JsonString? = null @Inject @Claim("user_id") open var userId: JsonNumber? = null
Dit word bereik met die kombinasie van die Inject
en Claim
-aantekeninge. Die naam wat onder die Claim
-aantekening geplaas word, definieer watter eis ons wil invoeg. Ons moet versigtig wees met die tipe waarmee ons ons parameters definieer. In ons voorbeeld het ons net JsonString
en JsonNumber
-tipes nodig. Kom ons kyk eers hoe ons rekeninge en gebruikers skep:
@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() ) ) }
Die skep van rekeninge en gebruikers
Die doel hier is om metodes te kan skei en hulle verskillende toestemmings te gee. In ons voorbeeld skep hulle albei net 'n rekening, maar dit is belangrik om op te let dat slegs gebruikers met rollegebruikers die createUser-metode kan gebruik. Op dieselfde manier kan slegs gebruikers met rolle van kliënt en krediet toegang kry tot die metode createAccount. Kom ons kyk nou in detail na die PUT-versoekmetode van hierdie hulpbron:
@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) }
Inbetaal
Ons weet dat aantekening PUT
aandui dat hierdie metode slegs toeganklik is met versoeke van tipe PUT
. Annotation Path sê dan vir Jetty dat die pad na hierdie metode 'n waarde is. Dit staan ook bekend as 'n PathParam
. Laastens kan ons hierdie metode definieer wat slegs toegelaat word om gebruik te word deur gebruikers met rolle admin of kliënt. Die invoerwaarde word dan deur die gebruik van die PathParam na ons Langwaarde-veranderlike oorgedra. As ons geen rolle definieer nie, sal enige gebruiker met die regte token toegang tot hierdie metodes kan verkry. CreditResource
word in dieselfde geïmplementeer manier:
@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) } }
Die enigste verskil is dat in plaas daarvan om rolle admin
en client
te gebruik, gebruik ons nou admin
en credit
rolle. Let ook op dat rekeninge vir gebruikers nooit in hierdie resource
geskep sal word nie. Dit is slegs moontlik via die rekening se resource
. Noudat ons weet hoe die kode geïmplementeer word, laat ons eers opsom watter metodes ons in ons REST
diens beskikbaar gestel het.
Kom ons kyk na die lys van die dienste wat gebruik word:
Tik, URL, Loonvrag, Resultaat, Rolle toegelaat
PLAAS,
PLAAS,
KRY,
PUT,
KRY,
KRY,
KRY,
PUT,
KRY,
KRY,
Ek het 'n bash
-lêer in die wortelgids geskep. Hierdie lêer word "setupCertificates.sh" genoem. Kom ons kyk daarna om 'n idee te kry van wat dit doen:
#!/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
Omgewing generasie
Volg asseblief die lêer terwyl ek verduidelik wat dit doen. Dit is belangrik sodat ons presies verstaan wat dit doen. Ons skep eers private en publieke sleutels in 'n PEM
formaat. Ons gebruik dan die private sleutel met ons hardloopbare "your-finance-jwt-generator.jar" . Dit is ons loopbare pot wat die vinnige skepping van tokens moontlik maak. Die uitreiker kan nie later verander word nie. Uiteindelik skep dit 'n teken. Ons sal later sien hoe om hierdie teken te lees. Hierdie teken bevat 3 ekstra Kop-eise. Dit is "kind", "typ" en "alg". Dit volg die volgende formaat:
{ "kid": "jwt.key", "typ": "JWT", "alg": "RS256" }
Die kop van die JWT
Kom ons kyk van naderby na hierdie bewerings:
IANA
mediatipes te verklaar. Daar is drie opsies JWT
(JSON Web-token), JWE
(JSON Web Encryption), en JWA
(JSON Web Algorithms). Hierdie tipes is nie relevant vir ons eksperiment nie. Ons sal net sien dat ons teken nie regtig goed geïnkripteer is nie en dat dit regtig maklik is om dit te dekripteer. Ons sal ook sien dat alhoewel ons tokens kan dekripteer, ons nie so maklik kan peuter om ander aksies uit te voer nie.Met ons publieke sleutel kan ons dit uiteindelik gebruik om ons sjabloon te verander. Die nuwe config.yml-lêer moet so lyk:
kumuluzee: name: your-financeje-banking version: 1.0.0 jwt-auth: public-key: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO.FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN issuer: joaofilipesabinoesperancinha healthy: true
config.yml
Die tweede stap is om vier lêers te skep. Vir elke enkele gewone token in die gids " jwt-plain-tokens
", sal ons vier opdragte skep. Die eerste opdrag is om gebruikers te skep wat effektief dinge met hul rekeninge kan doen. Dit is gebruikers met profiele " admin
", " client
" en " credit
". Kom ons hardloop die lêer " createAccount.sh
", om hulle te skep. Die tweede opdrag sal die res van die gebruikers skep wat nog geen regte het nie. Dit is die lêer "createUser.sh". Kom ons bestuur dit. Nou sal ons sien dat alle gebruikers uiteindelik geskep is. Kom ons kyk nou na besonderhede oor transaksies en kyk na die oorblywende twee opdragte. Een om te "cashin" en 'n ander om meer krediet te vra. Die eerste gegenereerde lêer is die "sendMoney.sh" bash script. Hier kan ons alle versoeke vind om te " cashin
". In hierdie lêer sal jy 'n krulversoek vind om ewekansige geldhoeveelhede per gebruiker aan gebruikers te stuur. Kom ons kyk na die admin saak:
#!/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 uittreksel
Dieselfde gebruikers het ook hul kredietversoeke aan hulle toegewys:
#!/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 uittreksel
Al ons characters
is deel van die Liga van Nature
. In wese net 'n groep mense om deel te wees van hierdie bankstelsel. In hierdie konteks verdedig hulle die omgewing. Dit is nie regtig relevant vir die artikel wat hierdie groep mense doen of waar in die storie hulle inpas nie, maar vir konteks neem hulle deel aan aksies om die omgewing te verdedig en die uitwerking van klimaatsverandering te vertraag. Sommige van ons characters
kan alles doen, ander kan niks doen nie en ander kan net “cashin” of net “vra vir krediet”. Let ook op dat ek sensitiewe inligting vertroebel. Hierdie tekens moet normaalweg nie gedeel word of sigbaar wees op 'n spesifieke URL nie. Hulle is ja, altyd beskikbaar via die blaaier-ontwikkelaarkonsole, maar dit is in elk geval om sommige versoeke wat gemaak word, protect
. Dit is 'n konsep wat bekend staan as "sekuriteit-per-duisternis" and
hoewel dit nie tegnies verhoed dat die gebruiker bewus word van die teken wat gebruik word nie, werk dit wel as 'n afskrikmiddel. In beide metodes, wanneer ons 'n deposito maak of wanneer ons vra vir krediet, let op dat ons vir elke versoek 'n ewekansige getal tussen 1 en 500 stuur. Ons is nou amper gereed om ons aansoek te begin, maar eers, laat ons 'n duik in 'n bietjie meer teorie neem.
JWT
token gemaak
Noudat ons ons tokens gegenereer het, kom ons kyk na een van hulle. Ek gaan vir jou 'n verduisterde teken wys, en ons gaan dit gebruik om dit te verstaan. Hier is ons teken: FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKE
. FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETO
. FAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKENFAKETOKEN
Wat hier belangrik is om op te let, is dat ons teken in drie dele verdeel is:
Header
en die Payload
. Ons besluit die algoritme wat ons wil gebruik en hierdie deel van die teken sal basies bepaal of die boodskap wat ons stuur, vertrou moet word. Dit is uniek aan daardie kombinasie en ons bediener sal die "publieke sleutel" wat ons geskep het gebruik om te bepaal of ons 'n pasmaat het. As jy van bogenoemde onthou gebruik ons RS256
in ons voorbeeld.
Voordat ons voortgaan, let asseblief daarop dat beide die Header
en die Payload
in ons voorbeeld decyphered
kan word. Ons "kan" net nie met die loonvrag of die kopstuk peuter en dit steeds vertrou nie. Die beskerming teen die potensiële gevolge van 'n kwaadwillige teken kan slegs beskerm word deur die algoritme wat ons kies. Kies dus verstandig. As jy in 'n organisasie werk waar hoogs geheime inligting 'n bekommernis is, soos 'n bank, MOET asseblief NIE doen wat ons gaan doen nie. Dit is slegs 'n manier vir ons om die inhoud van die tokens wat ons plaaslik gegenereer het aanlyn na te gaan. Kom ons gaan eers na https://jwt.io/ en vul ons JWT
token in. Gebruik die teken wat jy pas gegenereer het:
Gebruik https://jwt.io/ om die inhoud van ons teken na te gaan. Kom ons ondersoek wat ons hier het. Dit is ons administrateur-token. Daardie persoon is "Admin" in ons voorbeeld. Ons kan sien dat ons parameters almal beskikbaar is. In ons lys sien ons "sub", "aud", "upn", "access", "user_id", "iss", "name", "groups" en laastens die "jti". Ons het ook 'n paar ekstra eise. Kom ons kyk na hulle:
" auth_time " — Dit is wanneer die verifikasie plaasgevind het. Ons teken soos geverifieer op Sondag, 17 Julie 2022 16:15:47 GMT+02:00 DST" iat " — Dit is wanneer die teken geskep is. In ons geval gebeur dit gelyktydig as die auth_time." exp " - Dit is die vervaldatum van die teken. Dit verval op Sondag 17 Julie 2022 16:32:27 GMT+02:00 DST. Ons het geen vervaldatum in ons teken gespesifiseer nie. Dit beteken dat JWT
sy verstekwaarde van ~15 minute gebruik.
Kom ons doen nou 'n paar toetse.
Die kode is gereed om op GitHub gebruik te word. As ons die kode nagaan en dit met Intellij oopmaak, moet ons bewus wees dat ons nie hierdie toepassing soos 'n Spring Boot-toepassing kan laat loop nie. Daar is geen "psvm" om dit te laat loop nie. In plaas daarvan kan ons net die gegenereerde jar direk laat loop en seker maak dat ons net voor 'n "mvn build" maak. Hier is hoe ek dit op die oomblik gebruik:
[ ] https://github.com/jesperancinha/your-finance-je "Omgewingsopstelling om die toepassing te laat loop")
Laat ons nou weer die " setupCertificates.sh
"-skrip laat loop. Ek weet nie hoeveel tyd jy geneem het om hier te kom nie, maar dit is baie waarskynlik dat die 15 minute reeds verby is op hierdie stadium. Net vir ingeval, laat hulle net weer hardloop. Kom ons begin ons toepassing! Ons kan dit so begin:
mvn clean install java -jar your-financeje-banking/target/your-financeje-banking.jar
Of ons kan dit net deur ons gereed-om-te-loop-konfigurasie laat loop. Gaan die repo en die Makefile vooraf na as jy alles wil verstaan wat dit doen:
make dcup-full-action
Hierdie skrip sal 2 dienste laat loop. Een op poort 8080
en die ander op poort 8081
. Op poort 8080
sal ons 'n weergawe van hierdie sagteware laat loop met ons eie kode om JWT
tokens te genereer. Op poort 8081 sal ons 'n weergawe gebruik met behulp van die jwtknizr
kragopwekker geskep deur Adam Bien
. Ons sal hierdie artikel egter fokus op die diens wat op poort 8080
loop. As jy wil, kan jy ook cypress
hardloop met:
make cypress-open
Dit sal die cypress
open
, en jy sal die toetse met die blaaier van jou keuse kan uitvoer. Blaaieropsies is egter steeds beperk op hierdie stadium. Die meeste van die versoeke sal eintlik opdragreëlversoeke wees wat deur cypress
verskaf word. Kom ons gaan vir eers nie na " cypress
" nie. Gaan asseblief na jou blaaier en gaan na hierdie ligging:
http://localhost:8080/accounts/all
Ons behoort 'n resultaat soos hierdie te kry:
Soos ons kan sien, het " Malory
", " Jack Fallout
" en " Jitska
" geen krediet of geld nie. Dit is omdat hulle slegs die gebruikersgroep gegee is. Let ook op dat Shikka
geen krediet gekry het nie. " Shikka
", is ons enigste kliënt wat nie die groepkrediet het nie. As ons na die logboeke kyk, kan ons sien dat suksesvolle bedrywighede hierdie formaat aanneem:
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"}
'n 200 laat ons weet dat die operasie suksesvol afgeloop het. In die geval van "Malory", "Jack Fallout" en "Jitska", misluk albei operasies en dan sal ons hierdie soort boodskap kry:
Sending money to jitska HTTP/1.1 403 Forbidden X-Powered-By: KumuluzEE/4.1.0 Content-Length: 0 Server: Jetty(10.0.9)
'n 403 laat ons weet dat ons JWT
token bekragtig is en dat dit vertrou word. Die gebruiker word egter verbied om daardie operasie uit te voer. Met ander woorde, hulle het geen toegang tot die aangewese metode nie.
Kom ons peuter bietjie met ons tokens. As ons sommige van die tekens van die sendMoney.sh-lêer verander. Ons behoort hierdie te kry:
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)
Gaan in volskermmodus Verlaat volskermmodus
Hierdie 401
beteken dat ons teken nie bekragtig is nie. Dit beteken dat die publieke sleutel wat die bediener gebruik om te kyk of ons token vertrou moet word, geen ooreenstemming gevind het nie. As die publieke sleutel nie die handtekening van die JWT-token kan evalueer en bekragtig nie, sal dit dit dan verwerp.
As 'n opsomming, die kopskrif en die "loonvrag" is nie geïnkripteer nie. Hulle is net basis 64 "geënkodeer". Dit beteken dat "Dekodering" ons altyd in staat stel om binne te kyk wat die loonvrag eintlik is. As ons soek om ons loonvrag te beskerm teen afluistering, moet ons nie die "Payload" van die teken vir enigiets anders gebruik as om identifikasieparameters te kies nie. Die probleem lê eintlik wanneer iemand die JWT
-token in die hande kry, byvoorbeeld wanneer die TLS-tonnel gekompromitteer is en iemand die inhoud van die uitgeruilde boodskappe kan lees. Wanneer dit gebeur, is daar nog 'n ander beskerming. En dit is die handtekening. Die enigste een wat 'n boodskap wat ingaan kan valideer, is die bediener wat die publieke sleutel bevat. Hierdie publieke sleutel, hoewel publiek, laat slegs toe om die inkomende boodskap te bekragtig deur teen die handtekening en die "Header + Payload" te hardloop.
Ons het die einde van ons sessie bereik. Dankie dat jy dit volg. Ons kan sien hoe JWT
-tokens kompak en baie minder breedvoerig is as hul XML-eweknie, die SAML
-tokens. Ons het gesien hoe maklik dit is om tokens te skep en te gebruik om sekere magtigings te kry wat nodig is vir sekere metodes en hoe ons daar kom via 'n getekende token. Ek vind egter baie belangrik om 'n idee te kry van hoe JWT
werk. Hopelik het ek hiermee vir jou 'n goeie inleiding gegee oor hoe JWT
-tokens werk. Om 'n beter idee te kry van hoe dit alles werk, raai ek jou aan om rond te speel met die geïmplementeerde cypress
. Dit is 'n goeie manier om te sien hoe versoeke gemaak word en wat ons toets en wat verwag word. Dan sal jy ook 'n beter idee kry van hoekom sommige gebruikers sekere bewerkings kan uitvoer en ander nie. Ek het al die bronkode van hierdie toepassing op GitHub geplaas. Ek hoop dat jy hierdie artikel soveel geniet het as wat ek dit geniet het om te skryf dit. Dankie dat jy gelees het!