spring-authorization-server(Oauth2) 2024 – 1

By | 2024년 8월 13일
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
//  }

}

답글 남기기