Унифицированная схема аутентификации микросервисов Spring Cloud OAuth2 + JWT

Spring Cloud

1. Идея единой аутентификации в рамках микросервисной архитектуры

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

    Решение Session также имеет недостатки, такие как использование файлов cookie, которые нельзя эффективно использовать на мобильных терминалах и т. д.

  • Метод аутентификации на основе токенов: в методе аутентификации на основе токенов серверу не нужно хранить данные аутентификации, которые просты в обслуживании и обладают высокой масштабируемостью.Клиент может хранить токен в любом Он может реализовать единый механизм аутентификации в сети и в приложении. Его недостатки также очевидны: из-за замкнутой информации токен обычно имеет большой объем данных, и каждый запрос нужно передавать, поэтому он занимает больше пропускной способности. Кроме того, операция проверки подписи токена также создаст дополнительную нагрузку на ЦП.

Во-вторых, OAUTH2 открытое соглашение о авторизации / стандарт

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

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

OAuth2 является продолжением протокола OAuth, но полностью устаревает OAuth1, не будучи обратно совместимым с OAuth1.

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

Например: Все сторонние авторизации для входа — это OAuth.Если вы не хотите регистрироваться на стороннем веб-сайте, вы можете использовать QQ и WeChat для авторизации входа и открытия некоторых функций.

2. Роли и процессы протокола OAuth2

  • Владелец ресурса: может пониматься как сам пользователь
  • Клиент (Клиент): веб-сайт или приложение, в которое мы хотим войти, например сеть pull hook.
  • Сервер аутентификации (Сервер авторизации): может пониматься как WeChat или QQ
  • Сервер ресурсов: его можно понимать как WeChat или QQ.

3. Когда следует использовать OAuth2?

  • Сторонняя авторизация Спортивная сцена: авторизация WeChat, авторизация QQ, авторизация Weibo и т. д. — это типичный сценарий использования OAuth2.
  • Сценарий единого входа: в сценарии микрослужбы вы можете специализироваться на центре проверки подлинности (выступающем в качестве платформы проверки подлинности), и все службы должны обращаться в этот центр проверки подлинности для проверки подлинности.Бесплатная сериализация в службах в рамках авторизации.

4. Метод авторизации токена OAuth2

  1. Код авторизации
  2. Пароль (пароль) предоставляет имя пользователя + пароль в обмен на токен токена
  3. неявный (неявный)
  4. учетные данные клиента

Режим кода авторизации использует адрес обратного вызова и является наиболее сложным методом авторизации.Этот режим используется для сторонних входов, таких как Weibo, WeChat и QQ. Как правило, используется режим пароля (укажите имя пользователя + пароль в обмен на токен).

3. Реализация Spring Cloud OAuth2 + JWT

1. Введение в Spring Cloud OAuth2

Spring Cloud OAuth2 — это реализация протокола OAuth2 в системе Spring Cloud, которую можно использовать для унифицированной аутентификации (проверки легитимности личности) и авторизации (проверки полномочий) для нескольких микросервисов. При отправке определенного типа grant_type в службу OAuth2 (унифицированная служба проверки подлинности и авторизации) для централизованной проверки подлинности и авторизации получается access_token (токен доступа), и этот токен является доверенным для других микросервисов.

Примечание: Суть использования OAuth2 для решения проблемы заключается во введении уровня аутентификации и авторизации.Слой аутентификации и авторизации подключает владельца ресурса.На уровне авторизации владелец ресурса может авторизовать сторонние приложения для доступа Некоторые из наших охраняемых ресурсов.

2. Spring Cloud OAuth2 создает идею унифицированной службы аутентификации для микросервисов.

Примечание. Если сравнивать его со сторонним входом в систему, в нашем сценарии унифицированной аутентификации сервер ресурсов фактически является нашими различными защищенными микросервисами, а различные интерфейсы доступа к API в микросервисах — это ресурсы, которые инициируют просмотр HTTP-запросов. Сервер — это клиент-клиент (соответствующий стороннему приложению), а ресурсный сервер — это имя аватара и другая информация, полученная от стороннего логина (например, QQ).

