Spring Security 5 - Log Masuk OAuth2

1. Gambaran keseluruhan

Spring Security 5 memperkenalkan kelas OAuth2LoginConfigurer baru yang boleh kita gunakan untuk mengkonfigurasi Pelayan Pengesahan luaran.

Dalam artikel ini, kami akan meneroka beberapa pilihan konfigurasi yang tersedia untuk elemen oauth2Login () .

2. Pergantungan Maven

Dalam projek Spring Boot, semua yang kita perlukan adalah menambahkan starter spring-boot-starter-oauth2-client :

 org.springframework.boot spring-boot-starter-oauth2-client 2.3.3.RELEASE 

Dalam projek bukan Boot, selain pergantungan Keselamatan Musim Semi dan Musim Semi standard, kami juga perlu menambahkan ketergantungan klien-keselamatan-oauth2-pelanggan dan keselamatan-musim semi-keselamatan-oauth2-jose secara eksplisit :

 org.springframework.security spring-security-oauth2-client 5.3.4.RELEASE   org.springframework.security spring-security-oauth2-jose 5.3.4.RELEASE 

3. Persediaan Pelanggan

Dalam projek Spring Boot, semua yang perlu kita lakukan adalah menambahkan beberapa sifat standard untuk setiap pelanggan yang ingin kita konfigurasikan.

Mari siapkan projek kami untuk log masuk dengan pelanggan yang berdaftar dengan Google dan Facebook sebagai penyedia pengesahan.

3.1. Memperolehi kelayakan Pelanggan

Untuk mendapatkan kelayakan pelanggan untuk pengesahan Google OAuth2, terus ke Konsol API Google - bahagian "Kredensial".

Di sini kami akan membuat kelayakan jenis "ID Pelanggan OAuth2" untuk aplikasi web kami. Ini mengakibatkan Google menyediakan id dan rahsia pelanggan untuk kami.

Kami juga harus mengkonfigurasi URI pengalihan yang dibenarkan di Konsol Google, yang merupakan jalan yang akan diarahkan pengguna setelah mereka berjaya masuk dengan Google.

Secara lalai, Spring Boot mengkonfigurasi URI pengalihan ini sebagai / login / oauth2 / code / {registrationId}. Oleh itu, untuk Google kami akan menambahkan URI:

//localhost:8081/login/oauth2/code/google

Untuk mendapatkan bukti kelayakan klien untuk pengesahan dengan Facebook, kami perlu mendaftarkan aplikasi di laman web Facebook for Developers dan menyediakan URI yang sesuai sebagai "URI pengalihan OAuth yang sah":

//localhost:8081/login/oauth2/code/facebook

3.3. Konfigurasi Keselamatan

Seterusnya, kita perlu menambahkan kelayakan klien ke fail application.properties . Properti Spring Security diawali dengan "spring.security.oauth2.client.registration" diikuti dengan nama pelanggan, kemudian nama harta klien:

spring.security.oauth2.client.registration.google.client-id= spring.security.oauth2.client.registration.google.client-secret= spring.security.oauth2.client.registration.facebook.client-id= spring.security.oauth2.client.registration.facebook.client-secret=

Menambah sifat ini untuk sekurang-kurangnya satu pelanggan akan membolehkan kelas Oauth2ClientAutoConfiguration yang menetapkan semua kacang yang diperlukan.

Konfigurasi keselamatan web automatik setara dengan menentukan elemen oauth2Login () sederhana :

@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login(); } }

Di sini, kita dapat melihat elemen oauth2Login () digunakan dengan cara yang serupa dengan elemen httpBasic () dan formLogin () yang sudah diketahui .

Sekarang, ketika kita mencoba mengakses URL yang dilindungi, aplikasi akan memaparkan halaman masuk yang dihasilkan secara automatik dengan dua klien:

3.4. Pelanggan Lain

Perhatikan bahawa selain Google dan Facebook, projek Spring Security juga mengandungi konfigurasi lalai untuk GitHub dan Okta. Konfigurasi lalai ini memberikan semua maklumat yang diperlukan untuk pengesahan, itulah yang membolehkan kami hanya memasukkan bukti kelayakan pelanggan.

Sekiranya kami ingin menggunakan penyedia pengesahan yang berbeza yang tidak dikonfigurasi dalam Spring Security, kami perlu menentukan konfigurasi penuh, dengan maklumat seperti URI kebenaran dan URI token. Berikut adalah konfigurasi lalai dalam Spring Security untuk mengetahui idea sifat yang diperlukan.

4. Siapkan Projek Tanpa Boot

4.1. Membuat Kacang Pendaftaran Pelanggan

