Table of Contents
spring-authorization-server(Oauth2) Customizing
참조 를 기반으로 운영서버 적용 가능한지 테스트 해봅니다.
Use MySQL
build.gradle
dependencies {
// runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java:8.0.25'
}
application.yml
server:
port: 9000
spring:
datasource:
url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/db_oauth2_test
username: root
password:
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update # 개발용 only
show-sql: true
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
org.springframework.security.oauth2: INFO
# org.springframework.boot.autoconfigure: DEBUG
스키마 생성
스키마를 생성합니다.
CREATE DATABASE `db_oauth2_test`;
CREATE TABLE oauth2_registered_client (
id varchar(100) NOT NULL,
client_id varchar(100) NOT NULL,
client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
client_secret varchar(200) DEFAULT NULL,
client_secret_expires_at TIMESTAMP NULL DEFAULT NULL,
client_name varchar(200) NOT NULL,
client_authentication_methods varchar(1000) NOT NULL,
authorization_grant_types varchar(1000) NOT NULL,
redirect_uris varchar(1000) DEFAULT NULL,
scopes varchar(1000) NOT NULL,
client_settings varchar(2000) NOT NULL,
token_settings varchar(2000) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE oauth2_authorization_consent (
registered_client_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
authorities varchar(1000) NOT NULL,
PRIMARY KEY (registered_client_id, principal_name)
);
CREATE TABLE oauth2_authorization (
id varchar(100) NOT NULL,
registered_client_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
authorization_grant_type varchar(100) NOT NULL,
attributes varchar(4000) DEFAULT NULL,
state varchar(500) DEFAULT NULL,
authorization_code_value blob DEFAULT NULL,
authorization_code_issued_at timestamp NULL DEFAULT NULL,
authorization_code_expires_at timestamp NULL DEFAULT NULL,
authorization_code_metadata varchar(2000) DEFAULT NULL,
access_token_value blob DEFAULT NULL,
access_token_issued_at timestamp NULL DEFAULT NULL,
access_token_expires_at timestamp NULL DEFAULT NULL,
access_token_metadata varchar(2000) DEFAULT NULL,
access_token_type varchar(100) DEFAULT NULL,
access_token_scopes varchar(1000) DEFAULT NULL,
oidc_id_token_value blob DEFAULT NULL,
oidc_id_token_issued_at timestamp NULL DEFAULT NULL,
oidc_id_token_expires_at timestamp NULL DEFAULT NULL,
oidc_id_token_metadata varchar(2000) DEFAULT NULL,
refresh_token_value blob DEFAULT NULL,
refresh_token_issued_at timestamp NULL DEFAULT NULL,
refresh_token_expires_at timestamp NULL DEFAULT NULL,
refresh_token_metadata varchar(2000) DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE TABLE users (
id INT(10) NOT NULL AUTO_INCREMENT,
username VARCHAR(100) NOT NULL,
password VARCHAR(200) DEFAULT NULL,
enabled BOOLEAN NOT NULL,
acc_locked BOOLEAN NULL,
acc_expired BOOLEAN NULL,
creds_expired BOOLEAN NULL,
PRIMARY KEY (id)
);
CREATE TABLE authorities (
id INT(10) NOT NULL AUTO_INCREMENT,
username VARCHAR(100) NOT NULL,
authority VARCHAR(200) NOT NULL,
PRIMARY KEY (id)
);
AuthorizationServerConfig.java
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
// @formatter:off
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
// Save registered client in db as if in-memory
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
JdbcRegisteredClientRepository.RegisteredClientParametersMapper registeredClientParametersMapper = new JdbcRegisteredClientRepository.RegisteredClientParametersMapper();
PasswordEncoder encoder = new BCryptPasswordEncoder();
registeredClientParametersMapper.setPasswordEncoder(encoder);
registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper);
registeredClientRepository.save(registeredClient);
return registeredClientRepository;
}
// @formatter:on
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().issuer("http://auth-server:9000").build();
}
}
DefaultSecurityConfig.java
@EnableWebSecurity
public class DefaultSecurityConfig {
private final DataSource dataSource;
@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
public DefaultSecurityConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
// @formatter:off
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
// @formatter:on
// @formatter:off
@Bean
UserDetailsService users() {
JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
try {
UserDetails user = jdbcUserDetailsManager.loadUserByUsername("user1");
} catch (UsernameNotFoundException ex) {
PasswordEncoder encoder = new BCryptPasswordEncoder();
UserDetails user = User
.withUsername("user1")
.passwordEncoder(encoder::encode)
.password("password")
.roles("USER")
.build();
jdbcUserDetailsManager.createUser(user);
}
return jdbcUserDetailsManager;
}
// @formatter:on
}
프로젝트 재실행
프로젝트 종료 후 다시 실행합니다.
SELECT * FROM oauth2_registered_client
SELECT * FROM oauth2_authorization_consent
SELECT * FROM oauth2_authorization
SELECT * FROM users;
SELECT * FROM authorities;
http://localhost:8080/ 에 접속해서 로그인이 가능한 것을 확인할 수 있습니다.
느낀점
문서가 없다.
귀찮아서 안만들었다기보다는 API 자체가 변할 가능성이 너무 높아 작성하지 못한다는 느낌이다.
소스가 아직 깔끔하지 않다.
운영서버에 적용하기에는 위험부담이 너무 많다.
느낌상 이 프로젝트가 안정화될때까지는 spring boot oauth2 가 날라갈 가능성은 없어 보이니,
spring boot oauth2 가 사라지는 일은 없을 것 같고…