Вероятно, самое подробное руководство по единому входу Spring Cloud OAuth2 во всей сети.

Spring Cloud

WeChat поиск общедоступной учетной записи "древний воздушный змей», технический публичный аккаунт, посвященный не только технологиям. Серия статей Spring Cloud завершена, и вы можете перейти кмой гитхабПолную серию смотрите на .

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

OAuth 2 имеет четыре режима авторизации, а именно режим кода авторизации (код авторизации), упрощенный режим (неявный), режим пароля (учетные данные пароля владельца ресурса) и режим клиента (учетные данные клиента).Подробнее о том, что такое OAuth2, см. эта статья статья. (http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)

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

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

Когда использовать OAuth2

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

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

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

Реализовать унифицированную функцию аутентификации

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

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

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

Описание архитектуры системы

Удостоверяющий центр: oauth2-auth-server, основная сторона реализации OAuth2, генерация, обновление и проверка токена выполняются в центре аутентификации.

Заказать услугу: oauth2-client-order-server, один из микросервисов, отправится в центр аутентификации для проверки после получения запроса.

Сервис пользователя: oauth2-client-user-server, второй микросервис, отправится в центр аутентификации для проверки после получения запроса.

Клиент: например, приложение, веб и другие терминалы

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

Создайте и настройте сервер аутентификации

Наиболее настроенным является сервер аутентификации, проверка учетной записи и пароля, сохранение токена, проверка токена, обновление токена и т. д. — все это работа сервера аутентификации.

1. Импортируйте необходимый пакет maven

<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

spring-cloud-starter-oauth2содержитspring-cloud-starter-security, так что нет необходимости вводить его отдельно. Причина, по которой был введен пакет redis, заключается в том, что ниже будет представлен способ хранения токенов с помощью redis.

2. Настройте application.yml

Установите базовую конфигурацию проекта и добавьте конфигурацию, связанную с Redis, которая будет использоваться позже.

spring:
application:
name: auth-server
redis:
database: 2
host: localhost
port: 32768
password: 1qaz@WSX
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
timeout: 100ms

server:
port: 6001

management:
endpoint:
health:
enabled: true

3. Базовая конфигурация безопасности Spring

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

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

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

использовать@EnableWebSecurityМодификация аннотации и наследуется отWebSecurityConfigurerAdapterсвоего рода.

Смысл этого класса в том, чтобы объявитьPasswordEncoderиAuthenticationManagerдве фасоли. будет использоваться позже. вBCryptPasswordEncoderЯвляется классом инструмента шифрования паролей, он может обеспечить необратимое шифрование,AuthenticationManagerЭто bean-компонент управления авторизацией, который необходимо указать для реализации режима пароля OAuth2.

4. Внедрить службу сведений о пользователях

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

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

@Slf4j
@Component(value = "kiteUserDetailsService")
public class KiteUserDetailsService implements UserDetailsService {


@Autowired
private PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("usernameis:" + 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".

5. Файл конфигурации OAuth2

Создайте файл конфигурации, который наследуется отAuthorizationServerConfigurerAdapter.

@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

@Autowired
public PasswordEncoder passwordEncoder;

@Autowired
public UserDetailsService kiteUserDetailsService;

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private TokenStore redisTokenStore;

@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
/**
* redis token 方式
*/
endpoints.authenticationManager(authenticationManager)
.userDetailsService(kiteUserDetailsService)
.tokenStore(redisTokenStore);

}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("order-client")
.secret(passwordEncoder.encode("order-secret-8888"))
.authorizedGrantTypes("refresh_token", "authorization_code", "password")
.accessTokenValiditySeconds(3600)
.scopes("all")
.and()
.withClient("user-client")
.secret(passwordEncoder.encode("user-secret-8888"))
.authorizedGrantTypes("refresh_token", "authorization_code", "password")
.accessTokenValiditySeconds(3600)
.scopes("all");
}

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

Есть три переопределения метода configure.

AuthorizationServerEndpointsConfigurerПереопределение параметров

endpoints.authenticationManager(authenticationManager)
.userDetailsService(kiteUserDetailsService)
.tokenStore(redisTokenStore);

authenticationManage()Вызовите этот метод для поддержки режима пароля.

userDetailsService()Настройте службу аутентификации пользователей.

tokenStore()Указывает, как хранится токен.

Определение redisTokenStore Bean выглядит следующим образом:

@Configuration
public class RedisTokenStoreConfig {

@Autowired
private RedisConnectionFactory redisConnectionFactory;

@Bean
public TokenStore redisTokenStore (){
return new RedisTokenStore(redisConnectionFactory);
}
}

ClientDetailsServiceConfigurerПереопределение параметров, где определены ограничения для каждого конца. включают

ClientId, Client-Secret: эти два параметра соответствуют cleint-id и client-secret, определенным запрашивающей стороной.

authorGrantTypes может включать один или несколько следующих параметров:

  • авторизация_код: Тип кода авторизации.
  • неявный: неявный тип предоставления.
  • password: Тип пароля владельца ресурса (т.е. пользователя).
  • client_credentials: тип учетных данных клиента (идентификатор клиента и ключ).
  • refresh_token: Получите новый токен с помощью токена обновления, полученного с помощью вышеуказанной авторизации.

accessTokenValiditySeconds: срок действия токена.

Области: используется для ограничения прав доступа клиента. Параметр области будет включен при обмене токена. Обмен токеном возможен только в пределах определения областей.

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

Конкретный метод заключается в следующем:

  1. Добавить таблицу в базу данных и вставить данные
create table oauth_client_details (
client_id VARCHAR(256) PRIMARY KEY,
resource_ids VARCHAR(256),
client_secret VARCHAR(256),
scope VARCHAR(256),
authorized_grant_types VARCHAR(256),
web_server_redirect_uri VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove VARCHAR(256)
);
INSERT INTO oauth_client_details
(client_id, client_secret, scope, authorized_grant_types,
web_server_redirect_uri, authorities, access_token_validity,
refresh_token_validity, additional_information, autoapprove)
VALUES
('user-client', '$2a$10$o2l5kA7z.Caekp72h5kU7uqdTDrlamLq.57M1F6ulJln9tRtOJufq', 'all',
'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);

INSERT INTO oauth_client_details
(client_id, client_secret, scope, authorized_grant_types,
web_server_redirect_uri, authorities, access_token_validity,
refresh_token_validity, additional_information, autoapprove)
VALUES
('order-client', '$2a$10$GoIOhjqFKVyrabUNcie8d.ADX.qZSxpYbO6YK4L2gsNzlCIxEUDlW', 'all',
'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);

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

  1. Затем добавьте конфигурацию базы данных в файл конфигурации application.yml.
spring:
datasource:
url: jdbc:mysql://localhost:3306/spring_cloud?characterEncoding=UTF-8&useSSL=false
username: root
password: password
hikari:
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
maximum-pool-size: 9

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

  1. Добавление внедрения DataSource в класс конфигурации OAuth2 (OAuth2Config)
@Autowired
private DataSource dataSource;
  1. будетpublic void configure(ClientDetailsServiceConfigurer clients)Переопределенный метод изменяется следующим образом:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
jcsb.passwordEncoder(passwordEncoder);
}

Существует также переопределенный методpublic void configure(AuthorizationServerSecurityConfigurer security), этот метод ограничивает доступ клиента к интерфейсу аутентификации.

security.allowFormAuthenticationForClients();
security.checkTokenAccess("isAuthenticated()");
security.tokenKeyAccess("isAuthenticated()");

Первая строка кода позволяет клиенту получить доступ к интерфейсу авторизации OAuth2, иначе токен запроса вернет 401.

Вторая и третья строки позволяют авторизованным пользователям получать доступ к интерфейсу checkToken и интерфейсу получения токена соответственно.

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

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

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

Создать пользовательский клиентский проект

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

1. Ссылка на соответствующий пакет maven

<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. конфигурационный файл application.yml

spring:
application:
name: client-user
redis:
database: 2
host: localhost
port: 32768
password: 1qaz@WSX
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
timeout: 100ms
server:
port: 6101
servlet:
context-path: /client-user

security:
oauth2:
client:
client-id: user-client
client-secret: user-secret-8888
user-authorization-uri: http://localhost:6001/oauth/authorize
access-token-uri: http://localhost:6001/oauth/token
resource:
id: user-client
user-info-uri: user-info
authorization:
check-token-access: http://localhost:6001/oauth/check_token

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

Идентификатор клиента и секрет клиента должны соответствовать конфигурации в службе проверки подлинности, если используется inMemory или jdbc.

user-authorization-uri требуется методом аутентификации кода авторизации, который будет рассмотрен в следующей статье.

access-token-uri — это интерфейс для получения токена, необходимого для режима пароля.

авторизация.check-token-access также является ключевой информацией.Когда сервер получает запрос от клиента, он должен взять токен в запросе на сервер аутентификации для проверки токена, который является запрошенным интерфейсом

3. Файл конфигурации ресурса

В концепции OAuth2 все интерфейсы называются ресурсами, а разрешения интерфейсов также являются разрешениями ресурсов, поэтому Spring Security OAuth2 предоставляет аннотации о ресурсах.@EnableResourceServer@EnableWebSecurityимеет аналогичный эффект.

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

@Value("${security.oauth2.client.client-id}")
private String clientId;

@Value("${security.oauth2.client.client-secret}")
private String secret;

@Value("${security.oauth2.authorization.check-token-access}")
private String checkTokenEndpointUrl;

@Autowired
private RedisConnectionFactory redisConnectionFactory;

@Bean
public TokenStore redisTokenStore (){
return new RedisTokenStore(redisConnectionFactory);
}

@Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setClientId(clientId);
tokenService.setClientSecret(secret);
tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
return tokenService;
}

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenService());
}
}

