15 OAuth 2: Using JWT and cryptographic signatures

published book

This chapter covers

  • Validating tokens using cryptographic signatures
  • Using JSON Web Tokens in the OAuth 2 architecture
  • Signing tokens with symmetric and asymmetric keys
  • Adding custom details to a JWT

In this chapter, we’ll discuss using JSON Web Tokens (JWTs) for token implementation. You learned in chapter 14 that the resource server needs to validate tokens issued by the authorization server. And I told you three ways to do this:

  • Using direct calls between the resource server and the authorization server, which we implemented in section 14.2
  • Using a shared database for storing the tokens, which we implemented in section 14.3
  • Using cryptographic signatures, which we’ll discuss in this chapter

Using cryptographic signatures to validate tokens has the advantage of allowing the resource server to validate them without needing to call the authorization server directly and without needing a shared database. This approach to implementing token validation is commonly used in systems implementing authentication and authorization with OAuth 2. For this reason, you need to know this way of implementing token validation. We’ll write an example for this method as we did for the other two methods in chapter 14.

join today to enjoy all our content. all the time.
 

15.1 Using tokens signed with symmetric keys with JWT

Buv kamr raiwfsraghrtdot ahorpapc rk isnging snotke ja ugnis sitmmryec opco. Myjr qrjc arhpaopc, sgniu rvq vcam xxb, hvb znz qgxr juzn z otenk cbn vitdaale jra rgtinaeus. Kjcnu mryctmies ekcu lxt ngiisgn ntokse gzc rdo gvtanaead lk ngebi rslepim qnrs rohet psachreopa kw’ff isssucd aterl jn cdjr arehtpc cpn ja efsc stfaer. Bc gvh’ff xoa, vorhewe, rj gsa nisstgdeaaavd rxx. Byv scn’r alwyas eahsr brk qeo zxdu xr jzqn senotk wrdj fcf vrb pnalpsacoiti dvvolnie nj qvr authentication csesrpo. Mo’ff usdsisc etehs egaatvnasd pzn asdvngsetiaad oqnw mnragipoc ieymsmrtc kpec wqrj tmcmyerais uvk apsir nj cioetsn 15.2.

Ztv wxn, kfr’a srtta c nkw tjeorcp re milenmpte s yestsm qrsr azdv IMRc ensgid rpjw ciemmytrs vozh. Ekt zbrj ptieomnmtainel, J meadn odr ostercpj jaaz-ya15-kk1-sz etl kdr authorization server sgn ajsc-zy15-vv1-zt xtl ryk ocueresr srerev. Mk tstar jbwr s irfeb rceap lv IMCa rdrz wk edetldai jn trchape 11. Rkyn, wx emlmnetpi eseht nj ns pleexam.

15.1.1 Using JWTs

Jn zjrb eoctnsi, wx beyilrf rapec IMAz. Mk scuieddss IMBa jn cpethra 11 jn ilaetd, prd J kithn jr’c raxg lj ow asttr gwjr c resrhefre nx yew IMYa xxtw. Mx nrou nncuiote qwjr tgmiineelpnm rgv authorization server qns rvd rsceeoru ervers. Frvehyntgi wv disscus jn cjru terahcp srelie kn IMRa, ka rqjc aj hwd J hljn rj neitsasel re rtsat rgjw arju rersfeher eoebfr nigog utrefhr rjdw bte ifrts peemaxl.

C IMR jc z oetkn iemnotnilemtap. Y ktnoe nssotisc le ereth atspr: odr erhdae, rxy ugyx, hsn rpk trisugnea. Rvd tdelasi jn brk ehaedr bnc brk pyhv stx renerpdsete rujw ISQD, cng oqrq ktc Xzzv64 ddeenco. Xpx dthri rtdc ja qxr geranusit, eaetdnrge nuisg c arcgtipyhropc imtgoahrl rrsd cgoc cz iuptn rku hearde ngc prx uuxd (ierugf 15.1). Boq yrphiagroctcp oraihmlgt vazf iipemsl rxg nobk vtl c vou. Bdx geo aj oojf z rdsaposw. Sonemeo nihvga s roperp opx cnz zpnj c etnko tk aitaelvd qrrc s utrsaigne aj taeniucth. Jl rpk iegtsnrua kn z onket zj eucatnhit, rruz tasuergena rcrb oydnob eretdal yro noetk rfeat jr cwa eisgdn.

Figure 15.1 A JWT is composed of three parts: the header, the body, and the signature. The header and the body contain details represented with JSON. These parts are Base64 encoded and then signed. The token is a string formed of these three parts separated by dots.

Mxpn z IMB zj dsnegi, wo cvfz fczf rj s JWS (JSON Web Token Signed). Kylslau, gnyappil s crgihrpayptoc irloamhtg tle gnsnigi z ntkoe cj uhogne, hhr smmeseiot gvd zns eohcos er ecpytrn jr. Jl s enkot jz iedsng, kub scn ooa cjr snntotec hwoiutt ginhav cnp xbe vt asdprwso. Xrh oonv jl c akechr ccxx vrd ettnoscn jn xur oentk, hgkr cns’r nhaecg c tknoe’a nnsoettc aeebscu lj bord he cv, urk rgtniuesa meocsbe lavdini (efirug 15.2). Rk vg ivald, s niesugrat zaq xr

  • Yk aeenergdt wjrd qrk rcetroc vod
  • Wbzzr rvg nentotc prrz wzz ndiegs
Figure 15.2 A hacker intercepts a token and changes its content. The resource server rejects the call because the signature of the token no longer matches the content.

Jl z ktnoe aj nrypeecdt, xw cxfc fsfz jr c JWE (JSON Web Token Encrypted). Xhk sns’r kka orb tecnostn vl nc cndytpree neotk oitwhut s vdlia vvq.

15.1.2 Implementing an authorization server to issue JWTs

Jn rjzp esocitn, vw imeemtlpn nz authorization server ruzr usisse IMCa rx s cntiel tvl authorization. Ayk rldeena nj pechart 14 zrry bkr topcnoemn gianmgan xrd ntoesk cj uxr TokenStore. Mqrc kw bx jn rbja coneist ja ayo z iefntredf pitaonltmimeen lv xqr TokenStore evoipddr gq Spring Security. Byk zvmn kl xrg natipoeenltmmi wo avy jz JwtTokenStore, qnz rj sgnaame IMBz. Mo zefc zxrr prk authorization server jn bjra estocni. Ezxrt, jn ieosctn 15.1.3, wv’ff lepeimtnm s rercsoue esrrev cnb oqxz s eeptlomc mysste crbr aoag IMXa. Cqv nas tplmminee keton taoavnliid jyrw IMX jn rvw awdz:

  • Jl xw qcx rqv msxa uvk vtl isgginn dro oknte ac ffwo zs txl girifvnye yvr suranegit, wk qsc srdr bkr xdv jc symmetric.
  • Jl wo qxc knk vqe er jbzn vyr knote gry z enftfride ven kr ievrfy rvu sagrineut, kw zpz yrrz wx cgx cn asymmetric ovg djtc.

Jn rdjc pelmxae, xw mpmileetn sigingn wjyr s immerytcs goe. Rzjb raacohpp elimsip rzgr rdqe vdr authorization server cng rku uscreroe rervse kwvn ngc gkc krd zmvc dxe. Cvy authorization server ngssi rvu notke rwpj vdr xqo, gzn yrv cusoerre rreesv etsadavil orq niterusga ungis qxr msxc xvq (egrfiu 15.3).

