1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/torlesse-liang-torlesse-oauth2

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Оauth2.0

Прежде всего, давайте разберёмся, что такое Оauth2.0. Говоря простым языком, Оauth2.0 — это протокол авторизации. Возможно, вы слышали, что Оauth2.0 используется для реализации единого входа и сторонней аутентификации. Что же такое авторизация?

Рассмотрим простой пример: человек А хочет войти в здание компании B, чтобы провести деловую встречу. Поскольку А не является сотрудником компании B, по соображениям безопасности он не может свободно входить в здание. Когда А подходит к стойке регистрации компании B, ему нужно объяснить цель своего визита и предъявить приглашение (доказательство доступа), после чего сотрудник стойки регистрации выдаст ему временный пропуск, который позволит ему войти в здание.

В этом примере у человека А нет пропуска, поэтому он не может войти в здание компании В. Сотрудник стойки регистрации выдаёт человеку А временный пропуск. Эта операция аналогична авторизации.

Таким образом, протокол авторизации Оauth 2.0 гарантирует, что стороннее программное обеспечение (третья сторона) может получить доступ к данным авторизованного лица только после получения разрешения.

1. Протокол авторизации Оauth2.0

Протокол авторизации Оauth2.0 включает в себя несколько механизмов предоставления разрешений:

  • механизм предоставления разрешений на основе кода;
  • механизм предоставления разрешений с использованием учётных данных клиента;
  • механизм предоставления разрешений с использованием учётных данных владельца ресурса (пароль);
  • неявный механизм предоставления разрешений.

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

— Владелец ресурса (может быть владельцем ресурса или иметь доступ к ресурсу). — Клиент (может пониматься как сторонняя система/программное обеспечение). — Служба авторизации (система проверки подлинности и авторизации). — Защищённый ресурс (ресурсы или данные, к которым пользователь имеет доступ в системе).

1.1. Механизм предоставления разрешений на основе кода

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

Авторизация в этом сценарии позволяет стороннему программному обеспечению получать доступ к ресурсам владельца после получения кода авторизации и client_id и client_secret, полученных при регистрации.

Последовательность действий:

  1. Пользователь использует приложение, но не хочет вручную вводить имя пользователя и пароль для входа. Вместо этого он использует функцию входа через WeChat.
  2. Приложение открывает страницу авторизации WeChat.
  3. Сервер авторизации генерирует страницу авторизации, и пользователь видит её. После подтверждения авторизации страница перенаправляется на приложение.
  4. Сервер авторизации проверяет личность пользователя, а затем генерирует код запроса. После того как пользователь подтверждает авторизацию, страница перенаправляет его на приложение с кодом запроса.
  5. Приложение получает код запроса и отправляет запрос на получение токена доступа access_token на сервер авторизации.
  6. После успешной проверки токена access_token сервер авторизации возвращает данные пользователя (личные данные, псевдоним, местоположение и т. д.).
  7. После успешного входа пользователь продолжает использовать функции приложения.

Почему необходимо генерировать код запроса и получать токен доступа access_token? Если исключить процесс получения токена доступа из последовательности действий, то после того, как пользователь подтвердит авторизацию, связь между владельцем ресурса и сервером авторизации будет установлена, а связь между владельцем ресурса и клиентом будет разорвана, и интерфейс останется на странице авторизации. Затем сервер авторизации напрямую отправит токен доступа клиенту, и клиент получит доступ к защищённому ресурсу с помощью токена доступа. Хотя данные были получены, как клиент может уведомить пользователя? Поэтому необходимо восстановить связь между пользователем и клиентом. Вот почему сервер авторизации генерирует код запроса, а затем перенаправляет клиента на страницу клиента.