3. (Войдите в разработку) Возьмите сервер авторизации и сервер ресурсов

Создайте сервер авторизации, отвечающий за выдачу токенов

  • Создайте новый проект lagou-cloud-oauth-server-9999.

  • Введите зависимость pom.xml

        <!--导入Eureka Client依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>


        <!--导入spring cloud oauth2依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.security.oauth.boot</groupId>
                    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.1.11.RELEASE</version>
        </dependency>
        <!--引入security对oauth2的支持-->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.4.RELEASE</version>
        </dependency>
  • application.yml (соберите сервер аутентификации, в файле конфигурации нет ничего особенного)
server:
  port: 9999
Spring:
  application:
    name: lagou-cloud-oauth-server
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
    username: root
    password: 123456
    druid:
      initialSize: 10
      minIdle: 10
      maxActive: 30
      maxWait: 50000
eureka:
  client:
    serviceUrl: # eureka server的路径
      defaultZone: http://lagoucloudeurekaservera:8761/eureka/,http://lagoucloudeurekaserverb:8762/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表
  instance:
    #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
    prefer-ip-address: true
    #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@

  • В классе входа нет ничего особенного (@SpringBootApplication @EnableDiscoveryClient все еще второй)
  • Класс конфигурации сервера аутентификации
package config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

/**
 * @author: 190coder <190coder.cn>
 * @description:  当前类为Oauth2 server的配置类(需要继承特定的⽗类)
 * @create: 2020-07-30 20:03
 */

@Configuration
@EnableAuthorizationServer // 开启认证服务器功能
public class OauthServerConfiger  extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private AuthenticationManager authenticationManager;
    
    /**
     * 认证服务器最终是以api接⼝的⽅式对外提供服务(校验合法性并⽣成令牌、校验令牌等)
     * 那么,以api接⼝⽅式对外的话,就涉及到接⼝的访问权限,我们需要在这⾥进⾏必要的配置
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);

        // 相当于打开endpoints 访问接⼝的开关,这样的话后期我们能够访问该接⼝
        security.
                // 允许客户端表单认证
                allowFormAuthenticationForClients()
                // 开启端⼝/oauth/token_key的访问权限(允许)
                .tokenKeyAccess("permitAll()")
                // 开启端⼝/oauth/check_token的访问权限(允许)
                .checkTokenAccess("permitAll()");

    }

    /**
     * 客户端详情配置,
     * ⽐如client_id,secret
     * 当前这个服务就如同QQ平台,拉勾⽹作为客户端需要qq平台进⾏登录授权认证等,提前需
       要到QQ平台注册,QQ平台会给拉勾⽹
     * 颁发client_id等必要参数,表明客户端是谁
     *
     * @param clients
     * @throws Exception
     *
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        super.configure(clients);

        clients.
                // 客户端信息存储在什么地⽅,可以在内存中,可以在数据库⾥
                inMemory()
                // 添加⼀个client配置,指定其client_id
                .withClient("client_lagou")
                // 指定客户端的密码/安全码
                .secret("abcxyz")
                // 指定客户端所能访问资源
                .resourceIds("autodeliver")
                // 认证类型/令牌颁发模式,可以配置多个在这⾥,但是不⼀定都⽤,具体使⽤哪种⽅式颁发token,需要客户端调⽤的时候传递参数指定
                .authorizedGrantTypes("password","refresh_token")
                // 客户端的权限范围,此处配置为all全部即可
                .scopes("all");
    }

    /**
     * 认证服务器是玩转token的,那么这⾥配置token令牌管理相关(token此时就是⼀个字符
        串,当下的token需要在服务器端存储,那么存储在哪⾥呢?都是在这⾥配置)
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);

        endpoints
                // 指定token的存储⽅法
                .tokenStore(tokenStore())
                //token服务的⼀个描述,可以认为是token⽣成细节的描述,⽐如有效时间多少等
                .tokenServices(authorizationServerTokenServices())
                // 指定认证管 理器,随后注⼊⼀个到当前类使⽤即可
                .authenticationManager(authenticationManager)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
    }

    /**
     * 该⽅法⽤户获取⼀个token服务对象(该对象描述了token有效期等信息)
     *
     * @return
     */
    private AuthorizationServerTokenServices authorizationServerTokenServices() {

        // 使⽤默认实现
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();

        // 是否开启令牌刷新
        defaultTokenServices.setSupportRefreshToken(true);
        // token以什么形式存储
        defaultTokenServices.setTokenStore(tokenStore());
        // access_token就是我们请求资源需要携带的令牌
        defaultTokenServices.setAccessTokenValiditySeconds(30);
        // 设置刷新令牌的有效时间 3天
        defaultTokenServices.setRefreshTokenValiditySeconds(259200);

        return defaultTokenServices;

    }

    /**
     * 该⽅法⽤于创建tokenStore对象(令牌存储对象)token以什么形式存储
    */
    public TokenStore tokenStore(){
        return new InMemoryTokenStore();
    }
}