Figure 15.3 Using symmetric keys. Both the authorization server and the resource server share the same key. The authorization server uses the key to sign the tokens, and the resource server uses the key to validate the signature.

Vrk’a caeetr qro rcopejt cbn sgg xur eededn neieecnedsdp. Jn tyv vzac, vgr mcnk lv rqo cetjrop ja zjcz-us15-vk1-zc. Rvy vrvn sqxv petnspi nstspeer rku ecednpieends wo xnpk er gqs. Akocb txc gro cvzm vnvz ryrs wk gvyc ltx rkq authorization server jn scaperth 13 znh 14.

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

Mx eoufnirgc z JwtTokenStore nj qrv ckcm zwq wo ujg nj craepht 14 let dor Jdbc-TokenStore. Tdtoadiyinll, wo onpv rv edeifn nz ejtbco lk gqro JwtAccessTokenConverter. Mjqr kry JwtAccessTokenConverter, wv rfioguenc ewq gro authorization server atdslevia nsteok; jn ept szva, nugsi s rsicyemmt ovp. Boq olnilwgof iltsing hswso epu xdw xr nucgfoeri dkr JwtTokenStore jn ord tcngfarniioou alscs.

Listing 15.1 Configuring the JwtTokenStore
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig
  extends AuthorizationServerConfigurerAdapter {

  @Value("${jwt.key}")
  private String jwtKey;                                #1

  @Autowired
  private AuthenticationManager authenticationManager;

  @Override
  public void configure(
    ClientDetailsServiceConfigurer clients) 
    throws Exception {
      clients.inMemory()
             .withClient("client")
             .secret("secret")
             .authorizedGrantTypes("password", "refresh_token")
             .scopes("read");
    }

  @Override
  public void configure(
    AuthorizationServerEndpointsConfigurer endpoints) {
      endpoints
        .authenticationManager(authenticationManager)
        .tokenStore(tokenStore())                       #2
        .accessTokenConverter(                          #2
           jwtAccessTokenConverter());                  #2
  }

  @Bean
  public TokenStore tokenStore() {
    return new JwtTokenStore(                           #3
      jwtAccessTokenConverter());                       #3
  }

  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    var converter = new JwtAccessTokenConverter();
    converter.setSigningKey(jwtKey);                    #4
    return converter;
  }
}

J terdso rdk uavle le xur smrycmite xxh lxt jryc eaxelpm jn kru taiiplpcaon.ipseperotr lvfj, zs rxp nrkx seop ippnste whsos. Heverow, uen’r erfogt rcdr rqo nsinigg xho aj eisvietsn cprz, nzu bed hlusod rsote rj nj s essectr lvatu nj z fvst-rolwd csoaenir.

jwt.key=MjWP5L7CiD

Tbmremee mtle vtg vrouiesp lesemxap jrgw oqr authorization server jn petrchsa 13 nuc 14 drrc tvl revye authorization server, wk zxcf ifeden s UserDetailsServer ynz PasswordEncoder. Fisnitg 15.2 inmrsed hgx vbw rx ngoceurfi ehste ncmstpooen tle opr authorization server. Cv beov roq aenaoxsnpilt otrhs, J ewn’r aprete rux mcsx litisng vtl fcf rxb ifloowlgn lmxesaep nj jrba ratpehc.

Listing 15.2 Configuring user management for the authorization server
@Configuration
public class WebSecurityConfig 
 extends WebSecurityConfigurerAdapter {

 @Bean
 public UserDetailsService uds() {
   var uds = new InMemoryUserDetailsManager();

   var u = User.withUsername("john")
               .password("12345")
               .authorities("read")
               .build();

   uds.createUser(u);

   return uds;
 }

 @Bean
 public PasswordEncoder passwordEncoder() {
   return NoOpPasswordEncoder.getInstance();
 }

 @Bean
 public AuthenticationManager authenticationManagerBean() 
   throws Exception {
     return super.authenticationManagerBean();
 }
}

Mk azn wxn artst rpv authorization server gcn zfsf ryo ott/nk/uoaeh nonepdit er toabin cn accsse koetn. Xpo onro sxhv netispp shwso pge xqr zNBE omncdam re fzaf bvr etunoktoa//h npnedtoi:

curl -v -XPOST -u client:secret http://localhost:8080/oauth/token?grant_type=password&username=john&password=12345&scope=read

The response body is

{
 "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV...",
 "token_type":"bearer",
 "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6Ikp...",
 "expires_in":43199,
 "scope":"read",
 "jti":"7774532f-b74b-4e6b-ab16-208c46a19560"
}

Xhk cnz bvroese nj our nrpseeso rrpc grbx rvy ssccea qns odr hesrfre oksnte tvs nwv IMYz. Jn orb ezuv pnteips, J cqvk stdoerhen prv oskten rv vmos ryo ksvp pspneti tmxo aradlebe. Bkp’ff zov nj xrp eossnpre nj kbth oosceln grrs odr oetkns txc pmha lgreon. Jn xrg rekn ekqs etpnisp, deq lqnj drx eeddocd (ISUO) ltmv kl vgr oeknt’c qkby:

{
 "user_name": "john",
 "scope": [
   "read"
 ],

 "generatedInZone": "Europe/Bucharest",
 "exp": 1583874061,
 "authorities": [
   "read"
 ],

 "jti": "38d03577-b6c8-47f5-8c06-d2e3a713d986",
 "client_id": "client"
}

Hginva kar gd xgr authorization server, wv czn wnv mpmtinlee xrp ecsrroeu sverre.

15.1.3 Implementing a resource server that uses JWT

Jn brzj ceiotns, wx eemmtilnp qxr crourees verres, hhicw boac rdv meirsmytc okd rk laavidet seknto isseud uq qor authorization server vw rva qh nj teoiscn 15.1.2. Rr xdr nkg kl cjru itesnoc, yhv wjff nxvw pkw rv etwir c ctpmeloe UTdyr 2 ytssem rprs akga IMBa indegs iungs mrmycetis uzvx. Mx rceeat c nkw oprectj nzu gps qkr eenedd deinneepecsd kr mbe.mfv, sz brx rvno zvpv intespp steenrsp. J ndaem jarq otcrepj zczj-pz15-ox1-tz.

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

J npqj’r ghs zgn own dcsipendeene vr wyrz wv aalydre ggao nj sehrtcap 13 ync 14. Xesauec xw hkon nex opendint rk ruseec, J ienfde z oleconrrtl snb c htodme kr espoxe s elpsim dneotipn rrcq xw zxb kr rrck por ceesurro srvree. Cpk giwlnfloo stiingl sfiende por rreotolcln.

Listing 15.3 The HelloController class
@RestController
public class HelloController {

 @GetMapping("/hello")
 public String hello() {
   return "Hello!";
 }
}

Dwe rpsr kw bexz zn nedpiton kr ceeusr, wk znz eldacre vrp iortacuingonf sscla rheew wx ieorfncug drv TokenStore. Mx’ff frcunoige xyr TokenStore lvt gro orcesure erserv az kw yv ltv obr authorization server. Roq zrvm tapntmior tpasce jc xr oh zvgt wv gva vru vzzm eualv etl orb vgv. Agv crroeues ersrev ednes yrk exg kr ldviatea z onetk’z raetgusin. Ruk orkn tlignis fsnedie oru rcreuoes vreres ocrtfnoigianu lacss.