Поскольку это так, зачем серверу авторизации отправлять токен доступа напрямую, вместо того чтобы перенаправить клиента? Во-первых, нельзя гарантировать, что перенаправление будет осуществляться по протоколу HTTPS, и не все клиенты поддерживают HTTPS. Отправка токена доступа таким образом увеличит риск кражи токена. Несмотря на то, что токену доступа требуется client_id, client_secret для проверки доступа к защищённым ресурсам, с точки зрения безопасности это не рекомендуется. В этом контексте роль кода запроса заключается в защите токена доступа от кражи.

Можно ли раскрыть код запроса?

  1. Код запроса можно использовать только один раз, и он быстро становится недействительным, что затрудняет его использование после перехвата.
  2. Для получения токена доступа необходим код запроса вместе с client_id/client_secret. Без них невозможно получить токен доступа.

Где хранить токен доступа? Если токен доступа нельзя раскрывать в браузере, где его хранить? Как клиент получает доступ к защищённым ресурсам?

На начальном этапе работы с Оauth2.0 я также был озадачен. Если токен доступа не раскрывается в браузере, куда его поместить? Я подумал, что после проверки кода запроса и client_id и secret для получения токена доступа можно сохранить токен доступа в localStorage, поскольку localStorage является постоянным хранилищем, но токен доступа имеет срок действия. По истечении срока действия токен доступа больше не сможет получить доступ к защищённым ресурсам.

1.2. Отличие между sessionStorage и localStorage

  1. sessionStorage (хранилище сеансов)
    • Срок действия: от открытия браузера до закрытия.
    • Размер: 5 МБ.
    • Хранится в браузере.
// Хранение данных
sessionStorage.setItem("name", "nameValue");
// Получение данных
sessionStorage.getItem("name");
// Удаление данных
sessionStorage.removeItem("name");
// Очистка всех данных
sessionStorage.clear();
  1. localStorage (постоянное хранилище)
    • Срок действия: постоянный, удаляется только вручную.
    • Размер: 5 МБ или больше.
    • Хранится в браузере.
// Хранение данных
localStorage.setItem("name", "nameValue");
// Получение данных
localStorage.getItem("name");
// Удаление данных
localStorage.removeItem("name");

Обратите внимание, что разные браузеры не могут совместно использовать информацию в localStorage или sessionStorage. Страницы и вкладки одного и того же домена и порта в одном браузере могут совместно использовать localStorage, но страницы и вкладки не могут совместно использовать sessionStorage. ### 2.2. Авторизация и получение access_token

  1. Нажмите «Войти» и авторизуйтесь.

    image-20220413011034132

  2. Получите код авторизации:

https://www.baidu.com/?code=SBkZt5
  1. Нажмите кнопку «Авторизоваться».

    image-20220413011106572

  2. Используйте Postman для тестирования запроса на получение access_token:

http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=torlesse002&client_secret=123456&code=SBkZt5&redirect_uri=https://baidu.com

2.3. Настройка клиента

2.3.1. Конфигурация OAuth2Client

OAuthClientConfig — конфигурация клиента OAuth2:

@Configuration
@EnableOAuth2Sso
public class OAuthClientConfig {
    /**
     * Определяет OAuth2RestTemplate. Можно прочитать конфигурацию oauth из файла application.yml и внедрить её в OAuth2ProtectedResourceDetails.
     * @param oAuth2ClientContext
     * @param details
     * @return
     */
    @Bean
    public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oAuth2ClientContext,
                                                 OAuth2ProtectedResourceDetails details) {
        return new OAuth2RestTemplate(details, oAuth2ClientContext);
    }
}

2.3.2. Spring Security

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

@Configuration
@Order(200)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * Разрешает доступ к /demo** и /login**. Все остальные пути требуют аутентификации.
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers( "/demo**","/login**")
                .permitAll()
                .antMatchers("/test/hello")
                .anonymous()
                .anyRequest()
                .authenticated();
    }
}

2.3.3. Контроллер DemoController

Контроллер для имитации сценария:

@RestController
@RequestMapping("/demo")
public class DemoController {
    @Autowired
    OAuth2RestTemplate restTemplate;

