Upgrade to Pro — share decks privately, control downloads, hide ads and more …

OAuth 2.0 with Spring Security #jjug_ccc #jjug_ccc_b / oauth2-with-spring-security

Masatoshi Tada
November 07, 2020

OAuth 2.0 with Spring Security #jjug_ccc #jjug_ccc_b / oauth2-with-spring-security

JJUG CCC 2020 Fallでの講演資料です。Spring SecurityのOAuth 2.0機能について解説しています。OAuth 2.0自体は理解している前提の、中級者向け資料です。

Masatoshi Tada

November 07, 2020
Tweet

More Decks by Masatoshi Tada

Other Decks in Technology

Transcript

  1. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ඞཁͳલఏ஌ࣝ ▸ ͜ͷηογϣϯ͸ʲதڃऀ޲͚ʳͰ͢ ▸ ҎԼͷલఏ஌͕ࣝඞཁͰ͢ ▸

    "VUIPSJ[BUJPO$PEF(SBOU'MPXΛઆ໌Ͱ͖Δ ▸ 4QSJOH4FDVSJUZΛ࢖ͬͨ͜ͱ͕͋Δ ▸ 0"VUIػೳͰͳͯ͘΋0, 
  2. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ࣗݾ঺հ ▸ ଟాਅහʢ!TVLF@NBTBʣ ▸ ݚमτϨʔφʔ!ΧαϨΞϧ ▸

    +BWB(PMBOH.JDSPTFSWJDFT ,VCFSOFUFT1ZUIPOػցֶश ▸ 7.XBSFೝఆߨࢣ ▸ ೔ຊ4QSJOHϢʔβձελοϑ 
  3. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 4QSJOHܥίʔε  ▸ جૅ͔Βͷ4QSJOH#PPUʹΑΔ 8FCΞϓϦέʔγϣϯ։ൃʢ೔ؒʣ ▸

    7.XBSF5BO[Vೝఆ4QSJOH$PSF5SBJOJOHʢ೔ؒʣ ▸ 7.XBSF5BO[Vೝఆ4QSJOH$MPVE%FWFMPQFSʢ೔ؒʣ ▸ جૅ͔Βͷ4QSJOH4FDVSJUZʢ೔ؒʣ ▸ جૅ͔Βͷ4QSJOH#BUDIʢ೔ؒʣ
  4. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ໨࣍ ▸ 0"VUIΛβοͱ෮श ▸ ΫϥΠΞϯτͷجຊػೳͱߏ଄ ▸

    ΫϥΠΞϯτͰͷ8FC$MJFOUͷར༻ ▸ Ϧιʔεαʔόʔͷجຊػೳͱߏ଄ ▸ 5PLFO*OUSPTQFDUJPO ▸ 5PLFO1SPQBHBUJPO ▸ ·ͱΊ 
  5. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ໨࣍ ▸ 0"VUIΛβοͱ෮श ▸ ΫϥΠΞϯτͷجຊػೳͱߏ଄ ▸

    ΫϥΠΞϯτͰͷ8FC$MJFOUͷར༻ ▸ Ϧιʔεαʔόʔͷجຊػೳͱߏ଄ ▸ 5PLFO*OUSPTQFDUJPO ▸ 5PLFO1SPQBHBUJPO ▸ ·ͱΊ 
  6. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 0"VUIͱ͸ ▸ ೝՄͷྲྀΕΛنఆͨ͠ϓϩτίϧ ▸ 3'$ ೔ຊޠ൛΋͋Δ

     ▸ 0"VUIͱ͸ผ෺ ▸ 4USVUTͱ4USVUT͘Β͍ҧ͏ ▸ ೝূϓϩτίϧ0QFO*%$POOFDU ͷϕʔεʹͳ͍ͬͯΔ 
  7. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 0"VUIͷొ৔ਓ෺ ᶃ ϦιʔεΦʔφʔ 3FTPVSDF0XOFS  ▸

    ৘ใͷ࣋ͪओɻଟ͘ͷέʔεͰ͸ਓؒɻ ᶄ Ϧιʔεαʔόʔ 3FTPVSDF4FSWFS  ▸ ৘ใΛอ࣋͢Δαʔόʔɻ ᶅ ΫϥΠΞϯτ $MJFOU  ▸ Ϧιʔεαʔόʔ͔Β΋Βͬͨ৘ใΛѻ͏ΞϓϦέʔγϣϯɻ ᶆ ೝՄαʔόʔ "VUIPSJ[BUJPO4FSWFS  ▸ ΞΫηετʔΫϯΛൃߦ͢Δαʔόʔɻ 
  8. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 5XJUUFSͷྫͰొ৔ਓ෺·ͱΊ  twitter.com ͜Μʹͪ͸!
 ָ͠Έͩͳʔ Ϧιʔε


    Φʔφʔ ΫϥΠΞϯτ Ϧιʔεαʔόʔ ೝՄαʔόʔ ೝՄ ΞΫηε
 τʔΫϯ
 ෇༩ ΞΫηε
 τʔΫϯ ͭͿ΍͖ ※ຊ౰͸Twitter͸OAuth 1.0Λ࢖͍ͬͯ·͢
  9. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc άϥϯτλΠϓʢΞΫηετʔΫϯͷऔಘํ๏ʣ ᶃ ೝՄίʔυ ▸ ओʹαʔόʔαΠυ8FCΞϓϦέʔγϣϯ ᶄ

    ΠϯϓϦγοτʢඇਪ঑ʣ ▸ ओʹΫϥΠΞϯταΠυ8FCΞϓϦέʔγϣϯ ᶅ ϦιʔεΦʔφʔύεϫʔυΫϨσϯγϟϧʢඇਪ঑ʣ ▸ ओʹެࣜͷεϚϗΞϓϦͳͲ ᶆ ΫϥΠΞϯτΫϨσϯγϟϧ ▸ ΫϥΠΞϯτࣗ਎ͷ৘ใऔಘ 
  10. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc <ࢀߟ>0"VUIͷ࢓༷ࡦఆ͕ਐߦத ▸ 0"VUIͷ࢓༷ʴͦͷଞΛ࠶੔ཧͨ͠΋ͷ ˠطʹඇਪ঑ͱͳ͍ͬͯΔػೳ͕࡟আ͞ΕΔ ▸ ΠϯϓϦγοτ

    ▸ ϦιʔεΦʔφʔύεϫʔυΫϨσϯγϟϧ ▸ ৄࡉ͸ͪ͜Βͷهࣄ΁ ▸ 0"VUIͷඪ४Խ͕ਐΊΒΕ͍ͯ·͢ IUUQTRJJUBDPNqBOP@ZVLJJUFNTCGBFFFCFFEDD ▸ 4QSJOH4FDVSJUZͷ0"VUIରԠ༧ఆ͸ɺݱ࣌఺Ͱ͸ෆ໌ 
  11. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘ  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β※ ※Webϒϥ΢β͸ɺ࢓༷ॻͰ͸ʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ ᶃॳճΞΫηε
  12. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘ  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β※ ※Webϒϥ΢β͸ɺ࢓༷ॻͰ͸ʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ ᶃॳճΞΫηε ᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτ
  13. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘ  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β※ ※Webϒϥ΢β͸ɺ࢓༷ॻͰ͸ʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ ᶃॳճΞΫηε ᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτ ᶅೝՄը໘
  14. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘ  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β※ ※Webϒϥ΢β͸ɺ࢓༷ॻͰ͸ʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ ᶃॳճΞΫηε ᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτ ᶅೝՄը໘ ᶆೝՄ
  15. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘ  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β※ ※Webϒϥ΢β͸ɺ࢓༷ॻͰ͸ʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ ᶃॳճΞΫηε ᶇೝՄίʔυൃߦʴϦμΠϨΫτ ᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτ ᶅೝՄը໘ ᶆೝՄ
  16. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘ  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β※ ※Webϒϥ΢β͸ɺ࢓༷ॻͰ͸ʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ ᶃॳճΞΫηε ᶇೝՄίʔυൃߦʴϦμΠϨΫτ ᶈೝՄίʔυ ᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτ ᶅೝՄը໘ ᶆೝՄ
  17. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘ  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β※ ※Webϒϥ΢β͸ɺ࢓༷ॻͰ͸ʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ ᶃॳճΞΫηε ᶇೝՄίʔυൃߦʴϦμΠϨΫτ ᶈೝՄίʔυ ᶉΞΫηετʔΫϯ ᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτ ᶅೝՄը໘ ᶆೝՄ
  18. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηε  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β ੥ٻॻ࡞੒
 ࢿྉ༣ૹ Ϧιʔε
 αʔόʔ ᶃϦΫΤετ ᶄϦιʔεʹΞΫηε
 with ΞΫηετʔΫϯ
  19. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηε  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β ੥ٻॻ࡞੒
 ࢿྉ༣ૹ Ϧιʔε
 αʔόʔ ᶃϦΫΤετ ᶅΞΫηε
 ɹτʔΫϯ
 ɹݕূ ᶄϦιʔεʹΞΫηε
 with ΞΫηετʔΫϯ
  20. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηε  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β ੥ٻॻ࡞੒
 ࢿྉ༣ૹ Ϧιʔε
 αʔόʔ ᶃϦΫΤετ ᶅΞΫηε
 ɹτʔΫϯ
 ɹݕূ ᶆݕূ݁ՌΛฦ͢ ᶄϦιʔεʹΞΫηε
 with ΞΫηετʔΫϯ
  21. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηε  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β ੥ٻॻ࡞੒
 ࢿྉ༣ૹ Ϧιʔε
 αʔόʔ ᶃϦΫΤετ ᶅΞΫηε
 ɹτʔΫϯ
 ɹݕূ ᶆݕূ݁ՌΛฦ͢ ᶇݕূ݁ՌΛ
 ɹ֬ೝ ᶄϦιʔεʹΞΫηε
 with ΞΫηετʔΫϯ
  22. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηε  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β ੥ٻॻ࡞੒
 ࢿྉ༣ૹ Ϧιʔε
 αʔόʔ ᶃϦΫΤετ ᶅΞΫηε
 ɹτʔΫϯ
 ɹݕূ ᶆݕূ݁ՌΛฦ͢ ᶈϨεϙϯε ᶇݕূ݁ՌΛ
 ɹ֬ೝ ᶄϦιʔεʹΞΫηε
 with ΞΫηετʔΫϯ
  23. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηε  ೝՄαʔόʔ ΫϥΠΞϯτ Ϧιʔε
 Φʔφʔ

    Web
 ϒϥ΢β ੥ٻॻ࡞੒
 ࢿྉ༣ૹ Ϧιʔε
 αʔόʔ ᶃϦΫΤετ ᶅΞΫηε
 ɹτʔΫϯ
 ɹݕূ ᶆݕূ݁ՌΛฦ͢ ᶈϨεϙϯε ᶉϨεϙϯε ᶇݕূ݁ՌΛ
 ɹ֬ೝ ᶄϦιʔεʹΞΫηε
 with ΞΫηετʔΫϯ
  24. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ໨࣍ ▸ 0"VUIΛβοͱ෮श ▸ ΫϥΠΞϯτͷجຊػೳͱߏ଄ ▸

    ΫϥΠΞϯτͰͷ8FC$MJFOUͷར༻ ▸ Ϧιʔεαʔόʔͷجຊػೳͱߏ଄ ▸ 5PLFO*OUSPTQFDUJPO ▸ 5PLFO1SPQBHBUJPO ▸ ·ͱΊ 
  25. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc BQQMJDBUJPOZNM  spring.security.oauth2.client.registration.todo: provider: ೚ҙͷ໊લ(.registrationͷޙʹ΋ࢦఆ͢Δ) client-id:

    ΫϥΠΞϯτID client-secret: ΫϥΠΞϯτγʔΫϨοτ client-name: ೚ҙͷ໊લ(ը໘දࣔͰ࢖ΘΕΔ) client-authentication-method: ΫϥΠΞϯτೝূํ๏ authorization-grant-type: άϥϯτλΠϓ redirect-uri: ϦμΠϨΫτΤϯυϙΠϯτͷURL scope: ͜ͷΞϓϦͷείʔϓΛΧϯϚ۠੾ΓͰࢦఆ ※εϖʔεͷ౎߹্ͰYAMLܗࣜͰॻ͍͍ͯ·͕͢ɺݸਓతʹ͸properties೿Ͱ͢
  26. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc BQQMJDBUJPOZNMʢଓ͖ʣ  spring.security.oauth2.client.provider.todo: authorization-uri: ೝՄΤϯυϙΠϯτͷURL token-uri:

    τʔΫϯΤϯυϙΠϯτͷURL user-info-uri: Ϣʔβʔ৘ใͷJSON͕ฦͬͯ͘ΔURL user-name-attribute: JSONͷதͷϢʔβʔ໊Λද͢ଐੑ໊ user-info-authentication-method: Ϣʔβʔ৘ใऔಘ࣌ͷೝূํࣜ jwk-set-uri: JWK Set͕ฦͬͯ͘ΔURL issuer-uri: ೝՄαʔόʔͷIssuer Identifier ※Keycloakͷ৔߹ɺissuer-uriΛࢦఆ͢Ε͹ଞͷϓϩύςΟ͸ࢦఆෆཁʢuser-name-attribute͸೚ҙʣ
  27. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc +BWB$POpH  @EnableWebSecurity public class SecurityConfig

    extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.oauth2Login() .loginPage("/login") .permitAll(); ... } } ϩάΠϯʹؔ͢Δઃఆ
  28. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 0"VUI"VUIPSJ[BUJPO3FRVFTU3FEJSFDU'JMUFS ▸ ೝՄ͕ඞཁͳ৔߹ɺೝՄαʔόʔͷ ೝՄΤϯυϙΠϯτʹϦμΠϨΫτ͍ͯ͠Δ  @Override

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request); if (authorizationRequest != null) { this.sendRedirectForAuthorization( request, response, authorizationRequest); return; } ... https://github.com/spring-projects/spring-security/blob/master/oauth2/ oauth2-client/src/main/java/org/springframework/security/oauth2/ client/web/OAuth2AuthorizationRequestRedirectFilter.java#L164
  29. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 0"VUI-PHJO"VUIFOUJDBUJPO'JMUFS಺Ͱͷॲཧ  OAuth2LoginAuthenticationFilter ProviderManager OidcAuthorizationCodeAuthenticationProvider DefaultAuthoriaztionCodeTokenResponseClient

    ᶃೝՄίʔυΛड͚औΔ ᶄೝՄίʔυΛ࣋ͬͨ ɹAuthenticationΛ౉͢ ᶅೝՄίʔυΛ࣋ͬͨ ɹAuthenticationΛ౉͢ ᶆݺͼग़͠ ᶇOAuth2AccessToken ɹResponseΛฦ͢ ᶈ+85͔Β"VUIPSJUZΛநग़ɹ ɹˠAuthenticationΛฦ͢ ᶉAuthenticationΛฦ͢ ᶊSecurityContextʹɹ ɹAuthenticationΛอଘ
  30. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc +BWB$POpHʹઃఆΛ௥Ճ  private DefaultAuthorizationCodeTokenResponseClient client() {

    RestTemplate restTemplate = restTemplateBuilder // λΠϜΞ΢τΛઃఆ .setConnectTimeout(Duration.ofMillis(1000)) .setReadTimeout(Duration.ofMillis(1000)) ... .build(); DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient(); // λΠϜΞ΢τઃఆࡁΈͷRestTemplateΛઃఆ client.setRestOperations(restTemplate); return client; }
  31. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc +BWB$POpHͷઃఆΛมߋ  @Override protected void configure(HttpSecurity

    http) throws Exception { http.oauth2Login(oauth2 -> oauth2 // λΠϜΞ΢τઃఆࡁΈͷTokenResponseClientΛࢦఆ .tokenEndpoint(token -> token .accessTokenResponseClient(client()) ) ... .loginPage("/login") .permitAll() ).authorizeRequests(auth -> auth ... ˞εϥΠυͰ͸εϖʔεͷ౎߹্ׂѪ͍ͯ͠·͕͢ɺOAuth2UserService΍ ɹOidcUserServiceʹ΋ಉ༷ͷઃఆ͕ඞཁͰ͢ʢৄࡉ͸(JU)VCࢀরʣ
  32. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ໨࣍ ▸ 0"VUIΛβοͱ෮श ▸ ΫϥΠΞϯτͷجຊػೳͱߏ଄ ▸

    ΫϥΠΞϯτͰͷ8FC$MJFOUͷར༻ ▸ Ϧιʔεαʔόʔͷجຊػೳͱߏ଄ ▸ 5PLFO*OUSPTQFDUJPO ▸ 5PLFO1SPQBHBUJPO ▸ ·ͱΊ 
  33. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 8FC$MJFOUͱ͸ʁ ▸ 4QSJOH8FC'MVYʹؚ·Ε͍ͯΔɺ ϦΞΫςΟϒͳ)551ΫϥΠΞϯτ ▸ 3FBDUPSͷ'MVY.POPΛ׆༻

    ▸ ྲྀΕΔΑ͏ͳ"1*  public List<Todo> findAll() { return webClient.get() .uri("/todos") .retrieve() .bodyToFlux(Todo.class) .collectList() .block(); }
  34. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 8FC$MJFOUʹҠߦ͢΂͖͔ʁ ▸ ·ͩ࢑͘͸3FTU5FNQMBUFͰΑ͍ ଟాݸਓͷҙݟ  ▸

    ΞϓϦέʔγϣϯ΍4QSJOHຊମͳͲ΁ͷӨڹ͕ඇৗʹେ͖͍ ͨΊɺ3FTU5FNQMBUF͕͙͢ʹ࡟আ͞ΕΔͱ͸ࢥ͑ͳ͍ ▸ 8FC$MJFOUΛ࢖͏ͱɺͲ͏ͯ͠΋'MVY.POPΛ࢖ͬͨ ϦΞΫςΟϒϓϩάϥϛϯάΛҙࣝ͠ͳ͚Ε͹ͳΒͳ͍ͨΊɺ ೉қ౓্͕͕Δ ▸ ͜ͷઅͰ͸͋͘·Ͱʮ8FC$MJFOUΛ࢖͏ͱ͜͏ͳΔΑʯ ͱ͍͏ྫΛ঺հ 
  35. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 8FC$MJFOUΛ4QSJOH.7$্Ͱ࢖͏  <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> ▸ TQSJOHCPPUTUBSUFSXFC .7$ ͱ TQSJOHCPPUTUBSUFSXFCqVY 8FC'MVY ͷ྆ํΛ ґଘੑʹؚΊΔͱɺ4QSJOH.7$͕༏ઌ͞ΕΔ ▸ IUUQTEPDTTQSJOHJPTQSJOHCPPUEPDTDVSSFOUSFGFSFODF IUNMTJOHMFCPPUGFBUVSFTXFCFOWJSPONFOU
  36. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 8FC$MJFOUͷ#FBOఆٛ  @Bean public WebClient webClient(...)

    { // λΠϜΞ΢τΛઃఆ Function<? super TcpClient, ? extends TcpClient> tcpMapper = tcpClient -> { // Connect TimeoutΛઃఆ return tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000) .doOnConnected(conn -> conn // Read TimeoutΛઃఆ .addHandlerLast( new ReadTimeoutHandler(1000, TimeUnit.MILLISECONDS)) // Write TimeoutΛઃఆ .addHandlerLast( new WriteTimeoutHandler(1000, TimeUnit.MILLISECONDS)) ); }; // ࣍ϖʔδʹଓ͘ λΠϜΞ΢τઃఆΛ๨Εͣʹʂ
  37. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 8FC$MJFOUͷ#FBOఆٛʢଓ͖ʣ  // લϖʔδ͔Βͷଓ͖ HttpClient httpClient

    = HttpClient.create().tcpConfiguration(tcpMapper); // OAuth2ؔ࿈ͷઃఆ ServletOAuth2AuthorizedClientExchangeFilterFunction oAuth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction( clientRegistrationRepository, authorizedClientRepository); return builder.baseUrl(resourceServerUri) // ࡞੒ͨ͠HttpClientΛ௥Ճ .clientConnector(new ReactorClientHttpConnector(httpClient)) // OAuth2ઃఆΛ௥Ճ .apply(oAuth2Client.oauth2Configuration()) ... .build(); }
  38. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 4FSWMFU0"VUI"VUIPSJ[FE$MJFOU &YDIBOHF'JMUFS'VODUJPO͸ԿΛͯ͘͠ΕΔ͔ ▸ 8FC$MJFOU͔ΒͷϦΫΤετ࣌ʹ Authorization: Bearer

    ΞΫηετʔΫϯ Λϔομʔʹ௥Ճͯ͘͠ΕΔ ▸ ΞΫηετʔΫϯͷ༗ޮظݶ͕੾Ε͍ͯͨΒ ϦϑϨογϡͯ͘͠ΕΔ 
  39. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc %FGBVMU3FGSFTI5PLFO5PLFO3FTQPOTF$MJFOU ͷ஫ҙ఺ ▸ σϑΥϧτͰ͸ɺ ಺෦Ͱ࢖͍ͬͯΔRestTemplateʹ λΠϜΞ΢τ͕ઃఆ͞Ε͍ͯͳ͍

    ˠԼهͷΑ͏ʹઃఆ  DefaultRefreshTokenTokenResponseClient tokenResponseClient = new DefaultRefreshTokenTokenResponseClient(); RestTemplate restTemplate = restTemplateBuilder .setConnectTimeout(Duration.ofMillis(1000)) .setReadTimeout(Duration.ofMillis(1000)) ... .build(); tokenResponseClient.setRestOperations(restTemplate);
  40. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 4FSWMFU0"VUI"VUIPSJ[FE$MJFOU &YDIBOHF'JMUFS'VODUJPOʹλΠϜΞ΢τΛઃఆ  ▸ ۩ମతͳίʔυ͸ϝνϟ௕͍ͷͰ(JU)VCࢀর ▸

    IUUQTHJUIVCDPN.BTBUPTIJ5BEBPBVUIXJUI TQSJOHTFDVSJUZCMPCNBTUFSDMJFOUKXUTSDNBJO KBWBDPNFYBNQMFDMJFOUKXU $MJFOU+XU"QQMJDBUJPOKBWB-
  41. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ໨࣍ ▸ 0"VUIΛβοͱ෮श ▸ ΫϥΠΞϯτͷجຊػೳͱߏ଄ ▸

    ΫϥΠΞϯτͰͷ8FC$MJFOUͷར༻ ▸ Ϧιʔεαʔόʔͷجຊػೳͱߏ଄ ▸ 5PLFO*OUSPTQFDUJPO ▸ 5PLFO1SPQBHBUJPO ▸ ·ͱΊ 
  42. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ґଘੑ  <dependency> <groupId>org.springframework.boot</groupId> <artifactId> spring-boot-starter-oauth2-resource-server

    </artifactId> </dependency> wTQSJOHTFDVSJUZDPOpH wTQSJOHTFDVSJUZPBVUISFTPVSDFTFSWFS wTQSJOHTFDVSJUZPBVUIKPTF ͳͲؚ͕·Ε͍ͯΔ
  43. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc BQQMJDBUJPOZNM  spring.security.oauth2.resourceserver.jwt: jwk-set-uri: JWK Set͕ฦͬͯ͘ΔURL

    issuer-uri: ೝՄαʔόʔͷIssuer Identifier ※Keycloakͷ৔߹ɺissuer-uriΛࢦఆ͢Ε͹jwk-set-uri͸ࢦఆෆཁ
  44. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc +BWB$POpH  @EnableWebSecurity public class SecurityConfig

    extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { ... http.authorizeRequests() .mvcMatchers("อޢର৅ͷURL") .hasAuthority("SCOPE_είʔϓ໊") ...; http.oauth2ResourceServer() .jwt(); ... } }
  45. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc #FBSFS5PLFO"VUIFOUJDBUJPO'JMUFS಺Ͱͷॲཧ  BearerTokenAuthenticationFilter ProviderManager JwtAuthenticationProvider NimbusJwtDecoder

    ᶃϦΫΤετϔομʔͰ ɹ+85จࣈྻΛड͚औΔ ᶄ+85จࣈྻΛ࣋ͬͨ ɹ"VUIFOUJDBUJPOΛ౉͢ ᶅ+85จࣈྻΛ࣋ͬͨ ɹ"VUIFOUJDBUJPOΛ౉͢ ᶆ+85จࣈྻΛ౉͢ ᶇ+85จࣈྻΛݕূޙɺ ɹ+XUΦϒδΣΫτʹม׵ ᶈ+XU͔Β"VUIPSJUZΛநग़ɹ ɹˠ"VUIFOUJDBUJPOΛฦ͢ ᶉ"VUIFOUJDBUJPOΛฦ͢ ᶊ4FDVSJUZ$POUFYUʹɹ ɹ"VUIFOUJDBUJPOΛอଘ ݕূࣦഊ࣌͸ ྫ֎ൃੜ
  46. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ໨࣍ ▸ 0"VUIΛβοͱ෮श ▸ ΫϥΠΞϯτͷجຊػೳͱߏ଄ ▸

    ΫϥΠΞϯτͰͷ8FC$MJFOUͷར༻ ▸ Ϧιʔεαʔόʔͷجຊػೳͱߏ଄ ▸ 5PLFO*OUSPTQFDUJPO ▸ 5PLFO1SPQBHBUJPO ▸ ·ͱΊ 
  47. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ͦ͜Ͱ5PLFO*OUSPTQFDUJPOʂ ▸ 3'$ ▸ IUUQTUPPMTJFUGPSHIUNMSGD ▸

    Ϧιʔεαʔόʔ͕ೝՄαʔόʔʹ ΞΫηετʔΫϯΛૹ৴͢Δ͜ͱͰɺ ᶃΞΫηετʔΫϯࣗମʹؚ·Ε͍ͯͳ͍৘ใΛ औಘͰ͖Δ ᶄΞΫηετʔΫϯͷ༗ޮੑΛνΣοΫͰ͖Δ 
  48. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ϦΫΤετ  ੥ٻॻ࡞੒
 ࢿྉ༣ૹ Ϧιʔε αʔόʔ

    ೝՄ αʔόʔ POST /introspect HTTP/1.1 Host: keycloak.example.com Accept: application/json Content-Type: application/x-www-form-urlencoded Authorization: Basic dXNlcjpwYXNzd29yZAo= token=ΞΫηετʔΫϯ
  49. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc Ϩεϙϯε  ੥ٻॻ࡞੒
 ࢿྉ༣ૹ Ϧιʔε αʔόʔ

    ೝՄ αʔόʔ HTTP/1.1 200 OK Content-Type: application/json { "active": true·ͨ͸false, "client_id": "ΫϥΠΞϯτͷID", "username": "ϦιʔεΦʔφʔͷϢʔβʔ໊", "scope": "είʔϓ", ... }
  50. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc BQQMJDBUJPOZNM  spring.security.oauth2.resourceserver.opaquetoken: client-id: ϦιʔεαʔόʔͷID client-secret:

    Ϧιʔεαʔόʔͷύεϫʔυ introspection-uri: ೝՄαʔόʔͷ ɹɹɹɹɹɹɹɹɹɹɹɹ ΠϯτϩεϖΫγϣϯΤϯυϙΠϯτ
  51. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc +BWB$POpH  @EnableWebSecurity public class SecurityConfig

    extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.oauth2ResourceServer() .opaqueToken(); } }
  52. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc #FBSFS5PLFO"VUIFOUJDBUJPO'JMUFS಺Ͱͷॲཧ  BearerTokenAuthenticationFilter ProviderManager OpaqueTokenAuthenticationProvider NimbusOpaqueTokenIntrospector

    ᶃϦΫΤετϔομʔͰ ɹΞΫηετʔΫϯΛड͚औΔ ᶄΞΫηετʔΫϯΛ࣋ͬͨ ɹ"VUIFOUJDBUJPOΛ౉͢ ᶅΞΫηετʔΫϯΛ࣋ͬͨ ɹ"VUIFOUJDBUJPOΛ౉͢ ᶆΞΫηετʔΫϯΛ౉͢ ᶇ5PLFO*OUSPTQFDUJPO࣮ߦ ɹˠ0"VUI"VUIFOUJDBUJPO ɹɹ1SJODJQBMΛฦ͢ ᶈ"VUIFOUJDBUJPOΛฦ͢ ᶉ"VUIFOUJDBUJPOΛฦ͢ ᶊ4FDVSJUZ$POUFYUʹɹ ɹ"VUIFOUJDBUJPOΛอଘ ݕূࣦഊ࣌͸ ྫ֎ൃੜ
  53. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ೝূํ๏ͷΧελϚΠζ  @EnableWebSecurity public class SecurityConfig

    extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.oauth2ResourceServer() .opaqueToken() .introspector(new MyIntrospector()); } } public class MyIntrospector implements OpaqueTokenIntrospector { ... }
  54. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ΧελϚΠζྫ  @Configuration public class IntrospectorConfig

    { @Bean public NimbusOpaqueTokenIntrospector introspector( RestTemplateBuilder builder, OAuth2ResourceServerProperties prop) { OAuth2ResourceServerProperties.Opaquetoken opaquetoken = prop.getOpaquetoken(); RestTemplate rest = builder .setReadTimeout(Duration.ofMillis(1000)) .setConnectTimeout(Duration.ofMillis(1000)) .basicAuthentication(opaquetoken.getClientId(), opaquetoken.getClientSecret()) .build(); return new NimbusOpaqueTokenIntrospector( opaquetoken.getOpaquetoken().getIntrospectionUri(), rest); } } λΠϜΞ΢τઃఆࡁΈͷ RestTemplateΛར༻͢Δ NimbusOpaqueToken IntrospectorΛ#FBOఆٛ
  55. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ΧελϚΠζྫ  @EnableWebSecurity public class SecurityConfig

    extends WebSecurityConfigurerAdapter { @Autowired OpaqueTokenIntrospector introspector; @Override protected void configure(HttpSecurity http) throws Exception { http.oauth2ResourceServer() .opaqueToken() .introspector(introspector); } ΧελϚΠζͨ͠ NimbusOpaqueTokenIntrospector ͷ#FBOΛࢦఆ
  56. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ໨࣍ ▸ 0"VUIΛβοͱ෮श ▸ ΫϥΠΞϯτͷجຊػೳͱߏ଄ ▸

    ΫϥΠΞϯτͰͷ8FC$MJFOUͷར༻ ▸ Ϧιʔεαʔόʔͷجຊػೳͱߏ଄ ▸ 5PLFO*OUSPTQFDUJPO ▸ 5PLFO1SPQBHBUJPO ▸ ·ͱΊ 
  57. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 5PLFO1SPQBHBUJPOͷઃఆ ▸ WebClientʹ ServletBearerExchangeFilterFunction Λ௥Ճ 

    @Bean public WebClient webClient(...) { ... return builder.baseUrl(resourceServerUri) .clientConnector(new ReactorClientHttpConnector(httpClient)) .filter(new ServletBearerExchangeFilterFunction()) ... .build(); }
  58. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc 5PLFO1SPQBHBUJPOͷ࢓૊Έ ▸ ϦΫΤετૹ৴લʹ ᶃ "VUIFOUJDBUJPO͔ΒΞΫηετʔΫϯΛऔಘ ᶄ

    "VUIPSJ[BUJPOϔομʔʹΞΫηετʔΫϯΛ௥Ճ ‣ ৄࡉ͸͜͜Β΁Μͷίʔυࢀর ‣ IUUQTHJUIVCDPNTQSJOHQSPKFDUTTQSJOHTFDVSJUZCMPC NBTUFSPBVUIPBVUISFTPVSDFTFSWFSTSDNBJOKBWBPSH TQSJOHGSBNFXPSLTFDVSJUZPBVUITFSWFSSFTPVSDFXFC SFBDUJWFGVODUJPODMJFOU 4FSWMFU#FBSFS&YDIBOHF'JMUFS'VODUJPOKBWB- 
  59. $ $"4"3&"- *OD"MMSJHIUTSFTFSWFE #jjug_ccc ໨࣍ ▸ 0"VUIΛβοͱ෮श ▸ ΫϥΠΞϯτͷجຊػೳͱߏ଄ ▸

    ΫϥΠΞϯτͰͷ8FC$MJFOUͷར༻ ▸ Ϧιʔεαʔόʔͷجຊػೳͱߏ଄ ▸ 5PLFO*OUSPTQFDUJPO ▸ 5PLFO1SPQBHBUJPO ▸ ·ͱΊ