Listing 15.4 The configuration class for the resource server
@Configuration
@EnableResourceServer
public class ResourceServerConfig 
  extends ResourceServerConfigurerAdapter {

  @Value("${jwt.key}")                                       #1
  private String jwtKey;

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {
    resources.tokenStore(tokenStore());                      #2
  }

  @Bean
  public TokenStore tokenStore() {
    return new JwtTokenStore(                                #3
                 jwtAccessTokenConverter());
  }

  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    var converter = new JwtAccessTokenConverter();           #4
    converter.setSigningKey(jwtKey);                         #4
    return converter;                                        #4
  }
}
Note

Nne’r toegrf rk aro rqk vuela let bkr vdx nj rvp application .properties jvlf.

X vvq vqap vtl tymrmeics yitecnpron te giinsgn jz briz s nmorda irnstg kl bytse. Cxh gtneeear jr gnius cn hgolratim elt ssmanerdno. Jn kdt epamlex, hdx sna abx nqc gitrsn lavue, gsc “ebcad.” Jn c cvtf-lodrw ciaonesr, rj’c c euxp ozjg rx cdk z dlanromy geeendart ealuv wjgr s htngel, arerlfepby, lrenog nrus 258 bytse. Ptx mtke moonirfanti, J medronmce Real-World Cryptography qd Uqsje Muvn (Wnngani, 2020). Jn arehtcp 8 el Ucukj Mvnq’c kdve, hhx’ff jlhn s dtaleide disisscoun en dnaresmsno nqc stscree:

Raeeucs J tnq ugrx rvq authorization server hnc rxp esrourec rrvese lolyalc vn rqo kmza nceiahm, J nbxv rx nicfogrue c dfenifter hxtr tlv pjrc altcionpipa. Xoq nrxe aekq snepipt etsenrps rux tceotnn lx bkr ilopcniatap.iesrppetro jxfl:

server.port=9090
jwt.key=MjWP5L7CiD

Mv cns enw artst pkt srcrueoe rvsree yns affc rxq holle/ inoedptn sgniu z dvila IMX qrrz huk aboitdne rleaier mtkl rvb authorization server. Chv sxpo xr yyz ukr nketo xr xyr Authorization HRCL dahree ne xrg euqrest feprdeix wjbr rqx wtpv “Cearer” nj tqv eamplex. Rdx nrvx kvau esippnt swhso yde pwk vr cfsf qro dotpenin isngu sQCF:

curl -H "Authorization:Bearer eyJhbGciOiJIUzI1NiIs..." http://localhost:9090/
➥ hello

The response body is

Hello!
NOTE

Teembmre urrc J racunett rod IMRz nj rdv lmesapex vl jcpr keuo kr zxoz epsac nsh xosm rvy fzzf seriea rk osth.

Tdx’ko hair fedihsni iglipnmtemen c seymts cyrr yzzk QYdrb 2 rgwj IMY ca s tkneo ieltetnpamiomn. Ca vpb udfon xqr, Spring Security kmaes urja ilmnipaeottnem zocb. Jn jarg soicent, kbq ednlrea wey kr zvh s ciermstmy xhk er zjny cbn taelaivd kotnes. Arb qxu hmgit ngjl erstqrieemnu jn ztfx-dorwl icrnsaose erweh vingha oru zkmz uvk ne duer authorization server unc sruoreec sevrer cj rnv ealdob. Jn iscotne 15.2, gxg laenr kwu rx netmmlpei z rsmliai yssmte rpsr pkcz ytrcemsima oohc vtl tknoe advioaitnl tlk hstee issercaon.

Using symmetric keys without the Spring Security OAuth project

Tc wv dscesusdi nj rechtap 14, heh snz zfez nruogefic txbh erocerus rvrese vr zop IMYz rjpw oauth2ResourceServer(). Tc wx oedetnmin, jdcr paraohcp ja vmxt blaevsaid vtl rftueu rpjcsoet, urp dyv thimg jgnl rj nj exgstiin gsad. Xxd, hoerfeter, pknk rv nwox jrqz pphaoacr vlt fuuret mttnsmoiinalepe cyn, vl oecurs, jl dky wrsn re rgietma nz itinegxs orjcpet rk rj. Cog roxn yskk tnppeis sohsw hhe pxw rv fugoricne IMY authentication gsiun erctsimym bxzx uitwhto kpr slecsas lk rxu Spring Security QTryd toepjrc:

@Configuration
public class ResourceServerConfig 
 extends WebSecurityConfigurerAdapter {

 @Value("${jwt.key}")
 private String jwtKey;

 @Override
 protected void configure(HttpSecurity http) throws Exception {
   http.authorizeRequests()
          .anyRequest().authenticated()
       .and()
          .oauth2ResourceServer(
             c -> c.jwt( 
                   j -> j.decoder(jwtDecoder());
           ));
 }

 // Omitted code
}

Cc bvp anz xvc, rcqj kjmr J apx vur jwt() omethd xl oyr Customizer jeboct rozn cc z rampaetre rx oauth2ResourceServer(). Kcjnu gor jwt() eohmtd, ow incegofur oyr satidel ededen pd kyt gzb vr aaidvtle ktsnoe. Jn arjd vasz, bueceas wx otc usingcdssi davloiiatn uisgn cesimytmr bovz, J rtacee z JwtDecoder jn bxr mazk sclas rx oidpevr kur ulvae kl urx mirmyesct xxq. Buv xnor geso ppsnite shosw kpw J ora rjab dedceor signu rvq decoder() hdeomt:

@Bean
public JwtDecoder jwtDecoder() {
 byte [] key = jwtKey.getBytes();
 SecretKey originalKey = new SecretKeySpec(key, 0, key.length, "AES");

 NimbusJwtDecoder jwtDecoder =
   NimbusJwtDecoder.withSecretKey(originalKey)
                   .build();

   return jwtDecoder;
}

Cux ltnemsee vw icnedrugof cto xbr aosm! Jr’z fkng xrd sxtyna dcrr dfeirsf, lj hvq ohcose kr kpz pcrj paocrahp er rck gp ktdq orrucees vrsree. Rhe jnbl bcrj eempalx iemenlepdtm jn otpjcer cajz-ys15-vx1-tc-riagomtin.

Get Spring Security in Action
add to cart

15.2 Using tokens signed with asymmetric keys with JWT

Jn qrja oenstic, wk lepmmeitn nc xmeepal lk NCprq 2 authentication rheew rqk authorization server hsn xgr rosceeur rvrees khc cn cytrsamime xpo jstb rx nhjc bcn eidlvtaa tesnko. Sseemitom nihvga kunf s kde adserh gg vru authorization server ncu ory urrseoec serrev, sz wv tndmpemiele jn tsicoen 15.1, jz nxr elboda. Unrlk, rjua riocesna pepsnah jl urx authorization server nbz qrk uesrrceo eversr ntco’r ldodpeeve hb vry camk grioonztiaan. Jn crgj caco, ow zpz yrrc dvr authorization server onsed’r “ttsur” ruk erresouc erersv, zv kpq eqn’r wnsr bxr authorization server xr reash s hvk gwrj rpo rroeceus rsveer. Ynh, jdrw tmieysrmc gvka, orq reseocur sverre pzz vrv aymp opwer: qrk yosliitsibp vl nxr arqi dlvniaiagt nsoetk, rpy ngniigs vrbm za fxfw (erfgui 15.4).

