Table of Contents
spring-authorization-server(Oauth2) 2024 – 1
2021 년도에 테스트 해보고 3년이 지났다.
이제는 얼마나 안정적인지 확인해 보고싶었다.
실행방법은 아래와 같다.
소스 다운로드
https://github.com/spring-projects/spring-authorization-server
에서 소스를 다운받는다.
샘플 서버 실행
demo-authorizationserver 실행
./gradlew -b samples/demo-authorizationserver/samples-demo-authorizationserver.gradle bootRun
경고가 뜨지만 정상적으로 실행된다.
경고를 제거하려면 아래 의존성을 추가해 주면 된다.
implementation 'com.google.code.findbugs:jsr305:3.0.2'
demo-client 실행
./gradlew -b samples/demo-client/samples-demo-client.gradle bootRun
정상적으로 실행된다.
messages-resource 실행
./gradlew -b samples/messages-resource/samples-messages-resource.gradle bootRun
정상적으로 실행된다.
접속
http://127.0.0.1:8080
에 접속해 본다.
크롬에서 접속하려 하면 오류가 발생한다.
user1 \ password
크롬 접속 오류 해결
chrome://net-internals/#hsts
에 접속합니다.
Delete domain security policies 에 localhost 를 등록해 줍니다.
chrome://flags/#allow-insecure-localhost
에 접속합니다.
localhost 접속을 허용해 줍니다.
http://localhost:8080/ 로 접속합니다.
로그인/로그아웃 커스터마이징
DefaultSecurityConfig.java
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests(authorize ->
authorize
.requestMatchers("/assets/**", "/login", "/error").permitAll()
.anyRequest().authenticated()
)
// 로그인 페이지
.formLogin(formLogin ->
formLogin
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler((request, response, authentication) -> {
response.sendRedirect("/home");
})
).logout(logout ->
logout
.logoutUrl("/logout")
.logoutSuccessHandler((request, response, authentication) -> {
response.sendRedirect("/");
})
);
return http.build();
}
MariaDB 사용
build.gradle
dependencies {
// runtimeOnly "com.h2database:h2"
runtimeOnly 'com.mysql:mysql-connector-j'
}
application.yml
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
스키마 생성
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 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,
post_logout_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,
authorized_scopes varchar(1000) DEFAULT NULL,
attributes blob DEFAULT NULL,
state varchar(500) DEFAULT NULL,
authorization_code_value blob DEFAULT NULL,
authorization_code_issued_at timestamp DEFAULT NULL,
authorization_code_expires_at timestamp DEFAULT NULL,
authorization_code_metadata blob DEFAULT NULL,
access_token_value blob DEFAULT NULL,
access_token_issued_at timestamp DEFAULT NULL,
access_token_expires_at timestamp DEFAULT NULL,
access_token_metadata blob 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 DEFAULT NULL,
oidc_id_token_expires_at timestamp DEFAULT NULL,
oidc_id_token_metadata blob DEFAULT NULL,
refresh_token_value blob DEFAULT NULL,
refresh_token_issued_at timestamp DEFAULT NULL,
refresh_token_expires_at timestamp DEFAULT NULL,
refresh_token_metadata blob DEFAULT NULL,
user_code_value blob DEFAULT NULL,
user_code_issued_at timestamp DEFAULT NULL,
user_code_expires_at timestamp DEFAULT NULL,
user_code_metadata blob DEFAULT NULL,
device_code_value blob DEFAULT NULL,
device_code_issued_at timestamp DEFAULT NULL,
device_code_expires_at timestamp DEFAULT NULL,
device_code_metadata blob 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)
);
DefaultSecurityConfig.java
@Configuration
@EnableWebSecurity
public class DefaultSecurityConfig {
private final DataSource dataSource;
@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
public DefaultSecurityConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public UserDetailsService userDetailsService() {
JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
try {
UserDetails user = jdbcUserDetailsManager.loadUserByUsername("user");
} catch (UsernameNotFoundException ex) {
PasswordEncoder encoder = new BCryptPasswordEncoder();
UserDetails user = User
.withUsername("user")
.passwordEncoder(encoder::encode)
.password("password")
.roles("USER")
.build();
jdbcUserDetailsManager.createUser(user);
}
return jdbcUserDetailsManager;
}
}
AuthorizationServerConfig.java
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
// ......
// @formatter:off
@Bean
public JdbcRegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
PasswordEncoder encoder = new BCryptPasswordEncoder();
RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret(encoder.encode("secret"))
// ......
.build();
// ......
RegisteredClient tokenExchangeClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("token-client")
.clientSecret(encoder.encode("token"))
// ......
.build();
// ......
// Save registered client's in db as if in-memory
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
if (registeredClientRepository.findByClientId("messaging-client") == null) {
registeredClientRepository.save(messagingClient);
registeredClientRepository.save(deviceClient);
registeredClientRepository.save(tokenExchangeClient);
registeredClientRepository.save(mtlsDemoClient);
}
return registeredClientRepository;
}
// @formatter:on
// ......
// @Bean
// public EmbeddedDatabase embeddedDatabase() {
// // @formatter:off
// return new EmbeddedDatabaseBuilder()
// .generateUniqueName(true)
// .setType(EmbeddedDatabaseType.H2)
// .setScriptEncoding("UTF-8")
// .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
// .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
// .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
// .build();
// // @formatter:on
// }
}