Весеннее облако Alibaba бой (4) Oauth2

Java Spring Cloud
Весеннее облако Alibaba бой (4) Oauth2

Учебная комната Cabbage Java охватывает основные знания

Spring Cloud Alibaba Actual Combat (1) Подготовка
Spring Cloud Alibaba Actual Combat (2) Nacos
Spring Cloud Alibaba Actual Combat (3) Sentinel
Весеннее облако Alibaba бой (4) Oauth2
Spring Cloud Alibaba Actual Combat (5) Zuul
Реальные бои Spring Cloud Alibaba (6) Статьи RocketMQ
Spring Cloud Alibaba Actual Combat (7) Seata
Spring Cloud Alibaba Actual Combat (8) SkyWalking

Адрес GitHub проекта:GitHub.com/D2C-CAI/Хайер…

1. Введение в Oauth2

OAuth2 на самом деле является сетевым стандартом авторизации. Он формулирует идеи дизайна и рабочие процедуры. Используя этот стандарт, мы можем фактически реализовать процесс аутентификации OAuth2 самостоятельно. spring-cloud-starter-oauth2 на самом деле является конкретной реализацией, упакованной Spring Cloud в соответствии со стандартом OAuth2 и объединенной с spring-security.

Прежде всего, то, с чем все больше всего знакомы, это то, что почти все использовали, например, вход в систему с помощью WeChat, вход с помощью QQ, вход с помощью Weibo, вход с учетной записью Google, вход с авторизацией github и т. д. типичные сценарии использования OAuth2.

Текущий процесс OAuth 2.0:

  1. (A) После того, как пользователь открывает клиент, клиент запрашивает у пользователя авторизацию.
  2. (B) Пользователь соглашается предоставить Клиенту авторизацию.
  3. (C) Клиент использует авторизацию, полученную на предыдущем шаге, для запроса маркера с сервера аутентификации.
  4. (D) После того, как сервер аутентификации аутентифицирует клиента, он подтверждает правильность и соглашается выдать токен.
  5. (E) Клиент использует токен для обращения к серверу ресурсов для получения ресурса.
  6. (F) Сервер ресурсов подтверждает правильность токена и соглашается открыть ресурс клиенту.

OAuth 2 имеет четыре режима авторизации, а именно режим кода авторизации, упрощенный режим (неявный), режим пароля (учетные данные владельца ресурса) и режим клиента (учетные данные клиента).

Предположим, мы построили собственную сервисную платформу.Если мы не используем метод входа в систему OAuth2, нам нужно, чтобы пользователь сначала завершил регистрацию, а затем вошел в систему с паролем учетной записи регистрационного номера или с кодом подтверждения мобильного телефона. Я считаю, что после использования OAuth2 многие люди использовали или даже разработали официальные веб-службы учетной записи и небольшие программы.Когда мы заходим на веб-страницу и в интерфейс небольшой программы, нет необходимости регистрироваться для первого использования, и мы можем войти в систему напрямую с Авторизация WeChat, что значительно повышает эффективность использования. Поскольку у каждого есть учетная запись WeChat, с WeChat вы можете сразу же использовать сторонние сервисы, и опыт не слишком хорош. Для нашей службы нам не нужно хранить пароль пользователя, просто хранить уникальный идентификатор и информацию о пользователе, возвращаемую платформой аутентификации.

Выше приведен режим кода авторизации с использованием OAuth2, который использует стороннюю авторитетную платформу для реализации аутентификации пользователя. Конечно, если в вашей компании много сервисов, вы можете выделить удостоверяющий центр, этот удостоверяющий центр будет выступать в качестве упомянутой выше авторитетной сертификационной платформы, и все услуги должны идти в этот удостоверяющий центр для сертификации.

Таким образом я узнал, что на самом деле это функция единого входа. Это еще один сценарий использования. Для мультисервисной платформы OAuth2 можно использовать для реализации единого входа для сервисов. Всего один логин позволяет свободно перемещаться между несколькими сервисами. Конечно, он ограничен сервисами и интерфейсами внутри объем полномочий.

2. Реализация Oauth2

Эта система в основном вводитрежим пароляРеализован единый вход.

В режиме пароля (предоставление учетных данных владельца ресурса) пользователь предоставляет клиенту свое имя пользователя и пароль. Клиент использует эту информацию для запроса авторизации у «поставщика услуг».