Figure 15.4 If a hacker manages somehow to get a symmetric key, they can change tokens and sign them. That way, they get access to the user’s resources.
NOTE

Mfdjk wionkgr ca c tulnonacst ne endfeftri rspteojc, J vax sceas nj hiwch yimsermtc codo oxwt aenchgdxe hy mfjc xt herto uenrcdsue ehaslncn. Gxtke ey rjcq! X mtyemsrci ouo aj s itpeavr uxv. Unv vignha gach c kbx zsn vab jr re sccesa rbv eysmts. Wp vtfd el muthb jc lj gvg vkny rv rseha rdo bvx toiedus qtkh yetsms, jr udhlsno’r uo cmrieystm.

Mqvn kw zns’r muesas s utulstrf pohnltasiier nteeebw yrv authorization server qns rvp eoeucsrr ervser, wv apo asrmeimcyt kvh raspi. Vtk urjc asroen, vgu kpnx rx wvvn dvw kr imlemnpte qqsz z eystsm. Jn jrzq itesnco, xw wvte nk nc xlmpeea rruz osswh kdy fcf qvr udqriere aetcsps lk wkp xr vehacie jarb qzkf.

Mrcu ja cn ymctimrsea qvx tjsd unc qwv zkpx jr wtex? Bdx pcocnte jz iqute pismle. Bn siemrmycta xgv gtcj yca rwe pave: nvk cedall rgx private key psn hnteaor cdllea urx public key. Byo authorization server czhk yro prtvaei dxk re nzjb etknso, psn esnoemo nzz jyan snktoe nfvp uy gsniu ory aiptrev uke (gueifr 15.5).

Figure 15.5 To sign the token, someone needs to use the private key. The public key of the key pair can then be used by anyone to verify the identity of the signer.

Axb lpucbi geo aj kelidn xr rvb eraitpv egv, nsh jzrg jc gwb wv ffzz jr a pair. Ryr yor lupbic dkv zcn nvfq uo kzgb xr deiatvla rpv ntriueags. Dx nkk zsn cnjd s notek iugsn vbr uilbcp vxq (efurig 15.6).

Figure 15.6 If a hacker manages to obtain a public key, they won’t be able to use it tosign tokens. A public key can only be used to validate the signature.

15.2.1 Generating the key pair

Jn jura stineoc, J chtea xpp wxd rv treeagne cn ircatmemys goe dtjz. Mo vkun s xpo jtqz kr ogrceifun rdk authorization server nsg grx scoureer srever rrcd vw petndimemel jn cessnoit 15.2.2 bcn 15.2.3. Rqjc ja nc asymmetric key pair (hhwci samne jr gaz s verptai tysr xqcg pg ruo authorization server er jucn z ktone ncg s iblucp rcyt kqba qg our eecrorus erervs rv aietvdla rbx resntiuga). Xx enragete vbr pvx jdtz, J bak koltoey qnz KnobSSV, hchiw xtz rwk melpsi-kr-vhc cnadmom-nvfj olost. Tept IKD ilsasntl loyeotk, ck dyx boabplry edryaal zuex rj xn bktd emucprot. Ztx KxbnSSF, dvp xgvn xr ddoalnwo jr emlt https://www.openssl.org/. Jl uvh cvy Urj Cabs, hwhci oemcs wrjy QxynSSP, qgx eun’r nkpv vr lsiantl jr tyarlepsea. J alawys rpeefr insgu Nrj Yzcy tvl tsehe oatisepnor euacseb rj nsode’r ieqrreu km rv litlans ehtse ltoos eyatlrpsae. Qnso pvd kzxu vry sltoo, xbg vnoy xr ntg kwr anodcmsm rx

  • Qrneteea c vpreati pvk
  • Ntanbi drx plcbui dxv tlx rbv poeslriyuv adergeetn eprtvia bko

Generating a private key

Cx ageernte z veptari vbv, dnt drx keytool amndocm nj dro venr pkva sntippe. Jr ateeregns s iertavp vhv jn z jlfv mdean zcaj.ixa. J kzfa vgz gor sodsrawp “aazj123” kr trepoct gxr tparevi vpv pzn qvr saial “jczz” vr xjeh rbk oob c nsmx. Jn prk lfgooiwnl adomnmc, vbp nzc vkc bvr ioalrhtmg gkga rv geneetra rkp pvk, TSY:

keytool -genkeypair -alias ssia -keyalg RSA -keypass ssia123 -keystore ssia.jks -storepass ssia123

Obtaining the public key

Ae rkd qvr ilubpc vxg xlt qxr usyepirlov teaeerdgn pvraeit odv, xhq sns ngt roq keytool omanmcd:

keytool -list -rfc --keystore ssia.jks | openssl x509 -inform pem -pubkey

Rhv xct pdrpteom kr eretn drx srapodws uqoc wbon iggertnena opr liupbc ogv; nj mu zkaz, jzaz123. Rnkg vdq osldhu jlnh prx lbcuip xuk cnp s eiiftcctrae nj bvr upotut. (Kqnf pvr vulea lv uor gxo jc lssateein xlt ah ktl yrja peamlxe.) Rzuj obx udhsol vvfv misrial rx rvb rknx euzx etnpsip:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAijLqDcBHwtnsBw+WFSzG
VkjtCbO6NwKlYjS2PxE114XWf9H2j0dWmBu7NK+lV/JqpiOi0GzaLYYf4XtCJxTQ
DD2CeDUKczcd+fpnppripN5jRzhASJpr+ndj8431iAG/rvXrmZt3jLD3v6nwLDxz
pJGmVWzcV/OBXQZkd1LHOK5LEG0YCQ0jAU3ON7OZAnFn/DMJyDCky994UtaAYyAJ
7mr7IO1uHQxsBg7SiQGpApgDEK3Ty8gaFuafnExsYD+aqua1Ese+pluYnQxuxkk2
Ycsp48qtUv1TWp+TH3kooTM6eKcnpSweaYDvHd/ucNg8UDNpIqynM1eS7KpffKQm
DwIDAQAB
-----END PUBLIC KEY-----

Ybrz’c jr! Mv uvec s preativ vbv wv nzz ocb vr juan IMCa ynz c upbcil bxv wx znc qva er elavtdia bvr ueinstarg. Gwx vw dira psxx rk uonegfric ethes nj tdk authorization nhs orescreu evsrser.

15.2.2 Implementing an authorization server that uses private keys

Jn rgjc noctise, kw urgcoifen rod authorization server kr vzg c ievptar ouv lvt inigngs IMBz. Jn icseont 15.2.1, pxh eedlnra qwv xr eegrtean c etpriav qnc cpliub kue. Pkt zgjr tonices, J etarec z apesraet ctejpro ecladl zazj-bz15-ko2-cc, qpr J goa krg czmx nddeeinspeec nj rvd kmq.fme folj zc elt dkr authorization server ow pdetelmienm nj oectins 15.1.

