Рубрики
Без рубрики

Весенняя безопасность и OpenID Connect (Наследие)

Узнайте, как настроить OpenID Connect (от Google) с помощью простого приложения Spring Security.

Автор оригинала: baeldung.

Обратите внимание, что это содержимое устарело и использует устаревший стек OAuth. Взгляните на Последняя поддержка OAuth от Spring Security .

1. Обзор

В этом быстром учебнике мы сосредоточимся на настройке OpenID Connect с помощью реализации Spring Security OAuth2.

OpenID Подключение это простой слой идентификации, построенный поверх протокола OAuth 2.0.

И, более конкретно, мы узнаем, как проверить подлинность пользователей с помощью Реализация OpenID Connect с Google .

2. Конфигурация Maven

Во-первых, мы должны добавить следующие зависимости в наше приложение Spring Boot:


    org.springframework.boot
    spring-boot-starter-security


    org.springframework.security.oauth
    spring-security-oauth2

3. Токен Id

Прежде чем углубиться в детали реализации, давайте посмотрим, как работает OpenID и как мы будем с ним взаимодействовать.

На данный момент, это, конечно, важно уже иметь понимание OAuth2, так как OpenID построен на вершине OAuth.

Во-первых, для того, чтобы использовать функциональность идентификации, мы будем использовать новую область OAuth2 под названием openid . Это приведет к дополнительному полю в нашем токене Access Token – “ id_token “.

id_token является JWT (JSON Web Token), который содержит идентификационную информацию о пользователе, подписанную поставщиком идентификационных данных (в нашем случае Google).

Наконец, оба сервер (Код авторизации) и неявное потоки являются наиболее часто используемыми способами получения id_token , в нашем примере, мы будем использовать серверный поток .

3. Конфигурация клиента OAuth2

Далее, давайте настраивать наш клиент OAuth2 – следующим образом:

@Configuration
@EnableOAuth2Client
public class GoogleOpenIdConnectConfig {
    @Value("${google.clientId}")
    private String clientId;

    @Value("${google.clientSecret}")
    private String clientSecret;

    @Value("${google.accessTokenUri}")
    private String accessTokenUri;

    @Value("${google.userAuthorizationUri}")
    private String userAuthorizationUri;

    @Value("${google.redirectUri}")
    private String redirectUri;

    @Bean
    public OAuth2ProtectedResourceDetails googleOpenId() {
        AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
        details.setClientId(clientId);
        details.setClientSecret(clientSecret);
        details.setAccessTokenUri(accessTokenUri);
        details.setUserAuthorizationUri(userAuthorizationUri);
        details.setScope(Arrays.asList("openid", "email"));
        details.setPreEstablishedRedirectUri(redirectUri);
        details.setUseCurrentUri(false);
        return details;
    }

    @Bean
    public OAuth2RestTemplate googleOpenIdTemplate(OAuth2ClientContext clientContext) {
        return new OAuth2RestTemplate(googleOpenId(), clientContext);
    }
}

А вот и application.properts :

google.clientId=
google.clientSecret=
google.accessTokenUri=https://www.googleapis.com/oauth2/v3/token
google.userAuthorizationUri=https://accounts.google.com/o/oauth2/auth
google.redirectUri=http://localhost:8081/google-login

Обратите внимание, что:

  • Сначала вам нужно получить учетные данные OAuth 2.0 для вашего веб-приложения Google от Google Разработчики Консоли .
  • Мы использовали область openid для получения id_token .
  • мы также использовали дополнительную область электронной включить электронную почту пользователя в id_token идентификационной информации.
  • Перенаправление URI http://localhost:8081/google-login это тот же, который используется в нашем веб-приложении Google.

4. Пользовательский фильтр подключения OpenID

Теперь нам нужно создать свой собственный пользовательский OpenIdConnectFilter извлечь аутентификацию из id_token – следующим образом:

public class OpenIdConnectFilter extends AbstractAuthenticationProcessingFilter {

    public OpenIdConnectFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
        setAuthenticationManager(new NoopAuthenticationManager());
    }
    @Override
    public Authentication attemptAuthentication(
      HttpServletRequest request, HttpServletResponse response) 
      throws AuthenticationException, IOException, ServletException {
        OAuth2AccessToken accessToken;
        try {
            accessToken = restTemplate.getAccessToken();
        } catch (OAuth2Exception e) {
            throw new BadCredentialsException("Could not obtain access token", e);
        }
        try {
            String idToken = accessToken.getAdditionalInformation().get("id_token").toString();
            String kid = JwtHelper.headers(idToken).get("kid");
            Jwt tokenDecoded = JwtHelper.decodeAndVerify(idToken, verifier(kid));
            Map authInfo = new ObjectMapper()
              .readValue(tokenDecoded.getClaims(), Map.class);
            verifyClaims(authInfo);
            OpenIdConnectUserDetails user = new OpenIdConnectUserDetails(authInfo, accessToken);
            return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
        } catch (InvalidTokenException e) {
            throw new BadCredentialsException("Could not obtain user details from token", e);
        }
    }
}

И вот наш простой OpenIdConnectUserDetails :

