JJUG CCC 2020 Fallでの講演資料です。Spring SecurityのOAuth 2.0機能について解説しています。OAuth 2.0自体は理解している前提の、中級者向け資料です。
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc0"VUIXJUI4QSJOH4FDVSJUZגΧαϨΞϧଟాਅහ݄++6($$$'BMM
View Slide
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc͜ͷηογϣϯʹ͍ͭͯ▸ 4QSJOH4FDVSJUZͷ0"VUIؔ࿈ػೳΛɺΈؚΊ͔ͯΓ͘͢ղઆ͠·͢▸ αϯϓϧίʔυ▸ IUUQTHJUIVCDPN.BTBUPTIJ5BEBPBVUIXJUITQSJOHTFDVSJUZ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccඞཁͳલఏࣝ▸ ͜ͷηογϣϯʲதڃऀ͚ʳͰ͢▸ ҎԼͷલఏ͕ࣝඞཁͰ͢▸ "VUIPSJ[BUJPO$PEF(SBOU'MPXΛઆ໌Ͱ͖Δ▸ 4QSJOH4FDVSJUZΛͬͨ͜ͱ͕͋Δ▸ 0"VUIػೳͰͳͯ͘0,
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccઌʹ݁▸ 4QSJOH4FDVSJUZ෦ͷRestTemplateʹɺλΠϜΞτ͕ઃఆ͞Ε͍ͯͳ͍͜ͱ͕΄ͱΜͲˠͪΌΜͱλΠϜΞτΛઃఆ͠·͠ΐ͏
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc0"VUIͷجૅΛΓ͍ͨํʜhttps://www.slideshare.net/masatoshitada7/oauth-20spring-security-51-121418814
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc4QSJOH4FDVSJUZͷΞʔΩςΫνϟΛΓ͍ͨํʜhttps://www.slideshare.net/masatoshitada7/spring-security-meetup
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccࣗݾհ▸ ଟాਅහʢ[email protected]ʣ▸ ݚमτϨʔφʔ!ΧαϨΞϧ▸ +BWB(PMBOH.JDSPTFSWJDFT,VCFSOFUFT1ZUIPOػցֶश▸ 7.XBSFೝఆߨࢣ▸ ຊ4QSJOHϢʔβձελοϑ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccઌʹ͓ͼ͓͖ͯ͠·͢▸ ່ࢯʢϲ݄ʣͷ͕ͱ͜ΖͲ͜ΖೖΔ͔͠Ε·ͤΜ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccגࣜձࣾΧαϨΞϧ▸ ଞࣾʹແ͍৭ʑͳϓϩάϥϛϯάݴޠͷݚमΛఏڙ͍ͯ͠·͢ʂ/&8
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc4QSJOHܥίʔε▸ جૅ͔Βͷ4QSJOH#PPUʹΑΔ8FCΞϓϦέʔγϣϯ։ൃʢؒʣ▸ 7.XBSF5BO[Vೝఆ4QSJOH$PSF5SBJOJOHʢؒʣ▸ 7.XBSF5BO[Vೝఆ4QSJOH$MPVE%FWFMPQFSʢؒʣ▸ جૅ͔Βͷ4QSJOH4FDVSJUZʢؒʣ▸ جૅ͔Βͷ4QSJOH#BUDIʢؒʣ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc৽نίʔεϦϦʔε(PݴޠʹΑΔΫϥυωΠςΟϒΞϓϦέʔγϣϯ։ൃجຊจ๏ɺ8FCΞϓϦέʔγϣϯɺϚΠΫϩαʔϏε1ZUIPOೖجຊจ๏ɺϥΠϒϥϦͷར༻ɺ8FCεΫϨΠϐϯά1ZUIPOʹΑΔػցֶशೖʢԾʣ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΦϯϥΠϯݚमɺ͡Ί·ͨ͠ߨࢣडߨऀ༷▸ ʮֶशޮՌ௨ৗͱมΘΒͳ͍ʯͱධͰ͢ʂ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc࣍▸ 0"VUIΛβοͱ෮श▸ ΫϥΠΞϯτͷجຊػೳͱߏ▸ ΫϥΠΞϯτͰͷ8FC$MJFOUͷར༻▸ Ϧιʔεαʔόʔͷجຊػೳͱߏ▸ 5PLFO*OUSPTQFDUJPO▸ 5PLFO1SPQBHBUJPO▸ ·ͱΊ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc0"VUIͱ▸ ೝՄͷྲྀΕΛنఆͨ͠ϓϩτίϧ▸ 3'$ ຊޠ൛͋Δ▸ 0"VUIͱผ▸ 4USVUTͱ4USVUT͘Β͍ҧ͏▸ ೝূϓϩτίϧ0QFO*%$POOFDUͷϕʔεʹͳ͍ͬͯΔ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc0"VUIͷొਓᶃ ϦιʔεΦʔφʔ 3FTPVSDF0XOFS▸ ใͷ࣋ͪओɻଟ͘ͷέʔεͰਓؒɻᶄ Ϧιʔεαʔόʔ 3FTPVSDF4FSWFS▸ ใΛอ࣋͢Δαʔόʔɻᶅ ΫϥΠΞϯτ $MJFOU▸ Ϧιʔεαʔόʔ͔ΒΒͬͨใΛѻ͏ΞϓϦέʔγϣϯɻᶆ ೝՄαʔόʔ "VUIPSJ[BUJPO4FSWFS▸ ΞΫηετʔΫϯΛൃߦ͢Δαʔόʔɻ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc5XJUUFSͷྫͰొਓ·ͱΊtwitter.com͜Μʹͪ! ָ͠ΈͩͳʔϦιʔε ΦʔφʔΫϥΠΞϯτϦιʔεαʔόʔೝՄαʔόʔೝՄΞΫηε τʔΫϯ ༩ΞΫηε τʔΫϯͭͿ͖※ຊTwitterOAuth 1.0Λ͍ͬͯ·͢
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccάϥϯτλΠϓʢΞΫηετʔΫϯͷऔಘํ๏ʣᶃ ೝՄίʔυ▸ ओʹαʔόʔαΠυ8FCΞϓϦέʔγϣϯᶄ ΠϯϓϦγοτʢඇਪʣ▸ ओʹΫϥΠΞϯταΠυ8FCΞϓϦέʔγϣϯᶅ ϦιʔεΦʔφʔύεϫʔυΫϨσϯγϟϧʢඇਪʣ▸ ओʹެࣜͷεϚϗΞϓϦͳͲᶆ ΫϥΠΞϯτΫϨσϯγϟϧ▸ ΫϥΠΞϯτࣗͷใऔಘ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc<ࢀߟ>0"VUIͷ༷ࡦఆ͕ਐߦத▸ 0"VUIͷ༷ʴͦͷଞΛ࠶ཧͨ͠ͷˠطʹඇਪͱͳ͍ͬͯΔػೳ͕আ͞ΕΔ▸ ΠϯϓϦγοτ▸ ϦιʔεΦʔφʔύεϫʔυΫϨσϯγϟϧ▸ ৄࡉͪ͜Βͷهࣄ▸ 0"VUIͷඪ४Խ͕ਐΊΒΕ͍ͯ·͢[email protected]▸ 4QSJOH4FDVSJUZͷ0"VUIରԠ༧ఆɺݱ࣌Ͱෆ໌
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβ※※Webϒϥβɺ༷ॻͰʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ᶃॳճΞΫηε
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβ※※Webϒϥβɺ༷ॻͰʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ᶃॳճΞΫηεᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβ※※Webϒϥβɺ༷ॻͰʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ᶃॳճΞΫηεᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτᶅೝՄը໘
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβ※※Webϒϥβɺ༷ॻͰʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ᶃॳճΞΫηεᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτᶅೝՄը໘ᶆೝՄ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβ※※Webϒϥβɺ༷ॻͰʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ᶃॳճΞΫηεᶇೝՄίʔυൃߦʴϦμΠϨΫτᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτᶅೝՄը໘ᶆೝՄ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβ※※Webϒϥβɺ༷ॻͰʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ᶃॳճΞΫηεᶇೝՄίʔυൃߦʴϦμΠϨΫτᶈೝՄίʔυᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτᶅೝՄը໘ᶆೝՄ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccೝՄίʔυʹΑΔΞΫηετʔΫϯऔಘೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβ※※Webϒϥβɺ༷ॻͰʮϢʔβʔΤʔδΣϯτʯͱهࡌ͞Ε͍ͯ·͢ᶃॳճΞΫηεᶇೝՄίʔυൃߦʴϦμΠϨΫτᶈೝՄίʔυᶉΞΫηετʔΫϯᶄೝՄΤϯυϙΠϯτʹϦμΠϨΫτᶅೝՄը໘ᶆೝՄ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηεೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβٻॻ࡞ ࢿྉ༣ૹϦιʔε αʔόʔᶃϦΫΤετ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηεೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβٻॻ࡞ ࢿྉ༣ૹϦιʔε αʔόʔᶃϦΫΤετᶄϦιʔεʹΞΫηε with ΞΫηετʔΫϯ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηεೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβٻॻ࡞ ࢿྉ༣ૹϦιʔε αʔόʔᶃϦΫΤετᶅΞΫηε ɹτʔΫϯ ɹݕূᶄϦιʔεʹΞΫηε with ΞΫηετʔΫϯ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηεೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβٻॻ࡞ ࢿྉ༣ૹϦιʔε αʔόʔᶃϦΫΤετᶅΞΫηε ɹτʔΫϯ ɹݕূᶆݕূ݁ՌΛฦ͢ᶄϦιʔεʹΞΫηε with ΞΫηετʔΫϯ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηεೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβٻॻ࡞ ࢿྉ༣ૹϦιʔε αʔόʔᶃϦΫΤετᶅΞΫηε ɹτʔΫϯ ɹݕূᶆݕূ݁ՌΛฦ͢ᶇݕূ݁ՌΛ ɹ֬ೝᶄϦιʔεʹΞΫηε with ΞΫηετʔΫϯ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηεೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβٻॻ࡞ ࢿྉ༣ૹϦιʔε αʔόʔᶃϦΫΤετᶅΞΫηε ɹτʔΫϯ ɹݕূᶆݕূ݁ՌΛฦ͢ᶈϨεϙϯεᶇݕূ݁ՌΛ ɹ֬ೝᶄϦιʔεʹΞΫηε with ΞΫηετʔΫϯ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΞΫηετʔΫϯΛར༻ͨ͠ϦιʔεΞΫηεೝՄαʔόʔΫϥΠΞϯτϦιʔε ΦʔφʔWeb ϒϥβٻॻ࡞ ࢿྉ༣ૹϦιʔε αʔόʔᶃϦΫΤετᶅΞΫηε ɹτʔΫϯ ɹݕূᶆݕূ݁ՌΛฦ͢ᶈϨεϙϯεᶉϨεϙϯεᶇݕূ݁ՌΛ ɹ֬ೝᶄϦιʔεʹΞΫηε with ΞΫηετʔΫϯ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccґଘੑorg.springframework.bootspring-boot-starter-oauth2-clientwTQSJOHTFDVSJUZDPOpHwTQSJOHTFDVSJUZPBVUIDMJFOUͳͲؚ͕·Ε͍ͯΔ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccBQQMJDBUJPOZNMspring.security.oauth2.client.registration.todo:provider: ҙͷ໊લ(.registrationͷޙʹࢦఆ͢Δ)client-id: ΫϥΠΞϯτIDclient-secret: ΫϥΠΞϯτγʔΫϨοτclient-name: ҙͷ໊લ(ը໘දࣔͰΘΕΔ)client-authentication-method: ΫϥΠΞϯτೝূํ๏authorization-grant-type: άϥϯτλΠϓredirect-uri: ϦμΠϨΫτΤϯυϙΠϯτͷURLscope: ͜ͷΞϓϦͷείʔϓΛΧϯϚ۠ΓͰࢦఆ※εϖʔεͷ߹্ͰYAMLܗࣜͰॻ͍͍ͯ·͕͢ɺݸਓతʹpropertiesͰ͢
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccBQQMJDBUJPOZNMʢଓ͖ʣspring.security.oauth2.client.provider.todo:authorization-uri: ೝՄΤϯυϙΠϯτͷURLtoken-uri: τʔΫϯΤϯυϙΠϯτͷURLuser-info-uri: ϢʔβʔใͷJSON͕ฦͬͯ͘ΔURLuser-name-attribute: JSONͷதͷϢʔβʔ໊Λද͢ଐੑ໊user-info-authentication-method: Ϣʔβʔใऔಘ࣌ͷೝূํࣜjwk-set-uri: JWK Set͕ฦͬͯ͘ΔURLissuer-uri: ೝՄαʔόʔͷIssuer Identifier※Keycloakͷ߹ɺissuer-uriΛࢦఆ͢ΕଞͷϓϩύςΟࢦఆෆཁʢuser-name-attributeҙʣ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc+BWB$POpH@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.oauth2Login().loginPage("/login").permitAll();...}}ϩάΠϯʹؔ͢Δઃఆ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc4QSJOH4FDVSJUZ'JMUFS$IBJOSecurityContextPersistenceFilterLogoutFilterOAuth2AuthorizationRequestRedirectFilterOAuth2LoginAuthenticationFilterExceptionTranslationFilterFilterSecurityInterceptorϦΫΤετೝՄΤϯυϙΠϯτʹϦμΠϨΫτ͢ΔೝূΛߦ͏
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc0"VUI"VUIPSJ[BUJPO3FRVFTU3FEJSFDU'JMUFS▸ ೝՄ͕ඞཁͳ߹ɺೝՄαʔόʔͷೝՄΤϯυϙΠϯτʹϦμΠϨΫτ͍ͯ͠Δ@Overrideprotected 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
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc0"VUI-PHJO"VUIFOUJDBUJPO'JMUFSͰͷॲཧOAuth2LoginAuthenticationFilterProviderManagerOidcAuthorizationCodeAuthenticationProviderDefaultAuthoriaztionCodeTokenResponseClientᶃೝՄίʔυΛड͚औΔᶄೝՄίʔυΛ࣋ͬͨɹAuthenticationΛ͢ᶅೝՄίʔυΛ࣋ͬͨɹAuthenticationΛ͢ᶆݺͼग़͠ᶇOAuth2AccessTokenɹResponseΛฦ͢ᶈ+85͔Β"VUIPSJUZΛநग़ɹɹˠAuthenticationΛฦ͢ᶉAuthenticationΛฦ͢ᶊSecurityContextʹɹɹAuthenticationΛอଘ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc%FGBVMU"VUIPSJ[BUJPO$PEF5PLFO3FTQPOTF$MJFOU▸ RestTemplateͰೝՄαʔόʔʹΞΫηεͯ͠ɺΞΫηετʔΫϯͷऔಘΛߦ͏▸ ͜ͷRestTemplateʹɺσϑΥϧτͰλΠϜΞτ͕ઃఆ͞Ε͍ͯͳ͍
$$"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;}
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc+BWB$POpHͷઃఆΛมߋ@Overrideprotected 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ࢀরʣ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc8FC$MJFOUͱʁ▸ 4QSJOH8FC'MVYʹؚ·Ε͍ͯΔɺϦΞΫςΟϒͳ)551ΫϥΠΞϯτ▸ 3FBDUPSͷ'MVY.POPΛ׆༻▸ ྲྀΕΔΑ͏ͳ"1*public List findAll() {return webClient.get().uri("/todos").retrieve().bodyToFlux(Todo.class).collectList().block();}
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc3FTU5FNQMBUFͷ+BWBEPDΑΓhttps://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.htmlɾҎ߱ͰϝϯςφϯεϞʔυͰɾΘΓʹ8FC$MJFOUͷར༻Λݕ౼ͯ͠
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc8FC$MJFOUʹҠߦ͖͔͢ʁ▸ ·ͩ͘3FTU5FNQMBUFͰΑ͍ ଟాݸਓͷҙݟ▸ ΞϓϦέʔγϣϯ4QSJOHຊମͳͲͷӨڹ͕ඇৗʹେ͖͍ͨΊɺ3FTU5FNQMBUF͕͙͢ʹআ͞ΕΔͱࢥ͑ͳ͍▸ 8FC$MJFOUΛ͏ͱɺͲ͏ͯ͠'MVY.POPΛͬͨϦΞΫςΟϒϓϩάϥϛϯάΛҙࣝ͠ͳ͚ΕͳΒͳ͍ͨΊɺқ্͕͕Δ▸ ͜ͷઅͰ͋͘·Ͱʮ8FC$MJFOUΛ͏ͱ͜͏ͳΔΑʯͱ͍͏ྫΛհ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc8FC$MJFOUΛ4QSJOH.7$্Ͱ͏org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-webflux▸ TQSJOHCPPUTUBSUFSXFC .7$ͱTQSJOHCPPUTUBSUFSXFCqVY 8FC'MVYͷ྆ํΛґଘੑʹؚΊΔͱɺ4QSJOH.7$͕༏ઌ͞ΕΔ▸ IUUQTEPDTTQSJOHJPTQSJOHCPPUEPDTDVSSFOUSFGFSFODFIUNMTJOHMFCPPUGFBUVSFTXFCFOWJSPONFOU
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc8FC$MJFOUͷ#FBOఆٛ@Beanpublic 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)));};// ࣍ϖʔδʹଓ͘λΠϜΞτઃఆΛΕͣʹʂ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc8FC$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();}
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc4FSWMFU0"VUI"VUIPSJ[FE$MJFOU&YDIBOHF'JMUFS'VODUJPOԿΛͯ͘͠ΕΔ͔▸ 8FC$MJFOU͔ΒͷϦΫΤετ࣌ʹAuthorization: Bearer ΞΫηετʔΫϯΛϔομʔʹՃͯ͘͠ΕΔ▸ ΞΫηετʔΫϯͷ༗ޮظݶ͕Ε͍ͯͨΒϦϑϨογϡͯ͘͠ΕΔ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccϦϑϨογϡͷΈServletOAuth2AuthorizedClientExchangeFilterFunctionDefaultOAuth2AuthorizedClientManagerDelegatingOAuth2AuthorizedClientProviderRefreshTokenOAuth2AuthorizedClientProviderDefaultRefreshTokenTokenResponseClientݺͼग़͠ݺͼग़͠ݺͼग़͠ݺͼग़͠
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc%FGBVMU3FGSFTI5PLFO5PLFO3FTQPOTF$MJFOU▸ 3FTU5FNQMBUFͰೝՄαʔόʔʹΞΫηεͯ͠ɺΞΫηετʔΫϯͷϦϑϨογϡΛߦ͏▸ ৄࡉԼهͷهࣄࢀর▸ 4QSJOH4FDVSJUZYͰ0"VUIΞΫηετʔΫϯΛϦϑϨογϡ͢Δ[email protected]GCGDFCF
$$"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);
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc4FSWMFU0"VUI"VUIPSJ[FE$MJFOU&YDIBOHF'JMUFS'VODUJPOʹλΠϜΞτΛઃఆServletOAuth2AuthorizedClientExchangeFilterFunctionDefaultOAuth2AuthorizedClientManagerOAuth2AuthorizedClientProviderRestTemplateʢλΠϜΞτઃఆࡁΈʣೖೖೖೖDefaultRefreshTokenTokenResponseClient
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc4FSWMFU0"VUI"VUIPSJ[FE$MJFOU&YDIBOHF'JMUFS'VODUJPOʹλΠϜΞτΛઃఆ▸ ۩ମతͳίʔυϝνϟ͍ͷͰ(JU)VCࢀর▸ IUUQTHJUIVCDPN.BTBUPTIJ5BEBPBVUIXJUITQSJOHTFDVSJUZCMPCNBTUFSDMJFOUKXUTSDNBJOKBWBDPNFYBNQMFDMJFOUKXU$MJFOU+XU"QQMJDBUJPOKBWB-
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc*TTVFཱͯ·ͨ͠▸ ੋඇʮ͍͍Ͷʯ͍ͩ͘͞▸ IUUQTHJUIVCDPNTQSJOHQSPKFDUTTQSJOHTFDVSJUZJTTVFT
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccґଘੑorg.springframework.bootspring-boot-starter-oauth2-resource-serverwTQSJOHTFDVSJUZDPOpHwTQSJOHTFDVSJUZPBVUISFTPVSDFTFSWFSwTQSJOHTFDVSJUZPBVUIKPTFͳͲؚ͕·Ε͍ͯΔ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccBQQMJDBUJPOZNMspring.security.oauth2.resourceserver.jwt:jwk-set-uri: JWK Set͕ฦͬͯ͘ΔURLissuer-uri: ೝՄαʔόʔͷIssuer Identifier※Keycloakͷ߹ɺissuer-uriΛࢦఆ͢Εjwk-set-uriࢦఆෆཁ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc+BWB$POpH@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {...http.authorizeRequests().mvcMatchers("อޢରͷURL").hasAuthority("SCOPE_είʔϓ໊")...;http.oauth2ResourceServer().jwt();...}}
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc4QSJOH4FDVSJUZ'JMUFS$IBJOSecurityContextPersistenceFilterLogoutFilterBearerTokenAuthenticationFilterExceptionTranslationFilterFilterSecurityInterceptorϦΫΤετड৴ͨ͠+85Λͬͯೝূ͢Δ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc#FBSFS5PLFO"VUIFOUJDBUJPO'JMUFSͰͷॲཧBearerTokenAuthenticationFilterProviderManagerJwtAuthenticationProviderNimbusJwtDecoderᶃϦΫΤετϔομʔͰɹ+85จࣈྻΛड͚औΔᶄ+85จࣈྻΛ࣋ͬͨɹ"VUIFOUJDBUJPOΛ͢ᶅ+85จࣈྻΛ࣋ͬͨɹ"VUIFOUJDBUJPOΛ͢ᶆ+85จࣈྻΛ͢ᶇ+85จࣈྻΛݕূޙɺɹ+XUΦϒδΣΫτʹมᶈ+XU͔Β"VUIPSJUZΛநग़ɹɹˠ"VUIFOUJDBUJPOΛฦ͢ᶉ"VUIFOUJDBUJPOΛฦ͢ᶊ4FDVSJUZ$POUFYUʹɹɹ"VUIFOUJDBUJPOΛอଘݕূࣦഊ࣌ྫ֎ൃੜ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc+85ܗࣜΞΫηετʔΫϯͷ▸ +85ʹؚ·Ε͍ͯͳ͍ใऔಘͰ͖ͳ͍▸ +85ʹؚΉใ͕ଟ͗͢ΔͱτʔΫϯࣗମ͕େ͖͘ͳͬͯ͠·͏▸ ೝՄαʔόʔଆͰΞΫηετʔΫϯΛແޮԽͰ͖ͳ͍
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccͦ͜Ͱ5PLFO*OUSPTQFDUJPOʂ▸ 3'$▸ IUUQTUPPMTJFUGPSHIUNMSGD▸ Ϧιʔεαʔόʔ͕ೝՄαʔόʔʹΞΫηετʔΫϯΛૹ৴͢Δ͜ͱͰɺᶃΞΫηετʔΫϯࣗମʹؚ·Ε͍ͯͳ͍ใΛऔಘͰ͖ΔᶄΞΫηετʔΫϯͷ༗ޮੑΛνΣοΫͰ͖Δ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccϦΫΤετٻॻ࡞ ࢿྉ༣ૹϦιʔεαʔόʔೝՄαʔόʔPOST /introspect HTTP/1.1Host: keycloak.example.comAccept: application/jsonContent-Type: application/x-www-form-urlencodedAuthorization: Basic dXNlcjpwYXNzd29yZAo=token=ΞΫηετʔΫϯ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccϨεϙϯεٻॻ࡞ ࢿྉ༣ૹϦιʔεαʔόʔೝՄαʔόʔHTTP/1.1 200 OKContent-Type: application/json{"active": true·ͨfalse,"client_id": "ΫϥΠΞϯτͷID","username": "ϦιʔεΦʔφʔͷϢʔβʔ໊","scope": "είʔϓ",...}
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccϦιʔεαʔόʔͷೝূํ๏▸ ۩ମతͳํ๏ɺ༷Ͱఆ·͍ͬͯͳ͍▸ 4QSJOH4FDVSJUZͷσϑΥϧτ#BTJDೝূ▸ NimbusOpaqueTokenIntospector͕RestTemplateΛ࣮ͬͯߦ▸ ଞͷํ๏ʹΧελϚΠζՄೳʢޙड़ʣ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccBQQMJDBUJPOZNMspring.security.oauth2.resourceserver.opaquetoken:client-id: ϦιʔεαʔόʔͷIDclient-secret: Ϧιʔεαʔόʔͷύεϫʔυintrospection-uri: ೝՄαʔόʔͷɹɹɹɹɹɹɹɹɹɹɹɹ ΠϯτϩεϖΫγϣϯΤϯυϙΠϯτ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc+BWB$POpH@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.oauth2ResourceServer().opaqueToken();}}
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc4QSJOH4FDVSJUZ'JMUFS$IBJOSecurityContextPersistenceFilterLogoutFilterBearerTokenAuthenticationFilterExceptionTranslationFilterFilterSecurityInterceptorϦΫΤετड৴ͨ͠ΞΫηετʔΫϯΛͬͯೝূ͢Δ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc#FBSFS5PLFO"VUIFOUJDBUJPO'JMUFSͰͷॲཧBearerTokenAuthenticationFilterProviderManagerOpaqueTokenAuthenticationProviderNimbusOpaqueTokenIntrospectorᶃϦΫΤετϔομʔͰɹΞΫηετʔΫϯΛड͚औΔᶄΞΫηετʔΫϯΛ࣋ͬͨɹ"VUIFOUJDBUJPOΛ͢ᶅΞΫηετʔΫϯΛ࣋ͬͨɹ"VUIFOUJDBUJPOΛ͢ᶆΞΫηετʔΫϯΛ͢ᶇ5PLFO*OUSPTQFDUJPO࣮ߦɹˠ0"VUI"VUIFOUJDBUJPOɹɹ1SJODJQBMΛฦ͢ᶈ"VUIFOUJDBUJPOΛฦ͢ᶉ"VUIFOUJDBUJPOΛฦ͢ᶊ4FDVSJUZ$POUFYUʹɹɹ"VUIFOUJDBUJPOΛอଘݕূࣦഊ࣌ྫ֎ൃੜ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccೝূํ๏ͷΧελϚΠζ@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.oauth2ResourceServer().opaqueToken().introspector(new MyIntrospector());}}public class MyIntrospectorimplements OpaqueTokenIntrospector {...}
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΧελϚΠζྫ▸ NimbusOpaqueTokenIntospectorͷRestTemplateʹλΠϜΞτΛઃఆ▸ σϑΥϧτͰλΠϜΞτແ͠ͳͷͰɺઃఆͨ͠΄͏͕Α͍
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΧελϚΠζྫ@Configurationpublic class IntrospectorConfig {@Beanpublic 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Λར༻͢ΔNimbusOpaqueTokenIntrospectorΛ#FBOఆٛ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccΧελϚΠζྫ@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredOpaqueTokenIntrospector introspector;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.oauth2ResourceServer().opaqueToken().introspector(introspector);}ΧελϚΠζͨ͠NimbusOpaqueTokenIntrospectorͷ#FBOΛࢦఆ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc5PLFO1SPQBHBUJPOͱ▸ தؒͷϚΠΫϩαʔϏεɺ͕ࣗड͚औͬͨΞΫηετʔΫϯΛͦͷ··ԼྲྀͷϚΠΫϩαʔϏεʹ͢$MJFOU"1*(BUFXBZ3FTPVSDF4FSWFSΞΫηετʔΫϯΞΫηετʔΫϯ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc5PLFO1SPQBHBUJPOͷઃఆ▸ WebClientʹServletBearerExchangeFilterFunctionΛՃ@Beanpublic WebClient webClient(...) {...return builder.baseUrl(resourceServerUri).clientConnector(new ReactorClientHttpConnector(httpClient)).filter(new ServletBearerExchangeFilterFunction())....build();}
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc5PLFO1SPQBHBUJPOͷΈ▸ ϦΫΤετૹ৴લʹᶃ "VUIFOUJDBUJPO͔ΒΞΫηετʔΫϯΛऔಘᶄ "VUIPSJ[BUJPOϔομʔʹΞΫηετʔΫϯΛՃ‣ ৄࡉ͜͜ΒΜͷίʔυࢀর‣ IUUQTHJUIVCDPNTQSJOHQSPKFDUTTQSJOHTFDVSJUZCMPCNBTUFSPBVUIPBVUISFTPVSDFTFSWFSTSDNBJOKBWBPSHTQSJOHGSBNFXPSLTFDVSJUZPBVUITFSWFSSFTPVSDFXFCSFBDUJWFGVODUJPODMJFOU4FSWMFU#FBSFS&YDIBOHF'JMUFS'VODUJPOKBWB-
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc3FTU5FNQMBUFΛ͍͍ͨ߹▸ ಉͷػೳΛࣗͰ࡞Δඞཁ͕͋Δ▸ ίʔυྫϦϑΝϨϯεࢀরIUUQTEPDTTQSJOHJPTQSJOHTFDVSJUZTJUFEPDT3&-&"4&SFGFSFODFIUNMSFTUUFNQMBUFTVQQPSU
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc·ͱΊ▸ λΠϜΞτઃఆɺେࣄɻ
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_cccೝՄαʔόʔʁ▸ 4QSJOH"VUIPSJ[BUJPO4FSWFS͕։ൃਐߦதhttps://spring.io/blog/2020/08/21/get-the-very-first-bits-of-spring-authorization-server-0-0-1
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc4QSJOH4FDVSJUZϦϑΝϨϯε▸ ࠓճհ͍ͯ͠ͳ͍ػೳɺ༷ʑͳΧελϚΠζํ๏͕հ͞Ε͍ͯ·͢▸ IUUQTEPDTTQSJOHJPTQSJOHTFDVSJUZTJUFEPDT3&-&"4&SFGFSFODFIUNMPBVUI
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc0"VUIؔ࿈ॻ੶
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc0"VUI༷ॻ▸ 3'$5IF0"VUI"VUIPSJ[BUJPO'SBNFXPSL▸ IUUQTUPPMTJFUGPSHIUNMSGD▸ 3'$0"VUI5PLFO*OUSPTQFDUJPO▸ IUUQTUPPMTJFUGPSHIUNMSGD
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc<3*1>ݩ͞Μͷࢿྉ▸ ϚΠΫϩαʔϏε࣌ͷೝূͱೝՄ▸ [email protected]TT▸ جૅ͔Βͷ0"VUI▸ [email protected]EFWFMPQFSTJP
$$"4"3&"- *OD"MMSJHIUTSFTFSWFE#jjug_ccc͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