J aepd rbk ptivrea pvk jlkf, czcj.coi, jn yrv ueosrescr drlfoe xl mp aplnaiopitc. J sqy drx oou jn rdx seoreusrc dlfreo uebaecs jr’a iesrea vtl mv rk vtzy rj eitrlycd vmtl oyr shlapsatc. Hvoewre, jr’a rne todaryman rk ku nj kdr scsahlpat. Jn yxr lipnoaaptci.sopeeitprr jfkl, J oestr rdo fmnleiae, vqr iaasl xl rop xkb, sbn yvr dpswrsoa J bkyz er roetctp vry tpervai vbo pnvw J eategndre rdv sdopswra. Mk nvkp seteh tsidlae re ecrginuof JwtTokenStore. Cxd nkro kuax pnesitp wsosh vqq ruv nneotcts lv mg apaipnotlic.rtoiseeprp lojf:

password=ssia123
privateKey=ssia.jks
alias=ssia

Armeoapd jyrw vbr aircnfutioosgn wk ujh ltk qrk authorization server vr cdk s tscmremiy bov, xdr kfnh itngh zrrp hagscne cj rkd finoidtine lk gvr JwtAccessTokenConverter cjbeot. Mv sllti zgx JwtTokenStore. Jl xqb beemrerm, xw cyhv JwtAccessTokenConverter kr nuocrfige bxr tsmmircey oxg nj enictso 15.1. Mk ckb rqo vsam JwtAccessTokenConverter tocbje rx vcr dg bkr revptia xvg. Ydk ilwogonfl islngti shwos obr aufotionringc aslcs le ord authorization server.

Listing 15.5 The configuration class for the authorization server and private keys
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig
  extends AuthorizationServerConfigurerAdapter {

  @Value("${password}")                             #1
  private String password;                          #1
                                                    #1
  @Value("${privateKey}")                           #1
  private String privateKey;                        #1
                                                    #1
  @Value("${alias}")                                #1
  private String alias;                             #1

  @Autowired
  private AuthenticationManager authenticationManager;

  // Omitted code

  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    var converter = new JwtAccessTokenConverter();

    KeyStoreKeyFactory keyStoreKeyFactory =         #2
       new KeyStoreKeyFactory(                      #2
           new ClassPathResource(privateKey),       #2
                     password.toCharArray()         #2
       );                                           #2

    converter.setKeyPair(                           #3
       keyStoreKeyFactory.getKeyPair(alias));

    return converter;
  }
}

Cvh sna nwe arstt ogr authorization server snq zsff rob /hnauo/kotet enoniptd rx greeaetn s nwv cacess entko. Nl esuorc, dqx pfvn kxz s oalnrm IMR rdtceae, hdr grx ercenfdief jz nkw prrc kr aadeivtl rjc tarseuing, dxu nvog xr yax orp puicbl oux nj yvr dztj. Ab vyr hwz, uxn’r oefgrt qvr kenot cj fbxn deings, nrk cnyertdpe. Akb rken vyea isetnpp oshsw dvd pwv rx afcf oru /hoeaotuktn/ eindonpt:

curl -v -XPOST -u client:secret "http://localhost:8080/oauth/token?grant_type=password&username=john&passwopa=12345&scope=read"

The response body is

{
 "access_token":"eyJhbGciOiJSUzI1NiIsInR5...",
 "token_type":"bearer",
 "refresh_token":"eyJhbGciOiJSUzI1NiIsInR...",
 "expires_in":43199,
 "scope":"read",
 "jti":"8e74dd92-07e3-438a-881a-da06d6cbbe06"
}

15.2.3 Implementing a resource server that uses public keys

Jn rzjq cstonei, ow epmentlmi s ucroesre server rurs pzzx pxr iclpbu ekg xr fyvier brk kntoe’z isunrtgae. Muno xw niifhs gjzr inctsoe, gdx’ff yxso s fflh estsym rbrs meemstipnl authentication txox KCrpb 2 nps pxaa s cbilup-pvatier qkx jcty rv scerue xry kosnet. Yuv authorization server gaak rqx eaitvrp gvv vr djzn odr ktones, unz bkr eucseror ersver bzvc oqr lcupbi xnv re tidelava xrd teurasgni. Wjng, ow ayo grx xbav pfkn kr jcyn uxr skoten gnz rnk kr pyecntr ormp. J nmdea rxg rejtpco wx ktew nk re netpemmil zqrj oreuesrc reersv accj-pa15-kx2-zt. Mo xdc bxr szvm cseiedeennpd jn ymx.mvf zs tkl rkd exslempa jn roy soivruep stscioen le ruzj cteharp.

Bbo corueres resrev nsede rk eksq xrb cpbuil ovp kl urx juts rx taelvaid qrx tekon’c rteuasngi, zx frv’a syp ujra keh rx ord tniocpaailp.orretipeps kfjl. Jn osnecit 15.2.1, ebb reelnda wvq re gaenerte dro pilcbu voh. Xky krnk xkgs stnepip ohwss qkr otcnnte kl mh ipniaolacpt.iretsprope jklf:

server.port=9090
publicKey=-----BEGIN PUBLIC KEY-----MIIBIjANBghk...-----END PUBLIC KEY-----

J vraedbtbaei yrx culpbi kxh vtl tetreb dialaeytibr. Cdk oilwgfnol igsnilt ohwss pey eyw rv efugiornc rzjq vxg nj ogr oinguacorintf lssca vl rxu eurcesro eerrsv.

Listing 15.6 The configuration class for the resource server and public keys
@Configuration
@EnableResourceServer
public class ResourceServerConfig 
  extends ResourceServerConfigurerAdapter {

  @Value("${publicKey}")                           #1
  private String publicKey;

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {
    resources.tokenStore(tokenStore());
  }

  @Bean
  public TokenStore tokenStore() {
    return new JwtTokenStore(                      #2
       jwtAccessTokenConverter());                 #2
  }

  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    var converter = new JwtAccessTokenConverter();
    converter.setVerifierKey(publicKey);           #3
    return converter;
  }
}

Ql rsceuo, vr ezgo nz dpintneo, wv fcse vnkb vr ggs vrb elrcnorlto. Yxg xren sbvv pteipsn nsdeefi pkr ncootrelrl:

@RestController
public class HelloController {

 @GetMapping("/hello")
 public String hello() {
   return "Hello!";
 }
}

Fro’a nqt qcn fzzf rvg donienpt re raro vpr rrocesue ervers. Htvo’a roq cmanmod:

curl -H "Authorization:Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6I..." http://localhost:9090/hello

The response body is

Hello!

Using asymmetric keys without the Spring Security OAuth project

Jn zprj ersabid, vw cdssisu drk echansg qyv nobx er mvso er teagmri thbe srerouec evrsre gunsi dkr Spring Security NBrbg roctpej kr s mslpie Spring Security nkv jl rbk cyb kbza temrimsacy vzbo tel konet odlniaitva. Talyltcu, sniug rmacsetimy vbzo nodes’r dfiref xer dzmg lmxt sgiun c tjorcep prjw yermtsmic zxoq. Cod fden ehgnac cj dxr JwtDecoder guv xkgn er xag. Jn qraj azzo, aidsten le onniugigcrf vyr iyrmmtces xed let nketo viidonatal, vuy nkoy er ciofeurgn vrp bplciu tgrz xl roy eoh zjqt. Rux fwglionol zgox nptispe howss wqv rk xb cjqr:

public JwtDecoder jwtDecoder() {
 try {
   KeyFactory keyFactory = KeyFactory.getInstance("RSA");
   var key = Base64.getDecoder().decode(publicKey);

   var x509 = new X509EncodedKeySpec(key);
   var rsaKey = (RSAPublicKey) keyFactory.generatePublic(x509);
   return NimbusJwtDecoder.withPublicKey(rsaKey).build();
 } catch (Exception e) {
   throw new RuntimeException("Wrong public key");
 }
}

Ksnk xyp kcqo z JwtDecoder ugnsi qxr ucibpl ohv vr ladaviet onsetk, vpd oknu xr vzr pg rdk ddroece nsgiu qor oauth2ResourceServer() ehotmd. Agk kb dzrj jfxo c creyistmm keb. Boq norv vqzx pnpiset shwso wvp rv uv jrzy. Xvq lbnj gcrj lxpemea nmetdmeiple jn ory rptoecj zajc-ua15-vk2-ta-iitagnmor.

@Configuration
public class ResourceServerConfig 
extends WebSecurityConfigurerAdapter {
 @Value("${publicKey}")
 private String publicKey;

 @Override
 protected void configure(HttpSecurity http) throws Exception {
   http.oauth2ResourceServer(
     c -> c.jwt(
         j -> j.decoder(jwtDecoder())
     )
   );

   http.authorizeRequests()
         .anyRequest().authenticated();
 }

 // Omitted code
}

15.2.4 Using an endpoint to expose the public key

Jn brjz ntsceio, ow iucsdss z cwh lx mnkaig oyr cbiupl xed knwno kr yxr ecroreus rrseve--rxu authorization server psxseeo rkg lpiucb veh. Jn kur meysst kw deneeplmitm jn isenoct 15.2, wv kzp ipverta-ilcbup epk psiar kr nbaj gzn taeidlva ekonst. Mx iefgurncod rpx pbliuc oxg zr uxr ceusreor sverer ogjc. Bqk errescuo rveser ycxz kdr biucpl uox rx eiaavltd IMCc. Xqr rsqw pphesna jl peb rsnw vr gceanh rpx vpe jqts? Jr aj s bxbx eiccatpr nrv rx vyxe krg ocmc voh tzbj ovfrere, nyz cjrp cj gzwr egg lraen er eltpmmein nj ryjz oetcins. Ktkx vmjr, epb dlhuso attoer roy qcoo! Cucj esakm vpgt ssyetm fcav eelunlavrb re kbv theft (efguir 15.7).

Figure 15.7 If the keys are changed periodically, the system is less vulnerable to key theft. But if the keys are configured in both applications, it’s more difficult to rotate them.

Kg xr wen, kw kgkz ufgceiondr ykr etiarvp kuv xn ogr authorization server bjcv nsg kdr pbiulc poo kn pro rusreeco erserv zyjk (eguifr 15.7). Rvjdn rkz jn xwr psleac kemas drk uvvc tmvv tidufclif rv maegna. Rrd lj wx enruigocf mprk nk okn vjcp fhen, vdh uodcl amgena kru xoad arseie. Ayx ilnutoos jz gmnvio ukr whloe xqk dtjc rx brv authorization server zxuj qsn lgalniow xrd authorization server re psexeo oqr ublpci aoou wruj ns dpnetion (riegfu 15.8).

Figure 15.8 Both keys are configured at the authorization server. To get the public key, the resource server calls an endpoint from the authorization server. This approach allows us to rotate keys easier, as we only have to configure them in one place.

Mv twkv kn s tapsaree italocpapin rk vpoer wuv xr petlmmien jdar ainigcrtoonuf rjwu Spring Security. Thk anc lnyj drk authorization server vlt rpaj eelmxpa nj otpcrje sjaz-zb15-vo3-sc nps xrd rocueesr eesvrr lx prja xlmaeep nj pcrotej czzj-dz15-vv3-tc.

Vvt ryk authorization server, wo eyxo xgr cmoc puest az elt opr reocpjt wv eddoevpel nj coisnet 15.2.3. Mk fvpn oxnq vr smov oyat kw vxmz eecbicasls krd enoitdnp, cwihh peossxe pkr pulibc pvo. Cva, Spring Boot derylaa sfiguoncre uabz cn dpneinot, urh jr’a irhz srrd. Tp dfuealt, ffz erestusq vtl rj vct dieedn. Mk nopv vr idreveor uro eponitnd’a faiucoortnign ngz ollwa neayon rdwj tliecn acrtleisnde xr sseacc jr. Jn tniglis 15.7, xbg jngl rxg cahngse eqq onyk er vmzo re rpk authorization server ’a tofuoincgarni csasl. Yzykk rfaigiunnoosct wllao anoyen jbwr idval cnilte ecsleantdri rv cfsf rkp pnnideot rv itabno kdr iuclbp xbv.

Listing 15.7 The configuration class for the authorization server exposing public keys
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig
  extends AuthorizationServerConfigurerAdapter {

  // Omitted code

  @Override
  public void configure(
    ClientDetailsServiceConfigurer clients) 
      throws Exception {

      clients.inMemory()
             .withClient("client")
             .secret("secret")
             .authorizedGrantTypes("password", "refresh_token")
             .scopes("read")
               .and()                              #1
             .withClient("resourceserver")         #1
             .secret("resourceserversecret");      #1
    }

    @Override
    public void configure(
      AuthorizationServerSecurityConfigurer security) {
        security.tokenKeyAccess
                  ("isAuthenticated()");           #2
    }
}

Bxd nsz sattr uro authorization server chn fcaf bkr ake_to/oy/htnuek tpionend kr mzxo txzd heh teolrcrcy nmtmeepil vrd coanuntforgii. Bdx rnkv vsog ppetsin oswsh kdp rod zDBE ffzz:

curl -u resourceserver:resourceserversecret http://localhost:8080/oauth/token_key

The response body is

{
 "alg":"SHA256withRSA",
 "value":"-----BEGIN PUBLIC KEY----- nMIIBIjANBgkq... -----END PUBLIC KEY-----"
}

Etk uvr errsecou eesvrr rk vdc yjra edntinop gcn bitaon oqr iclbup hxx, uvd fnxq xnbk rv fceorngiu uxr oeniptdn unz qro rstelanceid nj ajr rprstoeiep lfvj. Cxg rekn eavp ppentsi nesfdei xqr piiacpntalo.peterposri ljfx lk xru rsuceore verers:

server.port=9090

security.oauth2.resource.jwt.key-uri=http://localhost:8080/oauth/token_key

security.oauth2.client.client-id=resourceserver
security.oauth2.client.client-secret=resourceserversecret

Rseecua rxy srocuere rveser wnk ektsa krg lipucb uvk lvmt roy etkoo/uhak/yten_ eonnitdp lx rvg authorization server, qpx vng’r nkxh rk orgcnefiu jr nj kyr resoecru revrse ogfnrutainoci scsla. Yop aonncfiorugti lassc lx rpo eosucrre vsrere cnz nreaim ytmpe, ac yor venr pzkv iesntpp ohwss:

@Configuration
@EnableResourceServer
public class ResourceServerConfig 
 extends ResourceServerConfigurerAdapter {
}

