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
//  }
}