В этом режиме пользователь должен сообщить свой пароль клиенту, но клиент не должен хранить пароль. Это обычно используется, когда пользователь имеет высокое доверие к клиенту, например, клиент является частью операционной системы или производится известной компанией. Сервер аутентификации должен использовать этот режим только в том случае, если другие режимы авторизации не могут быть реализованы.

Процесс работы в режиме пароля:

  1. (A) Пользователь предоставляет клиенту имя пользователя и пароль.
  2. (B) Клиент отправляет имя пользователя и пароль на сервер аутентификации и запрашивает токен у последнего.
  3. (C) После того, как сервер аутентификации подтвердит правильность, он предоставляет токен доступа клиенту.

Описание архитектуры системы (Конечно, это начальная архитектура без шлюза, а необоснованные аспекты этой архитектуры будут представлены позже.):

  • Клиент: терминалы, такие как приложение, веб и т. д.
  • Центр аутентификации: herring-oauth2, основная сторона реализации OAuth2, генерация токена, обновление и проверка выполняются в центре аутентификации.
  • Member service: herring-member-service, один из микросервисов, отправится в центр аутентификации для проверки после получения запроса.
  • Сервис заказов: herring-orders-service, второй микросервис, отправится в удостоверяющий центр для проверки после получения запроса.
  • Товарный сервис: селедка-товар-сервис, третий микросервис, отправится в удостоверяющий центр на проверку после получения запроса.

На приведенной выше диаграмме показан процесс запроса между клиентом и микрослужбой с использованием OAuth2. Общий процесс заключается в том, что клиент обменивает токен с именем пользователя и паролем на сервер аутентификации и возвращает его клиенту.Клиент передает токен каждой микрослужбе для запроса интерфейса данных.Как правило, токен помещается в заголовок. Когда микросервис получает запрос, он сначала отправляет токен на сервер аутентификации, чтобы проверить правильность токена и, если он действителен, динамически возвращает данные в соответствии с ролью и разрешениями пользователя.

3. Создайте центр аутентификации Oauth2 (сервер)

  1. Добавить зависимости файла pom:

spring-cloud-starter-oauth2 включает в себя spring-cloud-starter-security, поэтому нет необходимости вводить его отдельно.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
  1. Базовая конфигурация WebSecurity: WebSecurityConfig extends WebSecurityConfigurerAdapter

Украшен аннотацией @EnableWebSecurity и унаследован от класса WebSecurityConfigurerAdapter.

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 允许匿名访问所有接口 主要是 oauth2 接口
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").permitAll();
    }

}

Смысл этого класса в том, чтобы объявить два bean-компонента, PasswordEncoder и AuthenticationManager.

  • BCryptPasswordEncoder — это класс инструмента для шифрования паролей, который может обеспечить необратимое шифрование,
  • AuthenticationManager — это bean-компонент управления авторизацией, который необходимо указать для реализации режима пароля OAuth2.
  1. Реализовать службу UserDetails: HerringUserDetailsService implements UserDetailsService

Ядром UserDetailsService является метод loadUserByUsername, который получает строковый параметр, представляющий собой переданное имя пользователя, и возвращает объект UserDetails.

@Slf4j
@Component(value = "herringUserDetailsService")
public class HerringUserDetailsService implements UserDetailsService {

    @Resource
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username is:" + username);
        // 查询数据库操作
        if (!username.equals("admin")) {
            throw new UsernameNotFoundException("the user is not found");
        } else {
            // 用户角色也应在数据库中获取
            String role = "ROLE_ADMIN";
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority(role));
            // 线上环境应该通过用户名查询数据库获取加密后的密码
            String password = passwordEncoder.encode("123456");
            return new org.springframework.security.core.userdetails.User(username, password, authorities);
        }
    }

}

В демонстрационных целях имя пользователя, пароль и роли прописаны в коде.В формальной среде зашифрованный пароль и роли должны быть найдены в базе данных или других местах на основе имени пользователя. Учетная запись admin и пароль 123456, который будет использоваться позже при обмене токенов. И установите этому пользователю роль "ROLE_ADMIN".

  1. Файл конфигурации OAuth2: JwtOAuth2Config extends AuthorizationServerConfigurerAdapter

Создайте файл конфигурации, наследуемый от AuthorizationServerConfigurerAdapter, и включите аннотацию @EnableAuthorizationServer. Перед реализацией класса JwtOAuth2Config нам еще нужно провести ряд приготовлений.

