Статьи

Централизованная авторизация с OAuth2 & JWT с использованием Spring Boot 2

В этом руководстве описан процесс создания централизованного сервера аутентификации и авторизации с 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
  • Тип гранта

Сервер авторизации

Для построения нашего 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"
}

сноска

Опубликовано на Java Code Geeks с разрешения Маркоса Барберо, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Централизованная авторизация с OAuth2 + JWT с использованием Spring Boot 2

Мнения, высказанные участниками Java Code Geeks, являются их собственными.