    /**
     * Используется для тестирования однократной аутентификации
     * @param authentication
     * @return
     */
    @GetMapping("/userInfoPage")
    public ModelAndView securedPage(OAuth2Authentication authentication) {
        return new ModelAndView("userInfoPage").addObject("authentication", authentication);
    }

    /**
     * Доступ к защищённым ресурсам
     * @return
     */
    @GetMapping("/remoteCall")
    public String remoteCall() {
        ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://localhost:8081/user/name", String.class);
        return responseEntity.getBody();
    }
}

2.4. Настройка сервера ресурсов

2.4.1. Ресурсный сервер

Ресурсная конфигурация сервера:

@Configuration
@EnableResourceServer//Запуск ресурсного сервера
@EnableGlobalMethodSecurity(prePostEnabled = true)//Запуск контроля доступа с помощью аннотаций
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    /**
     * Объявляет идентификатор ресурсного сервера как torlesseservice и токен-хранилище как JWT.
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
       resources.resourceId("torlesseservice").tokenStore(tokenStore());
    }

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

    /**
     * Конфигурирует открытый ключ
     * @return
     */
    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        Resource resource = new ClassPathResource("public.cert");
        String publicKey = null;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        converter.setVerifierKey(publicKey);
        return converter;
    }


    /**
     * Настраивает, что все запросы, кроме /user, могут быть анонимными.
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(HttpMethod.POST,"/user/**").authenticated()
                .antMatchers(HttpMethod.GET,"/user/**").authenticated()
                .anyRequest().permitAll();
    }
}

2.4.2. Контроллер UserController

Здесь приведён неполный перевод. 2.4. Управление доступом на основе ролей

2.4.1. Постман имитирует доступ клиента к защищённым ресурсам

Используйте пользователя torlesse для получения токена, а затем используйте Postman для имитации доступа клиента к защищенным ресурсам.

http://localhost:8081/user/name

Postman используется в качестве имитирующего клиента для доступа к защищенным ресурсам:

http://localhost:8081/user/name

2.4.2. Доступ клиента к защищённым ресурсам

Доступ к странице через браузер: http://localhost:8083/torlesse/demo/remoteCall

Нажмите кнопку «Войти».

Вы успешно получили доступ к демонстрационным защищенным ресурсам.

2.4.3. Демонстрация единого входа

Подготовка

  1. Запустите два клиентских приложения с портами 8082 и 8083 соответственно.

1.1 Измените файл конфигурации клиента application.yml:

server:
  port: ${PORT:8083}

1.2 Настройте несколько клиентов:

Запустите приложение.

Проверка единого входа

Доступ к странице через браузер: http://localhost:8083/torlesse/demo/userInfoPage

Нажмите кнопку «Вход».

Доступ к той же странице через другой клиент: http://localhost:8082/torlesse/demo/userInfoPage.

Единый вход работает успешно.

2.5. Пользовательский режим авторизации

Oauth2.0 имеет множество механизмов авторизации: механизм авторизации кода, механизм клиентских учетных данных, механизм учетных данных владельца ресурса (режим пароля) и неявный механизм авторизации.

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

Если вам нужно реализовать функции входа по мобильному телефону или сканирования QR-кода WeChat, как это сделать?

Мы можем наследовать от класса AbstractTokenGranter для реализации пользовательского режима авторизации.

Режим мобильного телефона с кодом подтверждения

Наследуйте от класса AbstractTokenGranter и реализуйте пользовательский режим входа с помощью мобильного телефона.

@Slf4j
public class SmsCodeGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "sms_code";

    protected final AuthenticationManager authenticationManager;

    protected final UserMapper userMapper;

    public SmsCodeGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                          ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, UserMapper userMapper) {
        super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.authenticationManager = authenticationManager;
        this.userMapper = userMapper;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

        Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
        String telephone = parameters.get("telePhone");
        String code = parameters.get("code");

        if (StringUtils.isEmpty(telephone) || StringUtils.isEmpty(code)) {
            throw new InvalidGrantException("参数错误.");
        }
        CheckParam checkParam = new CheckParam();
        checkParam.setTelePhone(telephone);
``` **Пользователь user = userMapper.selectUserByCondition(checkParam.getTelePhone());**

log.info("telephone = {}, code = {}, user = {}", телефон, код, JSON.toJSONString(пользователь));  // 根据 мобильному номеру телефона запрашиваем информацию о пользователе

if (пользователь == null) {
    выбросить новое исключение InvalidGrantException("Ошибка в заполнении номера мобильного телефона.");
}

Authentication userAuth = new TelePhoneAuthenticationToken(пользователь.имя_пользователя(), пользователь.пароль(), телефон, код);
((AbstractAuthenticationToken) userAuth).setDetails(параметры);

try {
    userAuth = this.authenticationManager.authenticate(userAuth);
} catch (AccountStatusException var8) {
    выбросить новое исключение InvalidGrantException("Текущий пользователь уже заблокирован, пожалуйста, обратитесь в службу поддержки.");
} catch (BadCredentialsException var9) {
    выбросить новое исключение InvalidGrantException("Информация о пользователе получена с ошибкой, пожалуйста, убедитесь, что вы зарегистрированы.");
} catch (InternalAuthenticationServiceException var10) {
    выбросить новое исключение InvalidGrantException("Проверка кода не удалась.");
}

if (userAuth != null && userAuth.isAuthenticated()) {
    OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(клиент, tokenRequest);
    return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
    выбросить новое исключение InvalidGrantException("Не удалось аутентифицировать пользователя: " + телефон);
}

**Класс телефона AuthenticationToken расширяет UsernamePasswordAuthenticationToken**

private String telePhone;

private String code;

/**
 * @param principal имя пользователя
 */