@Configuration
@EnableAuthorizationServer
public class JwtOAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
	// ...
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
	// ...
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
	// ...
    }

}

Подготовка 1:
Переопределение метода configure (конечные конечные точки AuthorizationServerEndpointsConfigurer):

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        /**
         * jwt 增强模式
         */
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancerList = new ArrayList<>();
        enhancerList.add(jwtTokenEnhancer);
        enhancerList.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(enhancerList);
        endpoints.tokenStore(jwtTokenStore)
                .userDetailsService(herringUserDetailsService)
                /**
                 * 支持 password 模式
                 */
                .authenticationManager(authenticationManager)
                .tokenEnhancer(enhancerChain)
                .accessTokenConverter(jwtAccessTokenConverter);
    }
  • authenticationManage() вызывает этот метод для поддержки режима пароля.
  • userDetailsService() Устанавливает службу аутентификации пользователя.
  • tokenStore() указывает, как хранится токен.

Как правило, в системе есть два варианта: RedisToken и JwtToken. Каковы преимущества и недостатки этих двух методов токенов?

RedisToken (низкая производительность, высокий уровень безопасности, постоянство, проверка на стороне сервера):

  1. Дизайн RedisToken — это дизайн, который назначает случайные токены после входа в систему, а затем записывает соответствие между токенами и информацией о пользователе.
  2. RedisToken фактически требует серверного хранилища, и каждый раз при проверке необходимо запрашивать серверное хранилище Redis.
  3. RedisToken должен храниться и запрашиваться с постоянством, поэтому производительность низкая, но он может вовремя закрыть авторизацию, а авторизацию входа можно увидеть и проверить, и каждый токен будет иметь соответствующую запись.
  4. Режим RedisToken подходит для систем с высоким уровнем безопасности и анализа информации, таких как вход пользователя в систему, таких как правительственные системы, платежные системы и т. д., которые не могут допустить кражи токенов с высоким авторитетом, но не могут быть авторизованы вовремя.

JwtToken (более высокая производительность, низкий уровень безопасности, отсутствие постоянства, проверка клиента):

  1. JwtToken — это дизайн без сохранения состояния. Ключ информации для входа пользователя хранится в зашифрованных данных jwt. В этом дизайне серверу не нужно хранить зашифрованный текст jwt, и ему нужно только расшифровать, чтобы получить информацию о пользователе, например информацию об авторизации. Этот дизайн использует вычислительную мощность для снижения нагрузки и сложности проектирования базы данных и кэша в соответствии с дизайном токена, поэтому его суть заключается не в сохранении авторизации входа, а в сохранении информации авторизации через сам зашифрованный текст.
  2. JwtToken не требует серверного хранилища, сама информация хранится в самом jwt, этот режим не требует использования базы данных.
  3. У популярной модели JwtToken есть конструктивный недостаток.Он передает информацию о пользователе через зашифрованный текст, поэтому сервер не может закрыть авторизацию входа пользователя в этой инфраструктуре.Если зашифрованный текст jwt пользователя украден, то хакер может войти в систему как пользователь и не может закрыть украденный зашифрованный текст jwt, даже если он знает, что зашифрованный текст потерян.
  4. Чтобы решить эту проблему, JwtToken может использовать период действия внутренней проверки jwt и режим черного списка jwt, но срок действия никогда не может вовремя остановить авторизацию jwt, что является временным решением. Для режима черного списка jwt требуется база данных или память для хранения черного списка, что фактически нарушает принцип разработки jwt без базы данных.
  5. JwtToken больше подходит для серверов с низким уровнем безопасности, таких как обычные блоги, читалки и т. д. Этот сервис допускает свободную авторизацию входа, даже если зашифрованный текст будет потерян, это не нанесет серьезных потерь пользователям, но может получить более высокий уровень обслуживания. представление.

Здесь мы выбираем метод JwtToken. При использовании метода jwt нет необходимости хранить токен на сервере.jwt имеет собственный специальный метод шифрования, который может эффективно предотвратить подделку данных.Пока ключевая информация, такая как пароли пользователей, не помещается в jwt, безопасность может быть гарантирована.

4.1. Добавьте класс конфигурации JwtConfig:

JwtAccessTokenConverter предназначен для преобразования данных jwt, поскольку jwt имеет собственный уникальный формат данных.