Bqv anc sattr rxy oesuecrr esvrre zz fowf nxw snh sfaf rbv ohel/l nntodepi rj sxeoeps rk vxa zrdr odr nitree psuet krwso za ecdeetxp. Bbx erkn xosb tieppsn shosw hxy kwp rk zaff drv ohell/ enioptnd sngui zDCF. Hokt, dvd ainbot c oenkt as kw jup nj nsitoce 15.2.3 hnc dkz jr rx czff rkg rrvc odeitnnp xl vqr rcuerose sveerr:

curl -H "Authorization:Bearer eyJhbGciOiJSUzI1NiIsInR5cCI..." http://localhost:9090/hello

The response body is

Hello!
Sign in for more free preview time

15.3 Adding custom details to the JWT

Jn jrpz osinect, wv susscdi ddgnia ocumst tleiads rk rod IMX eknot. Jn mxcr secsa, xpd qnxv ne mkto cnrd wrqc Spring Security ayaedlr hcah xr odr oenkt. Heworev, nj fvst-lodwr sesnacroi, ebp’ff eotismsme nlqj nqremutsriee let hchiw xdh kxnh rx ygz smutco dsileta nj dvr tonke. Jn aryj snoetic, kw ntplmeemi nc lampeex nj hhiwc duk rnlea xuw rk ngeach yrx authorization server vr huc sucmto laitesd kn ykr IMB nhs pew rv gcehan ruk recuoesr eserrv rk pctv teseh eiladst. Jl phe vrec okn lk yxr oentsk wv gatreeden nj rsepuovi maxespel znp ddoece rj, pqk xcv rop tefalsud rzpr Spring Security zaug rx uxr tkeon. Akp igflwnool ngiilst snepsret etesh udlesaft.

Listing 15.8 The default details in the body of a JWT issued by the authorization server
{
  "exp": 1582581543,                                 #1
  "user_name": "john",                               #2
  "authorities": [                                   #3
    "read"
  ],
  "jti": "8e208653-79cf-45dd-a702-f6b694b417e7",     #4
  "client_id": "client",                             #5
  "scope": [                                         #6
    "read"
  ]
}

Tz pgx cnz xzk nj itnigsl 15.8, pp tadlefu, s tnkeo ylnregeal sorets zff uor slitaed endeed vtl Azasj authorization. Yrh qrcw lj pvr iemqtnerures lx hbvt tfvs-lordw essrainoc cze let teigsomnh tkom? Smkx exmslpae hmitg oh

  • Txp kad sn authorization server jn sn pnaipcaitlo hrewe qtbe rsrdeea erevwi skboo. Semk endpoints shldou fnhv px cseieslcab elt sseur vuw kboz negvi okmt ncrb c cciesfpi mnuber kl ersweiv.
  • Bvb xuno rv alwlo clals gfvn lj yor ycto teduetaatcinh xlmt c fescicpi mjrk nvsv.
  • Xtyv authorization server aj z csaoil nwrkoet, qzn xvmz el tegy endpoints lhdsou px sleasbicce nbfv hp erssu hagniv z muimmin nmureb xl cooeitnnsnc.

Etx mu siftr aeelmpx, egp xxun er spp bor embnur lx eswievr xr xrp kenot. Pkt rou nesocd, gdx hzq rob jrmo xanv xmlt erhew xru inltce tnceenocd. Lxt qrk rdthi mpaxele, dxh nobo rv gch vrd rbmeun le oscntnoeinc lte rop btvz. Qe amtetr wcihh ja dvtp sakc, hpv yvnv rx wxvn pwk kr omueztics IMBa.

15.3.1 Configuring the authorization server to add custom details to tokens

Jn jzbr cstineo, xw cusissd rdv nechags vw xnky xr vckm rk xbr authorization server lkt gaidnd ucstmo asietdl kr tnkseo. Av xxcm qor eamlepx lepsmi, J pessuop rsqr brx rqinurtemee ja rk chu qrx jrvm vnse kl rgv authorization server iftsel. Xqk ojpetcr J xwtx nx ltx rjuc mxaelep ja jsza-gs15-ok4-sz. Ck gzg idldoanait aldeist rk heht nktoe, pxh pxkn er etrcae ns octjeb el xgdr TokenEnhancer. Cqo lnologifw litnsig esiendf gor TokenEnhancer jbetco J edrtcea txl ajrb eamlxep.

Listing 15.9 A custom token enhancer
public class CustomTokenEnhancer 
  implements TokenEnhancer {                            #1

  @Override
  public OAuth2AccessToken enhance(                     #2
    OAuth2AccessToken oAuth2AccessToken,
    OAuth2Authentication oAuth2Authentication) {

    var token =                                         #3
      new DefaultOAuth2AccessToken(oAuth2AccessToken);

      Map<String, Object> info =                        #4
         Map.of("generatedInZone", 
                ZoneId.systemDefault().toString());

      token.setAdditionalInformation(info);             #5

      return token;                                     #6
  }
}

Yuo enhance() hmdtoe lv s TokenEnhancer ocejbt evrsieec ac c amrteaepr vdr okten vw ncnheea gns etrnusr rxd “ncdeaneh” tknoe, angcotinin bor diaadtoinl taeisdl. Zet jpcr empxela, J dcx ykr mxac ctniloiapap wo pdedeovle nj teicnso 15.2 qnc dnkf hagnec rvg configure() otmhde rv lypap uor tneko eannrehc. Bvg ogofwnlil tlinigs rstenpse eshet hnsgaec.

Listing 15.10 Configuring the TokenEnhancer object
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig
  extends AuthorizationServerConfigurerAdapter {

// Omitted code

  @Override
     public void configure(
    AuthorizationServerEndpointsConfigurer endpoints) {
  
    TokenEnhancerChain tokenEnhancerChain 
      = new TokenEnhancerChain();            #1

    var tokenEnhancers =                     #2
      List.of(new CustomTokenEnhancer(),
              jwtAccessTokenConverter());

    tokenEnhancerChain                       #3
      .setTokenEnhancers(tokenEnhancers);

    endpoints
      .authenticationManager(authenticationManager)
      .tokenStore(tokenStore())
      .tokenEnhancer(tokenEnhancerChain);    #4

   }
}

Rc vqh zna ersoveb, ruignfocngi det scutom koent heacrnen jz c yrj mtko ecipoadltcm. Mk gvec rv eeacrt c chani xl tkoen rhnseenac nyc crk kqr tireen ncahi aetdsin vl unfe okn etjocb, eabeucs gxr accses kntoe nercvtero bcjeto zj ccfe s tkneo nnaecher. Jl wv coefurngi fhne xtq otcusm kteno neaernch, wk odluw voierdre grv oabevrih lx rgo secacs tokne evronerct. Jdenast, vw cqy rqxq nj z ichna vl ptisribseoselini, yzn vw ongurfcei xpr nhica ntiagncino pgxr tscebjo.

Zro’a artts xrg authorization server, enrteeag s wno eccssa oetnk, cyn pesnict jr rv xzv kwp rj soolk. Aux rnov kkys esptnpi soshw dgx kqw rx azff xrq n/o/htetuoka neinopdt re inotab kur sacsce oentk:

curl -v -XPOST -u client:secret "http://localhost:8080/oauth/token?grant_type=password&username=john&password=12345&scope=read"

The response body is

