Autentificarea unui client generic cu Spring Security OAuth2 Client

  • 2 minute de citit
  • 8 exemple de cod în Java și JSON

Disponibil și în English

Salut tuturor 👋,

Recent am lucrat la un mic proiect personal scris în Java cu Spring Framework și am avut dificultăți la autentificarea printr-un client OAuth2 extern folosind Spring Security. Soluția la problema mea a devenit clară după ce am parcurs codul și documentația pachetului Spring OAuth Client.

Credeam că va trebui să scriu clase personalizate ca să-l configurez pentru nevoia mea specifică, dar s-a dovedit că problema mea ținea strict de configurare.

Problema era că aveam un client OAuth2 Confidential, iar configurația implicită a Spring Security presupune că clientul este Public.

RFC-ul descrie tipurile de clienți după cum urmează:

Tipul de client Confidential este pentru aplicațiile care pot păstra în siguranță secretul clientului, de exemplu o aplicație web de back-end.Tipul de client Public este pentru aplicațiile care nu pot păstra în siguranță secretele clientului, de exemplu o aplicație mobilă sau o aplicație front-end. Utilizatorul poate oricând să parcurgă codul sursă și să obțină secretele.

În proiectul meu Spring am configurat autentificarea în fișierul applications.properties. Observați că configurația clientului și a providerului se află sub cheia nuculabs.

# Configurarea clientului OAuth2spring.security.oauth2.client.registration.nuculabs.client-id=xxxspring.security.oauth2.client.registration.nuculabs.client-secret=xxxspring.security.oauth2.client.registration.nuculabs.scope=user:read,read:userspring.security.oauth2.client.registration.nuculabs.authorizationGrantType=authorization_codespring.security.oauth2.client.registration.nuculabs.redirectUri=http://localhost:8080/login/oauth2/code/nuculabsspring.security.oauth2.client.registration.nuculabs.client-authentication-method=client_secret_postspring.security.oauth2.client.provider.nuculabs.authorization-uri=https://www.example.com/oauth2/authorizespring.security.oauth2.client.provider.nuculabs.token-uri=https://www.example.com/api/oauth2/tokenspring.security.oauth2.client.provider.nuculabs.user-info-uri=https://www.example.com/api/mespring.security.oauth2.client.provider.nuculabs.user-name-attribute=me

Configurația pe care o ratam era client_secret_post.

Odată ce am terminat, a trebuit doar să adaug următoarea clasă SecurityConfig:

package dev.nuculabs.xenchat.xenchat.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.web.SecurityFilterChain;@Configuration@EnableWebSecuritypublic class SecurityConfig {    // OAuth2AuthorizationCodeGrantRequestEntityConverter    @Bean    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {        http                .authorizeHttpRequests(authorize -> authorize                        .requestMatchers("/error", "/webjars/**", "/login").permitAll()                        .requestMatchers("/").authenticated()                        .anyRequest().authenticated()                )                .oauth2Login(oauth2 -> oauth2                        .loginPage("/login")                        .defaultSuccessUrl("/", true)                )                .logout(logout -> logout                        .logoutSuccessUrl("/login")                        .permitAll()                );        return http.build();    }}

Și când vreau să autentific utilizatorii folosind clientul meu OAuth extern, trebuie doar să-i redirecționez către un link intern, ca să pornesc fluxul de autentificare implementat de Spring Security:

/oauth2/authorization/nuculabs

Ultimul pas este să configurez clientul OAuth extern cu următorul URL:

http://localhost:8080/login/oauth2/code/nuculabs

Acesta încheie din punct de vedere tehnic fluxul de autentificare. Ce urmează este că Spring va încerca să preia datele utilizatorului de la user-info-uri; în cazul meu, API-ul este protejat prin access token și va răspunde cu un obiect “me” care arată astfel:

{    "me": {    "about": "I’m Denis, a Software Engineer living in Romania. I’m passionate about cloud computing and software development ✨.",    "activity_visible": false,    "alert_optout": [],    "allow_post_profile": "members",    "allow_receive_news_feed": "members",    "allow_send_personal_conversation": "members",    "allow_view_identities": "members",    "allow_view_profile": "members",    "name": "Denis",    ...}

Ce am avut de făcut a fost să specific me ca user-name-attribute, astfel:

spring.security.oauth2.client.provider.nuculabs.user-name-attribute=me

Apoi pot accesa toate aceste câmpuri dintr-un controller, astfel:

package dev.nuculabs.xenchat.xenchat.controller;import org.springframework.security.core.annotation.AuthenticationPrincipal;import org.springframework.security.oauth2.core.user.OAuth2User;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.GetMapping;import java.util.LinkedHashMap;@Controllerpublic class HomeController {@GetMapping("/")public String home(@AuthenticationPrincipal OAuth2User principal, Model model) {LinkedHashMap<String, Object> userData = principal.getAttribute("me");        // Adaugă atributele utilizatorului în model        model.addAttribute("name", userData.get("username"));        model.addAttribute("email", userData.get("email"));        model.addAttribute("title", userData.get("custom_title"));        model.addAttribute("profileUrl", userData.get("view_url"));        model.addAttribute("isModerator", userData.get("is_moderator"));        // Adaugă toate atributele pentru depanare        model.addAttribute("attributes", principal.getAttributes());return "home";    }@GetMapping("/login")public String login() {return "login";    }}

Atât a fost tot, sper că acest articol v-a fost util! :D

Mulțumesc pentru lectură!