@Configuration
public class JwtTokenConfig {

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("sign-8888");
        return accessTokenConverter;
    }

}

4.2. Добавьте усилитель jwt:

Что делать, если я хочу добавить в jwt дополнительные поля (например, другую информацию о пользователе), конечно. spring security oauth2 предоставляет усилитель TokenEnhancer.

@Component
public class JwtTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Map<String, Object> info = new HashMap<>();
        info.put("jwt-ext", "JWT 扩展信息");
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
        return oAuth2AccessToken;
    }

}

Подготовка вторая:
Переопределение метода configure (клиенты ClientDetailsServiceConfigurer):

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
        jcsb.passwordEncoder(passwordEncoder);
    }

4.3. Добавить зависимость Mysql файла pom:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>

4.4. Добавьте конфигурацию базы данных в файл конфигурации application.yml.:

После Spring Boot 2.0 hikari по умолчанию используется в качестве пула соединений с базой данных. Если вы используете другие пулы соединений, вам необходимо ввести соответствующие пакеты, а затем соответствующим образом увеличить конфигурацию.

server:
  port: 10800

spring:
  application:
    name: oauth2-service

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://(安装Mysql的机器IP):3306/herring_oauth2?characterEncoding=UTF-8&useSSL=false
    username: root
    password: (你的root密码)
    hikari:
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      maximum-pool-size: 9

4.5. Добавьте таблицу oauth_client_details в базу данных и вставьте данные:

Примечание. Поле client_secret не может быть напрямую исходным значением секрета, оно должно быть зашифровано. Поскольку используется BCryptPasswordEncoder, последним вставленным значением должно быть значение после BCryptPasswordEncoder.encode().

DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

INSERT INTO `oauth_client_details` VALUES ('app-client', 'member-service,orders-service,product-service', '$2a$10$DVdqTrpMWBjed3C3F43v0ewmCpkg9V0scgGAS9dYoYrjLm6bHce5S', 'all', 'authorization_code,refresh_token,password', NULL, NULL, 7200, 72000, NULL, '1');
INSERT INTO `oauth_client_details` VALUES ('web-client', 'member-service,orders-service,product-service', '$2a$10$DVdqTrpMWBjed3C3F43v0ewmCpkg9V0scgGAS9dYoYrjLm6bHce5S', 'all', 'authorization_code,refresh_token,password', NULL, NULL, 7200, 72000, NULL, '1');

Некоторые параметры в таблице oauth_client_details объясняются следующим образом:

  • cleint-id: соответствует параметру client-id, определенному запрашивающей стороной;
  • client-secret: соответствует параметру client-secret, определенному запрашивающей стороной;
  • resource_ids: Опишите отношения между ресурсным сервером и клиентом.Например, я определил cleint-id=app-client, resource_ids=member-service,orders-service, product-service, то есть запрос от приложения-клиента сторона разрешает доступ к этим серверам ресурсов;
  • access_token_validity: период действия access_token (секунды);
  • refresh_token_validity: период действия refresh_token (в секундах);
  • scopesиauthorities: Разрешения, используемые для ограничения доступа клиентов (обратите внимание, что они не принадлежат пользователю). Эти два поля похожи, область действия — это поле в протоколе oauth2, а полномочия — это поле в структуре spring-security. При обмене токена будет принят параметр области действия.Только в пределах определения области действия токен может быть обменен обычным образом.
  • authorized_grant_types: Тип авторизации может включать один или несколько следующих параметров:
  1. авторизация_код: Тип кода авторизации.
  2. неявный: неявный тип предоставления.
  3. password: Тип пароля владельца ресурса (т.е. пользователя).
  4. client_credentials: тип учетных данных клиента (идентификатор клиента и ключ).
  5. refresh_token: Получите новый токен с помощью токена обновления, полученного с помощью вышеуказанной авторизации.

Подготовка третья:
Переопределение метода configure(AuthorizationServerSecurityConfigurer security):

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("isAuthenticated()");
        security.tokenKeyAccess("isAuthenticated()");
    }

Этот метод ограничивает доступ клиента к интерфейсу аутентификации.

  • allowFormAuthenticationForClients() — разрешить клиентам доступ к интерфейсу авторизации OAuth2, иначе токен запроса вернет 401.
  • checkTokenAccess() и tokenKeyAccess() позволяют авторизованным пользователям получать доступ к интерфейсу checkToken и интерфейсу получения токена соответственно.

