{"id":2294,"date":"2021-07-18T15:27:36","date_gmt":"2021-07-18T06:27:36","guid":{"rendered":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2294"},"modified":"2021-07-31T09:48:02","modified_gmt":"2021-07-31T00:48:02","slug":"spring-boot-oauth2-authorization-server-%ea%b5%ac%ec%b6%95","status":"publish","type":"post","link":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2294","title":{"rendered":"Spring Boot OAuth2 Authorization Server \uad6c\ucd95"},"content":{"rendered":"<h1>Spring Boot OAuth2 Authorization Server \uad6c\ucd95<\/h1>\n<p>\ucc38\uc870\uc0ac\uc774\ud2b8 : <a href=\"https:\/\/daddyprogrammer.org\/post\/1287\/spring-oauth2-authorizationserver-database\/\">https:\/\/daddyprogrammer.org\/post\/1287\/spring-oauth2-authorizationserver-database\/<\/a><br \/>\n\ucc38\uc870\uc0ac\uc774\ud2b8 : <a href=\"https:\/\/github.com\/codej99\/SpringOauth2AuthorizationServer\">https:\/\/github.com\/codej99\/SpringOauth2AuthorizationServer<\/a><br \/>\n\ucc38\uc870\uc0ac\uc774\ud2b8 : <a href=\"https:\/\/github.com\/Baeldung\/spring-security-registration\">https:\/\/github.com\/Baeldung\/spring-security-registration<\/a><\/p>\n<p><strong><code>@EnableAuthorizationServer<\/code> \uac00 deprecated \uc0c1\ud0dc\uc785\ub2c8\ub2e4.<\/strong><\/p>\n<p><code>Spring Boot<\/code> \ubc84\uc804 \uc120\ud0dd\uc2dc,<br \/>\n\uc6b0\uc120\uc801\uc73c\ub85c <code>@EnableAuthorizationServer<\/code> \uac00 \uc0ac\uc6a9\uac00\ub2a5\ud55c\uc9c0 \ud655\uc778\ud574\uc57c \ud569\ub2c8\ub2e4.<\/p>\n<h2>\ubaa9\ud45c<\/h2>\n<p>OAuth2 Authorization Server \ub97c \uad6c\ucd95\ud569\ub2c8\ub2e4.<\/p>\n<h2>\uc18c\uc2a4\ucf54\ub4dc<\/h2>\n<p><a href=\"https:\/\/github.com\/skyer9\/SpringBootOauth2AuthorizationServer\">\uc5ec\uae30<\/a> \uc5d0 \uc804\uccb4 \uc18c\uc2a4\ucf54\ub4dc\uac00 \uc62c\ub77c\uac00 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<h2>\ud504\ub85c\uc81d\ud2b8 \uc0dd\uc131<\/h2>\n<p>\uc2e0\uaddc \ud504\ub85c\uc81d\ud2b8\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.<\/p>\n<p>\ud504\ub85c\uc81d\ud2b8\uba85\uc740 oauth2authoricationserver \ub85c \ud569\ub2c8\ub2e4.<\/p>\n<p>\uc758\uc874\uc131\uc740 Spring Boot DevTools, Lombok, Spring Data JPA, MySQL Driver \uc744 \uc120\ud0dd\ud569\ub2c8\ub2e4.<\/p>\n<h2>build.gradle \uc218\uc815<\/h2>\n<p>\uc758\uc874\uc131\uc744 \ub2e4\uc74c\uacfc \uac19\uc774 \uc218\uc815\ud569\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-gradle\">dependencies {\n    implementation &#039;org.springframework.boot:spring-boot-starter-data-rest&#039;\n    implementation &#039;org.springframework.boot:spring-boot-starter-data-jpa&#039;\n    implementation &#039;org.springframework.boot:spring-boot-starter-security&#039;\n    implementation &#039;org.springframework.security:spring-security-jwt&#039;\n\n    \/\/ \ubc84\uc804\uc744 \uba85\uc2dc\uc801\uc73c\ub85c \uc9c0\uc815\ud574\uc57c \ud55c\ub2e4(?)\n    implementation &#039;org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.5.2&#039;\n\n    compileOnly &#039;org.projectlombok:lombok&#039;\n    developmentOnly &#039;org.springframework.boot:spring-boot-devtools&#039;\n    runtimeOnly &#039;mysql:mysql-connector-java&#039;\n    annotationProcessor &#039;org.projectlombok:lombok&#039;\n    testImplementation &#039;org.springframework.boot:spring-boot-starter-test&#039;\n    testImplementation &#039;org.springframework.security:spring-security-test&#039;\n}<\/code><\/pre>\n<h2>application.yml \uc218\uc815<\/h2>\n<pre><code class=\"language-yaml\">spring:\n  datasource:\n    url: jdbc:mysql:\/\/${MYSQL_HOST:localhost}:3306\/db_oauth2\n    username: root\n    password:\n    driver-class-name: com.mysql.jdbc.Driver\n  jpa:\n    hibernate:\n      ddl-auto: update    # \uac1c\ubc1c\uc6a9 only\n    show-sql: true\n\nserver:\n  port: 9000\n\nlogging:\n    level:\n        org:\n            springframework:\n                web: DEBUG\n                security: DEBUG<\/code><\/pre>\n<p>\uacc4\uc815\uc815\ubcf4\ub97c \uba54\ubaa8\ub9ac\uc5d0 \uc800\uc7a5\ud569\ub2c8\ub2e4.<br \/>\n\ud328\uc2a4\uc6cc\ub4dc \uc554\ud638\ud654\ub294 \uc5c6\ub3c4\ub85d \uc124\uc815\ud569\ub2c8\ub2e4.<\/p>\n<p>SecurityConfig.java<\/p>\n<pre><code class=\"language-java\">@Configuration\n@EnableWebSecurity\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    @Bean\n    public PasswordEncoder noOpPasswordEncoder() {\n        return NoOpPasswordEncoder.getInstance();\n    }\n\n    @Override\n    protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n        auth.inMemoryAuthentication()\n                .withUser(&quot;user&quot;)\n                .password(&quot;pass&quot;)\n                .roles(&quot;USER&quot;);\n    }\n\n    @Override\n    protected void configure(HttpSecurity security) throws Exception {\n        security\n                .csrf().disable()\n                .headers().frameOptions().disable()\n                .and()\n                    .authorizeRequests().antMatchers(&quot;\/oauth\/**&quot;).permitAll()\n                .and()\n                    .formLogin()\n                .and()\n                    .httpBasic();\n    }\n\n    @Override\n    @Bean\n    public AuthenticationManager authenticationManagerBean() throws Exception {\n        return super.authenticationManagerBean();\n    }\n}<\/code><\/pre>\n<p>AuthorizationServerConfig.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Configuration\n@EnableAuthorizationServer\npublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {\n\n    private final AuthenticationManager authenticationManager;\n\n    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {\n        clients.inMemory()\n                .withClient(&quot;foo&quot;)\n                .secret(&quot;bar&quot;)\n                .redirectUris(&quot;http:\/\/localhost:8080\/test\/auth&quot;)\n                .authorizedGrantTypes(&quot;authorization_code&quot;, &quot;password&quot;, &quot;client_credentials&quot;, &quot;implicit&quot;, &quot;refresh_token&quot;)\n                .scopes(&quot;read&quot;, &quot;write&quot;, &quot;email&quot;, &quot;profile&quot;)\n                .accessTokenValiditySeconds(30000);\n    }\n\n    @Override\n    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n        endpoints.authenticationManager(authenticationManager);\n    }\n}<\/code><\/pre>\n<h2>\ud504\ub85c\uc81d\ud2b8 \uc2e4\ud589<\/h2>\n<p>\ud504\ub85c\uc81d\ud2b8\ub97c \uc2e4\ud589\ud569\ub2c8\ub2e4.<\/p>\n<p>\uc544\ub798 \uba85\ub839\uc73c\ub85c \ud1a0\ud070\ud0a4\uac00 \ub9ac\ud134\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-bash\">curl foo:bar@localhost:9000\/oauth\/token -dgrant_type=password -dscope=read -d username=user -d password=pass<\/code><\/pre>\n<p>\uc544\ub798 \ub9c1\ud06c\uc5d0 \uc811\uc18d\ud558\uba74 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud560 \uc218 \uc788\ub294 \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub418\uace0,<br \/>\n\ube44\ubc00\ubc88\ud638 \uc785\ub825 \ud6c4 \uc778\uc99d\uc744 \ud5c8\uc6a9\ud560 \uac83\uc778\uc9c0\uc5d0 \ub300\ud55c \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<\/p>\n<p><a href=\"http:\/\/localhost:9000\/oauth\/authorize?response_type=code&amp;client_id=foo&amp;redirect_uri=http:\/\/localhost:8080\/test\/auth&amp;scope=read\">http:\/\/localhost:9000\/oauth\/authorize?response_type=code&#038;client_id=foo&#038;redirect_uri=http:\/\/localhost:8080\/test\/auth&#038;scope=read<\/a><\/p>\n<p>\uc544\ub798 \uba85\ub839\uc744 \uc2e4\ud589\ud558\uba74 \uc2e4\uc81c \ud1a0\ud070\uc774 \ubc1c\uae09\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-bash\">curl foo:bar@localhost:9000\/oauth\/token -dgrant_type=authorization_code -dcode=&lt;\uc778\uc99d\ucf54\ub4dc&gt; -dscope=read -dredirect_uri=http:\/\/localhost:8080\/test\/auth<\/code><\/pre>\n<p>\ubc1c\uae09\ub41c \ud1a0\ud070\uc815\ubcf4\uac00 \uba54\ubaa8\ub9ac\uc5d0 \uc800\uc7a5\ub429\ub2c8\ub2e4.<br \/>\n\uc778\uc99d\uc11c\ubc84\ub97c \uc7ac\ubd80\ud305\ud558\uba74 \ubc1c\uae09\ub418\uc5c8\ub358 \ud1a0\ud070 \uc815\ubcf4\uac00 \ubaa8\ub450 \ubb34\ud6a8\ud654\ub429\ub2c8\ub2e4.<\/p>\n<h2>JDBC \ubc29\uc2dd\uc73c\ub85c \ubcc0\uacbd<\/h2>\n<h3>Client \uc815\ubcf4 JDBC \ub85c \uad00\ub9ac<\/h3>\n<p>\ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4\ub97c MySQL \uc5d0\uc11c \uc77d\uc5b4\uc624\ub3c4\ub85d \uc218\uc815\ud569\ub2c8\ub2e4.<br \/>\n\ube44\ubc00\ubc88\ud638\ub294 \uc554\ud638\ud654\ud558\ub3c4\ub85d \uc124\uc815\uc744 \ubcc0\uacbd\ud569\ub2c8\ub2e4.<\/p>\n<p>\uc554\ud638\ud654 \ube44\ubc00\ubc88\ud638 \uc0dd\uc131\uc6a9 \uc2e4\ud589\ud30c\uc77c\uc744 \uc0dd\uc131\ud574 \uc90d\ub2c8\ub2e4.<\/p>\n<p>MyPasswordEncoder.java<\/p>\n<pre><code class=\"language-java\">public class MyPasswordEncoder {\n\n    public static void main(String[] args) {\n        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();\n        System.out.printf(&quot;bar : %s\\n&quot;, passwordEncoder.encode(&quot;bar&quot;));\n        System.out.printf(&quot;pass : %s\\n&quot;, passwordEncoder.encode(&quot;pass&quot;));\n    }\n}<\/code><\/pre>\n<p>schema.sql<\/p>\n<pre><code class=\"language-sql\">create database db_oauth2;\n\ncreate table IF NOT EXISTS oauth_client_details (\n    client_id VARCHAR(256) PRIMARY KEY,\n    resource_ids VARCHAR(256),\n    client_secret VARCHAR(256),\n    scope VARCHAR(256),\n    authorized_grant_types VARCHAR(256),\n    web_server_redirect_uri VARCHAR(256),\n    authorities VARCHAR(256),\n    access_token_validity INTEGER,\n    refresh_token_validity INTEGER,\n    additional_information VARCHAR(4096),\n    autoapprove VARCHAR(256)\n);\n\ninsert into oauth_client_details(client_id, resource_ids,client_secret,scope,authorized_grant_types,web_server_redirect_uri,authorities,access_token_validity,refresh_token_validity,additional_information,autoapprove)\nvalues(&#039;foo&#039;,null,&#039;{bcrypt}$2a$10$wPb4BM6c\/IqweuscNtQqgu0npxBn0i1qKbx3hGwJ26C3Wi5fHonuy&#039;,&#039;read,write,profile,email&#039;,&#039;authorization_code,password,client_credentials,implicit,refresh_token&#039;,&#039;http:\/\/localhost:8080\/login\/oauth2\/code\/local&#039;,&#039;ROLE_USER&#039;,36000,50000,null,null);<\/code><\/pre>\n<p>\ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4\ub97c JDBC \uc5d0\uc11c \uc77d\uc5b4\uc624\ub3c4\ub85d \ud558\uace0,<br \/>\n\ube44\ubc00\ubc88\ud638\uc5d0 \uc554\ud638\ud654\ub97c \ud558\ub3c4\ub85d \uc218\uc815\ud574 \uc90d\ub2c8\ub2e4.<\/p>\n<p>AuthorizationServerConfig.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Configuration\n@EnableAuthorizationServer\npublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {\n\n    private final DataSource dataSource;\n\n    private final PasswordEncoder passwordEncoder;\n\n    private final AuthenticationManager authenticationManager;\n\n    @Override\n    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {\n        clients.jdbc(dataSource).passwordEncoder(passwordEncoder);\n    }\n\n    @Override\n    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n        endpoints.authenticationManager(authenticationManager);\n    }\n}<\/code><\/pre>\n<p>\uacc4\uc815\uc815\ubcf4\uc758 \ube44\ubc00\ubc88\ud638\ub3c4 \uc554\ud638\ud654\ub97c \uc124\uc815\ud574 \uc90d\ub2c8\ub2e4.<\/p>\n<p>SecurityConfig.java<\/p>\n<pre><code class=\"language-java\">@Configuration\n@EnableWebSecurity\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return PasswordEncoderFactories.createDelegatingPasswordEncoder();\n    }\n\n    @Override\n    protected void configure(AuthenticationManagerBuilder auth) throws Exception {\n        auth.inMemoryAuthentication()\n                .withUser(&quot;user&quot;)\n                .password(&quot;{bcrypt}$2a$10$fMkleOIoPlxW.mWaleJj9Oo8uEJgCEsH2THjKF\/7S4tqdLWvWrEDq&quot;)\n                .roles(&quot;USER&quot;);\n    }\n\n    @Override\n    protected void configure(HttpSecurity security) throws Exception {\n        security\n                .csrf().disable()\n                .headers().frameOptions().disable()\n                .and()\n                    .authorizeRequests().antMatchers(&quot;\/oauth\/**&quot;).permitAll()\n                .and()\n                    .formLogin()\n                .and()\n                    .httpBasic();\n    }\n\n    @Override\n    @Bean\n    public AuthenticationManager authenticationManagerBean() throws Exception {\n        return super.authenticationManagerBean();\n    }\n}<\/code><\/pre>\n<p>\uc544\ub798 \ub9c1\ud06c\uc5d0 \uc811\uc18d\ud558\uba74 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud560 \uc218 \uc788\ub294 \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub418\uace0,<br \/>\n\ube44\ubc00\ubc88\ud638 \uc785\ub825 \ud6c4 \uc778\uc99d\uc744 \ud5c8\uc6a9\ud560 \uac83\uc778\uc9c0\uc5d0 \ub300\ud55c \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<\/p>\n<p><a href=\"http:\/\/localhost:9000\/oauth\/authorize?response_type=code&amp;client_id=foo&amp;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&amp;scope=read\">http:\/\/localhost:9000\/oauth\/authorize?response_type=code&#038;client_id=foo&#038;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&#038;scope=read<\/a><\/p>\n<p>\uc544\ub798 \uba85\ub839\uc744 \uc2e4\ud589\ud558\uba74 \uc2e4\uc81c \ud1a0\ud070\uc774 \ubc1c\uae09\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-bash\">curl foo:bar@localhost:9000\/oauth\/token -dgrant_type=authorization_code -dcode=&lt;\uc778\uc99d\ucf54\ub4dc&gt; -dscope=read -dredirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local<\/code><\/pre>\n<h3>\uace0\uac1d \uc815\ubcf4 JDBC \ub85c \uad00\ub9ac<\/h3>\n<p>\uace0\uac1d\uc815\ubcf4\ub3c4 MySQL \uc5d0\uc11c \uc77d\uc5b4\uc624\ub3c4\ub85d \uc218\uc815\ud569\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-java\">CREATE TABLE `tbl_user` (\n    `msrl` BIGINT(20) NOT NULL,\n    `name` VARCHAR(100) NOT NULL,\n    `password` VARCHAR(100) NULL DEFAULT NULL,\n    `provider` VARCHAR(100) NULL DEFAULT NULL,\n    `uid` VARCHAR(100) NOT NULL,\n    `email` VARCHAR(100) NOT NULL,\n    PRIMARY KEY (`msrl`),\n    UNIQUE INDEX `UK_tbl_user_uid` (`uid`)\n)\nCOLLATE=&#039;utf8_general_ci&#039;\nENGINE=InnoDB;\n\nCREATE TABLE `user_roles` (\n    `user_msrl` BIGINT(20) NOT NULL,\n    `roles` VARCHAR(255) NULL DEFAULT NULL,\n    INDEX `FK7ie1lfmnysdogxy1g91ernbkv` (`user_msrl`),\n    CONSTRAINT `FK_tbl_user_msrl` FOREIGN KEY (`user_msrl`) REFERENCES `tbl_user` (`msrl`)\n)\nCOLLATE=&#039;utf8_general_ci&#039;\nENGINE=INNODB;<\/code><\/pre>\n<p>User.java<\/p>\n<pre><code class=\"language-java\">@Builder\n@Getter\n@NoArgsConstructor\n@AllArgsConstructor\n@Entity\n@Table(name = &quot;tbl_user&quot;)\npublic class User implements UserDetails {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private long msrl;\n\n    @Column(nullable = false, unique = true, length = 50)\n    private String uid;\n\n    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)\n    @Column(length = 100)\n    private String password;\n\n    @Column(nullable = false, length = 100)\n    private String name;\n\n    @Column(nullable = false, length = 100)\n    private String email;\n\n    @Column(length = 100)\n    private String provider;\n\n    @ElementCollection(fetch = FetchType.EAGER)\n    @Builder.Default\n    private List&lt;String&gt; roles = new ArrayList&lt;&gt;();\n\n    @Override\n    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {\n        return this.roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());\n    }\n\n    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)\n    @Override\n    public String getUsername() {\n        return this.uid;\n    }\n\n    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)\n    @Override\n    public boolean isAccountNonExpired() {\n        return true;\n    }\n\n    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)\n    @Override\n    public boolean isAccountNonLocked() {\n        return true;\n    }\n\n    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)\n    @Override\n    public boolean isCredentialsNonExpired() {\n        return true;\n    }\n\n    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)\n    @Override\n    public boolean isEnabled() {\n        return true;\n    }\n}<\/code><\/pre>\n<p>UserRepository.java<\/p>\n<pre><code class=\"language-java\">public interface UserRepository extends JpaRepository&lt;User, Long&gt; {\n\n    Optional&lt;User&gt; findByUid(String uid);\n}<\/code><\/pre>\n<p>WebMvcConfig.java<\/p>\n<pre><code class=\"language-java\">@Configuration\npublic class WebMvcConfig implements WebMvcConfigurer {\n\n    private static final long MAX_AGE_SECONDS = 3600;\n\n    @Override\n    public void addCorsMappings(CorsRegistry registry) {\n        registry.addMapping(&quot;\/**&quot;)\n                .allowedOriginPatterns(&quot;*&quot;)\n                .allowedMethods(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;)\n                .allowedHeaders(&quot;*&quot;)\n                .allowCredentials(true)\n                .maxAge(MAX_AGE_SECONDS);\n    }\n\n    @Bean\n    public RestTemplate getRestTemplate() {\n        return new RestTemplate();\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return PasswordEncoderFactories.createDelegatingPasswordEncoder();\n    }\n}<\/code><\/pre>\n<p>CustomAuthenticationProvider.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Component\npublic class CustomAuthenticationProvider implements AuthenticationProvider {\n\n    private final PasswordEncoder passwordEncoder;\n\n    private final UserRepository userJpaRepo;\n\n    public CustomAuthenticationProvider(UserRepository userJpaRepo) {\n        this.userJpaRepo = userJpaRepo;\n        this.passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();\n    }\n\n    @Override\n    public Authentication authenticate(Authentication authentication) {\n        String name = authentication.getName();\n        String password = authentication.getCredentials().toString();\n        User user = userJpaRepo.findByUid(name).orElseThrow(() -&gt; new UsernameNotFoundException(&quot;user is not exists&quot;));\n        if (!passwordEncoder.matches(password, user.getPassword()))\n            throw new BadCredentialsException(&quot;password is not valid&quot;);\n        return new UsernamePasswordAuthenticationToken(name, password, user.getAuthorities());\n    }\n\n    @Override\n    public boolean supports(Class&lt;?&gt; authentication) {\n        return authentication.equals(\n                UsernamePasswordAuthenticationToken.class);\n    }\n}<\/code><\/pre>\n<p>SecurityConfig.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Configuration\n@EnableWebSecurity\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    private final CustomAuthenticationProvider authenticationProvider;\n\n    @Override\n    protected void configure(AuthenticationManagerBuilder auth) {\n        auth.authenticationProvider(authenticationProvider);\n    }\n\n    @Override\n    protected void configure(HttpSecurity security) throws Exception {\n        security\n                .csrf().disable()\n                .headers().frameOptions().disable()\n                .and()\n                    .authorizeRequests().antMatchers(&quot;\/oauth\/**&quot;).permitAll()\n                .and()\n                    .formLogin()\n                .and()\n                    .httpBasic();\n    }\n\n    @Override\n    @Bean\n    public AuthenticationManager authenticationManagerBean() throws Exception {\n        return super.authenticationManagerBean();\n    }\n}<\/code><\/pre>\n<p>\ud14c\uc2a4\ud2b8 \ud30c\uc77c\uc744 \ub9cc\ub4e4\uc5b4 \uacc4\uc815\uc815\ubcf4\ub97c \uc0dd\uc131\ud574 \uc90d\ub2c8\ub2e4.<\/p>\n<p>UserRepositoryTest.java<\/p>\n<pre><code class=\"language-java\">@RunWith(SpringRunner.class)\n@SpringBootTest\nclass UserRepositoryTest {\n\n    @Autowired\n    private UserRepository userRepository;\n\n    @Autowired\n    private PasswordEncoder passwordEncoder;\n\n    @Test\n    public void createUser() {\n        userRepository.save(User.builder()\n                .uid(&quot;user&quot;)\n                .password(passwordEncoder.encode(&quot;pass&quot;))\n                .name(&quot;user&quot;)\n                .email(&quot;skyer9@gmail.com&quot;)\n                .roles(Collections.singletonList(&quot;ROLE_USER&quot;))\n                .build());\n    }\n}<\/code><\/pre>\n<p>\uc544\ub798 \ub9c1\ud06c\uc5d0 \uc811\uc18d\ud558\uba74 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud560 \uc218 \uc788\ub294 \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub418\uace0,<br \/>\n\ube44\ubc00\ubc88\ud638 \uc785\ub825 \ud6c4 \uc778\uc99d\uc744 \ud5c8\uc6a9\ud560 \uac83\uc778\uc9c0\uc5d0 \ub300\ud55c \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<\/p>\n<p><a href=\"http:\/\/localhost:9000\/oauth\/authorize?response_type=code&amp;client_id=foo&amp;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&amp;scope=read\">http:\/\/localhost:9000\/oauth\/authorize?response_type=code&#038;client_id=foo&#038;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&#038;scope=read<\/a><\/p>\n<p>\uc544\ub798 \uba85\ub839\uc744 \uc2e4\ud589\ud558\uba74 \uc2e4\uc81c \ud1a0\ud070\uc774 \ubc1c\uae09\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-bash\">curl foo:bar@localhost:9000\/oauth\/token -dgrant_type=authorization_code -dcode=&lt;\uc778\uc99d\ucf54\ub4dc&gt; -dscope=read -dredirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local<\/code><\/pre>\n<h3>\uc778\uc99d\/\ud1a0\ud070 \uc815\ubcf4 JDBC \ub85c \uad00\ub9ac<\/h3>\n<p>\uc0dd\uc131\ub41c \ud1a0\ud070\uc815\ubcf4\ub97c JDBC \uc5d0 \uc800\uc7a5\ud569\ub2c8\ub2e4.<br \/>\n\uc11c\ubc84\ub97c \uc7ac\uc2e4\ud589\ud558\ub354\ub77c\ub3c4 \ubc1c\ud589\ud55c \ud1a0\ud070\uc815\ubcf4\uac00 \uc9c0\uc6cc\uc9c0\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-sql\">create table IF NOT EXISTS oauth_client_token (\n    token_id VARCHAR(256),\n    token BLOB,\n    authentication_id VARCHAR(256) PRIMARY KEY,\n    user_name VARCHAR(256),\n    client_id VARCHAR(256)\n);\n\ncreate table IF NOT EXISTS oauth_access_token (\n    token_id VARCHAR(256),\n    token BLOB,\n    authentication_id VARCHAR(256) PRIMARY KEY,\n    user_name VARCHAR(256),\n    client_id VARCHAR(256),\n    authentication BLOB,\n    refresh_token VARCHAR(256)\n);\n\ncreate table IF NOT EXISTS oauth_refresh_token (\n    token_id VARCHAR(256),\n    token BLOB,\n    authentication BLOB\n);\n\ncreate table IF NOT EXISTS oauth_code (\n    code VARCHAR(256), authentication BLOB\n);\n\ncreate table IF NOT EXISTS oauth_approvals (\n    userId VARCHAR(256),\n    clientId VARCHAR(256),\n    scope VARCHAR(256),\n    status VARCHAR(10),\n    expiresAt TIMESTAMP,\n    lastModifiedAt TIMESTAMP\n);<\/code><\/pre>\n<p><code>JdbcTokenStore<\/code> \ub97c \uc774\uc6a9\ud558\uc5ec JDBC \uc5d0 \ud1a0\ud070\uc815\ubcf4\ub97c \uc800\uc7a5\ud569\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Configuration\n@EnableAuthorizationServer\npublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {\n\n    private final DataSource dataSource;\n\n    private final PasswordEncoder passwordEncoder;\n\n    private final AuthenticationManager authenticationManager;\n\n    @Override\n    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {\n        clients.jdbc(dataSource).passwordEncoder(passwordEncoder);\n    }\n\n    @Override\n    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n        endpoints.authenticationManager(authenticationManager).tokenStore(new JdbcTokenStore(dataSource));\n    }\n}<\/code><\/pre>\n<p>\uc544\ub798 \ub9c1\ud06c\uc5d0 \uc811\uc18d\ud558\uba74 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud560 \uc218 \uc788\ub294 \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub418\uace0,<br \/>\n\ube44\ubc00\ubc88\ud638 \uc785\ub825 \ud6c4 \uc778\uc99d\uc744 \ud5c8\uc6a9\ud560 \uac83\uc778\uc9c0\uc5d0 \ub300\ud55c \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<\/p>\n<p><a href=\"http:\/\/localhost:9000\/oauth\/authorize?response_type=code&amp;client_id=foo&amp;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&amp;scope=read\">http:\/\/localhost:9000\/oauth\/authorize?response_type=code&#038;client_id=foo&#038;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&#038;scope=read<\/a><\/p>\n<p>\uc544\ub798 \uba85\ub839\uc744 \uc2e4\ud589\ud558\uba74 \uc2e4\uc81c \ud1a0\ud070\uc774 \ubc1c\uae09\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-bash\">curl foo:bar@localhost:9000\/oauth\/token -dgrant_type=authorization_code -dcode=&lt;\uc778\uc99d\ucf54\ub4dc&gt; -dscope=read -dredirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local<\/code><\/pre>\n<h2>JWT \ubc29\uc2dd<\/h2>\n<p>\uae30\uc874 \ubc29\uc2dd\uc740 \ubc1c\uae09\ud55c \ud1a0\ud070\uc758 \uc720\ud6a8\uc131\uc744 \uac80\uc99d\ud558\uae30 \uc704\ud574,<br \/>\n\ub9e4\ubc88 \uc778\uc99d\uc11c\ubc84\uc5d0 \uc720\ud6a8\uc131 \uac80\uc99d\uc744 \uc694\uccad\ud558\uac8c \ub429\ub2c8\ub2e4.<\/p>\n<p><code>JWT<\/code> \ubc29\uc2dd\uc740 \uc774\ub7f0 \ubb38\uc81c\uc810\uc744 \uac1c\uc120\ud558\uae30 \uc704\ud574,<br \/>\n\ud1a0\ud070 \uac80\uc99d\uc5d0 \ud544\uc694\ud55c \uc815\ubcf4\uae4c\uc9c0 \ud1a0\ud070\uc5d0 \ud3ec\ud568\ud574\uc11c \ubc1c\ud589\ud569\ub2c8\ub2e4.<\/p>\n<p>\ud1a0\ud070\uc758 \ud06c\uae30\ub294 \ucee4\uc9c0\uc9c0\ub9cc \ud1a0\ud070 \uc720\ud6a8\uc131 \uac80\uc99d\uc744 \ub9ac\uc18c\uc2a4\uc11c\ubc84\uc5d0\uc11c<br \/>\n\uc790\uccb4\uc801\uc73c\ub85c \ud560 \uc218 \uc788\uae30\uc5d0 \uc778\uc99d\uc11c\ubc84\uc758 \ubd80\ub2f4\uc774 \uc904\uc5b4\ub4ed\ub2c8\ub2e4.<\/p>\n<p>AuthorizationServerConfig.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Configuration\n@EnableAuthorizationServer\npublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {\n\n    private final DataSource dataSource;\n\n    private final PasswordEncoder passwordEncoder;\n\n    private final AuthenticationManager authenticationManager;\n\n    @Override\n    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {\n        clients.jdbc(dataSource).passwordEncoder(passwordEncoder);\n    }\n\n\/\/    \/\/ JDBC \ubc29\uc2dd\n\/\/    @Override\n\/\/    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n\/\/        endpoints.authenticationManager(authenticationManager).tokenStore(new JdbcTokenStore(dataSource));\n\/\/    }\n\n    \/\/ JWT \ubc29\uc2dd\n    @Override\n    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n        super.configure(endpoints);\n        endpoints.accessTokenConverter(jwtAccessTokenConverter());\n    }\n\n    @Bean\n    public JwtAccessTokenConverter jwtAccessTokenConverter() {\n        return new JwtAccessTokenConverter();\n    }\n}<\/code><\/pre>\n<p>\uc544\ub798 \ub9c1\ud06c\uc5d0 \uc811\uc18d\ud558\uba74 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud560 \uc218 \uc788\ub294 \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub418\uace0,<br \/>\n\ube44\ubc00\ubc88\ud638 \uc785\ub825 \ud6c4 \uc778\uc99d\uc744 \ud5c8\uc6a9\ud560 \uac83\uc778\uc9c0\uc5d0 \ub300\ud55c \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<\/p>\n<p><a href=\"http:\/\/localhost:9000\/oauth\/authorize?response_type=code&amp;client_id=foo&amp;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&amp;scope=read\">http:\/\/localhost:9000\/oauth\/authorize?response_type=code&#038;client_id=foo&#038;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&#038;scope=read<\/a><\/p>\n<p>\uc544\ub798 \uba85\ub839\uc744 \uc2e4\ud589\ud558\uba74 \uc2e4\uc81c \ud1a0\ud070\uc774 \ubc1c\uae09\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-bash\">curl foo:bar@localhost:9000\/oauth\/token -dgrant_type=authorization_code -dcode=&lt;\uc778\uc99d\ucf54\ub4dc&gt; -dscope=read -dredirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local<\/code><\/pre>\n<h2>JWT \ud1a0\ud070 \uc7ac\ubc1c\uae09<\/h2>\n<p>\uc704\uc5d0\uc11c\ub294 <code>signkey<\/code> \uac00 \uc11c\ubc84 \uba54\ubaa8\ub9ac\uc0c1\uc5d0 \uc800\uc7a5\ub429\ub2c8\ub2e4.<br \/>\n\ub530\ub77c\uc11c, \uc11c\ubc84\uac00 \uc7ac\ubd80\ud305\ud558\uac8c \ub418\uba74, \uac31\uc2e0\ud0a4\ub97c \ubc1c\uae09\ud558\uc9c0 \ubabb\ud558\uac8c \ub429\ub2c8\ub2e4.<\/p>\n<p>\uc544\ub798 \uc124\uc815\uc73c\ub85c <code>signkey<\/code> \ub97c \uace0\uc815\uc2dc\ud0a4\uba74,<br \/>\n\uc11c\ubc84\uac00 \uc7ac\ubd80\ud305\ud574\ub3c4 \uac31\uc2e0\ud0a4 \ubc1c\uae09\uc774 \uac00\ub2a5\ud574\uc9d1\ub2c8\ub2e4.<\/p>\n<p>application.yml<\/p>\n<pre><code class=\"language-yaml\">security:\n  oauth2:\n    jwt:\n      signkey: 123@#$<\/code><\/pre>\n<p>AuthorizationServerConfig.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Configuration\n@EnableAuthorizationServer\npublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {\n\n    @Value(&quot;${security.oauth2.jwt.signkey}&quot;)\n    private String signKey;\n\n    private final DataSource dataSource;\n\n    private final PasswordEncoder passwordEncoder;\n\n    private final AuthenticationManager authenticationManager;\n\n    private final CustomUserDetailService userDetailService;\n\n    @Override\n    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {\n        clients.jdbc(dataSource).passwordEncoder(passwordEncoder);\n    }\n\n\/\/    \/\/ JDBC \ubc29\uc2dd\n\/\/    @Override\n\/\/    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n\/\/        endpoints.authenticationManager(authenticationManager).tokenStore(new JdbcTokenStore(dataSource));\n\/\/    }\n\n    \/\/ JWT \ubc29\uc2dd\n    @Override\n    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {\n        super.configure(endpoints);\n        endpoints.accessTokenConverter(jwtAccessTokenConverter()).userDetailsService(userDetailService);\n    }\n\n    @Bean\n    public JwtAccessTokenConverter jwtAccessTokenConverter() {\n        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();\n        converter.setSigningKey(signKey);\n        return converter;\n    }\n}<\/code><\/pre>\n<p>\ud1a0\ud070 \uc7ac\ubc1c\uae09\uc744 \ud560 \uacbd\uc6b0, \uace0\uac1d\uc815\ubcf4\ub97c \ud655\uc778 \ud6c4 \uc7ac\ubc1c\uae09\ud569\ub2c8\ub2e4.<\/p>\n<p>CustomUserDetailService.java<\/p>\n<pre><code class=\"language-java\">@Service\npublic class CustomUserDetailService implements UserDetailsService {\n\n    @Autowired\n    private UserRepository userRepository;\n\n    private final AccountStatusUserDetailsChecker detailsChecker = new AccountStatusUserDetailsChecker();\n\n    @Override\n    public UserDetails loadUserByUsername(String name) {\n        User user = userRepository.findByUid(name).orElseThrow(() -&gt; new UsernameNotFoundException(&quot;user is not exists&quot;));\n        detailsChecker.check(user);\n        return user;\n    }\n}<\/code><\/pre>\n<p>\uc544\ub798 \ub9c1\ud06c\uc5d0 \uc811\uc18d\ud558\uba74 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud560 \uc218 \uc788\ub294 \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub418\uace0,<br \/>\n\ube44\ubc00\ubc88\ud638 \uc785\ub825 \ud6c4 \uc778\uc99d\uc744 \ud5c8\uc6a9\ud560 \uac83\uc778\uc9c0\uc5d0 \ub300\ud55c \ud398\uc774\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<\/p>\n<p><a href=\"http:\/\/localhost:9000\/oauth\/authorize?response_type=code&amp;client_id=foo&amp;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&amp;scope=read\">http:\/\/localhost:9000\/oauth\/authorize?response_type=code&#038;client_id=foo&#038;redirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local&#038;scope=read<\/a><\/p>\n<p>\uc544\ub798 \uba85\ub839\uc744 \uc2e4\ud589\ud558\uba74 \uc2e4\uc81c \ud1a0\ud070\uc774 \ubc1c\uae09\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-bash\">curl foo:bar@localhost:9000\/oauth\/token -dgrant_type=authorization_code -dcode=&lt;\uc778\uc99d\ucf54\ub4dc&gt; -dscope=read -dredirect_uri=http:\/\/localhost:8080\/login\/oauth2\/code\/local<\/code><\/pre>\n<pre><code class=\"language-bash\">curl foo:bar@localhost:9000\/oauth\/token -dgrant_type=refresh_token -drefresh_token=&lt;\ud1a0\ud070 \uac31\uc2e0\ud0a4&gt;<\/code><\/pre>\n<h2>JWT \ud1a0\ud070 \ube44\ub300\uce6d \uc554\ud638\ud654<\/h2>\n<p>\uacf5\uac1c\ud0a4\/\ube44\ubc00\ud0a4 \uae30\ubc18\uc758 \ube44\ub300\uce6d \ud1a0\ud070 \uc554\ud638\ud654\ub294 <code>\ub9ac\uc18c\uc2a4 \uc11c\ubc84<\/code> \uc5d0\uc11c \uc124\uba85\ud569\ub2c8\ub2e4.<\/p>\n<h2>Brute Force<\/h2>\n<p><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2472\">Spring Boot Brute Force(\ub79c\ub364 \ubb38\uc790\uc5f4 \uacf5\uaca9) \ub300\uc751<\/a><\/p>\n<h2>Spring Boot Cache<\/h2>\n<p><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2478\">Spring Boot Cache for Authorization Server<\/a><\/p>\n<h2>\ud504\ub85c\ud544 \uc11c\ubc84<\/h2>\n<p><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2500\">Spring Boot Profile Server for Authorization Server<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Spring Boot OAuth2 Authorization Server \uad6c\ucd95 \ucc38\uc870\uc0ac\uc774\ud2b8 : https:\/\/daddyprogrammer.org\/post\/1287\/spring-oauth2-authorizationserver-database\/ \ucc38\uc870\uc0ac\uc774\ud2b8 : https:\/\/github.com\/codej99\/SpringOauth2AuthorizationServer \ucc38\uc870\uc0ac\uc774\ud2b8 : https:\/\/github.com\/Baeldung\/spring-security-registration @EnableAuthorizationServer \uac00 deprecated \uc0c1\ud0dc\uc785\ub2c8\ub2e4. Spring Boot \ubc84\uc804 \uc120\ud0dd\uc2dc, \uc6b0\uc120\uc801\uc73c\ub85c @EnableAuthorizationServer \uac00 \uc0ac\uc6a9\uac00\ub2a5\ud55c\uc9c0 \ud655\uc778\ud574\uc57c \ud569\ub2c8\ub2e4. \ubaa9\ud45c OAuth2 Authorization Server \ub97c \uad6c\ucd95\ud569\ub2c8\ub2e4. \uc18c\uc2a4\ucf54\ub4dc \uc5ec\uae30 \uc5d0 \uc804\uccb4 \uc18c\uc2a4\ucf54\ub4dc\uac00 \uc62c\ub77c\uac00 \uc788\uc2b5\ub2c8\ub2e4. \ud504\ub85c\uc81d\ud2b8 \uc0dd\uc131 \uc2e0\uaddc \ud504\ub85c\uc81d\ud2b8\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4. \ud504\ub85c\uc81d\ud2b8\uba85\uc740 oauth2authoricationserver \ub85c \ud569\ub2c8\ub2e4. \uc758\uc874\uc131\uc740 Spring Boot DevTools,\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2294\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29],"tags":[],"class_list":["post-2294","post","type-post","status-publish","format-standard","hentry","category-spring-boot-2-5"],"_links":{"self":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2294","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2294"}],"version-history":[{"count":74,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2294\/revisions"}],"predecessor-version":[{"id":2503,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2294\/revisions\/2503"}],"wp:attachment":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2294"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2294"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2294"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}