Интерпретация класса конфигурации:

  • О трех методах настройки

    • configure(ClientDetailsServiceConfigurer client): здесь инициализируются данные клиента.Вы можете написать здесь данные клиента или сохранить данные о звонках в базе данных.
    • configure(AuthorizationServerEndpointsConfigurer endpoints): настроить конечные точки доступа к токенам, службы токенов и носители.
    • configure(AuthorizationServerSecurityConfigurer oauthServer): настроить права доступа и службы токенов.
  • О TokenStore

    • Инмеморитокенсторе:хранится в памяти.Дефолт, можно прекрасно сделать на одном сервере (т.е. если вы доступны и волосы не чешутся, то не сделает бекап при сбое), и большинство предметов можно использовать.Версия реализована на пробу , вы можете использовать его при разработке, потому что он не сохраняется на диск, поэтому его легче отлаживать.
    • JdbcTokenStore : это реализация на основе JDBC, в которой токен будетсохранить в реляционной базе данных. При использовании этой версии реализации вы можете обмениваться информацией о токенах между разными серверами.При использовании этой версии добавьте зависимость «springjdbc» в свой путь к классам.
    • JwtTokenStore : полное имя — JSON Web Token (JWT), который может кодировать данные, связанные с токенами (поэтому для серверных служб,его не нужно хранитьЭто будет ⼀ тяжелое увеличенное преимущество), недостатком является токен, учитываемый с использованием пространства, чем будет увеличен, если вы больше заполняете учетные данные ⽐ Использовать домохозяйства, JWTTTOKELSTORE не сохранит никаких данных.
  • Класс конфигурации безопасности сервера аутентификации
package config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.ArrayList;

/**
 * @author: 190coder <190coder.cn>
 * @description: 该配置类,主要处理⽤户名和密码的校验等事宜
 * @create: 2020-07-30 20:52
 */

@Configuration
public class SecurityConfiger extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 注册⼀个认证管理器对象到容器
     *
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 
     * 处理⽤户名和密码验证事宜
     * 1)客户端传递username和password参数到认证服务器
     * 2)⼀般来说,username和password会存储在数据库中的⽤户表中
     * 3)根据⽤户表中数据,验证当前传递过来的⽤户信息的合法性
     *
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 在这个⽅法中就可以去关联数据库了,当前我们先把⽤户信息配置在内存中
        // 实例化⼀个⽤户对象(相当于数据表中的⼀条⽤户记录)
        UserDetails user = new User("admin","123456",new ArrayList<>());
        auth.inMemoryAuthentication()
                .withUser(user).passwordEncoder(passwordEncoder);
    }

    /**
     * 密码编码对象(密码不进⾏加密处理)
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

  • Тест: запустите реестр Eureka Server и службу аутентификации.

Доступ: http://localhost:9998/oauth/token?client_secret=abcxyz&grant_type=password&username=admin&password=123456&client_id=client_lagou

  • конечная точка: /oauth/токен

  • Получить параметры, переносимые токеном

    • client_id: идентификатор клиента
    • client_secret: единый пароль клиента
    • grant_type: указывает, какой тип гранта использовать, пароль
    • имя пользователя: имя пользователя
    • пароль: пароль
  • Проверить токен: http://localhost:9998/oauth/check_token?token=1068b57c-0b2d-4789-8ce5-fa968c402d0a

  • Обновить токен: http://localhost:9998/oauth/token?grant_type=refresh_token&client_id=client_lagou&client_secret=abcxyz&refresh_token=7c47f959-17a6-4a09-8e7b-0f49bfaef725


Построить сервер ресурсов (надеюсь получить доступ к аутентифицированным микросервисам) Конфигурация сервера ресурсов

  • Представить соответствующий пакет jar outh2

  • Класс конфигурации службы ресурсов

package com.lagou.edu.config;

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.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;

/**
 * @author: 190coder <190coder.cn>
 * @description: 资源服务配置类
 * @create: 2020-07-30 21:20
 */