Поскольку Redis используется в качестве хранилища токенов, bean-компонент с именем tokenService должен быть специально настроен, с помощью которого может быть достигнута проверка токена.

4. Наконец, добавьте интерфейс RESTful

@Slf4j
@RestController
public class UserController {

@GetMapping(value = "get")
//@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public Object get(Authentication authentication){
//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authentication.getCredentials();
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
String token = details.getTokenValue();
return token;
}
}

Метод RESTful, к которому можно получить доступ только в том случае, если у запрашивающего пользователя есть разрешение ROLE_ADMIN, в противном случае возвращается 401 Unauthorized.

через параметр Authentication илиSecurityContextHolder.getContext().getAuthentication()Информация об авторизации может быть получена для просмотра.

Функция сертификации испытаний

1. Запустите сервер аутентификации, а порт запуска — 6001.

2. Запустите клиент службы пользователя, а порт запуска — 6101.

3. Запросите сервер аутентификации для получения токена.

Я использую REST Client для запроса доступа, формат запроса следующий:

POST http://localhost:6001/oauth/token?grant_type=password&username=admin&password=123456&scope=all
Accept: */*
Cache-Control: no-cache
Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

Предположим, мы используем его на веб-сайте, grant_type — это пароль, указывающий, что это режим пароля с использованием OAuth2.

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

scope=all относится к разрешениям, а область указана как all в OAuthConfig службы проверки подлинности.

Авторизация должна быть добавлена ​​в заголовок запроса, формат — Basic space base64(clientId:clientSecret), client-id этого клиента микросервиса — user-client, а client-secret — user-secret-8888. Соедините эти два значения. с двоеточиями, а значение после кодировки base64 (user-client:user-secret-8888) — dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==, которое можно получить через https://www.sojson.com/base64.html онлайн-кодирование.

После выполнения запроса, если параметры верны, возвращаемый контент выглядит следующим образом в формате json.

{
"access_token": "9f958300-5005-46ea-9061-323c9e6c7a4d",
"token_type": "bearer",
"refresh_token": "0f5871f5-98f1-405e-848e-80f641bab72e",
"expires_in": 3599,
"scope": "all"
}

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

4. Используйте полученный токен для запроса интерфейса ресурса

Мы определили интерфейс http://localhost:6101/client-user/get в пользовательском клиенте и теперь запрашиваем этот интерфейс с токеном, полученным на предыдущем шаге.

GET http://localhost:6101/client-user/get
Accept: */*
Cache-Control: no-cache
Authorization: bearer ce334918-e666-455a-8ecd-8bd680415d84

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