Sekiranya kita tidak bekerja dengan aplikasi Spring Boot, kita perlu menentukan kacang ClientRegistrationRepository yang mengandungi perwakilan dalaman maklumat klien yang dimiliki oleh pelayan kebenaran:

@Configuration @EnableWebSecurity @PropertySource("classpath:application.properties") public class SecurityConfig extends WebSecurityConfigurerAdapter { private static List clients = Arrays.asList("google", "facebook"); @Bean public ClientRegistrationRepository clientRegistrationRepository() { List registrations = clients.stream() .map(c -> getRegistration(c)) .filter(registration -> registration != null) .collect(Collectors.toList()); return new InMemoryClientRegistrationRepository(registrations); } }

Di sini kita membuat InMemoryClientRegistrationRepository dengan senarai objek ClientRegistration .

4.2. Membina Objek Pendaftaran Pelanggan

Mari lihat kaedah getRegistration () yang membina objek ini:

private static String CLIENT_PROPERTY_KEY = "spring.security.oauth2.client.registration."; @Autowired private Environment env; private ClientRegistration getRegistration(String client) { String clientId = env.getProperty( CLIENT_PROPERTY_KEY + client + ".client-id"); if (clientId == null) { return null; } String clientSecret = env.getProperty( CLIENT_PROPERTY_KEY + client + ".client-secret"); if (client.equals("google")) { return CommonOAuth2Provider.GOOGLE.getBuilder(client) .clientId(clientId).clientSecret(clientSecret).build(); } if (client.equals("facebook")) { return CommonOAuth2Provider.FACEBOOK.getBuilder(client) .clientId(clientId).clientSecret(clientSecret).build(); } return null; }

Di sini, kami membaca kelayakan pelanggan dari fail aplikasi.properties yang serupa , kemudian menggunakan enum CommonOauth2Provider yang telah ditentukan dalam Spring Security untuk sisa sifat pelanggan untuk pelanggan Google dan Facebook.

Setiap contoh ClientRegistration sesuai dengan klien.

4.3. Mendaftarkan ClientRegistrationRepository

Akhirnya, kita harus membuat kacang OAuth2AuthorizedClientService berdasarkan kacang ClientRegistrationRepository dan mendaftarkan keduanya dengan elemen oauth2Login () :

@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated() .and() .oauth2Login() .clientRegistrationRepository(clientRegistrationRepository()) .authorizedClientService(authorizedClientService()); } @Bean public OAuth2AuthorizedClientService authorizedClientService() { return new InMemoryOAuth2AuthorizedClientService( clientRegistrationRepository()); }

As evidenced here, we can use the clientRegistrationRepository() method of oauth2Login() to register a custom registration repository.

We'll also have to define a custom login page, as it won't be automatically generated anymore. We'll see more information on this in the next section.

Let's continue with further customization of our login process.

5. Customizing oauth2Login()

There are several elements that the OAuth 2 process uses and that we can customize using oauth2Login() methods.

Note that all these elements have default configurations in Spring Boot and explicit configuration isn't required.

Let's see how we can customize these in our configuration.

5.1. Custom Login Page

Even though Spring Boot generates a default login page for us, we'll usually want to define our own customized page.

Let's start with configuring a new login URL for the oauth2Login() element by using theloginPage() method:

@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/oauth_login") .permitAll() .anyRequest() .authenticated() .and() .oauth2Login() .loginPage("/oauth_login"); }

Here, we've set up our login URL to be /oauth_login.

Next, let's define a LoginController with a method that maps to this URL:

@Controller public class LoginController { private static String authorizationRequestBaseUri = "oauth2/authorization"; Map oauth2AuthenticationUrls = new HashMap(); @Autowired private ClientRegistrationRepository clientRegistrationRepository; @GetMapping("/oauth_login") public String getLoginPage(Model model) { // ... return "oauth_login"; } }

This method has to send a map of the clients available and their authorization endpoints to the view, which we'll obtain from the ClientRegistrationRepository bean:

public String getLoginPage(Model model) { Iterable clientRegistrations = null; ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository) .as(Iterable.class); if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) { clientRegistrations = (Iterable) clientRegistrationRepository; } clientRegistrations.forEach(registration -> oauth2AuthenticationUrls.put(registration.getClientName(), authorizationRequestBaseUri + "/" + registration.getRegistrationId())); model.addAttribute("urls", oauth2AuthenticationUrls); return "oauth_login"; }

Finally, we need to define our oauth_login.html page:

Login with:

Client

This is a simple HTML page that displays links to authenticate with each client.

After adding some styling to it, we can change the look of the login page:

5.2. Custom Authentication Success and Failure Behavior