@Configuration
@EnableResourceServer // 开启资源服务器功能
@EnableWebSecurity // 开启web访问安全
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {

    private String sign_key = "190coder.cn"; // jwt签名密钥

    /**
     * 该⽅法⽤于定义资源服务器向远程认证服务器发起请求,进⾏token校验等事宜
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("autodeliver");
        // 定义token服务对象(token校验就应该靠token服务对象)
        RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        // 校验端点/接⼝设置
        remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9998/oauth/check_token");
        // 携带客户端id和客户端安全码
        remoteTokenServices.setClientId("client_lagou");
        remoteTokenServices.setClientSecret("abcxyz");
        // 别忘了这⼀步
        resources.tokenServices(remoteTokenServices);

    }

    /**
     * 场景:⼀个服务中可能有很多资源(API接⼝)
     * 某⼀些API接⼝,需要先认证,才能访问
     * 某⼀些API接⼝,压根就不需要认证,本来就是对外开放的接⼝
     * 我们就需要对不同特点的接⼝区分对待(在当前configure⽅法中完成),设置
     是否需要经过认证
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                // 设置session的创建策略(根据需要创建即可)
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .authorizeRequests()
                // autodeliver为前缀的请求需要认证
                .antMatchers("/autodeliver/**").authenticated()
                // demo为前缀的请求需要认证
                .antMatchers("/demo/**").authenticated()
                .anyRequest().permitAll(); // 其他请求不认证
    }
}

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

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

Мы можем рассмотреть возможность использования JWT для преобразования.После использования механизма JWT серверу ресурсов не требуется доступ к серверу аутентификации...

5. JWT трансформирует механизм хранения токенов единого центра аутентификации и авторизации

5.1 Введение токена JWT:

В ходе вышеприведенного теста мы обнаружили, что когда служба ресурсов и служба авторизации не работают вместе, служба ресурсов использует RemoteTokenServices для удаленного запроса службы авторизации для проверки токена.Если объем доступа велик, производительность системы будет затронутый. Решение вышеуказанной проблемы: Вышеупомянутая проблема может быть решена с помощью токена в формате JWT. Пользователь получит токен JWT после прохождения аутентификации. Токен JWT уже содержит информацию, связанную с пользователем, и клиенту нужно только нести JWT для доступа к ресурсам Служба ресурсов самостоятельно выполняет проверку токена в соответствии с заранее согласованным алгоритмом и не требует каждый раз запрашивать службу аутентификации для завершения авторизации.

5.2 Что такое JWT?

JSON Web Token (JWT) — это открытый отраслевой стандарт (RFC 7519), который определяет краткий автономный формат протокола для передачи объектов json между сторонами связи.Передаваемая информация может быть подписана цифровой подписью.Аутентифицирована и доверена.JWT может быть подписан с использованием алгоритма HMAC или пары открытого/закрытого ключа RSA для предотвращения несанкционированного доступа..

5.3 Структура токена JWT

Токен JWT состоит из трех частей, каждая часть разделена точкой (.), например: xxxxx.yyyyy.zzzzz

  • Заголовок: заголовок включает тип токена (например, JWT) и используемый алгоритм хеширования (например, HMAC SHA256 или RSA), например
{
"alg": "HS256",
"typ": "JWT"
}

Будет ли содержимое топа с кодировкой Base64URL, получится строка, являющаяся частью токена JWT.

  • Полезная нагрузка Вторая часть — полезная нагрузка, а контент — тоже json-объект, место для хранения достоверной информации, может хранить готовые поля, предоставляемые jwt, такие как: iss (эмитент), exp (метка времени истечения), дополнительные (лицевые) пользователи) и т. д., поля также можно настраивать. В этой части не рекомендуется хранить конфиденциальную информацию, так как эта часть может быть декодирована для восстановления исходного содержимого. Наконец, закодируйте вторую часть полезной нагрузки с помощью Base64Url и получите строку, являющуюся второй частью токена JWT. Пример:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
  • Подпись Третья часть — это подпись, которая используется для предотвращения подделки содержимого jwt. Эта часть использует base64url для кодирования первых двух частей, использует точки (.) после кодирования для формирования строки и, наконец, использует для подписи алгоритм подписи, объявленный в заголовке.
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
- base64UrlEncode(header):jwt令牌的第⼀部分。
- base64UrlEncode(payload):jwt令牌的第⼆部分。
- secret:签名所使⽤的密钥。

5.4 Модификация кода (сервер аутентификации и сервер ресурсов)

Преобразование JWT на стороне сервера аутентификации (обновление основного класса конфигурации)

  • Измените носитель, на котором хранится токен, измените его на jwt, а затем настройте преобразователь для преобразования информации в токен jwt.
    /**
     * 该⽅法⽤于创建tokenStore对象(令牌存储对象)token以什么形式存储
    */
    public TokenStore tokenStore(){
        //return new InMemoryTokenStore();
        // 使⽤jwt令牌
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    private String sign_key = "xxxxx"; // jwt签名密钥
    
    /**
     * 返回jwt令牌转换器(帮助我们⽣成jwt令牌的)
     * 在这⾥,我们可以把签名密钥传递进去给转换器对象
     * @return
     */
    private JwtAccessTokenConverter jwtAccessTokenConverter() {

        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        // 签名密钥
        jwtAccessTokenConverter.setSigningKey(sign_key);
        // 验证时使⽤的密钥,和签名密钥保持⼀致
        jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));

        return jwtAccessTokenConverter;
    }
  • Изменить средства защиты JWT Token Service, в основном для добавления конвертера jwt

Сервер ресурсов проверяет токен JWT.

  • Не нужно взаимодействовать с удаленным сервером аутентификации, добавить локальный tokenStore, изменить метод configure
    /**
     * 该⽅法⽤于定义资源服务器向远程认证服务器发起请求,进⾏token校验等事宜
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//        resources.resourceId("autodeliver");
//        // 定义token服务对象(token校验就应该靠token服务对象)
//        RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
//        // 校验端点/接⼝设置
//        remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9998/oauth/check_token");
//        // 携带客户端id和客户端安全码
//        remoteTokenServices.setClientId("client_lagou");
//        remoteTokenServices.setClientSecret("abcxyz");
//        // 别忘了这⼀步
//        resources.tokenServices(remoteTokenServices);

        // JWT 令牌改造
        resources.resourceId("autodeliver").tokenStore(tokenStore()).stateless(true);

    }
  • Добавьте такой же как сервер аутентификации и попросите у него хранилище токенов
     /**
     * 该⽅法⽤于创建tokenStore对象(令牌存储对象)token以什么形式存储
     */
    public TokenStore tokenStore(){
        //return new InMemoryTokenStore();
        // 使⽤jwt令牌
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    private String sign_key = "190coder.cn"; // jwt签名密钥

    /**
     * 返回jwt令牌转换器(帮助我们⽣成jwt令牌的)
     * 在这⾥,我们可以把签名密钥传递进去给转换器对象
     * @return
     */
    private JwtAccessTokenConverter jwtAccessTokenConverter() {

        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        // 签名密钥
        jwtAccessTokenConverter.setSigningKey(sign_key);
        // 验证时使⽤的密钥,和签名密钥保持⼀致
        jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));

        return jwtAccessTokenConverter;
    }