5. По истечении срока действия токена обменяйте refresh_token на access_token.

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

### 换取 access_token
POST http://localhost:6001/oauth/token?grant_type=refresh_token&refresh_token=706dac10-d48e-4795-8379-efe8307a2282
Accept: */*
Cache-Control: no-cache
Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

Grant_type имеет значение refresh_token.

refresh_token устанавливается равным значению refresh_token, возвращаемому при запросе токена.

Заголовок запроса добавляется к авторизации, а формат по-прежнему Basic + пробел + base64 (идентификатор клиента: секрет клиента)

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

Замените redisToken на JWT

Вышеприведенное хранилище токенов использует решение Redis. Spring Security OAuth2 также обеспечивает поддержку jdbc и jwt. jdbc пока не рассматривается. Теперь давайте представим способ JWT для реализации хранилища токенов.

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

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

Сначала удалите конфигурацию, связанную с Redis.

Добавьте класс конфигурации JwtConfig
@Configuration
public class JwtTokenConfig {

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

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

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

Измените класс конфигурации OAuthConfig.
@Autowired
private TokenStore jwtTokenStore;

@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;

@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
/**
* 普通 jwt 模式
*/
endpoints.tokenStore(jwtTokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.userDetailsService(kiteUserDetailsService)
/**
* 支持 password 模式
*/
.authenticationManager(authenticationManager);
}

Внедрите bean-компоненты, связанные с JWT, а затем изменитеconfigure(final AuthorizationServerEndpointsConfigurer endpoints)Метод представляет собой режим хранения JWT.

Модернизация пользовательского клиента

Измените файл конфигурации application.yml.
security:
oauth2:
client:
client-id: user-client
client-secret: user-secret-8888
user-authorization-uri: http://localhost:6001/oauth/authorize
access-token-uri: http://localhost:6001/oauth/token
resource:
jwt:
key-uri: http://localhost:6001/oauth/token_key
key-value: dev

Обратите внимание на сервер аутентификацииJwtAccessTokenConverterНабор SigningKey должен быть таким же, как ключ-значение в файле конфигурации, иначе JWT не сможет нормально декодироваться, что приведет к сбою проверки.

Конфигурация класса ResourceServerConfig
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();

accessTokenConverter.setSigningKey("dev");
accessTokenConverter.setVerifierKey("dev");
return accessTokenConverter;
}

@Autowired
private TokenStore jwtTokenStore;

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(jwtTokenStore);
}
}
Запустите запрос к интерфейсу токена запроса
POST http://localhost:6001/oauth/token?grant_type=password&username=admin&password=123456&scope=all
Accept: */*
Cache-Control: no-cache
Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

Возвращаемые результаты следующие:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJleHAiOjE1NzE3NzU4OTQsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwianRpIjoiZjdkMjg4NDUtMmU2ZC00ZmRjLTg1OGYtMWNiY2RlNzI1ZmMyIiwiY2xpZW50X2lkIjoidXNlci1jbGllbnQifQ.vk_msYtbrAr93h5sK4wy6EC2_wRD_cD_UBS8O6eRziw",
"expires_in": 3599,
"scope": "all",
"jti": "8cca29af-ea77-4fe6-9fe1-327415dcd21d"
}

Мы увидели, что возвращенный токен имеет формат JWT, перейдите на веб-сайт онлайн-декодирования JWT.jwt.io/илиjwt.calebb.net/Расшифруйте токен и посмотрите

Смотрите его, в нем user_name, client_id и прочая информация.

Удерживайте возвращенный токен и запросите пользовательский интерфейс клиента.
GET http://localhost:6101/client-user/get
Accept: */*
Cache-Control: no-cache
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3NDM0OTQsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI4Y2NhMjlhZi1lYTc3LTRmZTYtOWZlMS0zMjc0MTVkY2QyMWQiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.0Ik3UwB1xjX2le5luEdtVAI_MEyu_OloRRYtPOvtvwM