We can control the post-authentication behavior by using different methods:

  • defaultSuccessUrl() and failureUrl() – to redirect the user to a given URL
  • successHandler() and failureHandler() – to execute custom logic following the authentication process

Let's see how we can set custom URL's to redirect the user to:

.oauth2Login() .defaultSuccessUrl("/loginSuccess") .failureUrl("/loginFailure");

If the user visited a secured page before authenticating, they will be redirected to that page after logging in; otherwise, they will be redirected to /loginSuccess.

If we want the user to always be sent to the /loginSuccess URL regardless if they were on a secured page before or not, we can use the method defaultSuccessUrl(“/loginSuccess”, true).

To use a custom handler, we would have to create a class that implements the AuthenticationSuccessHandler or AuthenticationFailureHandler interfaces, override the inherited methods, then set the beans using the successHandler() and failureHandler() methods.

5.3. Custom Authorization Endpoint

The authorization endpoint is the endpoint that Spring Security uses to trigger an authorization request to the external server.

First, let's set new properties for the authorization endpoint:

.oauth2Login() .authorizationEndpoint() .baseUri("/oauth2/authorize-client") .authorizationRequestRepository(authorizationRequestRepository());

Here, we've modified the baseUri to /oauth2/authorize-client instead of the default /oauth2/authorization. We're also explicitly setting an authorizationRequestRepository() bean that we have to define:

@Bean public AuthorizationRequestRepository authorizationRequestRepository() { return new HttpSessionOAuth2AuthorizationRequestRepository(); }

In our example, we've used the Spring-provided implementation for our bean, but we could also provide a custom one.

5.4. Custom Token Endpoint

The token endpoint processes access tokens.

Let's explicitly configure the tokenEndpoint()with the default response client implementation:

.oauth2Login() .tokenEndpoint() .accessTokenResponseClient(accessTokenResponseClient());

And here's the response client bean:

@Bean public OAuth2AccessTokenResponseClient accessTokenResponseClient() { return new NimbusAuthorizationCodeTokenResponseClient(); }

This configuration is the same as the default one and is using the Spring implementation which is based on exchanging an authorization code with the provider.

Of course, we could also substitute a custom response client.

5.5. Custom Redirection Endpoint

This is the endpoint to redirect to after authentication with the external provider.

Let's see how we can change the baseUri for the redirection endpoint:

.oauth2Login() .redirectionEndpoint() .baseUri("/oauth2/redirect")

The default URI is login/oauth2/code.

Note that if we change it, we also have to update the redirectUriTemplate property of each ClientRegistration and add the new URI as an authorized redirect URI for each client.

5.6. Custom User Information Endpoint

The user info endpoint is the location we can leverage to obtain user information.

We can customize this endpoint using the userInfoEndpoint() method. For this, we can use methods such as userService() and customUserType() to modify the way user information is retrieved.

6. Accessing User Information

A common task we may want to achieve is finding information about the logged-in user. For this, we can make a request to the user information endpoint.

First, we'll have to get the client corresponding to the current user token:

@Autowired private OAuth2AuthorizedClientService authorizedClientService; @GetMapping("/loginSuccess") public String getLoginInfo(Model model, OAuth2AuthenticationToken authentication) { OAuth2AuthorizedClient client = authorizedClientService .loadAuthorizedClient( authentication.getAuthorizedClientRegistrationId(), authentication.getName()); //... return "loginSuccess"; }

Next, we'll send a request to the client's user info endpoint and retrieve the userAttributes Map:

String userInfoEndpointUri = client.getClientRegistration() .getProviderDetails().getUserInfoEndpoint().getUri(); if (!StringUtils.isEmpty(userInfoEndpointUri)) { RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + client.getAccessToken() .getTokenValue()); HttpEntity entity = new HttpEntity("", headers); ResponseEntity response = restTemplate .exchange(userInfoEndpointUri, HttpMethod.GET, entity, Map.class); Map userAttributes = response.getBody(); model.addAttribute("name", userAttributes.get("name")); }

Dengan menambahkan nama properti sebagai atribut Model , kita dapat memaparkannya dalam paparan loginSuccess sebagai mesej selamat datang kepada pengguna:

Selain nama, yang userAttributes Peta juga mengandungi ciri-ciri seperti e-mel, FAMILY_NAME, picture, tempat-tempat kejadian.

7. Kesimpulannya

Dalam artikel ini, kita telah melihat bagaimana kita dapat menggunakan elemen oauth2Login () di Spring Security untuk mengesahkan dengan penyedia yang berbeza seperti Google dan Facebook. Kami juga telah melalui beberapa senario biasa untuk menyesuaikan proses ini.

Kod sumber lengkap contoh boleh didapati di GitHub.