4.6. Конфигурационный класс JwtConfig Окончательное содержимое:

@Configuration
@EnableAuthorizationServer
public class JwtOAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Resource
    private DataSource dataSource;
    @Resource
    private TokenStore jwtTokenStore;
    @Resource
    private TokenEnhancer jwtTokenEnhancer;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private UserDetailsService herringUserDetailsService;
    @Resource
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        /**
         * jwt 增强模式
         */
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancerList = new ArrayList<>();
        enhancerList.add(jwtTokenEnhancer);
        enhancerList.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(enhancerList);
        endpoints.tokenStore(jwtTokenStore)
                .userDetailsService(herringUserDetailsService)
                /**
                 * 支持 password 模式
                 */
                .authenticationManager(authenticationManager)
                .tokenEnhancer(enhancerChain)
                .accessTokenConverter(jwtAccessTokenConverter);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
        jcsb.passwordEncoder(passwordEncoder);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("isAuthenticated()");
        security.tokenKeyAccess("isAuthenticated()");
    }

}
  1. Запустите проект и просмотрите связанные интерфейсы:

Запустите проект, если вы используете IDEA, вы увидите интерфейс RESTful, связанный с oauth2, в окне сопоставления ниже.

Основные из них следующие:

POST /oauth/authorize  授权码模式认证授权接口
GET/POST /oauth/token  获取 token 的接口
POST  /oauth/check_token  检查 token 合法性接口

4. Создайте службу ресурсов Oauth2 (клиент)

Создайте один из следующих сервисов:

  • Member service: herring-member-service, один из микросервисов, отправится в центр аутентификации для проверки после получения запроса.
  • Сервис заказов: herring-orders-service, второй микросервис, отправится в удостоверяющий центр для проверки после получения запроса.
  • Товарный сервис: селедка-товар-сервис, третий микросервис, отправится в удостоверяющий центр на проверку после получения запроса.
  1. Добавить зависимости файла pom:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

Если в jwt добавляется дополнительная информация и после получения токена в формате jwt, пользовательский клиент должен разобрать jwt.

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
  1. файл конфигурации application.yml:

Идентификатор клиента и секрет клиента должны соответствовать конфигурации в службе проверки подлинности. access-token-uri — это интерфейс для получения токена, необходимого для режима пароля. user-authorization-uri требуется методом аутентификации кода авторизации и может быть опущен.

server:
  port: 10801
  servlet:
    context-path: /api/member

spring:
  application:
    name: member-service
    
security:
  oauth2:
    client:
      client-id: app-client
      client-secret: client-secret-8888
      user-authorization-uri: http://localhost:10800/oauth/authorize
      access-token-uri: http://localhost:10800/oauth/token
    resource:
      jwt:
        key-uri: http://localhost:10800/oauth/token_key
        key-value: sign-8888
  1. Конфигурация класса ResourceServerConfig:

Аннотация службы ресурсов @EnableResourceServer, обратите внимание, что signingKey, установленный JwtAccessTokenConverter, должен совпадать с ключом-значением в файле конфигурации, иначе он не сможет нормально декодировать jwt, что приведет к сбою проверки.

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JwtResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Resource
    private TokenStore jwtTokenStore;

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("sign-8888");
        accessTokenConverter.setVerifierKey("sign-8888");
        return accessTokenConverter;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(jwtTokenStore);
        resources.resourceId("member-service");
    }

}
  1. HelloController создает несколько тестовых интерфейсов:
@RestController
@RequestMapping
public class HelloController {

    @Resource
    private MemberService memberService;

    @RequestMapping("/service")
    public String service() {
        return memberService.sayHello();
    }

    @GetMapping(value = "/info/jwt")
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    public Object jwtParser(Authentication authentication) {
        authentication.getCredentials();
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
        String jwtToken = details.getTokenValue();
        Claims claims = Jwts.parser()
                .setSigningKey("sign-8888".getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(jwtToken)
                .getBody();
        return claims;
    }

}
@Service
public class MemberService {