Усовершенствованный JWT

Что делать, если я хочу добавить дополнительные поля в JWT (например, другую информацию о пользователе), конечно. Spring Security oauth2 обеспечиваетTokenEnhancerусилитель. На самом деле можно использовать не только JWT, но и RedisToken.

объявить энхансер
public class JWTokenEnhancer 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;
}
}

Через oAuth2Authentication мы можем получить такую ​​информацию, как имя пользователя, через которое мы можем запросить базу данных или кеш, чтобы получить дополнительную информацию, и эта информация может быть добавлена ​​как информация расширения JWT.

Изменение класса конфигурации OAuthConfig

усилитель впрыска

@Autowired
private TokenEnhancer jwtTokenEnhancer;

@Bean
public TokenEnhancer jwtTokenEnhancer(){
return new JWTokenEnhancer();
}

Исправлятьconfigure(final AuthorizationServerEndpointsConfigurer endpoints)метод

@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( kiteUserDetailsService )
/**
* 支持 password 模式
*/
.authenticationManager( authenticationManager )
.tokenEnhancer( enhancerChain )
.accessTokenConverter( jwtAccessTokenConverter );
}
Запросите токен еще раз, и возвращаемый контент имеет недавно добавленное поле jwt-ext.
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2E0MmMiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.5j4hNsVpktG2iKxNqR-q1rfcnhlyV3M6HUBx5cd6PiQ",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImE0NTUxZDllLWI3ZWQtNDc1OS1iMmYxLWYwYjliMjFjYTQyYyIsImV4cCI6MTU3MTc3NzU3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJmNTI3ODJlOS0wOGRjLTQ2NGUtYmJhYy03OTMwNzYwYmZiZjciLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.UQMf140CG8U0eWh08nGlctpIye9iJ7p2i6NYHkGAwhY",
"expires_in": 3599,
"scope": "all",
"jwt-ext": "JWT 扩展信息",
"jti": "a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c"
}

Пользовательский клиент, анализирующий данные JWT

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

Импорт пакета JWT
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
Добавьте интерфейс RESTful для разбора JWT.
@GetMapping(value = "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("dev".getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(jwtToken)
.getBody();
return claims;
}

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

Используйте токен из предыдущего шага, чтобы запросить вышеуказанный интерфейс.
### 解析 jwt
GET http://localhost:6101/client-user/jwt
Accept: */*
Cache-Control: no-cache
Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MTc0NTE3OCwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJhNDU1MWQ5ZS1iN2VkLTQ3NTktYjJmMS1mMGI5YjIxY2E0MmMiLCJjbGllbnRfaWQiOiJ1c2VyLWNsaWVudCJ9.5j4hNsVpktG2iKxNqR-q1rfcnhlyV3M6HUBx5cd6PiQ

Возвращаемый контент выглядит следующим образом:

{
"user_name": "admin",
"jwt-ext": "JWT 扩展信息",
"scope": [
"all"
],
"exp": 1571745178,
"authorities": [
"ROLE_ADMIN"
],
"jti": "a4551d9e-b7ed-4759-b2f1-f0b9b21ca42c",
"client_id": "user-client"
}

Выше приведен полный процесс режима пароля. Исходный код размещен на github. Вы можете посмотреть, если вам это нужно.

Творить нелегко, а лайк — это добродетель, а еще он может дать мне мотивацию к творчеству. Пожалуйста, похвалите меня!

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

Исходный код, соответствующий этой статье:Пожалуйста, нажмите здесь, чтобы просмотреть