Oauth2와 Zuul을 연동해서 구현했습니다.
Access Token을 가져올때는 문제가 없는데 Resource Server에 접근하면 에러가 나고 있습니다.
curl --location --request POST 'http://localhost:8080/oauth/token?grant_type=password&username=john&password=123' \
--header 'Authorization: Basic Zm9vQ2xpZW50SWRQYXNzd29yZDpzZWNyZXQ=' \
--data-raw ''
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJqb2huIiwic2NvcGUiOlsiZm9vIiwicmVhZCIsIndyaXRlIl0sIm9yZ2FuaXphdGlvbiI6ImpvaG5DalNDIiwiZXhwIjoxNjAwNjE1NjIzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiOWJmNGU4ZjctYmZkMy00ZTYyLTkwMGItYmNiZTg4Zjk3YjMwIiwiY2xpZW50X2lkIjoiZm9vQ2xpZW50SWRQYXNzd29yZCJ9.Y-UdQg-PFJ6uZR328DAD4oaJBv_c06HGzIVHp6o2RYzDaqBLqCHXK-g_6gJO5dxQ9Q-Yb5eH_xQS5fm4jqiqQa_T4L3kVG7PgMfMq3gj2L1j98Bc-I4dQKDW8Qmmcy7cg1wqb1JnqdAXHfzeU8YjQ0w0dohJW_Ypug_yg07LcHxFSWb5m2V0ncpVUaXn0ghMD_ZMWcR85kesj82jeGjwEopoXKUmzHgLf8QUqmOnrJ5w0-jJpzphvMT1kpmpKctvnOz0Jy5NCGmpD-jpISbz8unGVRvdn-unBBr4E5Qd9O6NQaVpiTjfy36z1BiIEE6BqcHIihRAzAeYg-KT42wxJw",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJqb2huIiwic2NvcGUiOlsiZm9vIiwicmVhZCIsIndyaXRlIl0sIm9yZ2FuaXphdGlvbiI6ImpvaG5DalNDIiwiYXRpIjoiOWJmNGU4ZjctYmZkMy00ZTYyLTkwMGItYmNiZTg4Zjk3YjMwIiwiZXhwIjoxNjAzMjA0MDIzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiNDkxYmNiMDAtNTYxZS00Nzc5LTgwNWUtNzNlYzVkMGQ0NDA0IiwiY2xpZW50X2lkIjoiZm9vQ2xpZW50SWRQYXNzd29yZCJ9.g1ve27IkqA-BoTRU69Ud8YF2kUEVxQejNg0xA97SYq7GK722qE-Uh9-VMRZB5DFDcGi5ZVTY3tofr60ix9Noflf5VgzgRJ-HwMbIAiRl_XG6nas4vYnSwQY15HU7ERG7ZZUuYv_UgoVDdx1FRIU7Yz5CevJlKBqIVRbN4qcQgRmg7iCkupxuK5Iu6ZHFMkQlgiZGh3TbrMMoX0htneHq3dv3eFD3cuo9lf3pWED4cs-1X_r5RYF9W_vizpalXhiKdTVp-R6gx-Q1wrw4ya1V-BlyI-Dh2a7uS_2yuoyfyfuPO-3K5IBrNUcZCvTUGa5irzxcWfN8BaRVKuIhOuEe3Q",
"expires_in": 3599,
"scope": "foo read write",
"organization": "johnCjSC",
"jti": "9bf4e8f7-bfd3-4e62-900b-bcbe88f97b30"
}
curl --location --request GET 'http://localhost:8080/spring-security-oauth-resource/foos/15' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJqb2huIiwic2NvcGUiOlsiZm9vIiwicmVhZCIsIndyaXRlIl0sIm9yZ2FuaXphdGlvbiI6ImpvaG5DalNDIiwiZXhwIjoxNjAwNjE1NjIzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiOWJmNGU4ZjctYmZkMy00ZTYyLTkwMGItYmNiZTg4Zjk3YjMwIiwiY2xpZW50X2lkIjoiZm9vQ2xpZW50SWRQYXNzd29yZCJ9.Y-UdQg-PFJ6uZR328DAD4oaJBv_c06HGzIVHp6o2RYzDaqBLqCHXK-g_6gJO5dxQ9Q-Yb5eH_xQS5fm4jqiqQa_T4L3kVG7PgMfMq3gj2L1j98Bc-I4dQKDW8Qmmcy7cg1wqb1JnqdAXHfzeU8YjQ0w0dohJW_Ypug_yg07LcHxFSWb5m2V0ncpVUaXn0ghMD_ZMWcR85kesj82jeGjwEopoXKUmzHgLf8QUqmOnrJ5w0-jJpzphvMT1kpmpKctvnOz0Jy5NCGmpD-jpISbz8unGVRvdn-unBBr4E5Qd9O6NQaVpiTjfy36z1BiIEE6BqcHIihRAzAeYg-KT42wxJw'
{
"error": "invalid_token",
"error_description": "Cannot convert access token to JSON"
}
아래에 Configuration 추가 하였습니다..
Zuul Config
package org.sjm.config;
import java.io.IOException;
import org.springframework.core.io.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
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 io.micrometer.core.instrument.util.IOUtils;
@Configuration
@EnableResourceServer
public class GatewayConfiguration extends ResourceServerConfigurerAdapter {
@Autowired
private CustomAccessTokenConverter customAccessTokenConverter;
@Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/oauth/**").permitAll().antMatchers("/**")
.access("hasAuthority('ROLE_USER') and #oauth2.hasScope('read')");
// authenticated();
}
@Override
public void configure(final ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServices());
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
/*
* @Bean public TokenStore tokenStore() { return new
* JwtTokenStore(accessTokenConverter()); }
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("public.txt");
String publicKey = null;
try {
publicKey = IOUtils.toString(resource.getInputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
return converter;
}
}
Authorization Server Config
package com.sjm.config;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
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;
/**
* @EnableAuthorizationServer
* oauth에 필요한 기본 설정 세팅
* (/oauth/token, /oauth/authorize 등 endpoint 생성)
*
* @author 6792291
*
*/
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfigJwt extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
/**
* 토큰 엔트포인터(/oauth/token)에 대한 보안 설정을 할 수 있다.
*/
@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
/**
* ClienDetailServiceConfigurer
* 클라이언트에 대한 설정을 할 수 있다 (DB, Inmemory)
* 클라이언트 별 제공할 Scope 나 권한을 정의
*/
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { // @formatter:off
clients.inMemory()
.withClient("sampleClientId")
.authorizedGrantTypes("implicit")
.scopes("read", "write", "foo", "bar")
.autoApprove(false)
.accessTokenValiditySeconds(3600)
.redirectUris("http://localhost:8083/","http://localhost:8086/")
.and()
.withClient("fooClientIdPassword")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "client_credentials")
.scopes("foo", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
.redirectUris("http://www.example.com","http://localhost:8089/","http://localhost:8080/login/oauth2/code/custom","http://localhost:8080/ui-thymeleaf/login/oauth2/code/custom", "http://localhost:8080/authorize/oauth2/code/bael", "http://localhost:8080/login/oauth2/code/bael")
.and()
.withClient("barClientIdPassword")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("password", "authorization_code", "refresh_token")
.scopes("bar", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
.and()
.withClient("testImplicitClientId")
.authorizedGrantTypes("implicit")
.scopes("read", "write", "foo", "bar")
.autoApprove(true)
.redirectUris("http://www.example.com");
} // @formatter:on
/**
* 인증, 토큰 엔드포인트, 토큰 서비스를 정의
*/
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new FileSystemResource("src/main/resources/oauth2jwt.jks"), "oauth2jwtpass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("oauth2jwt"));
return converter;
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Resource Server Config
package com.sjm.config;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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 org.springframework.core.io.Resource;
@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfigJwt extends ResourceServerConfigurerAdapter {
@Autowired
private CustomAccessTokenConverter customAccessTokenConverter;
@Override
public void configure(final HttpSecurity http) throws Exception {
// @formatter:off
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests().anyRequest().permitAll();
// @formatter:on
}
@Override
public void configure(final ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServices());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(customAccessTokenConverter);
final Resource resource = new ClassPathResource("public.txt");
String publicKey = null;
try {
publicKey = IOUtils.toString(resource.getInputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
converter.setSigningKey(publicKey);
return converter;
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
}