public class OpenIdConnectUserDetails implements UserDetails {
    private String userId;
    private String username;
    private OAuth2AccessToken token;

    public OpenIdConnectUserDetails(Map userInfo, OAuth2AccessToken token) {
        this.userId = userInfo.get("sub");
        this.username = userInfo.get("email");
        this.token = token;
    }
}

Обратите внимание, что:

  • Весенние меры JwtHelper расшифровать id_token .
  • id_token всегда содержит ” суб” поле, которое является уникальным идентификатором для пользователя.
  • id_token также будет содержать ” электронной ” поле, как мы добавили электронной сферы по нашей просьбе.

4.1. Проверка токена ID

В приведенной выше примере мы использовали декодироватьИверифицировать () метод JwtHelper для извлечения информации из id_token, но и для проверки его.

Первым шагом для этого является проверка того, что он был подписан одним из сертификатов, указанных в Google Discovery документ.

Они меняются примерно один раз в день, поэтому мы будем использовать утилиту библиотеки под названием jwks-rsa читать их:


    com.auth0
    jwks-rsa
    0.3.0

Давайте добавим URL-адрес, содержащий сертификаты, в application.properts файл:

google.jwkUrl=https://www.googleapis.com/oauth2/v2/certs

Теперь мы можем прочитать это свойство и построить RSAВерификатор объект:

@Value("${google.jwkUrl}")
private String jwkUrl;    

private RsaVerifier verifier(String kid) throws Exception {
    JwkProvider provider = new UrlJwkProvider(new URL(jwkUrl));
    Jwk jwk = provider.get(kid);
    return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
}

Наконец, мы также проверим утверждения в расшифровав маркере id:

public void verifyClaims(Map claims) {
    int exp = (int) claims.get("exp");
    Date expireDate = new Date(exp * 1000L);
    Date now = new Date();
    if (expireDate.before(now) || !claims.get("iss").equals(issuer) || 
      !claims.get("aud").equals(clientId)) {
        throw new RuntimeException("Invalid claims");
    }
}

проверитьClaims () метод проверяет, что токен id был выпущен Google и что он не истек.

Более подробную информацию об этом можно найти в Документация Google .

5. Конфигурация безопасности

Далее давайте обсудим нашу конфигурацию безопасности:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private OAuth2RestTemplate restTemplate;

    @Bean
    public OpenIdConnectFilter openIdConnectFilter() {
        OpenIdConnectFilter filter = new OpenIdConnectFilter("/google-login");
        filter.setRestTemplate(restTemplate);
        return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .addFilterAfter(new OAuth2ClientContextFilter(), 
          AbstractPreAuthenticatedProcessingFilter.class)
        .addFilterAfter(OpenIdConnectFilter(), 
          OAuth2ClientContextFilter.class)
        .httpBasic()
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/google-login"))
        .and()
        .authorizeRequests()
        .anyRequest().authenticated();
    }
}

Обратите внимание, что:

  • Мы добавили наши пользовательские OpenIdConnectFilter после OAuth2ClientContextFilter
  • Мы использовали простую конфигурацию безопасности, чтобы перенаправить пользователей на ” /Google-логин ” для проверки подлинности Google

6. Контроллер пользователя

Далее, вот простой контроллер для тестирования нашего приложения:

@Controller
public class HomeController {
    @RequestMapping("/")
    @ResponseBody
    public String home() {
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        return "Welcome, " + username;
    }
}

Пример ответа (после перенаправления в Google для утверждения органов по работе с приложениями):

7. Образец процесса подключения OpenID

Наконец, давайте рассмотрим пример процесса проверки подлинности OpenID Connect.

Во-первых, мы собираемся послать Запрос на аутентификацию :

https://accounts.google.com/o/oauth2/auth?
    client_id=sampleClientID
    response_type=code&
    scope=openid%20email&
    redirect_uri=http://localhost:8081/google-login&
    state=abc

Ответ ( после утверждения пользователем ) является перенаправлением на:

http://localhost:8081/google-login?state=abc&code=xyz

Далее мы обменяемся код для токена доступа и id_token :

POST https://www.googleapis.com/oauth2/v3/token 
    code=xyz&
    client_id= sampleClientID&
    client_secret= sampleClientSecret&
    redirect_uri=http://localhost:8081/google-login&
    grant_type=authorization_code

Вот пример ответа:

{
    "access_token": "SampleAccessToken",
    "id_token": "SampleIdToken",
    "token_type": "bearer",
    "expires_in": 3600,
    "refresh_token": "SampleRefreshToken"
}

Наконец, вот что информация о фактической id_token Похоже:

{
    "iss":"accounts.google.com",
    "at_hash":"AccessTokenHash",
    "sub":"12345678",
    "email_verified":true,
    "email":"[email protected]",
     ...
}

Таким образом, вы можете сразу увидеть, насколько полезна информация о пользователе внутри токена для предоставления идентификационной информации нашему собственному приложению.

8. Заключение

В этом быстром учебнике по интро мы узнали, как проверить подлинность пользователей с помощью реализации OpenID Connect от Google.

И, как всегда, вы можете найти исходный код более на GitHub .