1. Введение в OAuth2
- OAuth — это открытый стандарт, который позволяет пользователям разрешать сторонним приложениям доступ к закрытым ресурсам, хранящимся на определенном сайте (таким как аватар, фотографии, видео и т. д.), и при этом не нужно предоставлять имя пользователя и пароль третьим лицам. процесс приложение. Реализация этой функции заключается в доступе к данным, хранящимся у конкретного поставщика услуг, путем предоставления токена вместо имени пользователя и пароля.
- Каждый токен разрешает определенному веб-сайту доступ к определенному ресурсу в течение определенного периода времени. Таким образом, OAuth дает пользователям возможность разрешать сторонним веб-сайтам доступ к определенной информации, но не ко всему содержимому, хранящемуся на каком-либо другом ресурсном сервере. В настоящее время основные авторизованные методы входа в систему QQ, WeChat и другие сторонние методы реализованы на основе OAuth2.
- OAuth 2 — это следующая версия протокола OAuth, но она не имеет обратной совместимости с OAuth 1.0. OAuth 2 ориентирован на простоту для разработчиков на стороне клиента, обеспечивая при этом специализированные потоки аутентификации для веб-приложений, настольных приложений, мобильных устройств и устройств в жилых комнатах.
- Традиционная аутентификация при входе в веб-разработку обычно основана на сеансе, но будет неудобно продолжать использовать сеанс в разделенной архитектуре внешнего и внутреннего интерфейса, поскольку мобильные терминалы (Android, iOS, апплет WeChat и т. д.) или не поддерживают куки (апплет WeChat и т.д.) ), или очень неудобно использовать.Для решения этих проблем можно использовать аутентификацию OAuth 2.
2. Четыре роли OAuth2
В Oauth2 всего четыре роли:
название | английское имя | описывать |
---|---|---|
владелец ресурса | Resource Owner | Сущность, способная предоставлять доступ к защищенным ресурсам. Когда владельцем ресурса является человек, это пользователь. |
сервер ресурсов | Resource Server | Сервер, на котором размещен защищенный ресурс, способный принимать и отвечать на запросы защищенных ресурсов с использованием токена доступа. Это веб-сайт или API веб-службы, где хранятся защищенные данные пользователя. |
клиент | Client | Приложения, которые запрашивают защищенные ресурсы от имени владельцев ресурсов и их полномочий. Обычно это веб-приложение или приложение для беспроводной связи, которому требуется доступ к защищенным ресурсам пользователя. |
Сервер авторизации | Authorization Server | После успешной аутентификации владельца ресурса и авторизации сервер выдает клиенту токен доступа. |
Сервер авторизации может быть тем же сервером, что и ресурсный сервер, или отдельным сервером. Один сервер авторизации может выдавать маркеры доступа, которые принимаются несколькими серверами ресурсов. Алгоритм авторизации примерно такой:
- (A) Клиент запрашивает авторизацию у владельца ресурса. Запросы на авторизацию можно направлять непосредственно владельцу ресурса (как показано) или косвенно через сервер авторизации в качестве посредника (предпочтительный сценарий).
- (B) Владелец ресурса разрешает авторизацию и возвращает учетные данные (например, код).
- (C) Клиент аутентифицируется на сервере авторизации, предоставляет учетные данные авторизации (например, код) и запрашивает токен доступа.
- (D) Сервер авторизации аутентифицирует клиента и проверяет учетные данные авторизации и, если они действительны, выдает токен доступа.
- (E) Клиент запрашивает защищенный ресурс с сервера ресурсов и аутентифицируется, предоставляя маркер доступа.
- (F) Сервер ресурсов проверяет маркер доступа и возвращает защищенный ресурс, если он правильный.
3. Процесс авторизации OAuth2
Из запущенного процесса нетрудно увидеть, что для получения токена доступа сначала должна быть получена авторизация пользователя (разрешение на авторизацию), ну и что, если такая авторизация пользователя получена? OAuth 2.0 определяет четыре типа типов авторизации:
- Код авторизации
- Упрощенный режим (неявный)
- Режим пароля (учетные данные пароля владельца ресурса)
- Режим клиента (учетные данные клиента)
3.1 Режим кода авторизации
Действуйте следующим образом:Режим кода авторизации — это режим авторизации с наиболее полными функциями, наиболее широко используемым и самым строгим процессом. Поскольку это поток, основанный на перенаправлении, клиент должен иметь возможность взаимодействовать с пользовательским агентом владельца ресурса (обычно это веб-браузер) и иметь возможность получать входящие запросы (через перенаправления) с сервера авторизации.
- (A) Пользователь обращается к клиенту, который направляет его на сервер аутентификации.
- (B) Пользователь выбирает, авторизовать ли клиента.
- (C) Предполагая, что пользователь предоставляет авторизацию, сервер аутентификации направляет пользователя на «URI перенаправления» (URI перенаправления), заранее указанный клиентом, вместе с кодом авторизации.
- (D) Клиент получает код авторизации, прикрепляет более ранний «URI перенаправления» и запрашивает токен с сервера аутентификации. Этот шаг выполняется на сервере в фоновом режиме клиента, невидимом для пользователя.
- (E) Сервер аутентификации проверяет код авторизации и URI перенаправления и после подтверждения их правильности отправляет токен доступа и токен обновления клиенту.
Параметры, необходимые для каждого шага, описаны ниже:
На шаге A клиент запрашивает URI запроса авторизации, включая следующие параметры:
- response_type Тип гранта. Обязательный, его значение фиксируется в коде.
- client_id Идентификатор клиента. Требуется для идентификации зарегистрированного клиента на сервере авторизации.
- redirect_uri URI перенаправления. Необязательно, если не заполнено, будет использоваться redirect_uri, зарегистрированный на сервере авторизации и соответствующий client_id.
- область действия Область разрешений приложения, например чтение или запись. Необязательно, если запрошенный доступ выходит за пределы рабочего диапазона, определенного сервером авторизации, он не будет выполнен.
- state представляет текущее состояние клиента. Необязательно, вы можете указать любое значение, сервер авторизации вернет это значение без изменений.
На шаге C сервер отвечает на URI клиента со следующими параметрами:
- codeКод авторизации. Обязательно. Срок действия кода авторизации должен истечь вскоре после его выдачи, чтобы снизить риск утечки. Максимальное время рекомендуется установить на 10 минут. Клиент может использовать код только один раз, иначе он будет отклонен сервером авторизации. Существует однозначное соответствие между этим кодом и идентификатором клиента и URI перенаправления.
- состояние Если запрос клиента содержит этот параметр, ответ сервера аутентификации также должен точно содержать этот параметр.
На шаге D клиент делает HTTP-запрос токена с сервера аутентификации, включая следующие параметры:
- grant_type Тип гранта (режим гранта). Обязательное, фиксированное значение здесь — код авторизации.
- code Код авторизации, полученный на предыдущем шаге. обязательный.
- redirect_uri представляет URI перенаправления. Обязательное и должно совпадать со значением параметра на шаге A.
- client_id указывает идентификатор клиента, который требуется.
существуетSpring Security Oauth2Приведенные выше API выглядят следующим образом, и эти API называются конечными точками:
- Конечная точка авторизации /oauth/authorize: конечная точка авторизации, перейдите на сервер авторизации через эту конечную точку для аутентификации и выполните первый запрос. Несут следующие параметры:
имя параметра | Требуется ли | описывать |
---|---|---|
response_type | необходимые | должен быть код |
client_id | необходимые | идентификатор клиента |
redirect_uri | по желанию | Адрес перенаправления после получения кода авторизации |
scope | по желанию | Область применения |
state | рекомендовать | Текущее состояние клиента можно указать любое значение, сервер аутентификации вернет это значение в неизменном виде, рекомендуется. |
В случае успешной авторизации он будет перенаправлен на redirect_uri со следующими двумя параметрами:
имя параметра | должен вернуться | описывать |
---|---|---|
code | да | Код авторизации, сгенерированный сервером авторизации. Срок действия кодов авторизации должен истекать вскоре после их выдачи, чтобы снизить риск утечки. Максимальное время жизни кода авторизации составляет 10 минут. |
state | да | Если параметр состояния был предоставлен на предыдущем шаге, это значение будет возвращено без изменений. |
- конечная точка токена /oauth/token: конечная точка токена, проверьте и сгенерируйте токен с помощью кода авторизации, полученного на предыдущем шаге, выполните второй запрос и передайте следующие параметры:
имя параметра | Требуется ли | описывать |
---|---|---|
grant_type | необходимые | Используемый режим авторизации, значение фиксируется как "authorization_code" |
code | необходимые | Код авторизации, полученный на предыдущем шаге |
redirect_uri | необходимые | URI перенаправления, который должен совпадать со значением параметра на предыдущем шаге. |
client_id | необходимые | ID клиента |
scope | рекомендовать | Область авторизации, которая должна быть такой же, как и на первом этапе. |
Если запрос токена доступа действителен и авторизован, сервер авторизации выдаст токен доступа и необязательный токен обновления со следующими параметрами ответа:
имя параметра | должен вернуться | описывать |
---|---|---|
access_token | да | Токен доступа, выданный сервером авторизации |
token_type | да | Тип токена, значение не чувствительно к регистру, может быть тип носителя или тип mac |
expires_in | Нет (возврат, если установлен срок действия) | Срок действия, в секундах |
refresh_token | нет | Представляет токен обновления, который используется для получения следующего токена доступа. |
scope | нет | Область разрешений, если таковая имеется, совпадает с областью, запрошенной клиентом. |
3.2 Упрощенный режим (неявное предоставление)
Упрощенный режим (тип неявного гранта) не проходит через сервер стороннего приложения, а напрямую запрашивает токен с сервера аутентификации в браузере, пропуская шаг «код авторизации», отсюда и название. Все шаги выполняются в браузере, токен виден посетителю, а клиент не требует аутентификации. Конкретные шаги см. в RFC6749 4.2 (tools.I ETF.org/HTML/RFC674…) Фестиваль.
Действуйте следующим образом:
- (A) Клиент направляет пользователя на сервер аутентификации.
- (B) Пользователь решает, авторизовать ли клиента.
- (C) Предполагая, что пользователь предоставляет авторизацию, сервер аутентификации направляет пользователя на «URI перенаправления», указанный клиентом, и включает токен доступа в хэш-часть URI.
- (D) Браузер делает запрос к серверу ресурсов, который не включает хеш-значение, полученное на предыдущем шаге.
- (E) Сервер ресурсов возвращает веб-страницу, содержащую код для получения токена в хеш-значении.
- (F) Браузер выполняет сценарий, полученный на предыдущем шаге, для извлечения токена.
- (G) Браузер отправляет токен клиенту.
3.3 Режим пароля (предоставление учетных данных владельца ресурса)
В режиме пароля пользователь предоставляет клиенту свое имя пользователя и пароль. Клиент использует эту информацию для запроса авторизации у «поставщика услуг». В этом режиме пользователь должен сообщить свой пароль клиенту, но клиент не должен хранить пароль. Это обычно используется, когда пользователь имеет высокое доверие к клиенту, например, клиент является частью операционной системы или производится известной компанией. Сервер аутентификации должен использовать этот режим только в том случае, если другие режимы авторизации не могут быть реализованы. См. RFC6749 4.3 (tools.I ETF.org/HTML/RFC674…) Фестиваль.
Действуйте следующим образом:
- (A) Пользователь предоставляет клиенту имя пользователя и пароль.
- (B) Клиент отправляет имя пользователя и пароль на сервер аутентификации и запрашивает токен у последнего.
- (C) После того, как сервер аутентификации подтвердит правильность, он предоставляет токен доступа клиенту.
На этом шаге разрабатывается только один запрос. В Spring Security Oauth2 конечная точка выглядит следующим образом:
- конечная точка токена /oauth/token: конечная точка токена, запрос со следующими параметрами:
параметры запроса | Требуется ли | описывать |
---|---|---|
grant_type | необходимые | Используемый режим пароля, значение фиксируется как пароль |
username | необходимые | имя пользователя |
password | необходимые | пароль |
scope | по желанию | Область разрешения запроса |
Ответ на запрос относится к шаблону кода авторизации.
3.4 Режим клиента (предоставление учетных данных клиента)
Режим клиента (предоставление учетных данных клиента) означает, что клиент аутентифицируется у «поставщика услуг» от своего имени, а не от имени пользователя. Строго говоря, клиентский режим не является частью проблемы, которую призван решить фреймворк OAuth. В этом режиме пользователь регистрируется напрямую у клиента, а клиент запрашивает у «поставщика услуг» предоставление услуг от своего имени, по сути проблемы с авторизацией не возникает. См. RFC6749 4.4 (tools.I ETF.org/HTML/RFC674…) Фестиваль.
Действуйте следующим образом:
- (A) Клиент аутентифицируется на сервере аутентификации и запрашивает токен доступа.
- (B) После того, как сервер аутентификации подтвердит правильность, он предоставляет токен доступа клиенту.
На этом шаге разрабатывается только один запрос. В Spring Security Oauth2 конечная точка выглядит следующим образом:
- конечная точка токена /oauth/token: конечная точка токена, запрос со следующими параметрами:
параметры запроса | Требуется ли | описывать |
---|---|---|
grant_type | необходимые | Используемый режим шифрования, значение фиксируется на client_credentials. |
scope | по желанию | Область разрешения запроса |
Ответ на запрос относится к шаблону кода авторизации.
4. Создайте демонстрационный проект
Этот случай в основном демонстрирует метод аутентификации и авторизации режима пароля, а затем наследует Oauth2 в Spring Cloud и добавляет другие режимы;
4.1 Добавить зависимости
pom.xml выглядит следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hxmec</groupId>
<artifactId>spring-security-oauth2-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-oauth2-demo</name>
<description>spring security oauth2 demo</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<!-- jdk版本 -->
<java.version>1.8</java.version>
<!-- SpringCloud版本号,官方最新稳定版本 -->
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<resources>
<!-- 先指定 src/main/resources下所有文件及文件夹为资源文件 -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${maven.compiler.encoding}</encoding>
</configuration>
</plugin>
<plugin>
<!--打包跳过测试-->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!-- resources资源插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- java文档插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
4.2 Конфигурация сервера авторизации
Сервер авторизации и сервер ресурсов могут быть одним и тем же сервером или разными серверами, в этом случае предполагается, что используется один и тот же сервер, а сервер авторизации и сервер ресурсов включены через разные конфигурации.
Ниже приведен код конфигурации сервера авторизации. Создайте пользовательский класс, который наследуется от AuthorizationServerConfigurerAdapter, завершите настройку сервера авторизации, а затем включите сервер авторизации через аннотацию @EnableAuthorizationServer:
Примечание. authorGrantTypes("password", "refresh_token") указывает, что режим авторизации в OAuth 2 — "пароль" и "refresh_token". В стандартном протоколе OAuth 2 режим авторизации не включает «refresh_token», но он классифицируется как таковой в реализации Spring Security, поэтому, если вам нужно обновить access_token, вам нужен такой режим авторизации.
AuthorizationServerConfig.java
/**
* 功能描述: 授权服务器配置
* @author Trazen
* @date 2020/7/8 22:26
*/
/**
* 功能描述: 授权服务器配置
* @author Trazen
* @date 2020/7/8 22:26
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 该对象用来支持 password 模式
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 该对象用来将令牌信息存储到内存中
* OAuth2令牌持久化主要有以下几种
* InMemoryTokenStore 内存存储 OAuth2默认储存方式
* JdbcTokenStore 数据库存储
* RedisTokenStore Redis存储
* JwkTokenStore & JwtTokenStore
*/
@Autowired(required = false)
private TokenStore inMemoryTokenStore;
/**
* 该对象将为刷新token提供支持
*
* InMemoryUserDetailsManager:从内存中加载用户账号 默认
* JdbcUserDetailsManager:从数据库中加载用户账号
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 指定密码的加密方式
* 推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
// 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10)
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 表示支持 client_id 和 client_secret 做登录认证
security.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("password")
//授权模式为password和refresh_token两种
.authorizedGrantTypes("password", "refresh_token")
// 配置access_token的过期时间
.accessTokenValiditySeconds(1800)
//配置资源id
.resourceIds("rid")
.scopes("all")
//123加密后的密码
.secret("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置令牌的存储(这里存放在内存中)
endpoints.tokenStore(inMemoryTokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
4.3 Конфигурация сервера ресурсов
Пользовательский класс наследуется от ResourceServerConfigurerAdapter и добавляет аннотацию @EnableResourceServer для включения конфигурации сервера ресурсов.
ResourceServerConfig.java
/**
* 功能描述: 资源服务器配置
* @author Trazen
* @date 2020/7/8 22:40
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
// 配置资源id,这里的资源id和授权服务器中的资源id一致
resources.resourceId("resId")
// 设置这些资源仅基于令牌认证
.stateless(true);
}
/**
* 配置 URL 访问权限
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated();
}
}
4.4 Конфигурация безопасности
HttpSecurity участвует в этой конфигурации Spring Security и в приведенной выше конфигурации сервера ресурсов. Приоритет конфигурации в Spring Security выше, чем конфигурация на сервере ресурсов, то есть адрес запроса сначала проходит через HttpSecurity Spring Security, а затем HttpSecurity сервера ресурсов.
/**
* 功能描述: Spring Security 配置
* @author Trazen
* @date 2020/7/8 22:42
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
/**
* 增加一个admin角色用户,一个user角色用户
* admin----123456
* trazen----123456
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26")
.roles("admin")
.and()
.withUser("trazen")
.password("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26")
.roles("user");
}
/**
* 开放oauth2授权端点
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth/**").authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.and().csrf().disable();
}
}
4.5 Добавить тестовый интерфейс
DemoController.java
/**
* 功能描述: 测试接口
* @author Trazen
* @date 2020/7/8 22:44
*/
@RestController
public class DemoController {
@GetMapping("/admin/hello")
public String admin() {
return "hello admin";
}
@GetMapping("/user/hello")
public String user() {
return "hello user";
}
}
5. Тест
- После запуска проекта получите AccessToken через конечную точку токена.
- Адрес запроса: oauth/токен
- Заголовок запроса: Basic base64encode(client_id:client_secret)
- Параметры запроса: имя пользователя, пароль, режим авторизации, область действия
- Возвращаемый результат: access_token указывает токен, используемый для получения других ресурсов, refresh_token используется для обновления токена, expires_in указывает время истечения срока действия access_token.
Добавить базовую аутентификацию
Заполните параметры запроса
При доступе к ресурсам заголовок запроса может сопровождаться AccessToken
6. Расширения
6.1 RedisTokenStore для хранения токенов
Есть проблема, требующая особого пояснения:
Если в spring security oauth2, когда служба авторизации использует redis для хранения токена, сообщается об ошибке: java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V Это показывает, что есть проблема с версией, решение — обновить версию oauth2 до 2.3.3.
Внедрение зависимостей Redis
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 指明版本,解决redis存储出现的问题:java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V问题 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
Изменение конфигурации сервера авторизации
/**
* 功能描述: 授权服务器配置
* @author Trazen
* @date 2020/7/8 22:26
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 该对象用来支持 password 模式
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 该对象用来将令牌信息存储到内存中
* OAuth2令牌持久化主要有以下几种
* InMemoryTokenStore 内存存储 OAuth2默认储存方式
* JdbcTokenStore 数据库存储
* RedisTokenStore Redis存储
* JwkTokenStore & JwtTokenStore
*/
@Autowired(required = false)
private TokenStore inMemoryTokenStore;
/**
* 该对象将为刷新token提供支持
*
* InMemoryUserDetailsManager:从内存中加载用户账号 默认
* JdbcUserDetailsManager:从数据库中加载用户账号
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 该对象用来将令牌信息存储到Redis中
*/
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 指定密码的加密方式
* 推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
// 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10)
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 表示支持 client_id 和 client_secret 做登录认证
security.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("app")
//授权模式为password和refresh_token两种
.authorizedGrantTypes("password", "refresh_token")
// 配置access_token的过期时间
.accessTokenValiditySeconds(1800)
//配置资源id
.resourceIds("resId")
.scopes("all")
//123456加密后的密码
.secret("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置令牌的存储(这里存放在内存中)
// endpoints.tokenStore(inMemoryTokenStore)
// .authenticationManager(authenticationManager)
// .userDetailsService(userDetailsService);
//配置令牌存放在Redis中
endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
контрольная работа После получения AccessToken по запросу проверьте, есть ли соответствующие данные в Redis
6.2 JwtTokenStore для хранения токенов
Введите зависимости, связанные с jwt:
<!-- jwt -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
Добавить класс конфигурации для токена хранилища Jwt
/**
* 功能描述: 使用Jwt存储token的配置
* @author Trazen
* @date 2020/7/10 15:00
*/
@Configuration
public class JwtTokenStoreConfig {
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
//配置JWT使用的秘钥
accessTokenConverter.setSigningKey("test_key");
return accessTokenConverter;
}
}
Изменение конфигурации сервера авторизации
/**
* 功能描述: 授权服务器配置
* @author Trazen
* @date 2020/7/8 22:26
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 该对象用来支持 password 模式
*/
@Autowired
private AuthenticationManager authenticationManager;
/*****************InMemoryTokenStore引入 ****************************/
/**
*
* 该对象用来将令牌信息存储到内存中
* OAuth2令牌持久化主要有以下几种
* InMemoryTokenStore 内存存储 OAuth2默认储存方式
* JdbcTokenStore 数据库存储
* RedisTokenStore Redis存储
* JwkTokenStore & JwtTokenStore
*/
@Autowired(required = false)
private TokenStore inMemoryTokenStore;
/**
* 该对象将为刷新token提供支持
*
* InMemoryUserDetailsManager:从内存中加载用户账号 默认
* JdbcUserDetailsManager:从数据库中加载用户账号
*/
@Autowired
private UserDetailsService userDetailsService;
/*****************redisTokenStore引入 ****************************/
/**
* 使用Redis存储时添加
* 该对象用来将令牌信息存储到Redis中
*/
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/*****************jwtTokenStore引入 ****************************/
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/**
* 指定密码的加密方式
* 推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
// 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10)
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 表示支持 client_id 和 client_secret 做登录认证
security.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("app")
//授权模式为password和refresh_token两种
.authorizedGrantTypes("password", "refresh_token")
// 配置access_token的过期时间
.accessTokenValiditySeconds(1800)
//配置资源id
.resourceIds("resId")
.scopes("all")
//123456加密后的密码
.secret("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置令牌的存储(这里存放在内存中)
// endpoints.tokenStore(inMemoryTokenStore)
// .authenticationManager(authenticationManager)
// .userDetailsService(userDetailsService);
//配置令牌存放在Redis中
// endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
// .authenticationManager(authenticationManager)
// .userDetailsService(userDetailsService);
//配置使用jwt存储token
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.accessTokenConverter(jwtAccessTokenConverter);
}
}
Получите AccessToken по запросу, чтобы увидеть, вступит ли он в силу
6.3 Информация о маркере, возвращаемая пользовательским расширением
Обычно запрос конечной точки токена возвращается, как показано на рисунке ниже, но в реальной работе нам часто приходится настраивать нашу собственную возвращаемую информацию на этой основе, что требует от нас настройки этой вещи.Ниже показано, как использовать TokenEnhancer для автоматического Определить информацию о возврате
1. Настройте класс TokenEnhancer, добавьте информацию, которую необходимо настроить и вернуть, код выглядит следующим образом.
/**
* 功能描述: Token增强
* @author Trazen
* @date 2020/7/10 16:29
*/
@Component
public class HxTokenEnhancer implements TokenEnhancer {
private final static String CLIENT_CREDENTIALS = "client_credentials";
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
/**
* 客户端模式不修改
*/
if (CLIENT_CREDENTIALS.equals(authentication.getOAuth2Request().getGrantType())) {
return accessToken;
}
final Map<String, Object> additionalInfo = new HashMap<>(8);
Map<String, Object> info = new LinkedHashMap<>();
info.put("author", "Trazen");
info.put("email", "trazen@126.com");
info.put("GitHub", "https://github.com/ty1972873004/spring-security-oauth2-demo");
info.put("user", SecurityContextHolder.getContext().getAuthentication().getPrincipal());
additionalInfo.put("info", info);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
Изменить конфигурацию сервера авторизации
/**
* 功能描述: 授权服务器配置
* @author Trazen
* @date 2020/7/8 22:26
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 该对象用来支持 password 模式
*/
@Autowired
private AuthenticationManager authenticationManager;
/*****************InMemoryTokenStore引入 ****************************/
/**
*
* 该对象用来将令牌信息存储到内存中
* OAuth2令牌持久化主要有以下几种
* InMemoryTokenStore 内存存储 OAuth2默认储存方式
* JdbcTokenStore 数据库存储
* RedisTokenStore Redis存储
* JwkTokenStore & JwtTokenStore
*/
@Autowired(required = false)
private TokenStore inMemoryTokenStore;
/**
* 该对象将为刷新token提供支持
*
* InMemoryUserDetailsManager:从内存中加载用户账号 默认
* JdbcUserDetailsManager:从数据库中加载用户账号
*/
@Autowired
private UserDetailsService userDetailsService;
/*****************redisTokenStore引入 ****************************/
/**
* 使用Redis存储时添加
* 该对象用来将令牌信息存储到Redis中
*/
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/*****************jwtTokenStore引入 ****************************/
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private HxTokenEnhancer tokenEnhancer;
/**
* 指定密码的加密方式
* 推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
// 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10)
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 表示支持 client_id 和 client_secret 做登录认证
security.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("app")
//授权模式为password和refresh_token两种
.authorizedGrantTypes("password", "refresh_token")
// 配置access_token的过期时间
.accessTokenValiditySeconds(1800)
//配置资源id
.resourceIds("resId")
.scopes("all")
//123456加密后的密码
.secret("$2a$10$tnj.nZjSzCBckTh2fRRK9.ZTYfU0y4pDiZZChKxxeOElBsxaQCn26");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//配置令牌的存储(这里存放在内存中)
// endpoints.tokenStore(inMemoryTokenStore)
// .authenticationManager(authenticationManager)
// .userDetailsService(userDetailsService);
//配置令牌存放在Redis中
// endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
// .authenticationManager(authenticationManager)
// .userDetailsService(userDetailsService);
//配置使用jwt存储token
//添加自定义Token信息配置
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancerList = new ArrayList();
enhancerList.add(jwtAccessTokenConverter);
enhancerList.add(tokenEnhancer);
tokenEnhancerChain.setTokenEnhancers(enhancerList);
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenEnhancer(tokenEnhancerChain)
.accessTokenConverter(jwtAccessTokenConverter);
}
}
Тест выглядит следующим образом:
7. Адрес источника
- github[GitHub.com/tianya197287300…]