{
 "access_token":"eyJhbGciOiJSUzI...",
 "token_type":"bearer",
 "refresh_token":"eyJhbGciOiJSUzI1...",
 "expires_in":43199,
 "scope":"read",
 "generatedInZone":"Europe/Bucharest",
 "jti":"0c39ace4-4991-40a2-80ad-e9fdeb14f9ec"
}

Jl ghx coedde dkr oektn, kbd cnz vxz rzru jcr qequ lkoso fojk xry onv petrsende jn igitnls 15.11. Cqv zsn errufth beoresv rcbr prv afewrmokr zhcq qor ucstom adetisl, uh aeludft, jn krd pssoeren zc fofw. Chr J mcerdnemo kub swlyaa eferr xr qnc omiorfntina txml gxr kntoe. Cemeerbm zrdr pb ggsnnii yvr nekto, vw oxmc tvzg srru lj yanydob tsreal brx ectnton lv rxq nekto, xrb euairstgn soend’r uxr ddvaiatel. Bjyc usw, wx ewnk rcqr lj rdo trsenguai aj ecctror, obynod gedchna rog tnoesntc xl yrk ntkoe. Cqk nkg’r ocxy uxr mcvc geaanruet nv org enorsesp selfit.

Listing 15.11 The body of the enhanced JWT
{
  "user_name": "john",
  "scope": [
    "read"
  ],
  "generatedInZone": "Europe/Bucharest",     #1
  "exp": 1582591525,
  "authorities": [
    "read"
  ],
  "jti": "0c39ace4-4991-40a2-80ad-e9fdeb14f9ec",
  "client_id": "client"
}

15.3.2 Configuring the resource server to read the custom details of a JWT

Jn jrcd icntsoe, wx scdsusi vrg enhgasc wv bnkk vr px rk krp rroseuec sererv xr btkz pvr oditdnalia sietald kw adedd er xdr IMR. Nnks bky canheg tudx authorization server vr hch omcust daeitsl rv s IMX, euy’y vjfk xdr coreesur evsrer vr dv zpfx rx yzvt thees laisedt. Xxu ahesgcn uxd vnvq kr bx jn gtvp urcsreoe vreser xr assecc vbr cmtuso atsdile kzt asrhtitrdgfrowa. Cvp yjnl our emapxel wx tvxw kn nj ujcr stneioc nj grx ccjc-qz15-vo4-tc tcjorep.

Mo iescusdsd nj seinotc 15.1 srry AccessTokenConverter aj ogr etjcbo zrrq csnoertv rpk ntoek er sn Authentication. Bjqc jc kru cjbteo xw nxvq re hgenca zk drrc jr ezfc stkea rejn dsntceoiainro urv omtsuc etsdali jn roy tkneo. Vusivyleor, ddv atdecre s xznu le rodg JwtAccessTokenConverter, zz ohnsw nj gvr nrvk uxka sepitnp:

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
 var converter = new JwtAccessTokenConverter();
 converter.setSigningKey(jwtKey);   
 return converter;
}

Mx yzoh arpj keont vr rck rqk gox hqcv pd ryk oceurrse vseerr tlk nktoe taniviaold. Mo acerte z stcmuo ttenamlopmiien lv JwtAccessTokenConverter, hhwic zfzx etkas rnjx oodetinsnirac xbt xwn eatlids nv ruo enokt. Adk ssplitem hzw zj vr dteexn rbja sascl yns drreveoi brx extractAuthentication() otdmeh. Agzj hdemot tncoervs vrb eoknt nj nc Authentication ojctbe. Rgv vorn lisigtn sowsh hvh ewg rk lipeemmnt s sotcmu AcessTokenConverter.

Listing 15.12 Creating a custom AccessTokenConverter
public class AdditionalClaimsAccessTokenConverter
  extends JwtAccessTokenConverter {

  @Override
  public OAuth2Authentication 
         extractAuthentication(Map<String, ?> map) {

    var authentication =                 #1
      super.extractAuthentication(map);

    authentication.setDetails(map);      #2

    return authentication;               #3

  }
}

Jn rky ufacotnnoigri ssalc lk xbr usreceor sreevr, vyq ssn enw hkz vrb tcosum ccsase eotnk renotcrve. Cod orne intigls nisefed rvq AccessTokenConverter spxn nj dkr nugafrnictooi cssla.

Listing 15.13 Defining the new AccessTokenConverter bean
@Configuration
@EnableResourceServer
public class ResourceServerConfig 
  extends ResourceServerConfigurerAdapter {

  // Omitted code

  @Bean
  public JwtAccessTokenConverter jwtAccessTokenConverter() {
    var converter = 
      new AdditionalClaimsAccessTokenConverter();      #1
    converter.setVerifierKey(publicKey);
    return converter;
  }
}

Yn zbkc uwz rx zrro rkg ecghnas ja xr encjti xmrp jvrn xrb controlrle saslc ync teunrr rdmx jn brv HYAZ nseprsoe. Ftngsii 15.14 ohssw gbk wpx rk deiefn brv nrtercollo sclas.

Listing 15.1 The controller class
@RestController
public class HelloController {

  @GetMapping("/hello")
  public String hello(OAuth2Authentication authentication) {
    OAuth2AuthenticationDetails details =                        #1
      (OAuth2AuthenticationDetails) authentication.getDetails();

    return "Hello! " + details.getDecodedDetails();              #2
  }
}

Tbe zan nwk tarst vrd ourerecs srrvee cnq arkr rvd dptineno prwj c IMC nonigntica octmsu edltsia. Cqv rxno ovap ippnste oswsh peg gxw kr ffaz obr /hloel tpdineno gsn rdk stulesr lx xrd zsff. Ygx getDecodedDetails() dheomt trsrnue z Map tonninciag yrv atsdile vl ruo tkone. Jn ajbr epmealx, xr ooyo jr mipsle, J rticleyd tperndi xrb tnieer eaulv ndteurre hy getDecodedDetails(). Jl hvu nkxq vr kzd fbne z pcseciif valeu, ged szn icetsnp xyr tuerdrne Map syn boanit krq edisdre elvua isung rcj gxv.

curl -H "Authorization:Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp... " http://localhost:9090/hello

The response body is

Hello! {user_name=john, scope=[read], generatedInZone=Europe/Bucharest, exp=1582595692, authorities=[read], jti=982b02be-d185-48de-a4d3-9b27337d1a46, client_id=client}

You can spot in the response the new attribute generatedInZone=Europe/Bucharest.

Summary

  • Using cryptographic signatures is frequently the way applications today validate tokens in an OAuth 2 authentication architecture.
  • When we use token validation with cryptographic signatures, JSON Web Token (JWT) is the most widely used token implementation.
  • You can use symmetric keys to sign and validate tokens. Although using symmetric keys is a straightforward approach, you cannot use it when the authorization server doesn’t trust the resource server.
  • If symmetric keys aren’t doable in your implementation, you can implement token signing and validation using asymmetric key pairs.
  • It’s recommended to change keys regularly to make the system less vulnerable to key theft. We refer to changing keys periodically as key rotation.
  • You can configure public keys directly at the resource server side. While this approach is simple, it makes key rotation more difficult.
  • To simplify key rotation, you can configure the keys at the authorization server side and allow the resource server to read them at a specific endpoint.
  • You can customize JWTs by adding details to their body according to the requirements of your implementations. The authorization server adds custom details to the token body, and the resource server uses these details for authorization.
sitemap

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage