В этом руководстве описан процесс создания централизованного сервера аутентификации и авторизации с Spring Boot 2, также будет предоставлен демонстрационный сервер ресурсов.
Если вы не знакомы с OAuth2, я рекомендую прочитать это.
Pre-REQ
- JDK 1.8
- Текстовый редактор или ваш любимый IDE
- Maven 3.0+
Обзор реализации
Для этого проекта мы будем использовать Spring Security 5 через Spring Boot. Если вы знакомы с более ранними версиями, может пригодиться это руководство по миграции весенней загрузки .
OAuth2 Терминология
- Владелец ресурса
- Пользователь, который авторизует приложение для доступа к своей учетной записи. Доступ ограничен
scope
.
- Пользователь, который авторизует приложение для доступа к своей учетной записи. Доступ ограничен
- Ресурсный сервер :
- Сервер, который обрабатывает аутентифицированные запросы после того, как
client
получилaccess token
.
- Сервер, который обрабатывает аутентифицированные запросы после того, как
- клиент
- Приложение, которое обращается к защищенным ресурсам от имени владельца ресурса.
- Сервер авторизации
- Сервер, который выдает токены доступа после успешной аутентификации
client
иresource owner
и авторизации запроса.
- Сервер, который выдает токены доступа после успешной аутентификации
- Токен доступа
- Уникальный токен, используемый для доступа к защищенным ресурсам
- Сфера
- Разрешение
- JWT
- JSON Web Token — это метод для безопасного представления заявок между двумя сторонами, как определено в RFC 7519
- Тип гранта
-
grant
— это метод получения токена доступа. - Подробнее о типах грантов читайте здесь
-
Сервер авторизации
Для построения нашего Authorization Server
мы будем использовать Spring Security 5.x до Spring Boot 2.1.x.
зависимости
Вы можете перейти к start.spring.io и создать новый проект, а затем добавить следующие зависимости:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
< dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-security</ artifactId > </ dependency > < dependency > < groupId >org.springframework.security.oauth.boot</ groupId > < artifactId >spring-security-oauth2-autoconfigure</ artifactId > < version >2.1.2.RELEASE</ version > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-jdbc</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-configuration-processor</ artifactId > < optional >true</ optional > </ dependency > < dependency > < groupId >com.h2database</ groupId > < artifactId >h2</ artifactId > < scope >runtime</ scope > </ dependency > </ dependencies > |
База данных
Для этого урока мы будем использовать базу данных H2 .
Здесь вы можете найти справочную схему SQL OAuth2, необходимую для Spring Security.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
CREATE TABLE IF NOT EXISTS oauth_client_details ( client_id VARCHAR (256) PRIMARY KEY , resource_ids VARCHAR (256), client_secret VARCHAR (256) NOT NULL , 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 (4000), autoapprove VARCHAR (256) ); CREATE TABLE IF NOT EXISTS oauth_client_token ( token_id VARCHAR (256), token BLOB, authentication_id VARCHAR (256) PRIMARY KEY , user_name VARCHAR (256), client_id VARCHAR (256) ); CREATE TABLE IF NOT EXISTS oauth_access_token ( token_id VARCHAR (256), token BLOB, authentication_id VARCHAR (256), user_name VARCHAR (256), client_id VARCHAR (256), authentication BLOB, refresh_token VARCHAR (256) ); CREATE TABLE IF NOT EXISTS oauth_refresh_token ( token_id VARCHAR (256), token BLOB, authentication BLOB ); CREATE TABLE IF NOT EXISTS oauth_code ( code VARCHAR (256), authentication BLOB ); |
Примечание. Поскольку в этом руководстве используется
JWT
не все таблицы являются обязательными.
А затем добавьте следующую запись
1
2
3
|
-- The encrypted client_secret it `secret` INSERT INTO oauth_client_details (client_id, client_secret, scope, authorized_grant_types, authorities, access_token_validity) VALUES ( 'clientId' , '{bcrypt}$2a$10$vCXMWCn7fDZWOcLnIEhmK.74dvK1Eh8ae2WrWlhr2ETPLoxQctN4.' , 'read,write' , 'password,refresh_token,client_credentials' , 'ROLE_CLIENT' , 300); |
client_secret
вышеclient_secret
был создан с использованием bcrypt .
Префикс{bcrypt}
является обязательным, потому что мы будем использовать новую функцию DelegatingPasswordEncoder в Spring Security 5.x.
Ниже вы можете найти справочную SQL-схему User
and Authority
используемую Spring org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY , username VARCHAR (256) NOT NULL , password VARCHAR (256) NOT NULL , enabled TINYINT(1), UNIQUE KEY unique_username(username) ); CREATE TABLE IF NOT EXISTS authorities ( username VARCHAR (256) NOT NULL , authority VARCHAR (256) NOT NULL , PRIMARY KEY (username, authority) ); |
Как и прежде, добавьте следующие записи для пользователя и его полномочий.
1
2
3
|
-- The encrypted password is `pass` INSERT INTO users (id, username, password , enabled) VALUES (1, 'user' , '{bcrypt}$2a$10$cyf5NfobcruKQ8XGjUJkEegr9ZWFqaea6vjpXWEaSqTa2xL9wjgQC' , 1); INSERT INTO authorities (username, authority) VALUES ( 'user' , 'ROLE_USER' ); |
Конфигурация Spring Security
Добавьте следующий класс конфигурации Spring.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import javax.sql.DataSource; @EnableWebSecurity public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { private final DataSource dataSource; private PasswordEncoder passwordEncoder; private UserDetailsService userDetailsService; public WebSecurityConfiguration( final DataSource dataSource) { this .dataSource = dataSource; } @Override protected void configure( final AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super .authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { if (passwordEncoder == null ) { passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); } return passwordEncoder; } @Bean public UserDetailsService userDetailsService() { if (userDetailsService == null ) { userDetailsService = new JdbcDaoImpl(); ((JdbcDaoImpl) userDetailsService).setDataSource(dataSource); } return userDetailsService; } } |
Цитата из весеннего блога :
Аннотация @EnableWebSecurity и WebSecurityConfigurerAdapter работают вместе для обеспечения безопасности на основе Интернета.
Если вы используете Spring Boot, объект DataSource
будет автоматически настроен, и вы можете просто внедрить его в класс, а не определять его самостоятельно. он должен быть JdbcDaoImpl
в UserDetailsService
в котором будет использоваться предоставленный JdbcDaoImpl
предоставленный Spring Security, при необходимости вы можете заменить его своей собственной реализацией.
Так как AuthenticationManager
Spring Security требуется некоторыми автоматически настроенными Spring @Bean
, необходимо переопределить метод authenticationManagerBean
а аннотировать — как @Bean
.
PasswordEncoder
будет обрабатываться PasswordEncoderFactories.createDelegatingPasswordEncoder()
который обрабатывает несколько кодировщиков и делегатов паролей на основе префикса, в нашем примере мы начинаем с паролей {bcrypt}
.
Настройка сервера авторизации
Сервер авторизации проверяет учетные данные client
и user
и предоставляет токены. В этом руководстве мы будем создавать JSON Web Tokens
известные как JWT
.
Чтобы подписать сгенерированные токены JWT
мы будем использовать самозаверяющий сертификат, и прежде чем мы начнем с Spring Configuration, давайте создадим класс @ConfigurationProperties
для привязки наших свойств конфигурации.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.io.Resource; @ConfigurationProperties ( "security" ) public class SecurityProperties { private JwtProperties jwt; public JwtProperties getJwt() { return jwt; } public void setJwt(JwtProperties jwt) { this .jwt = jwt; } public static class JwtProperties { private Resource keyStore; private String keyStorePassword; private String keyPairAlias; private String keyPairPassword; public Resource getKeyStore() { return keyStore; } public void setKeyStore(Resource keyStore) { this .keyStore = keyStore; } public String getKeyStorePassword() { return keyStorePassword; } public void setKeyStorePassword(String keyStorePassword) { this .keyStorePassword = keyStorePassword; } public String getKeyPairAlias() { return keyPairAlias; } public void setKeyPairAlias(String keyPairAlias) { this .keyPairAlias = keyPairAlias; } public String getKeyPairPassword() { return keyPairPassword; } public void setKeyPairPassword(String keyPairPassword) { this .keyPairPassword = keyPairPassword; } } } |
Добавьте следующий класс конфигурации Spring.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
|
import com.marcosbarbero.lab.sec.oauth.jwt.config.props.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.password.PasswordEncoder; 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.ClientDetailsService; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; import javax.sql.DataSource; import java.security.KeyPair; @Configuration @EnableAuthorizationServer @EnableConfigurationProperties (SecurityProperties. class ) public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { private final DataSource dataSource; private final PasswordEncoder passwordEncoder; private final AuthenticationManager authenticationManager; private final SecurityProperties securityProperties; private JwtAccessTokenConverter jwtAccessTokenConverter; private TokenStore tokenStore; public AuthorizationServerConfiguration( final DataSource dataSource, final PasswordEncoder passwordEncoder, final AuthenticationManager authenticationManager, final SecurityProperties securityProperties) { this .dataSource = dataSource; this .passwordEncoder = passwordEncoder; this .authenticationManager = authenticationManager; this .securityProperties = securityProperties; } @Bean public TokenStore tokenStore() { if (tokenStore == null ) { tokenStore = new JwtTokenStore(jwtAccessTokenConverter()); } return tokenStore; } @Bean public DefaultTokenServices tokenServices( final TokenStore tokenStore, final ClientDetailsService clientDetailsService) { DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setSupportRefreshToken( true ); tokenServices.setTokenStore(tokenStore); tokenServices.setClientDetailsService(clientDetailsService); tokenServices.setAuthenticationManager( this .authenticationManager); return tokenServices; } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { if (jwtAccessTokenConverter != null ) { return jwtAccessTokenConverter; } SecurityProperties.JwtProperties jwtProperties = securityProperties.getJwt(); KeyPair keyPair = keyPair(jwtProperties, keyStoreKeyFactory(jwtProperties)); jwtAccessTokenConverter = new JwtAccessTokenConverter(); jwtAccessTokenConverter.setKeyPair(keyPair); return jwtAccessTokenConverter; } @Override public void configure( final ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc( this .dataSource); } @Override public void configure( final AuthorizationServerEndpointsConfigurer endpoints) { endpoints.authenticationManager( this .authenticationManager) .accessTokenConverter(jwtAccessTokenConverter()) .tokenStore(tokenStore()); } @Override public void configure( final AuthorizationServerSecurityConfigurer oauthServer) { oauthServer.passwordEncoder( this .passwordEncoder).tokenKeyAccess( "permitAll()" ) .checkTokenAccess( "isAuthenticated()" ); } private KeyPair keyPair(SecurityProperties.JwtProperties jwtProperties, KeyStoreKeyFactory keyStoreKeyFactory) { return keyStoreKeyFactory.getKeyPair(jwtProperties.getKeyPairAlias(), jwtProperties.getKeyPairPassword().toCharArray()); } private KeyStoreKeyFactory keyStoreKeyFactory(SecurityProperties.JwtProperties jwtProperties) { return new KeyStoreKeyFactory(jwtProperties.getKeyStore(), jwtProperties.getKeyStorePassword().toCharArray()); } } |
В вышеприведенном классе вы найдете все необходимые Spring @Bean
для JWT
. Наиболее важными @Bean
являются: JwtAccessTokenConverter
, JwtTokenStore
и DefaultTokenServices
.
JwtAccessTokenConverter
использует самозаверяющий сертификат для подписи сгенерированных токенов.
Реализация JwtTokenStore
которая просто считывает данные из самих токенов. На самом деле это не магазин, так как он никогда ничего не сохраняет и использует JwtAccessTokenConverter
для генерации и чтения токенов.
DefaultTokenServices
использует TokenStore
для сохранения токенов.
Следуйте этому руководству, чтобы создать самозаверяющий сертификат .
После создания вашего самозаверяющего сертификата настройте его в своем application.yml
1
2
3
4
5
6
|
security: jwt: key-store: classpath:keystore.jks key-store-password: letmein key-pair-alias: mytestkey key-pair-password: changeme |
Конфигурация сервера ресурсов
На сервере ресурсов размещаются HTTP-ресурсы, в которых может быть документ, фотография или что-то еще, в нашем случае это будет REST API, защищенный OAuth2.
зависимости
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
< dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-security</ artifactId > </ dependency > < dependency > < groupId >org.springframework.security.oauth.boot</ groupId > < artifactId >spring-security-oauth2-autoconfigure</ artifactId > < version >2.1.2.RELEASE</ version > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-configuration-processor</ artifactId > < optional >true</ optional > </ dependency > < dependency > < groupId >commons-io</ groupId > < artifactId >commons-io</ artifactId > < version >2.6</ version > </ dependency > </ dependencies > |
Определение нашего защищенного API
Приведенный ниже код определяет конечную точку /me
которая возвращает объект Principal
и для этого требуется, чтобы аутентифицированный пользователь имел ROLE_USER
для доступа.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.security.Principal; @RestController @RequestMapping ( "/me" ) public class UserController { @GetMapping @PreAuthorize ( "hasRole('ROLE_USER')" ) public ResponseEntity<Principal> get( final Principal principal) { return ResponseEntity.ok(principal); } } |
Аннотация @PreAuthorize
проверяет, имеет ли пользователь заданную роль перед выполнением кода, чтобы заставить его работать, необходимо включить аннотации prePost
, для этого добавьте следующий класс:
1
2
3
4
5
6
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; @EnableGlobalMethodSecurity (prePostEnabled = true ) public class WebSecurityConfiguration { } |
Важной частью здесь является @EnableGlobalMethodSecurity(prePostEnabled = true)
, для флага prePostEnabled
по умолчанию установлено значение false
.
Конфигурация сервера ресурсов
Для декодирования токена JWT
необходимо будет использовать public key
из самозаверяющего сертификата, используемого на сервере авторизации для подписи токена, для этого сначала создадим класс @ConfigurationProperties
для привязки свойств конфигурации.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.io.Resource; @ConfigurationProperties ( "security" ) public class SecurityProperties { private JwtProperties jwt; public JwtProperties getJwt() { return jwt; } public void setJwt(JwtProperties jwt) { this .jwt = jwt; } public static class JwtProperties { private Resource publicKey; public Resource getPublicKey() { return publicKey; } public void setPublicKey(Resource publicKey) { this .publicKey = publicKey; } } } |
Используйте следующую команду для экспорта public key
из сгенерированного JKS:
1
|
$ keytool -list -rfc --keystore keystore.jks | openssl x509 -inform pem -pubkey -noout |
Пример ответа выглядит так:
1
2
3
4
5
6
7
8
9
|
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmWI2jtKwvf0W1hdMdajc h+mFx9FZe3CZnKNvT/d0+2O6V1Pgkz7L2FcQx2uoV7gHgk5mmb2MZUsy/rDKj0dM fLzyXqBcCRxD6avALwu8AAiGRxe2dl8HqIHyo7P4R1nUaea1WCZB/i7AxZNAQtcC cSvMvF2t33p3vYXY6SqMucMD4yHOTXexoWhzwRqjyyC8I8uCYJ+xIfQvaK9Q1RzK Rj99IRa1qyNgdeHjkwW9v2Fd4O/Ln1Tzfnk/dMLqxaNsXPw37nw+OUhycFDPPQF/ H4Q4+UDJ3ATf5Z2yQKkUQlD45OO2mIXjkWprAmOCi76dLB2yzhCX/plGJwcgb8XH EQIDAQAB -----END PUBLIC KEY----- |
Скопируйте его в файл public.txt
и поместите его в /src/main/resources
а затем настройте ваш application.yml
указывая на этот файл:
1
2
3
|
security: jwt: public -key: classpath: public .txt |
Теперь давайте добавим конфигурацию Spring для сервера ресурсов.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
import org.apache.commons.io.IOUtils; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; 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.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import java.io.IOException; import static java.nio.charset.StandardCharsets.UTF_8; @Configuration @EnableResourceServer @EnableConfigurationProperties (SecurityProperties. class ) public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { private static final String ROOT_PATTERN = "/**" ; private final SecurityProperties securityProperties; private TokenStore tokenStore; public ResourceServerConfiguration( final SecurityProperties securityProperties) { this .securityProperties = securityProperties; } @Override public void configure( final ResourceServerSecurityConfigurer resources) { resources.tokenStore(tokenStore()); } @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(HttpMethod.GET, ROOT_PATTERN).access( "#oauth2.hasScope('read')" ) .antMatchers(HttpMethod.POST, ROOT_PATTERN).access( "#oauth2.hasScope('write')" ) .antMatchers(HttpMethod.PATCH, ROOT_PATTERN).access( "#oauth2.hasScope('write')" ) .antMatchers(HttpMethod.PUT, ROOT_PATTERN).access( "#oauth2.hasScope('write')" ) .antMatchers(HttpMethod.DELETE, ROOT_PATTERN).access( "#oauth2.hasScope('write')" ); } @Bean public DefaultTokenServices tokenServices( final TokenStore tokenStore) { DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(tokenStore); return tokenServices; } @Bean public TokenStore tokenStore() { if (tokenStore == null ) { tokenStore = new JwtTokenStore(jwtAccessTokenConverter()); } return tokenStore; } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setVerifierKey(getPublicKeyAsString()); return converter; } private String getPublicKeyAsString() { try { return IOUtils.toString(securityProperties.getJwt().getPublicKey().getInputStream(), UTF_8); } catch (IOException e) { throw new RuntimeException(e); } } } |
Важной частью этой конфигурации являются три @Bean
: JwtAccessTokenConverter
, TokenStore
и DefaultTokenServices
:
-
JwtAccessTokenConverter
используетpublic key
JwtAccessTokenConverter
. -
JwtTokenStore
используетJwtAccessTokenConverter
для чтения токенов. -
DefaultTokenServices
используетJwtTokenStore
для сохранения токенов.
Тестируем все вместе
Чтобы проверить все вместе, нам нужно раскрутить Authorization Server
Resource Server
, в моей настройке он будет работать на портах 9000
и 9100
соответственно.
Генерация токена
01
02
03
04
05
06
07
08
09
10
|
$ curl -u clientId:secret -X POST localhost: 9000 /oauth/token\?grant_type=password\&username=user\&password=pass { "access_token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDgxODk0NDUsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjFjYWQ3MTktZTkwMS00Njk5LTlhOWEtYTIwYzk2NDM5NjAzIiwiY2xpZW50X2lkIjoiY2xpZW50SWQiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXX0.LkQ3KAj2kPY7yKmwXlhIFaHtt-31mJGWPb-_VpC8PWo9IBUpZQxg76WpahBJjet6O1ICx8b5Ab2CxH7ErTl0tL1jk5VZ_kp66E9E7bUQn-C09CY0fqxAan3pzpGrJsUvcR4pzyzLoRCuAqVRF5K2mdDQUZ8NaP0oXeVRuxyRdgjwMAkQGHpFC_Fk-7Hbsq2Y0GikD0UdkaH2Ey_vVyKy5aj3NrAZs62KFvQfSbifxd4uBHzUJSkiFE2Cx3u1xKs3W2q8MladwMwlQmWJROH6lDjQiybUZOEhJaktxQYGAinScnm11-9WOdaqohcr65PAQt48__rMRi0TUgvsxpz6ow" , "token_type" : "bearer" , "refresh_token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl0sImF0aSI6ImIxY2FkNzE5LWU5MDEtNDY5OS05YTlhLWEyMGM5NjQzOTYwMyIsImV4cCI6MTU1MDc4MTE0NSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6Ijg2OWFjZjM2LTJiODAtNGY5Ni04MzUwLTA5NTgyMzE3NTAzMCIsImNsaWVudF9pZCI6ImNsaWVudElkIn0.TDQwUNb627-f0-Cjn1vWZXFpzZSGpeKZq85ivA9zY_atOXM2WfjOxTLE6phnNLevjLSNAGrx1skm_sx6leQlrrmDi36nwiR7lvhv8xMbn1DkF5KaoWPhldW7GHsSIiauMu_cJ5Kmq89ZOEOlxYoXlLwfWYo75ISkKNYqko98yDogGrRAJxtc1aKIBLypLchhoCf8w43efd11itwvBdaLIb5ACfN30kztUqQtbeL8voQP6tOsRZbCgbOOKMTulOCRyBvaora4GJDV2qdvXdCUT-kORKDj9liqt2ae7OJzb2FuuXCGqBUrxYYK-H-wdwh7XFkXVe74Lev9YDUbyEmDHg" , "expires_in" : 299 , "scope" : "read write" , "jti" : "b1cad719-e901-4699-9a9a-a20c96439603" } |
Доступ к ресурсу
Теперь, когда вы сгенерировали токен, скопируйте access_token
и добавьте его в запрос в HTTP-заголовке Authorization
, например:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
curl localhost:9100/me -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDgxODk0NDUsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjFjYWQ3MTktZTkwMS00Njk5LTlhOWEtYTIwYzk2NDM5NjAzIiwiY2xpZW50X2lkIjoiY2xpZW50SWQiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXX0.LkQ3KAj2kPY7yKmwXlhIFaHtt-31mJGWPb-_VpC8PWo9IBUpZQxg76WpahBJjet6O1ICx8b5Ab2CxH7ErTl0tL1jk5VZ_kp66E9E7bUQn-C09CY0fqxAan3pzpGrJsUvcR4pzyzLoRCuAqVRF5K2mdDQUZ8NaP0oXeVRuxyRdgjwMAkQGHpFC_Fk-7Hbsq2Y0GikD0UdkaH2Ey_vVyKy5aj3NrAZs62KFvQfSbifxd4uBHzUJSkiFE2Cx3u1xKs3W2q8MladwMwlQmWJROH6lDjQiybUZOEhJaktxQYGAinScnm11-9WOdaqohcr65PAQt48__rMRi0TUgvsxpz6ow" { "authorities" : [ { "authority" : "ROLE_GUEST" } ], "details" : { "remoteAddress" : "127.0.0.1" , "sessionId" : null , "tokenValue" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDgyMzcxNDEsInVzZXJfbmFtZSI6Imd1ZXN0IiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9HVUVTVCJdLCJqdGkiOiIzNDk1ODE1MC0wOGJkLTQwMDYtYmNhMC1lM2RkYjAxMGU2NjUiLCJjbGllbnRfaWQiOiJjbGllbnRJZCIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdfQ.WUwAh-aKgh_Bqk-a9ijw67EI6H8gFrb3D_WdwlEcITskIybhacHjT6E7cUXjdBT7GCRvvJ-yxzFJIQyI6y0t61SInpqVG2GlAwtTxR5reG0e4ZtcKoq2rbQghK8hWenGplGT31kjDY78zZv-WqCAc0-MM4cC06fTXFzdhsdueY789lCasSD4WMMC6bWbN098lHF96rMpCdlW13EalrPgcKeuvZtUBrC8ntL8Bg3LRMcU1bFKTRAwlVxw1aYyqeEN4NSxkiSgQod2dltA-b3c15L-fXoOWNGnPB68hqgK48ymuemRQTSg3eKmHFAQdDL6pxQ8_D_ZWAL3QhsKQVGDKg" , "tokenType" : "Bearer" , "decodedDetails" : null }, "authenticated" : true , "userAuthentication" : { "authorities" : [ { "authority" : "ROLE_GUEST" } ], "details" : null , "authenticated" : true , "principal" : "guest" , "credentials" : "N/A" , "name" : "guest" }, "credentials" : "" , "principal" : "guest" , "clientOnly" : false , "oauth2Request" : { "clientId" : "clientId" , "scope" : [ "read" , "write" ], "requestParameters" : { "client_id" : "clientId" }, "resourceIds" : [ ], "authorities" : [ ], "approved" : true , "refresh" : false , "redirectUri" : null , "responseTypes" : [ ], "extensions" : { }, "grantType" : null , "refreshTokenRequest" : null }, "name" : "guest" } |
сноска
- Код, используемый для этого урока, можно найти на GitHub
- OAuth 2.0
- Spring Security Java Config Предварительный просмотр
- Spring Boot 2 — Руководство по миграции
- Spring — Руководство для разработчиков OAuth2
Опубликовано на Java Code Geeks с разрешения Маркоса Барберо, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Централизованная авторизация с OAuth2 + JWT с использованием Spring Boot 2
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |