기존방식
- Client를 InMemoryRegisteredClientRepository방식으로 저장하고 있다!
- 즉, 프로그램을 종료하면 정보가 사라진다.
- Client에 JPA를 추가하자!
- AuthorizationServerConfig.java
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("my-client")
.clientSecret("{noop}mypassword")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://localhost:3000")
.postLogoutRedirectUri("http://localhost:3000/")
.scope(OidcScopes.OPENID)
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofSeconds(360)).build())
.build();
return new InMemoryRegisteredClientRepository(oidcClient);
}
Client
dependencies
dependencies {
implementation 'org.springframework.security:spring-security-cas'
}
Client Entity
- 공식문서 그대로 만들었다.
- Client.java
import java.time.Instant;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
public class Client {
@Id
private String id;
private String clientId;
private Instant clientIdIssuedAt;
private String clientSecret;
private Instant clientSecretExpiresAt;
private String clientName;
@Column(length = 1000)
private String clientAuthenticationMethods;
@Column(length = 1000)
private String authorizationGrantTypes;
@Column(length = 1000)
private String redirectUris;
@Column(length = 1000)
private String postLogoutRedirectUris;
@Column(length = 1000)
private String scopes;
@Column(length = 2000)
private String clientSettings;
@Column(length = 2000)
private String tokenSettings;
}
ClientRepository
@Repository
public interface ClientRepository extends JpaRepository<Client, String> {
Optional<Client> findByClientId(String clientId);
}
RegisteredClientRepository
- RegisteredClientRepository 다음 클래스를 상속받아 구현하면 레포지터리를 등록할 수 있다.
- RegisteredClient를 등록해야하기 때문에 만든 Client 객체와의 데이터 전환 과정이 필요하다!
- JpaRegisteredClientRepository.java
@Component
public class JpaRegisteredClientRepository implements RegisteredClientRepository {
private final ClientRepository clientRepository;
private final ObjectMapper objectMapper = new ObjectMapper();
public JpaRegisteredClientRepository(ClientRepository clientRepository) {
Assert.notNull(clientRepository, "clientRepository cannot be null");
this.clientRepository = clientRepository;
ClassLoader classLoader = JpaRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public void save(RegisteredClient registeredClient) {
Assert.notNull(registeredClient, "registeredClient cannot be null");
this.clientRepository.save(toEntity(registeredClient));
}
@Override
public RegisteredClient findById(String id) {
Assert.hasText(id, "id cannot be empty");
return this.clientRepository.findById(id).map(this::toObject).orElse(null);
}
@Override
public RegisteredClient findByClientId(String clientId) {
Assert.hasText(clientId, "clientId cannot be empty");
return this.clientRepository.findByClientId(clientId).map(this::toObject).orElse(null);
}
private RegisteredClient toObject(Client client) {
Set<String> clientAuthenticationMethods = StringUtils.commaDelimitedListToSet(
client.getClientAuthenticationMethods());
Set<String> authorizationGrantTypes = StringUtils.commaDelimitedListToSet(
client.getAuthorizationGrantTypes());
Set<String> redirectUris = StringUtils.commaDelimitedListToSet(
client.getRedirectUris());
Set<String> postLogoutRedirectUris = StringUtils.commaDelimitedListToSet(
client.getPostLogoutRedirectUris());
Set<String> clientScopes = StringUtils.commaDelimitedListToSet(
client.getScopes());
RegisteredClient.Builder builder = RegisteredClient.withId(client.getId())
.clientId(client.getClientId())
.clientIdIssuedAt(client.getClientIdIssuedAt())
.clientSecret(client.getClientSecret())
.clientSecretExpiresAt(client.getClientSecretExpiresAt())
.clientName(client.getClientName())
.clientAuthenticationMethods(authenticationMethods ->
clientAuthenticationMethods.forEach(authenticationMethod ->
authenticationMethods.add(resolveClientAuthenticationMethod(authenticationMethod))))
.authorizationGrantTypes((grantTypes) ->
authorizationGrantTypes.forEach(grantType ->
grantTypes.add(resolveAuthorizationGrantType(grantType))))
.redirectUris((uris) -> uris.addAll(redirectUris))
.postLogoutRedirectUris((uris) -> uris.addAll(postLogoutRedirectUris))
.scopes((scopes) -> scopes.addAll(clientScopes));
Map<String, Object> clientSettingsMap = parseMap(client.getClientSettings());
builder.clientSettings(ClientSettings.withSettings(clientSettingsMap).build());
Map<String, Object> tokenSettingsMap = parseMap(client.getTokenSettings());
builder.tokenSettings(TokenSettings.withSettings(tokenSettingsMap).build());
return builder.build();
}
private Client toEntity(RegisteredClient registeredClient) {
List<String> clientAuthenticationMethods = new ArrayList<>(registeredClient.getClientAuthenticationMethods().size());
registeredClient.getClientAuthenticationMethods().forEach(clientAuthenticationMethod ->
clientAuthenticationMethods.add(clientAuthenticationMethod.getValue()));
List<String> authorizationGrantTypes = new ArrayList<>(registeredClient.getAuthorizationGrantTypes().size());
registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
authorizationGrantTypes.add(authorizationGrantType.getValue()));
Client entity = new Client();
entity.setId(registeredClient.getId());
entity.setClientId(registeredClient.getClientId());
entity.setClientIdIssuedAt(registeredClient.getClientIdIssuedAt());
entity.setClientSecret(registeredClient.getClientSecret());
entity.setClientSecretExpiresAt(registeredClient.getClientSecretExpiresAt());
entity.setClientName(registeredClient.getClientName());
entity.setClientAuthenticationMethods(StringUtils.collectionToCommaDelimitedString(clientAuthenticationMethods));
entity.setAuthorizationGrantTypes(StringUtils.collectionToCommaDelimitedString(authorizationGrantTypes));
entity.setRedirectUris(StringUtils.collectionToCommaDelimitedString(registeredClient.getRedirectUris()));
entity.setPostLogoutRedirectUris(StringUtils.collectionToCommaDelimitedString(registeredClient.getPostLogoutRedirectUris()));
entity.setScopes(StringUtils.collectionToCommaDelimitedString(registeredClient.getScopes()));
entity.setClientSettings(writeMap(registeredClient.getClientSettings().getSettings()));
entity.setTokenSettings(writeMap(registeredClient.getTokenSettings().getSettings()));
return entity;
}
private Map<String, Object> parseMap(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
});
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private String writeMap(Map<String, Object> data) {
try {
return this.objectMapper.writeValueAsString(data);
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.REFRESH_TOKEN;
}
return new AuthorizationGrantType(authorizationGrantType);
}
private static ClientAuthenticationMethod resolveClientAuthenticationMethod(String clientAuthenticationMethod) {
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
} else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_POST;
} else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.NONE;
}
return new ClientAuthenticationMethod(clientAuthenticationMethod);
}
}
테스트를 이용해서 등록하기
application.properties 수정
- 기존의 data.sql을 사용하면서 실행시 테이블을 초기화를 한다. 이를 수정하자!
- application.properties
# JPA
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
#spring.jpa.defer-datasource-initialization=ture
spring.jpa.show-sql=true
test 코드 작성하기
@SpringBootTest
class SpringAuthorizationServerApplicationTests {
@Autowired
private JpaRegisteredClientRepository registeredClientRepository;
@Autowired
private CustomerRepository customerRepository;
@Autowired
private AuthorityRepository authorityRepository;
@Test
void contextLoads() {
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("my-client")
.clientSecret("{noop}mypassword")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://localhost:3000")
.postLogoutRedirectUri("http://localhost:3000/")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofSeconds(360)).build())
.build();
registeredClientRepository.save(oidcClient);
Customer customer = new Customer(0L, "admin", "example@com", "1234");
Customer savedCusomer = customerRepository.save(customer);
Authority authority = new Authority(0L, "ADMIN", savedCusomer);
authorityRepository.save(authority);
}
}
- 다음과 같이 Repository를 이용해서 데이터를 추가하였다.
실행해보기
