Spring Boot uygulamasına giriş ekleme
Bu hızlı başlangıç, Spring ekosisteminin standart OpenID Connect istemcisi olan Spring Security'nin OAuth 2.0 istemcisiyle bir Spring Boot 4.1 uygulamasına KeenAuth girişi ekler. KeenAuth'un henüz bir Java SDK'sı yoktur ve buna ihtiyaç da yoktur: KeenAuth standart bir OpenID Connect sağlayıcısıdır. Uygulamanız OAuth istemcisidir ve tüm belirteçleri sunucu tarafındaki oturumda tutar; tarayıcıya yalnızca bir oturum kimliği çerezi gider. Girişi yapılandırır, bir sayfayı korur, kullanıcının erişim belirteciyle korumalı bir API'yi çağırır, çıkış yapar ve uygulamayı çevrimdışı test edersiniz.
Uygulamanın rotaları şunlardır:
| Rota | Ne yapar |
|---|---|
GET / |
Herkese açık ana sayfa: bir giriş bağlantısı ya da kullanıcının adı ve bir çıkış düğmesi |
GET /dashboard |
Oturum gerektirir. Kullanıcıyı ve API'nizin döndürdüğü iletileri gösterir |
GET /oauth2/authorization/keenauth |
Spring Security tarafından sunulur: girişi başlatır |
GET /login/oauth2/code/keenauth |
Spring Security tarafından sunulur: geri çağırma adresi |
POST /logout |
Spring Security tarafından sunulur: oturumu burada ve KeenAuth'ta sonlandırır. CSRF belirtecini gerektirir |
Başlamadan önce
- Bir KeenAuth ortamı. Veren kuruluşu (issuer)
https://your-environment.eu.keenauth.appbiçimindedir. - Ortamda bir web uygulaması (Uygulama oluşturun) ve bir istemci gizli anahtarı (
client_secret_basic).developmentortamındahttp://localhost/login/oauth2/code/keenauthyönlendirme adresini vehttp://localhost/çıkış sonrası yönlendirme adresini kaydedin; döngü geri (loopback) adresi bağlantı noktasız kaydedilir ve8080gibi herhangi bir bağlantı noktasıyla eşleşir. - Ortamda
messages:readkapsamıyla kayıtlı bir API (API oluşturun). Örneğinhttps://api.example.comolanidentifierdeğeri, erişim belirteçlerinin hedef kitlesidir (audience). Express hızlı başlangıcı, bu belirteçleri kabul eden bir API oluşturur. - JDK 21 ve Maven 3.9.
1. Bağımlılıkları ekleyin
Spring Boot üst projesi (spring-boot-starter-parent 4.1.2) tüm sürümleri sabitler. Web, OAuth 2.0 istemcisi ve Thymeleaf starter'larını ekleyin:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
<exclusions>
<!-- Logback is EPL-1.0 OR LGPL-2.1, outside the licence allow list: Log4j 2 (Apache-2.0) logs instead. -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<!-- EPL-2.0 OR GPL-2.0 with Classpath Exception, outside the allow list; this app uses none of its annotations. -->
<exclusion>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- Spring Security's OAuth 2.0 client: OpenID Connect sign-in with discovery, PKCE and ID token checks. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Bu örnek, lisansları örneklerimizin lisans izin listesinin dışında kalan iki bağımlılığı değiştirir: Logback (EPL-1.0 veya LGPL-2.1) yerine Log4j 2 ve uygulamanın kullanmadığı jakarta.annotation-api (EPL-2.0 veya Classpath Exception ile GPL-2.0). Aynı nedenle testleri JUnit (EPL-2.0) yerine TestNG ile yazar. Kendi uygulamanızın bunları yapmasına gerek yoktur.
2. Yapılandırın
Uygulama yapılandırmasını yalnızca ortam değişkenlerinden okur. Değerleri asla kaydetmeyin.
| Değişken | Zorunlu | Anlamı |
|---|---|---|
KEENAUTH_ISSUER |
evet | Ortamınızın veren kuruluşu (issuer), https |
KEENAUTH_CLIENT_ID |
evet | Web uygulamasının client_id değeri |
KEENAUTH_CLIENT_SECRET |
evet | Web uygulamasının istemci gizli anahtarı |
APP_BASE_URL |
evet | Uygulamanın herkese açık adresi. Yönlendirme adresi bundan oluşturulur. http yalnızca localhost, 127.0.0.1 veya [::1] için kullanılabilir |
API_URL |
evet | Uygulamanın çağırdığı API, örneğin http://localhost:3001 |
KEENAUTH_API_AUDIENCE |
evet | Bu API'nin KeenAuth'taki tanımlayıcısı |
SESSION_SECURE_COOKIE |
hayır | Yalnızca düz http://localhost için false |
KeenAuthSettings.from, bu değerleri başlangıçta okur ve denetler. Eksik ya da kullanılamayan bir değer uygulamayı durdurur ve hiçbir hata iletisi bir değer içermez.
public static KeenAuthSettings from(Function<String, String> env) {
return new KeenAuthSettings(url(need(env, "KEENAUTH_ISSUER"), "KEENAUTH_ISSUER", true, false),
need(env, "KEENAUTH_CLIENT_ID"), need(env, "KEENAUTH_CLIENT_SECRET"),
url(need(env, "APP_BASE_URL"), "APP_BASE_URL", false, false),
url(need(env, "API_URL"), "API_URL", false, false),
// RFC 8707: the access token is requested for your API, which then accepts it as its audience.
url(need(env, "KEENAUTH_API_AUDIENCE"), "KEENAUTH_API_AUDIENCE", false, true));
}
3. İstemciyi kaydedin
İstemci kaydı, Spring Security'nin başlangıçta aldığı keşiften (<issuer>/.well-known/openid-configuration) gelir. Örnek, istemci kimlik bilgilerini, yönlendirme adresini ve kapsamları ayarlar ve bu gizli istemci için PKCE'yi açar:
/** The endpoints and the signing keys come from discovery: <issuer>/.well-known/openid-configuration. */
@Bean
ClientRegistrationRepository clientRegistrationRepository(KeenAuthSettings settings) {
return new InMemoryClientRegistrationRepository(
registration(ClientRegistrations.fromIssuerLocation(settings.issuer()), settings));
}
static ClientRegistration registration(ClientRegistration.Builder discovered, KeenAuthSettings settings) {
return discovered.registrationId("keenauth")
.clientId(settings.clientId())
.clientSecret(settings.clientSecret())
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri(settings.redirectUri())
// messages:read is a scope of your API; the example API requires it on /api/messages.
.scope("openid", "profile", "email", "messages:read")
// PKCE (RFC 7636) with S256 for this confidential client too; state and nonce are always on.
.clientSettings(ClientRegistration.ClientSettings.builder().requireProofKey(true).build())
.build();
}
4. Giriş, API hedef kitlesi ve çıkışı yapılandırın
Yetkilendirme isteği çözümleyicisi, API'nizin tanımlayıcısını resource olarak ekler (RFC 8707). Bu olmadan erişim belirteci API'niz için düzenlenmez ve API onu reddeder. Çıkış, CSRF belirteciyle birlikte Spring Security'nin POST /logout isteğidir; ardından id_token_hint ve post_logout_redirect_uri ile OpenID Connect RP-Initiated Logout gelir:
DefaultOAuth2AuthorizationRequestResolver resolver = new DefaultOAuth2AuthorizationRequestResolver(clients,
"/oauth2/authorization");
// RFC 8707: an access token for your API.
resolver.setAuthorizationRequestCustomizer(
(request) -> request.additionalParameters((params) -> params.put("resource", settings.apiAudience())));
OidcClientInitiatedLogoutSuccessHandler logout = new OidcClientInitiatedLogoutSuccessHandler(clients);
logout.setPostLogoutRedirectUri(settings.postLogoutRedirectUri());
http.authorizeHttpRequests((authorize) -> authorize.requestMatchers("/", "/error").permitAll()
.anyRequest()
.authenticated())
.oauth2Login((login) -> login.authorizationEndpoint((endpoint) -> endpoint.authorizationRequestResolver(resolver)))
// POST /logout with the CSRF token; then OpenID Connect RP-Initiated Logout with id_token_hint.
.logout((out) -> out.logoutSuccessHandler(logout));
return http.build();
Spring Security, geri çağırmada state değerini denetler, kodu PKCE doğrulayıcısıyla takas eder ve kimlik belirtecini denetler (ortamınızın anahtarlarıyla imza, RS256, iss, aud, exp, iat ve nonce). Girişten sonra kullanıcıya yeni bir oturum kimliği verir. Oturum sunucuda tutulur; birden fazla sunucu kullanıyorsanız ortak bir depoyla Spring Session kullanın.
Ana sayfa giriş bağlantısını gösterir ve çıkış için bir form gönderir; th:action CSRF belirtecini ekler:
<!-- th:action adds the CSRF token. -->
<form th:action="@{/logout}" method="post"><button type="submit">Sign out</button></form>
</div>
<p th:unless="${user}"><a href="/oauth2/authorization/keenauth">Sign in</a></p>
5. API'nizi çağırın
Pano sayfası bir oturum gerektirir. Erişim belirtecini, Spring Security'nin oturumda tuttuğu yetkili istemciden alır ve API'nizi onunla çağırır. Belirteç sunucuda kalır; tarayıcı yalnızca HTML alır.
/** Needs a session (SecurityConfig). The access token stays on the server; the browser receives only the HTML. */
@GetMapping("/dashboard")
public String dashboard(@AuthenticationPrincipal OidcUser user,
@RegisteredOAuth2AuthorizedClient("keenauth") OAuth2AuthorizedClient client, Model model) {
ResponseEntity<String> response = this.messages.fetch(client.getAccessToken().getTokenValue());
model.addAttribute("user", user);
model.addAttribute("status", response.getStatusCode().value());
model.addAttribute("messages", response.getStatusCode().is2xxSuccessful() ? response.getBody() : null);
return "dashboard";
}
public ResponseEntity<String> fetch(String accessToken) {
return this.rest.get()
.uri("/api/messages")
.headers((headers) -> headers.setBearerAuth(accessToken))
.retrieve()
.onStatus((status) -> true, (request, response) -> {
})
.toEntity(String.class);
}
Örnekte yenileme belirteci yoktur: erişim belirteci sona erdiğinde kullanıcı yeniden giriş yapar.
6. Uygulamayı çalıştırın
Express hızlı başlangıcındaki API'yi (ya da bu belirteçleri kabul eden herhangi bir API'yi) başlatın ve uygulamayı değişkenler dışa aktarılmış olarak çalıştırın:
mvn spring-boot:run
http://localhost:8080 adresini açın, Giriş yap seçeneğini seçin, KeenAuth'ta giriş yapın ve pano sayfasını açın.
7. Ağ olmadan test edin
Testler ağ ya da KeenAuth hesabı gerektirmez. Döngü geri bağlantı noktasında çalışan küçük bir HTTP sunucusu, ortamınızı (keşif, test çalıştırması için oluşturulmuş bir anahtar kümesi, belirteç uç noktası ve kullanıcı bilgisi) ve API'yi canlandırır. Test, aynı registration metoduyla istemci kaydını, bu sunucudan keşfedilen bir kayıtla değiştirir:
/** The registration of SecurityConfig, discovered from the fake environment instead of the issuer above. */
@TestConfiguration
static class FakeDiscovery {
@Bean
ClientRegistrationRepository clientRegistrationRepository(KeenAuthSettings settings) {
return new InMemoryClientRegistrationRepository(
SecurityConfig.registration(ClientRegistrations.fromIssuerLocation(KEENAUTH.issuer()), settings));
}
}
@Test
public void callbackWithAnUnknownStateIsRefusedBeforeAnyTokenRequest() throws Exception {
MockHttpSession session = new MockHttpSession();
startSignIn(session);
MvcResult result = returnFromKeenAuth(session, "not-the-state");
assertEquals(result.getResponse().getRedirectedUrl(), "/login?error");
assertTrue(KEENAUTH.requestsTo("/oauth2/token").isEmpty());
mvn dependency:list -DoutputFile=target/dependencies.txt
python3 ../scripts/maven_deps.py target/dependencies.txt # SHA-256 lock and licences
mvn test
Testler şunları denetler: yapılandırmanın başlangıçta doğrulanması (ve hiçbir hata iletisinin gizli anahtar içermemesi); girişin PKCE S256, state, nonce ve resource gönderdiği; kodun eşleşen doğrulayıcı ve istemci kimlik doğrulamasıyla takas edildiği; erişim belirtecinin Authorization başlığında API'ye gittiği ve asla bir çereze yazılmadığı; bilinmeyen bir state, başka bir istemci için kimlik belirteci ve farklı bir nonce içeren kimlik belirteci için oturum açılmadan reddedildiği; oturumu olmayan bir sayfanın girişi başlattığı; çıkışın CSRF belirtecini gerektirdiği ve id_token_hint ile çıkış uç noktasına gittiği.
Gerçek bir tarayıcıyla tam giriş bir KeenAuth ortamı gerektirir. Bu, çevrimdışı testlerin parçası değildir.
Bir proxy veya yük dengeleyici arkasında
APP_BASE_URL değerini uygulamanın herkese açık adresine ayarlayın; yönlendirme adresi bundan oluşturulur. Bir proxy TLS'i sonlandırıyorsa, server.forward-headers-strategy=framework ayarını yalnızca uygulamaya erişmenin tek yolu proxy ise kullanın.
Bugünkü sınırlar
- Giriş, Spring Security'nin varsayılan davranışı gibi
/oauth2/authorization/keenauthadresine birGETisteğiyle başlar. Geri çağırmayıstatedenetimi ve PKCE korur. - Spring Security,
issyetkilendirme yanıt parametresini (RFC 9207) denetlemez. - Spring Security varsayılan olarak RS256 kimlik belirteçlerini kabul eder. Ortamınız başka bir algoritmayla imzalıyorsa, o algoritmayla bir
OidcIdTokenDecoderFactorybean'i yapılandırın. - Yenileme belirteci ve sessiz giriş yoktur.
Sonraki adımlar
- Express API'sini koruyun: bu uygulamanın çağırdığı API.
- Belirteçlerin doğrulanması: API'nizin ara katmanının altındaki doğrulayıcı.