    public String sayHello() {
        return "Hello, Member! ";
    }

}
  1. Запустите службу ресурсов и проверьте процесс режима пароля Oauth2.:

Токен запроса к центру сертификации Oauth2 (серверу):

#### 向 Oauth2 认证中心(服务端)请求 token

POST http://localhost:10800/oauth/token?grant_type=password&username=admin&password=123456&client_id=app-client&client_secret=client-secret-8888&scope=all
Accept: */*
Cache-Control: no-cache

Получите результат запроса:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYxMjg1ODQxNywiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJiMGQ5ZTI1Yy1jZGE3LTQ4MDctOWJmZS02ZjcyYjM4NGVhNTMiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.w4M9zCahAVISQ_wfKdkT6n9Aaw6kFtoh5HmCJ_uy-vU",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImIwZDllMjVjLWNkYTctNDgwNy05YmZlLTZmNzJiMzg0ZWE1MyIsImV4cCI6MTYxMjkyMzIxNywiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiIzZmQ2MWM4ZS1kNTcyLTQ0YjYtYjViNC0zMzc3ODQ5NjY4YmQiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.WxisDVLUlfP45pepc4sQM1M7UCvzsET0O8JvF11tKAI",
  "expires_in": 7199,
  "scope": "all",
  "jwt-ext": "JWT 扩展信息",
  "jti": "b0d9e25c-cda7-4807-9bfe-6f72b384ea53"
}
  • access_token : это токен, который необходимо ввести в последующем запросе, а также основная цель этого запроса.
  • token_type: носитель, который является наиболее распространенной формой токена доступа.
  • refresh_token: вы можете использовать это значение для обмена на новый токен позже без ввода пароля учетной записи.
  • expires_in: время истечения срока действия токена (в секундах)

2. Запросить данные у службы ресурсов Oauth2 (клиент):

Запросы без жетона:

#### 向 Oauth2 资源服务(客户端)请求数据

GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache

Получите результат запроса:

{
  "error": "unauthorized",
  "error_description": "Full authentication is required to access this resource"
}

Запрос с неправильным токеном:

#### 向 Oauth2 资源服务(客户端)请求数据

GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache
Authorization: bearer 123456

Получите результат запроса:

{
  "error": "invalid_token",
  "error_description": "Cannot convert access token to JSON"
}

Даже если его можно разобрать в JSON, ошибка токена сообщит о других ошибках, и правильные данные запроса не будут получены.

В запросе указан правильный токен, требуется авторизация в заголовке запроса, формат — носитель + пробел + токен.:

#### 向 Oauth2 资源服务(客户端)请求数据

GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYxMjg1ODQxNywiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJiMGQ5ZTI1Yy1jZGE3LTQ4MDctOWJmZS02ZjcyYjM4NGVhNTMiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.w4M9zCahAVISQ_wfKdkT6n9Aaw6kFtoh5HmCJ_uy-vU

Получите результат запроса:

Hello, Member! 

3. Обновите токен в центре аутентификации Oauth2 (сервере):

#### 向 Oauth2 认证中心(服务端)刷新 token

POST http://localhost:10800/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImIwZDllMjVjLWNkYTctNDgwNy05YmZlLTZmNzJiMzg0ZWE1MyIsImV4cCI6MTYxMjkyMzIxNywiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiIzZmQ2MWM4ZS1kNTcyLTQ0YjYtYjViNC0zMzc3ODQ5NjY4YmQiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.WxisDVLUlfP45pepc4sQM1M7UCvzsET0O8JvF11tKAI&client_id=app-client&client_secret=client-secret-8888
Accept: */*
Cache-Control: no-cache

Получите результат запроса:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYxMjg1OTQxMSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJkYWVlNjlkZi02NTFhLTQ1MmItYjA0Yi05N2FhYTc2MjkzYTgiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.F30LZplYodM7zH0N6gwBA29uCBObZISgOPXf-PKB3aI",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImRhZWU2OWRmLTY1MWEtNDUyYi1iMDRiLTk3YWFhNzYyOTNhOCIsImV4cCI6MTYxMjkyMzIxNywiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiIzZmQ2MWM4ZS1kNTcyLTQ0YjYtYjViNC0zMzc3ODQ5NjY4YmQiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.8Ix-x4VWTsDKGeqGqjTzlVJk-P1OnD-ISn-zsQPQUG8",
  "expires_in": 7199,
  "scope": "all",
  "jwt-ext": "JWT 扩展信息",
  "jti": "daee69df-651a-452b-b04b-97aaa76293a8"
}

Запросить данные с новым токеном из службы ресурсов Oauth2 (клиент):

#### 向 Oauth2 资源服务(客户端)请求数据

GET http://localhost:10801/api/member/service
Accept: */*
Cache-Control: no-cache
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYxMjg1OTQxMSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJkYWVlNjlkZi02NTFhLTQ1MmItYjA0Yi05N2FhYTc2MjkzYTgiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.F30LZplYodM7zH0N6gwBA29uCBObZISgOPXf-PKB3aI

Получите результат запроса:

Hello, Member! 

В-четвертых, запросите службу ресурсов Oauth2 (клиент) для просмотра содержимого декодирования токена определенного jwt:

#### 向 Oauth2 资源服务(客户端)请求查看具体 jwt 的 token 解码内容

GET http://localhost:10801/api/member/info/jwt
Accept: */*
Cache-Control: no-cache
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYxMjg1OTQxMSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJkYWVlNjlkZi02NTFhLTQ1MmItYjA0Yi05N2FhYTc2MjkzYTgiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.F30LZplYodM7zH0N6gwBA29uCBObZISgOPXf-PKB3aI

Получите результат запроса:

{
  "aud": [
    "orders-service",
    "member-service",
    "product-service"
  ],
  "user_name": "admin",
  "jwt-ext": "JWT 扩展信息",
  "scope": [
    "all"
  ],
  "exp": 1612859411,
  "authorities": [
    "ROLE_ADMIN"
  ],
  "jti": "daee69df-651a-452b-b04b-97aaa76293a8",
  "client_id": "app-client"
}

5. Имитация ступенчатой ​​ямы аутентификации вызовов микросервисов

Допустим, мы построили почти те же три микросервиса по вышеуказанным шагам:

  • Member service: herring-member-service, один из микросервисов, отправится в центр аутентификации для проверки после получения запроса.
  • Сервис заказов: herring-orders-service, второй микросервис, отправится в удостоверяющий центр для проверки после получения запроса.
  • Товарный сервис: селедка-товар-сервис, третий микросервис, отправится в удостоверяющий центр на проверку после получения запроса.

В этот момент я пишу запрос, указывающий на членскую службу, ноMember-service необходимо вызвать удаленную службу заказов службы или Только продукт-услуга может вернуть правильный результат. Если мы не используем аутентификацию Oauth2, я думаю, все в порядке. Что, если мы используем аутентификацию Oauth2?? Давайте попробуем сейчас.

  1. Добавить зависимости файла pom:
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  1. Добавьте аннотацию @EnableFeignClients:
@EnableFeignClients
@SpringBootApplication
public class MemberServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(MemberServiceApplication.class, args);
    }

}
  1. член-сервис, сервис-заказы, сервис-продукт добавляют следующие похожие интерфейсы:
@RestController
@RequestMapping
public class HelloController {

    @Resource
    private MemberService memberService;

    @RequestMapping("/service")
    public String service() {
        return memberService.sayHello();
        // return ordersService.sayHello();
        // return productService.sayHello();
    }

}

Существует различие: посетите /api/member/xxx и верните соответствующий Hello, xxx!

@Service
public class MemberService {

    public String sayHello() {
        return "Hello, Member! ";
        // return "Hello, Orders! ";
        // return "Hello, Product! ";
    }

}
  1. Member-service Добавить клиентов для удаленного доступа к службам заказов и продуктам:
@FeignClient(name = "orders-service", path = "/api/orders")
public interface OrdersClient {

    @RequestMapping("/service")
    String service();

}
@FeignClient(name = "product-service", path = "/api/product")
public interface ProductClient {

    @RequestMapping("/service")
    String service();

}
  1. Member-service добавляет новый интерфейс /api/member/hello:
    @Resource
    private MemberService memberService;
    @Resource
    private ProductClient productClient;
    @Resource
    private OrdersClient ordersClient;

    @RequestMapping("/hello")
    public String hello() {
        String product = productClient.service();
        String orders = ordersClient.service();
        return memberService.sayHello() + product + orders;
    }
  1. С правильным токеном запросите данные /api/member/hello у службы ресурсов Oauth2 (клиент).:
#### 向 Oauth2 资源服务(客户端)请求数据

