Spring Boot - Использование JWT, OAuth и отдельных серверов ресурсов и аутентификации

Я пытаюсь создать приложение Spring, использующее токены JWT и протокол OAuth2. У меня работает сервер аутентификации благодаря это руководство. Однако мне трудно заставить сервер ресурсов работать должным образом. Из следования за статьей и благодаря ответу на предыдущий вопрос, это моя текущая попытка:

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

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.signing-key}")
    private String signingKey;

    @Value("${security.encoding-strength}")
    private Integer clientID;

    @Value("${security.security-realm}")
    private String securityRealm;

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(signingKey);
        return converter;
    }

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

    @Bean ResourceServerTokenServices tokenService() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        OAuth2AuthenticationManager authManager = new OAuth2AuthenticationManager();
        authManager.setTokenServices(tokenService());
        return authManager;
    }

}

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

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private ResourceServerTokenServices tokenServices;

@Value("${security.jwt.resource-ids}")
private String resourceIds;

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

@Override
public void configure(HttpSecurity http) throws Exception {
    http.requestMatchers().and().authorizeRequests().antMatchers("/actuator/**", "/api-docs/**").permitAll()
            .antMatchers("/**").authenticated();
}

}

Конфигурация безопасности для сервера авторизации (из указанного руководства):

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.signing-key}")
    private String signingKey;

    @Value("${security.encoding-strength}")
    private Integer encodingStrength;

    @Value("${security.security-realm}")
    private String securityRealm;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(new ShaPasswordEncoder(encodingStrength));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .httpBasic()
                .realmName(securityRealm)
                .and()
                .csrf()
                .disable();

    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(signingKey);
        return converter;
    }

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

    @Bean
    @Primary //Making this primary to avoid any accidental duplication with another token service instance of the same name
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
}

Теперь, когда я пытаюсь сделать запрос к серверу ресурсов, я получаю следующее сообщение об ошибке:

{"error":"invalid_token","error_description":"Invalid access token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiam9obi5kb2UiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXh wIjoxNTE1MTE3NTU4LCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF"}

У меня есть пара вопросов:

  • Насколько я понимаю, проблема часто связана с хранилищами токенов. Как справиться с разделением серверов при использовании JwtTokenStore?
  • Во-вторых, в настоящее время мое приложение Resource полагается на доступ к ключу. Насколько я понимаю спецификации JWT и 0Auth, в этом нет необходимости. Скорее, я должен иметь возможность делегировать проверку самому серверу аутентификации. Из документации Spring я подумал, что следующее свойство может быть применимо security.oauth2.resource.token-info-uri=http://localhost:8080/oauth/check_token. Однако, если я попытаюсь не полагаться на ключ с моим сервером ресурсов, я столкнусь с трудностями при установке моего ResourceServerTokenService. Служба ожидает хранилище токенов, JWTTokenStore использует JwtAccessTokenConverter, а конвертер использует ключ (удаление ключа привело к той же ошибке invalid token, которую я испытал ранее).

Я действительно изо всех сил пытаюсь найти статьи, которые показывают, как разделить сервер аутентификации и ресурсов. Любой совет будет принят во внимание.

РЕДАКТИРОВАТЬ: На самом деле код для сервера ресурсов теперь не компилируется со следующим сообщением:

Caused by: java.lang.IllegalStateException: For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key


person KellyM    schedule 04.01.2018    source источник
comment
Привет, Келли, я пытаюсь сделать то же самое, какой класс мне нужно разделить на сервер аутентификации и ресурсов? Спасибо   -  person seanie_oc    schedule 01.04.2019
comment
@seanie_oc, это будет зависеть от вашего приложения и его текущей структуры. У меня было два отдельных загрузочных приложения Spring. У одного был класс @Configuration, помеченный @EnableResourceServer и расширяющий ResourceServerConfigurerAdapter. У другого был класс@Configuration, помеченный знаком @EnableAuthorizationServer и расширяющий AuthorizationServerConfigurerAdapter. Учебное пособие, упомянутое в первом посте, содержит очень полезную информацию для начала работы. Надеюсь это поможет.   -  person KellyM    schedule 02.04.2019
comment
спасибо за советы, я последовал тому же руководству, которое вы упомянули в начале, но я немного запутался в том, что и где   -  person seanie_oc    schedule 02.04.2019


Ответы (2)


Я попробовал spring oauth и столкнулся с той же ошибкой:

Caused by: java.lang.IllegalStateException: For MAC signing you do not need to specify the verifier key separately, and if you do it must match the signing key

Моя ошибка заключалась в том, что мой публичный сертификат был:

-----BEGIN PUBLIC KEY-----
tadadada
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
tadadada
-----END CERTIFICATE-----

А это недопустимо. УДАЛИТЬ сертификат, просто оставив открытый ключ в этом файле:

-----BEGIN PUBLIC KEY-----
tadadada
-----END PUBLIC KEY-----

И ошибка запуска исчезнет.

Что касается вашего второго вопроса, вот что я понимаю:

  • Сервер аутентификации предоставляет вам зашифрованный токен (зашифрованный с помощью закрытого ключа), который содержит ВСЕ разрешения вашего пользователя.

  • Сервер ресурсов расшифровывает токен с помощью открытого ключа и предполагает, что разрешения, содержащиеся в токене, имеют значение ИСТИНА.

Надеюсь на эту помощь.

person Oreste Viron    schedule 11.01.2018
comment
Большое спасибо. Я помню, что я действительно сделал аналогичную ошибку, за исключением того, что я удалил строки --BEGIN PUBLIC KEY - и - END PUBLIC KEY -. Для всех, кто сталкивается с этой проблемой, как отмечает @Oreste Viron, вам необходимо удалить часть сертификата, но на самом деле сохранить строки - ---, содержащие открытый ключ. - person KellyM; 12.01.2018
comment
Спасибо. Я пропустил метку - как для закрытого, так и для открытого ключа. Я думал они не нужны - person mumbasa; 17.03.2020

Я столкнулся с этой проблемой, когда мой открытый ключ был отформатирован следующим образом:

"-----BEGIN RSA PUBLIC KEY-----\n${encoder.encodeToString(keyPair.public.encoded)}\n-----END RSA PUBLIC KEY-----\n"

Когда я изменил это на:

"-----BEGIN PUBLIC KEY-----\n${encoder.encodeToString(keyPair.public.encoded)}\n-----END PUBLIC KEY-----\n"

Ключ был принят.

person Bas Kuis    schedule 23.11.2018
comment
Это совершенно бесполезно. Как вы изменили свой код? У меня нет открытых ключей в моем JWT-коде, но я получаю эту ошибку - person parsecer; 23.06.2020