6. Загрузите информацию о клиенте Oauth2 из базы данных.

  • Создайте таблицу данных и инициализируйте ее. Данные можно получить по исходному коду (имя таблицы и поля остаются фиксированными)
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
配置数据源
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
BEGIN;
INSERT INTO `oauth_client_details` VALUES ('client_lagou123',
'autodeliver,resume', 'abcxyz', 'all', 'password,refresh_token', NULL, NULL,
7200, 259200, NULL, NULL);
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
  • Настроить источник данных
Spring:
  application:
    name: test-oauth2-9998
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/lagou_homework?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
    username: root
    password: 123321
    druid:
      initialSize: 10
      minIdle: 10
      maxActive: 30
      maxWait: 50000
  • Трансформация класса основной конфигурации сертифицированного сервера
/**
     * 客户端详情配置,
     * ⽐如client_id,secret
     * 当前这个服务就如同QQ平台,拉勾⽹作为客户端需要qq平台进⾏登录授权认证等,提前需
       要到QQ平台注册,QQ平台会给拉勾⽹
     * 颁发client_id等必要参数,表明客户端是谁
     *
     * @param clients
     * @throws Exception
     *
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        super.configure(clients);

//        clients.
//                // 客户端信息存储在什么地⽅,可以在内存中,可以在数据库⾥
//                inMemory()
//                // 添加⼀个client配置,指定其client_id
//                .withClient("client_lagou")
//                // 指定客户端的密码/安全码
//                .secret("abcxyz")
//                // 指定客户端所能访问资源
//                .resourceIds("autodeliver")
//                // 认证类型/令牌颁发模式,可以配置多个在这⾥,但是不⼀定都⽤,具体使⽤哪种⽅式颁发token,需要客户端调⽤的时候传递参数指定
//                .authorizedGrantTypes("password","refresh_token")
//                // 客户端的权限范围,此处配置为all全部即可
//                .scopes("all");

        // 从内存中加载客户端详情改为从数据库中加载客户端详情
        clients.withClientDetails(createJdbcClientDetailsService());
    }

    @Autowired
    private DataSource dataSource;
    @Bean
    public JdbcClientDetailsService createJdbcClientDetailsService() {
        JdbcClientDetailsService jdbcClientDetailsService = new
                JdbcClientDetailsService(dataSource);
        return jdbcClientDetailsService;
    }

7. Проверка легитимности пользователя из базы данных

  • Создайте пользователей таблицы данных (имя таблицы не нужно исправлять), инициализируйте данные
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` char(10) DEFAULT NULL,
`password` char(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of users
-- ----------------------------
BEGIN;
INSERT INTO `users` VALUES (4, 'lagou-user', 'iuxyzds');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
  • Разработайте класс реализации интерфейса UserDetailsService и загрузите информацию о пользователе из базы данных в соответствии с именем пользователя.
/**
 * @author: 190coder <190coder.cn>
 * @description:  UserDetailsService接⼝的实现类
 * @create: 2020-07-30 23:19
 */
@Service
public class JdbcUserDetailsService implements UserDetailsService {

    // Jpa 查询
    @Autowired
    private UsersRepository usersRepository;

    /**
     * 根据username查询出该⽤户的所有信息,封装成UserDetails类型的对象返回,⾄于密码,框
     * 架会⾃动匹配
     *
     * @param s
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        Users users = usersRepository.findByUsername(s);
        return new User(users.getUsername(),users.getPassword(),new
                ArrayList<>());
    }
}
  • Используйте пользовательский объект службы сведений о пользователе (класс конфигурации аутентификации)
@Autowired
    private JdbcUserDetailsService jdbcUserDetailsService;
    /**
     *
     * 处理⽤户名和密码验证事宜
     * 1)客户端传递username和password参数到认证服务器
     * 2)⼀般来说,username和password会存储在数据库中的⽤户表中
     * 3)根据⽤户表中数据,验证当前传递过来的⽤户信息的合法性
     *
     */
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 在这个⽅法中就可以去关联数据库了,当前我们先把⽤户信息配置在内存中
        // 实例化⼀个⽤户对象(相当于数据表中的⼀条⽤户记录)
//        UserDetails user = new User("admin","123456",new ArrayList<>());
//        auth.inMemoryAuthentication()
//                .withUser(user).passwordEncoder(passwordEncoder);