GET http://localhost:10801/api/member/hello
Accept: */*
Cache-Control: no-cache
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYxMjg1OTQxMSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJkYWVlNjlkZi02NTFhLTQ1MmItYjA0Yi05N2FhYTc2MjkzYTgiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.F30LZplYodM7zH0N6gwBA29uCBObZISgOPXf-PKB3aI

Получите результат запроса:

{
  "timestamp": 1612855021645,
  "status": 500,
  "error": "Internal Server Error",
  "message": "[401] during [GET] to [http://product-service/api/product/service] [ProductClient#service()]: [{\"error\":\"unauthorized\",\"error_description\":\"Full authentication is required to access this resource\"}]",
  "path": "/api/member/hello"
}

Зачем? У меня явно есть правильный токен в заголовке http-запроса, но я сообщаю об ошибке 401, запрещенной.

Причина в том, что когда я запрашиваю данные /api/member/hello, несмотря на то, что заголовок HTTP-запроса содержит правильный токен, при удаленном вызове сервисов-заказов и сервис-продуктов вновь созданный запрос с помощью feign не будет нести этот token Это два разных HTTP-запроса, что приводит к сообщению об ошибке 401.

Решение состоит в том, чтобы добавить RequestInterceptor в службу-член, службу-заказов и службу-продукт:

public class TokenRelayRequestInterceptor implements RequestInterceptor {

    public static final String AUTH_TOKEN = "Authorization";

    @Override
    public void apply(RequestTemplate template) {
        // 获取该次请求得token 将token传递
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader(AUTH_TOKEN);
        if (!StringUtils.isEmpty(token)) {
            template.header(AUTH_TOKEN, token);
        }
    }

}

И добавьте конфигурацию в application.yml:

feign:
  client:
    config:
      default:
        requestInterceptors:
          - com.herring.feign.interceptor.TokenRelayRequestInterceptor
  1. С правильным токеном снова запросите данные /api/member/hello у службы ресурсов Oauth2 (клиента).:
#### 向 Oauth2 资源服务(客户端)请求数据

GET http://localhost:10801/api/member/hello
Accept: */*
Cache-Control: no-cache
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib3JkZXJzLXNlcnZpY2UiLCJnYXRld2F5LXNlcnZpY2UiLCJtZW1iZXItc2VydmljZSIsInByb2R1Y3Qtc2VydmljZSJdLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTYxMjg1OTQxMSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJkYWVlNjlkZi02NTFhLTQ1MmItYjA0Yi05N2FhYTc2MjkzYTgiLCJjbGllbnRfaWQiOiJhcHAtY2xpZW50In0.F30LZplYodM7zH0N6gwBA29uCBObZISgOPXf-PKB3aI

Получите результат запроса:

Hello, Member! Hello, Product! Hello, Orders! 

Размышления об архитектуре системы Oauth2

Здесь построена система Oauth2, давайте задумаемся об архитектуре системы:

Как упоминалось ранее, в такой архитектуре есть проблема. В чем проблема? Не решена ли только что проблема аутентификации 401 запрещено? проблема в томЛогика аутентификации связана с микрослужбами, каждая микрослужба должна устанавливаться отдельно, и существует несколько повторяющихся логик аутентификации.(Конечно, это возможно с точки зрения безопасности).

Мы решили, что JwtToken можно допустить, потому что служба ресурсов Oauth2 (клиент) может выполнять аутентификацию локально.Если выбран RedisToken, когда вызывающая ссылка запрашиваемой микрослужбы очень длинная, не должна ли каждая микрослужба запрашивать службу аутентификации Oauth2 (сервер) один раз, что формирует отношение 1-к-N между запросами и аутентификациями. трафик относительно велик, служба oauth2 находится под большим давлением(Независимо от того, насколько быстр Redis, он не может так играть).

  • Внедрите zuul или шлюз шлюза, совокупную службу аутентификации и службу аутентификации для шлюза..

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

Spring Cloud Alibaba Actual Combat (1) Подготовка
Spring Cloud Alibaba Actual Combat (2) Nacos
Spring Cloud Alibaba Actual Combat (3) Sentinel
Весеннее облако Alibaba бой (4) Oauth2
Spring Cloud Alibaba Actual Combat (5) Zuul
Реальные бои Spring Cloud Alibaba (6) Статьи RocketMQ
Spring Cloud Alibaba Actual Combat (7) Seata
Spring Cloud Alibaba Actual Combat (8) SkyWalking

Адрес проекта на GitHub:GitHub.com/D2C-CAI/Хайер…