public TelePhoneAuthenticationToken(Object principal, Object credentials, String telePhone, String code) {
    super(principal, credentials);
    setAuthenticated(false);
    this.telePhone = telePhone;
    this.code = code;
}

public String getTelePhone() {
    вернуть telePhone;
}

public String getCode() {
    вернуть code;;
}

#### 2.5.2 Модификация конфигурации сервера авторизации

AuthorizationServerConfig класс конфигурации сервера авторизации добавляет TokenGranter

/**
* Инициализация всех TokenGranters
*/
private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {

ClientDetailsService clientDetails = endpoints.getClientDetailsService();
AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();

List<TokenGranter> tokenGranters = new ArrayList<>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
clientDetails, requestFactory));
tokenGranters.add(new SmsCodeGranter(authenticationManager, endpoints.getTokenServices(),
endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), userMapper));
}
return tokenGranters;
}

Модификация конфигурации сервера авторизации, добавление следующих элементов

@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer())); // Инициализируем все TokenGranters и устанавливаем тип CompositeTokenGranter List tokenGranters = getDefaultTokenGranters(endpoints); endpoints.approvalStore(approvalStore()) tokenGranter(new CompositeTokenGranter(tokenGranters)) .authorizationCodeServices(authorizationCodeServices()) .tokenStore(tokenStore()) .tokenEnhancer(tokenEnhancerChain) .authenticationManager(authenticationManager); }


#### 2.5.3 Демонстрация проверки в режиме авторизации с использованием верификации по номеру телефона

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

```http://localhost:8080/oauth/token?grant_type=sms_code&client_id=torlesse004&client_secret=123456&telePhone=12345678999&code=123123```

[Рисунок image-20220414225932247.png]

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

oauth2.0 случай: демонстрация реализации четырёх моделей авторизации и пользовательской модели авторизации. Развернуть Свернуть
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/torlesse-liang-torlesse-oauth2.git
git@api.gitlife.ru:oschina-mirror/torlesse-liang-torlesse-oauth2.git
oschina-mirror
torlesse-liang-torlesse-oauth2
torlesse-liang-torlesse-oauth2
master