        auth.userDetailsService(jdbcUserDetailsService).passwordEncoder(passwordEncoder);
    }

8. Расширение информации о токене JWT на основе Oauth2

Полезная нагрузка токена JWT, сгенерированная для нас OAuth2, имеет ограниченную информацию. Существует только одно имя пользователя для информации о пользователе. В некоторых сценариях мы хотим поместить некоторые расширенные информационные элементы, такие как IP (для повышения безопасности), UserId..., как следует Добавить расширенную информацию:

Сертифицированные серверы в токен JWT для информации о расширении (например, Clientip)

  • Унаследуйте класс DefaultAccessTokenConverter и перепишите метод convertAccessToken для хранения расширенной информации.
/**
 * @author: 190coder <190coder.cn>
 * @description: 自定义扩展信息
 * @create: 2020-07-30 23:41
 */
public class LagouAccessTokenConvertor extends DefaultAccessTokenConverter {

    @Override
    public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {

        // 获取到request对象
        HttpServletRequest request = ((ServletRequestAttributes)
                (RequestContextHolder.getRequestAttributes())).getRequest();
        // 获取客户端ip(注意:如果是经过代理之后到达当前服务的话,那么这种⽅式获取的并不是真实的浏览器客户端ip)
        String remoteAddr = request.getRemoteAddr();
        Map<String, String> stringMap = (Map<String, String>)
                super.convertAccessToken(token, authentication);
        stringMap.put("clientIp",remoteAddr);
        return stringMap;
    }
}
  • Вставьте пользовательский объект преобразователя в класс, возвращаемый конфигурацией jwt.
 /**
     * 返回jwt令牌转换器(帮助我们生成jwt令牌的)
     * 在这里,我们可以把签名密钥传递进去给转换器对象
     * @return
     */
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey(sign_key);  // 签名密钥
        jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));  // 验证时使用的密钥,和签名密钥保持一致
        jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor);
        return jwtAccessTokenConverter;
    }

9. Сервер ресурсов получает информацию о расширении токена JWT.

  • Серверу ресурсов также необходимо настроить класс преобразователя, унаследовать DefaultAccessTokenConverter, переопределить ExtractAuthentication метод извлечения, установите информацию полезной нагрузки в атрибут details объекта аутентификации.

  • Метод внедрения пользовательского объекта конвертера (возврат конвертера токенов jwt)

  • Для бизнес-классов, таких как класс Controller, вы можете получить объект проверки подлинности с помощью SecurityContextHolder.getContext().getAuthentication() и дополнительно получить расширенную информацию.

Object details = SecurityContextHolder.getContext().getAuthentication().getDetails();
  • После получения расширенной информации вы можете выполнить другую обработку, и userId ⽐ Поскольку в дальнейшем обрабатывается в ⼀, или в соответствии с обработкой clientIp, или другое возможно,

10. Меры предосторожности

  • Токен JWT — это формат организации данных, который можно проверить. Его игровой процесс очень гибкий. Здесь мы создаем и верифицируем токен JWT на основе Spring Cloud Oauth2.
  • Мы также можем написать собственные классы инструментов для генерации и проверки токенов JWT.
  • Не храните слишком конфиденциальную информацию в токене JWT, потому что мы знаем, что после получения токена мы можем расшифровать и увидеть информацию в части полезной нагрузки.
  • Токен JWT будет передаваться с каждым запросом. Слишком большой объем контента увеличивает использование пропускной способности сети.

Lagouedu разобраться

В этой статье используетсяmdniceнабор текста