<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>앞으로 가는 블로그</title>
    <link>https://authentication.tistory.com/</link>
    <description>안녕하세요. 행복한 sheriff 입니다.
개발을 좋아합니다.
테니스를 좋아합니다.
술을 좋아합니다.
음악을 좋아합니다.
게임을 좋아합니다.
좋아하는 것들을 하면서 살 수 있어서 행복합니다.

여러분은 행복 하신가요?? 아니면 행복해 보이시나요??</description>
    <language>ko</language>
    <pubDate>Sat, 11 Jul 2026 07:36:35 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>sheriff</managingEditor>
    <image>
      <title>앞으로 가는 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/3032612/attach/df6b9c42dc6d41f6874a0b51e52cf321</url>
      <link>https://authentication.tistory.com</link>
    </image>
    <item>
      <title>SpringBoot + JPA + H2(memory) DB 사용</title>
      <link>https://authentication.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;H2 DB를 사용하기 전에는 매번 DBMS에 접속하여 스키마를 변경하며 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 초반에는 스키마 변경이 잦기 때문에 기존 방식은 효율적이지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 많은 사람들이 H2 DB를 사용하여 개발을 진행하고 있을 것이기 때문에 글을 작성하는 것이 큰 의미는 없다 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 메모리 DB 이기 때문에 외부에서 접속하기 위해서는 TCP 연결을 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 안되는 경우가 있어서 나중에 다시 보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Spring 보다는 Intellij 팁에 가깝다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필수 항목&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Intellij Ultimate Edition&lt;/b&gt; (Intellij에 있는 Database tool을 사용하기 때문에)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Spring Boot Application 프로젝트 생성&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 의존성 추가&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;dependencies {
    // Database
    implementation 'com.h2database:h2'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    // Etc
    implementation 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor &quot;org.springframework.boot:spring-boot-configuration-processor&quot;
    // Test
    testImplementation 'org.springframework.boot:spring-boot-starter-test'    
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. application.yml&lt;/h4&gt;
&lt;pre id=&quot;code_1658072628945&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    hikari:
      driver-class-name: org.h2.Driver
      jdbc-url: jdbc:h2:mem:public;
      username: sa
      password: 1234
  jpa:
    hibernate:
      ddl-auto: create-drop
      naming:
        implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    show-sql: true
    database-platform: org.hibernate.dialect.H2Dialect
    properties:
      hibernate:
        format_sql: true
logging:
  level:
    org.hibernate: info
    org.springframework: info&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Configuration&lt;/h4&gt;
&lt;pre id=&quot;code_1658072667926&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.sql.SQLException;
import javax.sql.DataSource;
import org.h2.tools.Server;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class H2DBConfig {

    @Bean
    @ConfigurationProperties(prefix = &quot;spring.datasource.hikari&quot;)
    public DataSource dataSource() throws SQLException {
        Server.createTcpServer(&quot;-tcp&quot;, &quot;-tcpAllowOthers&quot;, &quot;-tcpPort&quot;, &quot;9093&quot;).start();
        return DataSourceBuilder.create().build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. Entity 생성&lt;/h4&gt;
&lt;pre id=&quot;code_1658073139166&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.time.ZonedDateTime;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import lombok.Getter;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public abstract class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, nullable = false)
    private Long id;

    private ZonedDateTime createdDate;

    private ZonedDateTime modifiedDate;

    @PrePersist
    public void createDate() {
        createdDate = ZonedDateTime.now();
        modifiedDate = createdDate;
    }

    @PreUpdate
    public void updateDate(){
        modifiedDate = ZonedDateTime.now();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1658073153279&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Table(name = &quot;users&quot;, indexes = @Index(name = &quot;idx_user&quot;, columnList = &quot;userId&quot;, unique = true))
@Entity
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseEntity {

    @Column(nullable = false, length = 20)
    private String userId;

    @Column(nullable = false, length = 128)
    private String password;

    @Column(nullable = false, length = 20)
    private String name;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. Application main (Test를 위한 코드이며 실무에서 따라 하지 마세요)&lt;/h4&gt;
&lt;pre id=&quot;code_1658073296523&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.example.h2db.domain.User;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class H2dbApplication implements CommandLineRunner {

    @PersistenceContext
    private EntityManager entityManager;

    public static void main(String[] args) {
        SpringApplication.run(H2dbApplication.class, args);
    }

    @Override
    @Transactional
    public void run(String... args) {
        User user = User.builder().userId(&quot;Kennen@email.com&quot;)
            .name(&quot;Kennen&quot;)
            .password(&quot;decryptedPassword&quot;)
            .build();

        entityManager.persist(user);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7. 실행&lt;/h4&gt;
&lt;pre id=&quot;code_1658073707987&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./gradlew bootRun&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8. Database Tool 확인&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgtPVp/btrHtwDOEk0/DjhzqLtFwjHFz3PCxTUDok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgtPVp/btrHtwDOEk0/DjhzqLtFwjHFz3PCxTUDok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgtPVp/btrHtwDOEk0/DjhzqLtFwjHFz3PCxTUDok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgtPVp%2FbtrHtwDOEk0%2FDjhzqLtFwjHFz3PCxTUDok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;292&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;787&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo0bIu/btrHpw5pr56/Vgha3EuxoHZqoYWAAjH1gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo0bIu/btrHpw5pr56/Vgha3EuxoHZqoYWAAjH1gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo0bIu/btrHpw5pr56/Vgha3EuxoHZqoYWAAjH1gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo0bIu%2FbtrHpw5pr56%2FVgha3EuxoHZqoYWAAjH1gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;518&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;787&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLFigH/btrHAxBczm7/VmkNA4NGdHRfwCm4tkdABK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLFigH/btrHAxBczm7/VmkNA4NGdHRfwCm4tkdABK/img.png&quot; data-alt=&quot;password는 application.yml 에 작성되어 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLFigH/btrHAxBczm7/VmkNA4NGdHRfwCm4tkdABK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLFigH%2FbtrHAxBczm7%2FVmkNA4NGdHRfwCm4tkdABK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;507&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;password는 application.yml 에 작성되어 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PAD8D/btrHxVCC6Sq/HjCAnevRZdcwLk7mrDABkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PAD8D/btrHxVCC6Sq/HjCAnevRZdcwLk7mrDABkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PAD8D/btrHxVCC6Sq/HjCAnevRZdcwLk7mrDABkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPAD8D%2FbtrHxVCC6Sq%2FHjCAnevRZdcwLk7mrDABkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;397&quot; height=&quot;360&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/babjOX/btrHqRaRjlp/KuFjscMNYdKnkHsKpxKHRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/babjOX/btrHqRaRjlp/KuFjscMNYdKnkHsKpxKHRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/babjOX/btrHqRaRjlp/KuFjscMNYdKnkHsKpxKHRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbabjOX%2FbtrHqRaRjlp%2FKuFjscMNYdKnkHsKpxKHRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;84&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;827&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sUVJT/btrHwkJoHDC/hbgttzf3oD6CoCGkdeELj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sUVJT/btrHwkJoHDC/hbgttzf3oD6CoCGkdeELj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sUVJT/btrHwkJoHDC/hbgttzf3oD6CoCGkdeELj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsUVJT%2FbtrHwkJoHDC%2Fhbgttzf3oD6CoCGkdeELj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;643&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;827&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D7BqT/btrHrNeZirJ/BXcmxl2dKRxPzXQPgKzKw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D7BqT/btrHrNeZirJ/BXcmxl2dKRxPzXQPgKzKw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D7BqT/btrHrNeZirJ/BXcmxl2dKRxPzXQPgKzKw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD7BqT%2FbtrHrNeZirJ%2FBXcmxl2dKRxPzXQPgKzKw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1007&quot; height=&quot;340&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 접근하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상.&lt;/p&gt;</description>
      <category>개발/[Intellij] 사용하기</category>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/45</guid>
      <comments>https://authentication.tistory.com/45#entry45comment</comments>
      <pubDate>Mon, 18 Jul 2022 01:11:34 +0900</pubDate>
    </item>
    <item>
      <title>[정규표현식] 하나씩 해보기 (1)</title>
      <link>https://authentication.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;정규표현식이 필요한 경우가 생기는데 그때 를 위해&amp;nbsp; 예제를 작성해놓고 참고하자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자 1개&lt;/li&gt;
&lt;li&gt;포함된 문자&lt;/li&gt;
&lt;li&gt;포함된 문자 조합&lt;/li&gt;
&lt;li&gt;한번에 다 써보기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자 1개&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 36px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 22.2093%; height: 18px;&quot;&gt;Regular Expression&lt;/td&gt;
&lt;td style=&quot;width: 19.0117%; height: 18px;&quot;&gt;Decription&lt;/td&gt;
&lt;td style=&quot;width: 58.7791%;&quot;&gt;example&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 22.2093%; height: 18px;&quot;&gt;.&lt;/td&gt;
&lt;td style=&quot;width: 19.0117%; height: 18px;&quot;&gt;문자 1개&lt;/td&gt;
&lt;td style=&quot;width: 58.7791%;&quot;&gt;&quot;h&quot;.matches(&quot;.&quot;);&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre id=&quot;code_1653977282831&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String text = &quot;a&quot;; // 문자 1개
System.out.println(&quot;a.matches(\&quot;.\&quot;) = &quot; + text.matches(&quot;.&quot;));
System.out.println(&quot;a.matches(\&quot;..\&quot;) = &quot; + text.matches(&quot;..&quot;));
output:
a.matches(&quot;.&quot;) = true
a.matches(&quot;..&quot;) = false

text = &quot;aa&quot;; // 문자 2개로 변경
System.out.println(&quot;a.matches(\&quot;.\&quot;) = &quot; + text.matches(&quot;.&quot;));
System.out.println(&quot;a.matches(\&quot;..\&quot;) = &quot; + text.matches(&quot;..&quot;));
output:
aa.matches(&quot;.&quot;) = false
aa.matches(&quot;..&quot;) = true

text = &quot;*1&quot;; // 특수문자와 숫자로 변경
System.out.println(&quot;*1.matches(\&quot;.\&quot;) = &quot; + text.matches(&quot;.&quot;));
System.out.println(&quot;*1.matches(\&quot;..\&quot;) = &quot; + text.matches(&quot;..&quot;));
output:
*1.matches(&quot;.&quot;) = false
*1.matches(&quot;..&quot;) = true

text = &quot;한글&quot;; // 한글로 변경
System.out.println(&quot;한글.matches(\&quot;.\&quot;) = &quot; + text.matches(&quot;.&quot;));
System.out.println(&quot;한글.matches(\&quot;..\&quot;) = &quot; + text.matches(&quot;..&quot;));
System.out.println(&quot;한글.matches(\&quot;.+\&quot;) = &quot; + text.matches(&quot;.+&quot;));
System.out.println(&quot;한글.matches(\&quot;.*\&quot;) = &quot; + text.matches(&quot;.*&quot;));
output:
한글.matches(&quot;.&quot;) = false
한글.matches(&quot;..&quot;) = true
한글.matches(&quot;.+&quot;) = true
한글.matches(&quot;.*&quot;) = true

text = &quot;&quot;; // 빈 문자열로 변경
System.out.println(&quot;\&quot;\&quot;.matches(\&quot;.+\&quot;) = &quot; + text.matches(&quot;.+&quot;));
System.out.println(&quot;\&quot;\&quot;.matches(\&quot;.*\&quot;) = &quot; + text.matches(&quot;.*&quot;));
output:
&quot;&quot;.matches(&quot;.+&quot;) = false // 1개 이상이라서 false
&quot;&quot;.matches(&quot;.*&quot;) = true // 0개 이상이라서 true

text = &quot;강아지&quot;;
System.out.println(&quot;강아지.matches(\&quot;강아.\&quot;) = &quot; + text.matches(&quot;강아.&quot;));
System.out.println(&quot;강아지.matches(\&quot;강아지.\&quot;) = &quot; + text.matches(&quot;강아지.&quot;));
output:
강아지.matches(&quot;강아.&quot;) = true
강아지.matches(&quot;강아지.&quot;) = false // 강아지 뒤에 문자가 없기 때문에 false

text = &quot;김철수&quot;;
System.out.println(&quot;김철수.matches(\&quot;김.수\&quot;) = &quot; + text.matches(&quot;김.수&quot;));
output:
김철수.matches(&quot;김.수&quot;) = true

text = &quot;김박수&quot;;
System.out.println(&quot;김박수.matches(\&quot;김.수\&quot;) = &quot; + text.matches(&quot;김.수&quot;));
output:
김박수.matches(&quot;김.수&quot;) = true&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표현식 한개당 문자 1개를 의미&lt;/li&gt;
&lt;li&gt;표현식을 두개 사용하면 문자 2개를 의미한다.&lt;/li&gt;
&lt;li&gt;특수문자, 숫자, 한글 모두 가능하다.&lt;/li&gt;
&lt;li&gt;표현식 뒤에 '+' 를 붙이면 '문자 1개 이상' 을 의미한다.&lt;/li&gt;
&lt;li&gt;표현식 뒤에 '*' 를 붙이면 '문자 0개 이상' 을 의미한다.&lt;/li&gt;
&lt;li&gt;정해진 문자가 있고 특정 위치에 임의의 문자가 필요한 경우 사용하면 좋을듯하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정규표현식에 포함된 문자 1개&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 89.4186%; height: 5px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 154.797px; height: 18px;&quot;&gt;Regular Expression&lt;/td&gt;
&lt;td style=&quot;width: 366.547px; height: 18px;&quot;&gt;Decription&lt;/td&gt;
&lt;td style=&quot;width: 286.656px; height: 18px;&quot;&gt;example&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 154.797px; height: 18px;&quot;&gt;[expression]&lt;/td&gt;
&lt;td style=&quot;width: 366.547px; height: 18px;&quot;&gt;expression에 포함된 문자열 중에 하나라도 존재하면 true&lt;/td&gt;
&lt;td style=&quot;width: 286.656px; height: 18px;&quot;&gt;&quot;a&quot;.matches(&quot;[abc]&quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre id=&quot;code_1654738222866&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String text = &quot;a&quot;;
System.out.println(&quot;a.matches(\&quot;[abc]\&quot;) = &quot; + text.matches(&quot;[abc]&quot;));
System.out.println(&quot;a.matches(\&quot;[bcd]\&quot;) = &quot; + text.matches(&quot;[bcd]&quot;));
output:
a.matches(&quot;[abc]&quot;) = true
a.matches(&quot;[bcd]&quot;) = false

text = &quot;abc&quot;;
System.out.println(&quot;abc.matches(\&quot;[abc]\&quot;) = &quot; + text.matches(&quot;[abc]&quot;));
System.out.println(&quot;abc.matches(\&quot;[abcd]\&quot;) = &quot; + text.matches(&quot;[abcd]&quot;));
output:
abc.matches(&quot;[abc]&quot;) = false
abc.matches(&quot;[abcd]&quot;) = false

text = &quot;My name is Kennen&quot;;
System.out.println(&quot;text.matches(\&quot;My [Nn]ame is Kennen\&quot;) = &quot; + text.matches(&quot;My [Nn]ame is Kennen&quot;));
output:
text.matches(&quot;My [Nn]ame is Kennen&quot;) = true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표현식에 포함된 문자 1개를 의미&lt;/li&gt;
&lt;li&gt;&lt;b&gt;!!주의 : 1개 이상을 표현하진 않기 때문에 문자 &quot;abc&quot;를 &quot;abc&quot; 에 적용했을 경우 false이며, &quot;abcd&quot; 에 적용했을 경우에도 false이다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정규표현식에 포함된 문자 1개 + 조합&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 89.4186%; height: 5px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.0975%; height: 18px;&quot;&gt;Regular Expression&lt;/td&gt;
&lt;td style=&quot;width: 39.4018%; height: 18px;&quot;&gt;Decription&lt;/td&gt;
&lt;td style=&quot;width: 35.3706%; height: 18px;&quot;&gt;example&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.0975%; height: 18px;&quot;&gt;[expression][expression]&lt;/td&gt;
&lt;td style=&quot;width: 39.4018%; height: 18px;&quot;&gt;정규표현식에 포함된 문자 1개 조합&lt;/td&gt;
&lt;td style=&quot;width: 35.3706%; height: 18px;&quot;&gt;&quot;af&quot;.matches(&quot;[abc][efg]&quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre id=&quot;code_1654741810404&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String text = &quot;af&quot;;
System.out.println(&quot;af.matches(\&quot;[ab][fg]\&quot;) = &quot; + text.matches(&quot;[ab][fg]&quot;));
System.out.println(&quot;af.matches(\&quot;[af]\&quot;) = &quot; + text.matches(&quot;[af]&quot;));
output:
af.matches(&quot;[ab][fg]&quot;) = true
af.matches(&quot;[af]&quot;) = false

text = &quot;afyl&quot;;
System.out.println(&quot;text.matches(\&quot;[ae][fg][yj][lm]\&quot;) = &quot; + text.matches(&quot;[ae][fg][yj][lm]&quot;));
output:
text.matches(&quot;[ae][fg][yj][lm]&quot;) = true&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반드시 포함되어야 하는 문자가 있는 경우 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;한번에 다 써보기&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1654742481506&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String text = &quot;My Name is Kennen&quot;;
System.out.println(&quot;text.matches(\&quot;My [Nn]ame is .[ea][nm][nm]en\&quot;) = &quot; + text.matches(&quot;My [Nn]ame is .[ea][nm][nm]..&quot;));
output:
text.matches(&quot;My [Nn]ame is .[ea][nm][nm]en&quot;) = true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>정규표현식 #정규표현식 문자1개 #정규표현식 문자포함</category>
      <category>정규표현식 기초</category>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/44</guid>
      <comments>https://authentication.tistory.com/44#entry44comment</comments>
      <pubDate>Thu, 9 Jun 2022 11:42:37 +0900</pubDate>
    </item>
    <item>
      <title>[ZonedDateTime] 그리고 OffsetDateTime</title>
      <link>https://authentication.tistory.com/43</link>
      <description>&lt;h1 data-ke-size=&quot;size16&quot;&gt;OffsetDateTime, ZonedDateTime&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LocalDateTime 외에 OffsetDateTime, ZonedDateTime이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 어떤 값을 가지는지 디버깅 모드에서 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 각각을 .now() 메소드를 통해 현재 시간을 가져온다.&lt;/p&gt;
&lt;pre id=&quot;code_1653964488058&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 디버깅 모드에서 각각의 값을 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rZlIk/btrDyVtr422/veK8emuUBXnaKUnUZuWmA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rZlIk/btrDyVtr422/veK8emuUBXnaKUnUZuWmA0/img.png&quot; data-alt=&quot;intellij 의  debugger 창 캡쳐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rZlIk/btrDyVtr422/veK8emuUBXnaKUnUZuWmA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrZlIk%2FbtrDyVtr422%2FveK8emuUBXnaKUnUZuWmA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;270&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;intellij 의  debugger 창 캡쳐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LocalDateTime : 날짜, 시간
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날짜와 시간만 존재한다.&lt;/li&gt;
&lt;li&gt;시차 정보가 없기 때문에 다른 위치에 있는 서버에서 동시에 저장되는 경우 다른 시간으로 저장 되며 같은 시간이라는 것을 알 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;OffsetDateTime : 날짜, 시간, offset(GMT 기준)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LocalDateTime에 offset 정보가 추가 됨&lt;/li&gt;
&lt;li&gt;시차 정보가 있기 때문에 다른 위치에 있는 서버에서 동시에 저장되는 경우 시차 정보를 통해 같은 시간임을 알 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;zonedDateTime : 날짜, 시간, offset(GMT 기준), zone
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OffsetDateTime에 zone 정보가 추가 됨&lt;/li&gt;
&lt;li&gt;시차 정보와 함께 지역 정보까지 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size16&quot;&gt;LocalDateTime + ZoneId&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;atZone을 사용하면 LocalDateTime 에 zone 을 추가할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1653971720903&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LocalDateTime now = LocalDateTime.now();
ZonedDateTime saoPauloZonedDateTime = now.atZone(ZoneId.of(&quot;America/Sao_Paulo&quot;));
ZonedDateTime seoulZonedDateTime = now.atZone(ZoneId.of(&quot;Asia/Seoul&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6apVA/btrDEAV7iAS/JiuuBNoPRdM9lVKsVoP0dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6apVA/btrDEAV7iAS/JiuuBNoPRdM9lVKsVoP0dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6apVA/btrDEAV7iAS/JiuuBNoPRdM9lVKsVoP0dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6apVA%2FbtrDEAV7iAS%2FJiuuBNoPRdM9lVKsVoP0dK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;134&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LocalDateTime에 Zone&amp;nbsp; 정보를 추가하여 Zone(Region), offset 정보를 나타냄&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;ZonedDateTime에서 Zone, Offset 을 제거한 LocalDateTime 으로 변환도 쉽습니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1653973109401&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LocalDateTime localDateTime = seoulZonedDateTime.toLocalDateTime();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;withZoneSameInstance를 사용하면 ZonedDateTime에 다른 Zone으로 변경도 가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1653973845218&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ZonedDateTime now = LocalDateTime.now().atZone(ZoneId.of(&quot;Asia/Seoul&quot;));
ZonedDateTime londonZonedDateTime = now.withZoneSameInstant(ZoneId.of(&quot;Europe/London&quot;));

output : 
now = 2022-05-31T14:17:24.852151+09:00[Asia/Seoul]
londonZonedDateTime = 2022-05-31T06:17:24.852151+01:00[Europe/London]&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1&gt;Period, Duration&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Perioed.between 메소드는 LocalDate 만을 매개변수로 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 OffsetDateTime이나 ZonedDateTime 을 사용할 경우 .toLocalDate() 메소드를 사용하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UBzr5/btrDDsKE277/uKRUOLxWIgRugP6eAbPCA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UBzr5/btrDDsKE277/uKRUOLxWIgRugP6eAbPCA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UBzr5/btrDDsKE277/uKRUOLxWIgRugP6eAbPCA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUBzr5%2FbtrDDsKE277%2FuKRUOLxWIgRugP6eAbPCA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;143&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Duration.between 메소드의 경우 Temporal(Interface) 을 매개변수로 받는데, LocalDateTime, OffsetDateTime, ZonedDateTime 모두 Temporal을 구현한 클래스이므로 사용이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bndGwj/btrDDtpheEP/1WkkOxEgq2fcKtieWTGYak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bndGwj/btrDDtpheEP/1WkkOxEgq2fcKtieWTGYak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bndGwj/btrDDtpheEP/1WkkOxEgq2fcKtieWTGYak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbndGwj%2FbtrDDtpheEP%2F1WkkOxEgq2fcKtieWTGYak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;447&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;1090&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 LocalDateTime과 ZoneDateTime을 각각 다르게 넘기는 것도 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1653971103451&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of(&quot;America/Sao_Paulo&quot;));

Duration duration = Duration.between(localDateTime, zonedDateTime);&lt;/code&gt;&lt;/pre&gt;</description>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/43</guid>
      <comments>https://authentication.tistory.com/43#entry43comment</comments>
      <pubDate>Tue, 31 May 2022 13:25:38 +0900</pubDate>
    </item>
    <item>
      <title>[LocalDateTime] 사용하기</title>
      <link>https://authentication.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;LocalDateTime 을 사용하는 방법에 대해서 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OffsetDateTime과 ZonedDateTime도 같이 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. toLocalDate, toLocalTime, get*&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1652703604517&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 현재 날짜 및 시간
LocalDateTime now = LocalDateTime.now();
System.out.println(&quot;[now] = &quot; + now);
System.out.println(&quot;now.getYear() = &quot; + now.getYear()); // 년
System.out.println(&quot;now.getMonth() = &quot; + now.getMonth()); // 월
System.out.println(&quot;now.getMonthValue() = &quot; + now.getMonthValue()); // 월(숫자)
System.out.println(&quot;now.getDayOfMonth() = &quot; + now.getDayOfMonth()); // 일
System.out.println(&quot;now.getHour() = &quot; + now.getHour()); // 시
System.out.println(&quot;now.getMinute() = &quot; + now.getMinute()); // 분
System.out.println(&quot;now.getSecond() = &quot; + now.getSecond()); // 초 

// 현재 날짜만 빼오기
LocalDate date = now.toLocalDate();
System.out.println(&quot;[date] = &quot; + date);
System.out.println(&quot;date.getYear() = &quot; + date.getYear()); // 년
System.out.println(&quot;date.getMonth() = &quot; + date.getMonth()); // 월
System.out.println(&quot;date.getMonthValue() = &quot; + date.getMonthValue()); // 월(숫자)
System.out.println(&quot;date.getDayOfMonth() = &quot; + date.getDayOfMonth()); // 일

// 현재 시간만 빼오기
LocalTime time = now.toLocalTime();
System.out.println(&quot;[time] = &quot; + time);
System.out.println(&quot;time.getHour() = &quot; + time.getHour()); // 시
System.out.println(&quot;time.getMinute() = &quot; + time.getMinute()); // 분
System.out.println(&quot;time.getSecond() = &quot; + time.getSecond()); // 초&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652703623810&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output:

[now] = 2022-05-16T21:33:19.350698
now.getYear() = 2022
now.getMonth() = MAY
now.getMonthValue() = 5
now.getDayOfMonth() = 16
now.getHour() = 21
now.getMinute() = 33
now.getSecond() = 19

[date] = 2022-05-16
date.getYear() = 2022
date.getMonth() = MAY
date.getMonthValue() = 5
date.getDayOfMonth() = 16

[time] = 21:33:19.350698
time.getHour() = 21
time.getMinute() = 33
time.getSecond() = 19&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 연산(Plus, Minus)&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1652706575551&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LocalDateTime now = LocalDateTime.now();
System.out.println(&quot;now = &quot; + now);

// PLUS
System.out.println(&quot;now.plusYears(2) = &quot; + now.plusYears(2));
System.out.println(&quot;now.plusMonths(5) = &quot; + now.plusMonths(5));
System.out.println(&quot;now.plusDays(10) = &quot; + now.plusDays(10));
System.out.println(&quot;now.plusHours(5) = &quot; + now.plusHours(5));
System.out.println(&quot;now.plusMinutes(10) = &quot; + now.plusMinutes(10));
System.out.println(&quot;now.plusSeconds(20) = &quot; + now.plusSeconds(20));

// MINUS
System.out.println(&quot;now.minusYears(2) = &quot; + now.minusYears(2));
System.out.println(&quot;now.minusMonths(5) = &quot; + now.minusMonths(5));
System.out.println(&quot;now.minusDays(10) = &quot; + now.minusDays(10));
System.out.println(&quot;now.minusHours(5) = &quot; + now.minusHours(5));
System.out.println(&quot;now.minusMinutes(10) = &quot; + now.minusMinutes(10));
System.out.println(&quot;now.minusSeconds(20) = &quot; + now.minusSeconds(20));

System.out.println(&quot;now = &quot; + now);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652706631305&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output:

now = 2022-05-16T22:08:59.398040

now.plusYears(2) = 2024-05-16T22:08:59.398040
now.plusMonths(5) = 2022-10-16T22:08:59.398040
now.plusDays(10) = 2022-05-26T22:08:59.398040
now.plusHours(5) = 2022-05-17T03:08:59.398040
now.plusMinutes(10) = 2022-05-16T22:18:59.398040
now.plusSeconds(20) = 2022-05-16T22:09:19.398040

now.minusYears(2) = 2020-05-16T22:08:59.398040
now.minusMonths(5) = 2021-12-16T22:08:59.398040
now.minusDays(10) = 2022-05-06T22:08:59.398040
now.minusHours(5) = 2022-05-16T17:08:59.398040
now.minusMinutes(10) = 2022-05-16T21:58:59.398040
now.minusSeconds(20) = 2022-05-16T22:08:39.398040

now = 2022-05-16T22:08:59.398040&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 첫줄과 마지막 줄의 now 가 같은 것이 보일 것이다. plus, minus 메소드를 통해서 연산을 하더라도 객체 생성시 가지고 있는 값은 변하지 않는다.&lt;/li&gt;
&lt;li&gt;plus, minus 메소드에서 다음 문구 확인 가능 : &lt;b&gt;This&amp;nbsp;instance&amp;nbsp;is&amp;nbsp;immutable&amp;nbsp;and&amp;nbsp;unaffected&amp;nbsp;by&amp;nbsp;this&amp;nbsp;method&amp;nbsp;call.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3.&amp;nbsp; 비교(isBefore, isAfter, isEqual)&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1652707267068&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LocalDateTime now = LocalDateTime.now();
System.out.println(&quot;now = &quot; + now);
LocalDateTime after2Years = now.plusYears(2);
System.out.println(&quot;after2Years = &quot; + after2Years);

System.out.println(&quot;now.isBefore(after2Years) = &quot; + now.isBefore(after2Years));
System.out.println(&quot;now.isAfter(after2Years) = &quot; + now.isAfter(after2Years));
System.out.println(&quot;now.isEqual(after2Years) = &quot; + now.isEqual(after2Years));&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652707289931&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output:

now = 2022-05-16T22:18:59.727866

after2Years = 2024-05-16T22:18:59.727866

now.isBefore(after2Years) = true
now.isAfter(after2Years) = false
now.isEqual(after2Years) = false&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4. Period&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1652714770271&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LocalDate startDate = LocalDate.now();
System.out.println(&quot;startDate = &quot; + startDate);

LocalDate endDate = startDate.plusYears(2).plusMonths(3).plusDays(4);
System.out.println(&quot;endDate = &quot; + endDate);

Period between = Period.between(startDate, endDate);

System.out.println(&quot;between.getYears() = &quot; + between.getYears());
System.out.println(&quot;between.getMonths() = &quot; + between.getMonths());
System.out.println(&quot;between.getDays() = &quot; + between.getDays());&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652714803842&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output:

startDate = 2022-05-17
endDate = 2024-08-21

between.getYears() = 2
between.getMonths() = 3
between.getDays() = 4&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날짜 또는 기간 차이에 대한 계산&lt;/li&gt;
&lt;li&gt;출력된 내용을 보면 getYear, getMonths, getDays 는 두 날짜의 시작과 끝의 대한 차이가 아니라는 것이 확인된다.&lt;/li&gt;
&lt;li&gt;각 항목에 대한 차이만 확인 가능하다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;2022년과 2024년 차이 = 2년&lt;/li&gt;
&lt;li&gt;5월과 8월 차이 = 3&lt;/li&gt;
&lt;li&gt;17일과 4일 차이 = 4&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;endDate가 startDate 보다 이전인 경우 음수로 표현된다. isNegative 메소드로 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Duration&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1652715272053&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LocalDateTime startDate = LocalDateTime.now();
LocalDateTime endDate = startDate.plusMonths(4);
Duration between = Duration.between(startDate, endDate);
System.out.println(&quot;between = &quot; + between);
System.out.println(&quot;between.toDays() = &quot; + between.toDays());
System.out.println(&quot;between.toHours() = &quot; + between.toHours());
System.out.println(&quot;between.toMinutes() = &quot; + between.toMinutes());
System.out.println(&quot;between.toSeconds() = &quot; + between.toSeconds());&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652715289498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output:

between = PT2952H
between.toDays() = 123
between.toHours() = 2952
between.toMinutes() = 177120
between.toSeconds() = 10627200&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간 차이에 대한 계산&lt;/li&gt;
&lt;li&gt;endDate가 startDate 보다 이전인 경우 음수로 표현된다. isNegative 메소드로 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;6. Date Format 지정하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;API 응답에 읽기 쉬운 포맷으로 날짜와 시간을 보내줘야 하는 경우가 있는데,  &lt;b&gt;DateTimeFormatter&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 사용하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1652701384468&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 현재 날짜 및 시간
LocalDateTime now = LocalDateTime.now();

// ISO : yyyy-MM-ddTHH:mm:ss
String isoDateTime = now.format(DateTimeFormatter.ISO_DATE_TIME);
System.out.println(&quot;isoDateTime = &quot; + isoDateTime);

// 사용자 입력
String customFormat1 = now.format(DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm:ss&quot;));
System.out.println(&quot;customFormat1 = &quot; + customFormat1);

// 오전 / 오후
String customFormat2 = now.format(DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd a HH:mm:ss&quot;));
System.out.println(&quot;customFormat2 = &quot; + customFormat2);

// Date / Time 각각
String formatDate = LocalDate.now().format(DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd&quot;));
System.out.println(&quot;formatDate = &quot; + formatDate);
String formatTime = LocalTime.now().format(DateTimeFormatter.ofPattern(&quot;a HH:mm:ss&quot;));
System.out.println(&quot;formatTime = &quot; + formatTime);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652701384469&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output :

isoDateTime = 2022-05-17T00:44:24.137939

customFormat1 = 2022-05-17 00:44:24
customFormat2 = 2022-05-17 오전 00:44:24

formatDate = 2022-05-17
formatTime = 오전 00:44:24&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/[Java] 사용하기</category>
      <category>DateTimeFormatter</category>
      <category>java date</category>
      <category>LocalDateTime between</category>
      <category>LocalDateTime compare</category>
      <category>LocalDateTime Duration</category>
      <category>LocalDateTime format</category>
      <category>LocalDateTime Period</category>
      <category>LocalDateTime 오전오후</category>
      <category>toLocalDate</category>
      <category>ToLocalTime</category>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/42</guid>
      <comments>https://authentication.tistory.com/42#entry42comment</comments>
      <pubDate>Mon, 16 May 2022 20:42:24 +0900</pubDate>
    </item>
    <item>
      <title>[LocalDateTime]  만들기</title>
      <link>https://authentication.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;LocalDateTime, LocalDate, LocalTime 에 대한 샘플 코드를 작성해본다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;LocalDateTime 의 경우 시간대(Offset, Zone) 정보 포함되어 있지 않기 때문에 여러 서버가 다른 지역에 있는 경우에 사용해서는 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;동시에 생성된 두개의 LocalDateTime이 각자 다른 지역에 있는 서버에서 생성이 됐을 경우를 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;시간대 정보가 있다면 두 객체의 시간이 다르더라도 시차를 계산하여 같은 시간에 만들어 졌다는 것을 알 수 있지만 시간대 정보가 없으면 두 객체가 동시에 만들어졌다는 것을 알 수 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이렇게 시차와 지역 정보가 필요한 경우에는 OffsetDateTime 또는 ZonedDateTime 을 사용하면 된다.(이것들도 작성할 예정이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이와 관련된 내용은 다시 정리하기로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;LocalDateTime의 사용법을 알고나면 OffsetDateTime과 ZonedDateTime의 사용은 수월하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이 글에서는 OffsetDateTime, ZonedDateTime 내용은 포함되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;현재 날짜 및 시간&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가장 간단한 내용으로 시작한다. 현재 시간&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1652681138269&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 현재 날짜 및 시간
LocalDateTime now = LocalDateTime.now();
System.out.println(&quot;now = &quot; + now);

// chicago 날짜 및 시간
LocalDateTime chicagoDateTime = LocalDateTime.now(ZoneId.of(&quot;America/Chicago&quot;));
System.out.println(&quot;chicagoDateTime = &quot; + chicagoDateTime);

// cuba 날짜 및 시간
LocalDateTime cubaDateTime = LocalDateTime.now(ZoneId.of(&quot;Cuba&quot;));
System.out.println(&quot;cubaDateTime = &quot; + cubaDateTime);

// LocalDate, LocalTime 으로 현재 날짜 및 시간
LocalDate dateNow = LocalDate.now(); // 현재 날짜
LocalTime timeNow = LocalTime.now(); // 현재 시간
LocalDateTime fromLocalDateAndTime = LocalDateTime.of(dateNow, timeNow);
System.out.println(&quot;fromLocalDateAndTime = &quot; + fromLocalDateAndTime);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652681364898&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output:

now = 2022-05-16T18:36:25.400357
chicagoDateTime = 2022-05-16T04:36:25.402610
cubaDateTime = 2022-05-16T05:36:25.404441
fromLocalDateAndTime = 2022-05-16T18:36:25.404835&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ZoneId 를 통해서 해당 지역의 현재 시간을 가져올 수도 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a title=&quot;전체 ZoneId 목록&quot; href=&quot;https://mkyong.com/java8/java-display-all-zoneid-and-its-utc-offset/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mkyong.com/java8/java-display-all-zoneid-and-its-utc-offset/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;특정 날짜 및 시간&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652700991543&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; // of custom
int year = 2020, month = 5, dayOfMonth = 16, hour = 6, minute = 24, second = 44;
LocalDateTime of = LocalDateTime.of(year, month, dayOfMonth, hour, minute, second);
System.out.println(&quot;of = &quot; + of);

// of LocalDate, LocalTime
LocalDate dateNow = LocalDate.of(year, month, dayOfMonth);
LocalTime timeNow = LocalTime.of(hour, minute, second); // second is Optional
LocalDateTime ofLocalDateAndTime = LocalDateTime.of(dateNow, timeNow);
System.out.println(&quot;ofLocalDateAndTime = &quot; + ofLocalDateAndTime);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652701072110&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output:

of = 2020-05-16T06:24:44
ofLocalDateAndTime = 2020-05-16T06:24:44&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;String To LocalDateTime&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652702997290&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String input = &quot;2022-05-16 21:07:12&quot;;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm:ss&quot;);
LocalDateTime localDateTime = LocalDateTime.parse(input, formatter);
System.out.println(&quot;localDateTime = &quot; + localDateTime);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652703027269&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output:

localDateTime = 2022-05-16T21:07:12&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;yyyy : 년&lt;/li&gt;
&lt;li&gt;MM : 월&lt;/li&gt;
&lt;li&gt;dd : 일&lt;/li&gt;
&lt;li&gt;HH : 시&lt;/li&gt;
&lt;li&gt;mm : 분&lt;/li&gt;
&lt;li&gt;ss : 초&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Date To LocalDateTime, Calendar To LocalDateTime&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1652701222900&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// from Date
Date date = new Date();
System.out.println(&quot;date = &quot; + date);
LocalDateTime fromDate = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println(&quot;ldtFromDate = &quot; + fromDate);

// from Calendar
Calendar calendar = Calendar.getInstance();
System.out.println(&quot;calendar = &quot; + calendar);
LocalDateTime fromCalendar = LocalDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());
System.out.println(&quot;fromCalendar = &quot; + fromCalendar);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652701262681&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;output:

date = Mon May 16 20:40:33 KST 2022
fromDate = 2022-05-16T20:40:33.709

calendar = java.util.GregorianCalendar[time=1652701233718,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id=&quot;Asia/Seoul&quot;,offset=32400000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2022,MONTH=4,WEEK_OF_YEAR=21,WEEK_OF_MONTH=3,DAY_OF_MONTH=16,DAY_OF_YEAR=136,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=8,HOUR_OF_DAY=20,MINUTE=40,SECOND=33,MILLISECOND=718,ZONE_OFFSET=32400000,DST_OFFSET=0]
fromCalendar = 2022-05-16T20:40:33.718&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글 : &lt;a href=&quot;https://authentication.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://authentication.tistory.com/42&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발/[Java] 사용하기</category>
      <category>Calendar To LocalDateTime</category>
      <category>Date To LocalDateTime</category>
      <category>java date</category>
      <category>Java 날짜</category>
      <category>LocalDateTime</category>
      <category>LocalDateTime of</category>
      <category>String to LocalDateTime</category>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/41</guid>
      <comments>https://authentication.tistory.com/41#entry41comment</comments>
      <pubDate>Mon, 16 May 2022 15:24:02 +0900</pubDate>
    </item>
    <item>
      <title>[LocalDateTime] 기록</title>
      <link>https://authentication.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;형제들 : LocalDate, LocalTime&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Java를 8 버전(또는 이후 버전)으로 시작하고 개발을 하고 있는 사람들은 날짜와 시간을 다룰 때 기본으로 사용하고 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만 Date, Calendar 를 사용하던 나에게는 귀인이나 선물과도 같은 존재다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예전부터 Java를 사용했거나 어떤 이유로 인해 오래된 프로젝트에서 Date 또는 Calendar 를 사용하는 사람들은 무슨 말인지 알 것이라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(모르는 사람들은 계속 몰라도 된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다른 언어들과 비교하면 어떨지 몰라도 어쨌든 8 버전 이전과 비교하면 엄청난 발전이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(다른 사람 보다는 어제의 나와 비교하는 것이 중요하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Date, Calendar 와의 비교라던지 장단점은 다른 블로그에 많으니 &lt;br /&gt;이곳엔 기초 사용법이나 팁을 남겨두고 기억이 나지 않을 때마다 확인하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음글 :&amp;nbsp;&lt;a title=&quot;LocalDateTime 만들기&quot; href=&quot;https://authentication.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://authentication.tistory.com/41&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발/[Java] 사용하기</category>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/40</guid>
      <comments>https://authentication.tistory.com/40#entry40comment</comments>
      <pubDate>Mon, 16 May 2022 12:37:08 +0900</pubDate>
    </item>
    <item>
      <title>Multiple CacheManager  이슈 기록</title>
      <link>https://authentication.tistory.com/38</link>
      <description>&lt;p style=&quot;font-size: 1.12em;&quot;&gt;spring framework로 개발을 진행하고 Cache를 사용해본 사람이 보면 좋을 것 같다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;프로젝트를 진행 중에 CacheManger를 두 개를 별도로 가져가야 할 일이 생겼다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;회사에서 사용중인 공통모듈에 EhCacheManager가 추가가 되었는데, 현재 진행중인 프로젝트에서 이미 EhCacheManager를 사용중인 상태여서 문제가 발생했다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;해결방법은 간단하다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;하지만 어디에서 Exception을 발생하는지 그리고 어떤 과정을 통해 진행 되다가 Exception이 발생하는지 궁금해서 기록을 남기려고 한다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;문제 재현&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;추가한 의존성은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1562900963278&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;parent&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;2.1.6.RELEASE&amp;lt;/version&amp;gt;
        &amp;lt;relativePath/&amp;gt; &amp;lt;!-- lookup parent from repository --&amp;gt;
&amp;lt;/parent&amp;gt;
...
etc..
...

&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;spring-boot-starter-cache&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;spring-boot-starter-security&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;net.sf.ehcache&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;ehcache-core&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;2.6.10&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;web 프로젝트를 추가하긴 했지만 실제로 브라우져에서 확인할 일은 없고, console에 서버가 잘 실행되는지 확인만 하려는 이유에서 추가했다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;security 프로젝트 역시 다중 Bean 설정을 확인하려고 추가한 것이고 다른 이유는 없다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;b&gt;spring-boot-starter-cache&lt;/b&gt;와,&amp;nbsp;&lt;b&gt;ehcache-core&lt;/b&gt; 는 정말 필요해서 추가했다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;우선 다음과 같이 설정을 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1562900685741&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching
public class DefaultCacheConfig {
    @Bean
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }
    @Bean
    public EhCacheManagerFactoryBean ehCacheCacheManager() {
        EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
        cacheManagerFactoryBean.setConfigLocation(new ClassPathResource(&quot;ehcache.xml&quot;));
        cacheManagerFactoryBean.setShared(true);
        return cacheManagerFactoryBean;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;위와 같이 설정한 뒤 어플리케이션을 실행시켜 보도록 하자.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;정상적으로 잘 실행이 될 것이다.(안된다면...로그를 잘 확인해보자.)&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;그럼 에러가 나는 상황을 만들어 보도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1562901506951&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching
public class DefaultCacheConfig {
    @Bean
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }
    
    @Bean
    public CacheManager cacheManagerAnother() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }
    
    @Bean
    public EhCacheManagerFactoryBean ehCacheCacheManager() {
        EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
        cacheManagerFactoryBean.setConfigLocation(new ClassPathResource(&quot;ehcache.xml&quot;));
        cacheManagerFactoryBean.setShared(true);
        return cacheManagerFactoryBean;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;CacheManager Bean을 하나 더 추가 했다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;그리고 다시 어플리케이션을 실행시켜 보면 다음과 같은 메세지를 출력하면서 Exception이 발생 할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ekz65q/btqwHqnx09G/rKlDdOWikHVppqhT9J5dX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ekz65q/btqwHqnx09G/rKlDdOWikHVppqhT9J5dX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ekz65q/btqwHqnx09G/rKlDdOWikHVppqhT9J5dX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fekz65q%2FbtqwHqnx09G%2FrKlDdOWikHVppqhT9J5dX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;빨간색 부분을 해석해 보면&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;CacheResolver가 지정되지 않았으며, CacheManager 타입으로 된 고유한 Bean이 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;b&gt;한 개를 Primary로 지정&lt;/b&gt;하거나, 사용할 CacheManager를 지정하세요.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;첫번 째 방법 : Primary&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;위에 나온대로 하나를 Primary로 설정해 보자.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;Java Config를 사용하는 경우에는 @Primary annotation을 추가하면 되고, xml config를 사용하는 경우 빈 설정 시 primary=true 를 추가하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1562901829546&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Bean
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }

    @Bean
    @Primary
    public CacheManager cacheManagerAnother() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;다시 실행해 보면 정말 Exception이 발생하지 않는 것을 확인할 수 있을 것이다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;잘 되니까 궁금해졌다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;그래서 &lt;b&gt;Primary&lt;/b&gt;로 지정하지 않은 상태에서 다시 어플리케이션을 실행하고 Exception을 찾아가본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK2ELL/btqwHLd1kCt/CPngkd3AwGbvmDFMGeLqdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK2ELL/btqwHLd1kCt/CPngkd3AwGbvmDFMGeLqdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK2ELL/btqwHLd1kCt/CPngkd3AwGbvmDFMGeLqdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK2ELL%2FbtqwHLd1kCt%2FCPngkd3AwGbvmDFMGeLqdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;b&gt;CacheAspectSupport&lt;/b&gt; 클래스의 &lt;b&gt;afterSingletonsInstantiated&lt;/b&gt; 메소드에서 &lt;b&gt;NoUniqueBeanDefinitionException&lt;/b&gt;이 발생한 것이다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;즉 노란색 박스로 되어 있는 &lt;b&gt;setCacheManager&lt;/b&gt; 메소드를 실행하는 과정에서 에러가 발생한 것이다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;더 자세하게는 Bean 관련 Exception이기 때문에&amp;nbsp;&lt;b&gt;beanFactory.getBean&lt;/b&gt;&amp;nbsp;메소드를 실행하는 과정에서 발생했을 것이다. 또 찾아가본다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;디버깅을 통해서 메소드를 따라가다 보면 &lt;b&gt;DefaultListableBeanFactory&lt;/b&gt; 클래스의&amp;nbsp;&lt;b&gt;resolveNamedBean&amp;nbsp;&lt;/b&gt;메소드를 만나게 된다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uRMvy/btqwHJngUky/3SmSCPzw8sg6Hip2xStAY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uRMvy/btqwHJngUky/3SmSCPzw8sg6Hip2xStAY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uRMvy/btqwHJngUky/3SmSCPzw8sg6Hip2xStAY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuRMvy%2FbtqwHJngUky%2F3SmSCPzw8sg6Hip2xStAY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;RequireType 이 org.springframework.cache.CacheManager 인 이름의 Bean 후보군들을 가져온다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;candidateNames 에 한 개만 있다면 그 이름으로 된 Bean 을 리턴한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;1개 보다 많다면 Bean이 Primary Bean 을 반환한다. 이 때 Primary로 구분이 불가능 하면 &lt;b&gt;NoUniqueBeanDefinitionException&amp;nbsp;&lt;/b&gt;을 throw 하게된다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;그렇다. 두 개 이상의 CacheManager를 Bean으로 등록해서 사용할 때에는 반드시 하나의 Bean에 Primary 설정을 해줘야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;두 번째 방법 :&amp;nbsp;&lt;span&gt;CachingConfigurer (구현체 :&amp;nbsp;&lt;span&gt;CachingConfigurerSupport)&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;하나의 Bean을 Primary로 설정하는 방법 말고 다른 한가지 방법이 더 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;span&gt;Config 클래스에서&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span&gt;CachingConfigurer&lt;/span&gt;&lt;/b&gt;&lt;span&gt;를 구현하거나&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;CachingConfigurerSupport&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;를 상속하면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;span&gt;&lt;span&gt;Bean에서 @Primary annotation을 삭제하고 &lt;b&gt;&lt;span&gt;&lt;span&gt;CachingConfigurerSupport&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;를 상속받은 뒤에 다시 실행시켜 보자.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1562928851539&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
    @Bean
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }
    @Bean
    public CacheManager cacheManagerA() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }
    @Bean
    public EhCacheManagerFactoryBean ehCacheCacheManager() {
        EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
        cacheManagerFactoryBean.setConfigLocation(new ClassPathResource(&quot;ehcache.xml&quot;));
        cacheManagerFactoryBean.setShared(true);
        return cacheManagerFactoryBean;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;문제 없이 잘 실행되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;왜일까?? 찾아가본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOgxxb/btqwIjP58NA/6Bn4RRr4QKkFvXgfpkULck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOgxxb/btqwIjP58NA/6Bn4RRr4QKkFvXgfpkULck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOgxxb/btqwIjP58NA/6Bn4RRr4QKkFvXgfpkULck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOgxxb%2FbtqwIjP58NA%2F6Bn4RRr4QKkFvXgfpkULck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 그림에서 확인할 수 있듯이 getCacheResolver() 메소드가 null 이 아니다.&lt;/p&gt;
&lt;p&gt;따라서 beanFactory.getBan() 메소드가 실행되지 않게 되고&amp;nbsp;&lt;b&gt;NoUniqueBeanDefinitionException&amp;nbsp;&lt;/b&gt;이 발생하지 않게 됐던 것이다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;getCacheResolver 메소드의 내용은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1562930900223&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Nullable
public CacheResolver getCacheResolver() {
	return SupplierUtils.resolve(this.cacheResolver);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉&amp;nbsp;&lt;b&gt;&lt;span&gt;&lt;span&gt;CachingConfigurerSupport&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;를 상속받은 뒤에&amp;nbsp;&lt;/span&gt;&lt;/span&gt;cacheResolver가 더이상 null이 아닌 상태가 되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;cacheResolver가 어떻게 null이 아닌 상태가 될 수 있었는지 확인해 보니 다음과 같은 순서대로 진행되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;순서대로 글을 쓴 뒤에 이미지를 첨부하려고 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;ProxyCachingConfiguration 설정 클래스가 실행되는 과정중에 상위 클래스인&amp;nbsp;&lt;/span&gt;AbstractCachingConfiguration 에 있는 setConfigurers 메소드가 실행이 된다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;setConfigurers 메소드에서&amp;nbsp;&lt;/span&gt;useCachingConfigurer 메소드를 호출하는데, 이 때 파라미터로 받은 CachingConfigurer 객체 에서 cacheManager 를 가져와서 this.cacheManager 에 주입한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;ProxyCachingConfiguration 설정 클래스에 있는&amp;nbsp;&lt;/span&gt;BeanFactoryCacheOperationSourceAdvisor Bean이 등록되는 과정에서 CacheInterceptor Bean이 추가로 등록된다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;CacheInterceptor Bean이 등록되는 과정에서 cacheResolver가 등록이 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;cacheResolver가 등록이 되었기 때문에 getCacheResolver 메소드가 null이 아니므로 Bean을 찾지 않게 되고 에러가 나지 않게 되는 것이다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;글로 요약하면 감이 잘 오지 않기 때문에 소스 이미지를 추가하면서 확인해 보겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;1.ProxyCachingConfiguration 설정 클래스가 실행되는 과정중에 상위 클래스인&amp;nbsp;&lt;/span&gt;AbstractCachingConfiguration 에 있는 setConfigurers 메소드가 실행이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEIb1J/btqwIX6W3cX/2e8OpeoGmXs1jFX4WByxy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEIb1J/btqwIX6W3cX/2e8OpeoGmXs1jFX4WByxy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEIb1J/btqwIX6W3cX/2e8OpeoGmXs1jFX4WByxy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEIb1J%2FbtqwIX6W3cX%2F2e8OpeoGmXs1jFX4WByxy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LlZDC/btqwJdBG30B/IRnPQTGkwRM3HbvQ9YFy11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LlZDC/btqwJdBG30B/IRnPQTGkwRM3HbvQ9YFy11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LlZDC/btqwJdBG30B/IRnPQTGkwRM3HbvQ9YFy11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLlZDC%2FbtqwJdBG30B%2FIRnPQTGkwRM3HbvQ9YFy11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span&gt;2. setConfigurers 메소드에서&amp;nbsp;&lt;/span&gt;useCachingConfigurer 메소드를 호출하는데, 이 때 파라미터로 받은 CachingConfigurer 객체 에서 cacheManager 를 가져와서 this.cacheManager 에 주입한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sWqRk/btqwLCUMAHw/GBvvmYwpzQx3K1uEsT6sUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sWqRk/btqwLCUMAHw/GBvvmYwpzQx3K1uEsT6sUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sWqRk/btqwLCUMAHw/GBvvmYwpzQx3K1uEsT6sUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsWqRk%2FbtqwLCUMAHw%2FGBvvmYwpzQx3K1uEsT6sUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span&gt;3.ProxyCachingConfiguration 설정 클래스에 있는&amp;nbsp;&lt;/span&gt;BeanFactoryCacheOperationSourceAdvisor Bean이 등록되는 과정에서 CacheInterceptor Bean이 추가로 등록된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cphP3S/btqwILyO7kJ/55EbhO1V3iO5APPculBos0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cphP3S/btqwILyO7kJ/55EbhO1V3iO5APPculBos0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cphP3S/btqwILyO7kJ/55EbhO1V3iO5APPculBos0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcphP3S%2FbtqwILyO7kJ%2F55EbhO1V3iO5APPculBos0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4.CacheInterceptor Bean이 등록되는 과정에서 cacheResolver가 등록이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zpfbL/btqwLefx31Y/k2BSukc6nrZL4WtoJ5AKF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zpfbL/btqwLefx31Y/k2BSukc6nrZL4WtoJ5AKF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zpfbL/btqwLefx31Y/k2BSukc6nrZL4WtoJ5AKF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzpfbL%2FbtqwLefx31Y%2Fk2BSukc6nrZL4WtoJ5AKF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OmTGA/btqwLTWhxsI/qdO4kgVuaz48aVNJrxu9K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OmTGA/btqwLTWhxsI/qdO4kgVuaz48aVNJrxu9K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OmTGA/btqwLTWhxsI/qdO4kgVuaz48aVNJrxu9K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOmTGA%2FbtqwLTWhxsI%2FqdO4kgVuaz48aVNJrxu9K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span&gt;5.cacheResolver가 등록이 되었기 때문에 getCacheResolver 메소드가 null이 아니므로 Bean을 찾지 않게 되고 에러가 나지 않게 되는 것이다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOgxxb/btqwIjP58NA/6Bn4RRr4QKkFvXgfpkULck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOgxxb/btqwIjP58NA/6Bn4RRr4QKkFvXgfpkULck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOgxxb/btqwIjP58NA/6Bn4RRr4QKkFvXgfpkULck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOgxxb%2FbtqwIjP58NA%2F6Bn4RRr4QKkFvXgfpkULck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로,&amp;nbsp;&lt;b&gt;&lt;span&gt;&lt;span&gt;CachingConfigurerSupport&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;클래스를 Config 클래스에서 상속할 때에만&amp;nbsp;&lt;b&gt;&lt;span&gt;AbstractCachingConfiguration&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;클래스의&amp;nbsp;&lt;b&gt;&lt;span&gt;setConfigurers&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;메소드가 실행되는 이유를 설명하고 끝내겠다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;스프링이 Bean을 등록하는 과정에서 @Autowired annotation이 있는 메소드가 있으면 실행하도록 되어 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;하지만 이 때 한가지 조건이 있는데 해당 메소드로 넘겨질 parameter가 있을 경우에만 실행이 된다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;아래 소스를 보자. ( 너무 길어서 캡쳐를 따로 했다. 이어서 보면 된다.)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh5G24/btqwLes5imq/f8B9k29UCWrShCe2u7QaYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh5G24/btqwLes5imq/f8B9k29UCWrShCe2u7QaYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh5G24/btqwLes5imq/f8B9k29UCWrShCe2u7QaYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh5G24%2FbtqwLes5imq%2Ff8B9k29UCWrShCe2u7QaYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9O9L1/btqwKRLDYoZ/ILy3rW4fD54VjumtdAPkB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9O9L1/btqwKRLDYoZ/ILy3rW4fD54VjumtdAPkB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9O9L1/btqwKRLDYoZ/ILy3rW4fD54VjumtdAPkB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9O9L1%2FbtqwKRLDYoZ%2FILy3rW4fD54VjumtdAPkB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;노란색 박스를 보면 arguments가 null 이 아니면 beanName으로 빈을 생성한다.&lt;/p&gt;
&lt;p&gt;빨간색 박스를 보면 arguments가 null 이 아니면 method 를 실행한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;method.invoke(bean, arguments); 가 실행이 되면&amp;nbsp;&lt;b&gt;&lt;span&gt;AbstractCachingConfiguration&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;클래스의&amp;nbsp;&lt;b&gt;&lt;span&gt;setConfigurers&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;메소드가 실행되는 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;그런데 arguments에는 무엇이 들어 있을까??&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUsvmT/btqwIYrjLwX/PqJdjGIJHkFaCHIM7N0c60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUsvmT/btqwIYrjLwX/PqJdjGIJHkFaCHIM7N0c60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUsvmT/btqwIYrjLwX/PqJdjGIJHkFaCHIM7N0c60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUsvmT%2FbtqwIYrjLwX%2FPqJdjGIJHkFaCHIM7N0c60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;beanFactory.resolveDependency 메소드를 실행 시켜서 Object 객체를 가져오게 되는데&lt;/p&gt;
&lt;p&gt;&lt;span&gt;org.springframework.cache.annotation.&lt;span&gt;CachingConfigurer 객체를 가져오려고 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;우리가 등록한 설정 클래스인&amp;nbsp;&lt;b&gt;&lt;span&gt;DefaultCacheConfig&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;클래스에서&amp;nbsp;&lt;b&gt;&lt;span&gt;CachingConfigurerSupport&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;클래스를 상속 받았고 이 클래스는&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;org.springframework.cache.annotation.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;CachingConfigurer 인터페이스의 구현체이기 때문에 위에 있는 이미지에서&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;Object &lt;/span&gt;&lt;span&gt;arg &lt;/span&gt;= &lt;span&gt;beanFactory&lt;/span&gt;.&lt;span&gt;resolveDependency&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;currDesc&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;beanName&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;autowiredBeans&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;typeConverter&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;를 실행한 결과 arg가 null 이 아니게 되며 arguments 역시 null아 아니게 되므로&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&lt;span&gt;method&lt;/span&gt;.&lt;span&gt;invoke&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bean&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;arguments&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;; 메소드가 실행이 되었던 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&lt;span&gt;이상으로 기록을 마친다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발/[Spring]기록</category>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/38</guid>
      <comments>https://authentication.tistory.com/38#entry38comment</comments>
      <pubDate>Fri, 12 Jul 2019 12:05:02 +0900</pubDate>
    </item>
    <item>
      <title>[SSO] CAS 클라이언트 (5) - 샘플 프로젝트 : 로그아웃</title>
      <link>https://authentication.tistory.com/36</link>
      <description>&lt;p&gt;이전 글까지 Client 에 CAS를 이용한 Single Sign On 설정을 하고 로그인 테스트까지 해봤다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 다음 내용에 대해 알아보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Client에 로그아웃을 설정한다. (클라이언트 로그아웃)&lt;/li&gt;
&lt;li&gt;Single Sign Out(단일 로그 아웃)을 설정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;먼저 Client에 로그아웃을 설정하는 것은 매우 간단하다.&lt;/p&gt;
&lt;p&gt;Spring Security 설정 부분에 logout을 추가하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XkmxZ/btqvYq9p4Pa/lVc7iKxGExt5tTYtc09cFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XkmxZ/btqvYq9p4Pa/lVc7iKxGExt5tTYtc09cFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XkmxZ/btqvYq9p4Pa/lVc7iKxGExt5tTYtc09cFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXkmxZ%2FbtqvYq9p4Pa%2FlVc7iKxGExt5tTYtc09cFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;background-color: #f41a18; color: #ffffff;&quot;&gt;빨간색 박스&lt;/span&gt; 안에 있는 내용중에 '&lt;b&gt;.logout()&lt;/b&gt;'을 추가하면 LogoutFilter 가 등록되고 URL Path에 '/logout/' 으로 입력하면 로그아웃 Filter가 실행 된다. 그리고&amp;nbsp; '.&lt;b&gt;logoutSuccessUrl(&quot;/secured&quot;)&lt;/b&gt; 를 추가하면 로그아웃이 성공하면 '/secured' 로 이동 하라는 설정이다. ( .logoutSuccessUrl을 설정하지 않으면 Default로 '/login?logout' 으로 이동한다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;background-color: #ffe999;&quot;&gt;노란색 박스&lt;/span&gt; 안에 있는 내용은 csrf 를 비활성화 시키는 것인데, 스프링 5 이후에는 csrf가 필수로 적용되어야 하는데 지금은 테스트를 하기 위한 작업이라서 비활성화 시켰다. (비활성화 해야 GET(HttpMethod)를 처리 한다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이전 글까지 했던 설정에 문제가 없었다면 다음 이미지와 같이 'localhost:9000/secured' 페이지를 확인 할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qcxaa/btqvWTEFrhA/HubIcKKOdQ1JnW4hfTG3l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qcxaa/btqvWTEFrhA/HubIcKKOdQ1JnW4hfTG3l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qcxaa/btqvWTEFrhA/HubIcKKOdQ1JnW4hfTG3l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqcxaa%2FbtqvWTEFrhA%2FHubIcKKOdQ1JnW4hfTG3l0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이제 path를 '/logout' 으로 변경하고 요청을 보내보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N4VRi/btqv0dOMjfN/bkcx3PvNG0EaSks7EtRhfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N4VRi/btqv0dOMjfN/bkcx3PvNG0EaSks7EtRhfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N4VRi/btqv0dOMjfN/bkcx3PvNG0EaSks7EtRhfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN4VRi%2Fbtqv0dOMjfN%2Fbkcx3PvNG0EaSks7EtRhfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4&gt;&lt;b&gt;어떻게 될까??&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qcxaa/btqvWTEFrhA/HubIcKKOdQ1JnW4hfTG3l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qcxaa/btqvWTEFrhA/HubIcKKOdQ1JnW4hfTG3l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qcxaa/btqvWTEFrhA/HubIcKKOdQ1JnW4hfTG3l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqcxaa%2FbtqvWTEFrhA%2FHubIcKKOdQ1JnW4hfTG3l0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;b&gt;Logout을 했는데 '/secured' 페이지에 접근이 가능하다!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;b&gt;이유는??&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Client에 &lt;b&gt;CAS SSO 를 설정하지 않고&lt;/b&gt; Security 설정에 formLogin() 을 사용 했다면 로그인 화면으로 이동 했을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SSO를 적용하지 않은 경우에는 이렇게 동작하는 것이 정상적인 동작이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 현재 작업중인 Client 프로젝트에는 &lt;b&gt;SSO 설정&lt;/b&gt;을 했기 때문에 로그아웃을 한 뒤에 '/secured' 에 접근해도 로그인 화면으로 이동하지 않는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;b&gt;간단하게 설명하면&lt;/b&gt;(자세한 설명은 별도로 작성할 예정이다.)&lt;/span&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ff9a85;&quot;&gt;Client에서는 로그아웃을 했지만&amp;nbsp;&lt;b&gt;CAS 인증서버&lt;/b&gt; 에서는 로그아웃 하지 않았기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;Client에서 로그아웃을 실행한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;Logout Filter에서 로그아웃을 진행하고 세션을 만료시킨다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;Logout이 성공하고 logoutSuccessUrl 로 설정한 '/secured' 페이지로 이동한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;'/secured' 페이지는 인증이 필요하므로 &lt;b&gt;CAS 인증 서버로 redirect&lt;/b&gt; 한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;CAS 인증 서버에는 아직 Client의 로그인 정보가 유효하므로 로그인 요청을 하지 않고 인증성공 응답을 한다.&lt;/li&gt;
&lt;li&gt;Client에서는 CAS 인증 서버에서 인증성공 응답이 왔으므로 인증성공 처리를 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위와 같은 이유로 Client 에서 로그아웃 처리를 했어도 인증이 필요한 페이지에 접근이 가능한 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 어떻게 해야 할까?? 간단하다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;&lt;b&gt;CAS 인증 서버에서 로그아웃을 하면 된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;CAS 인증 서버에 로그아웃 요청을 보내기 위해서는 Client 프로젝트에 다음과 같이 설정이 필요하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 CAS 인증서버 Logout을 하기 위해 필요한 Bean을 추가하는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;CAS Configuration 클래스에 다음과 같이 Bean을 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1560325705540&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class CasClientConfiguration {
	//기타 Bean...
    //@Bean
    //CasAuthenticationProvider 등등...
    @Bean
    public LogoutFilter casLogoutFilter() {
        LogoutFilter logoutFilter = new LogoutFilter( &quot;https://localhost:8443/cas/logout&quot;,securityContextLogoutHandler());
        logoutFilter.setFilterProcessesUrl(&quot;/logout/cas&quot;);
        return logoutFilter;
    }
    
    @Bean
    public SecurityContextLogoutHandler securityContextLogoutHandler() {
        return new SecurityContextLogoutHandler();
    }

    @Bean
    public SingleSignOutFilter singleSignOutFilter() {
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        singleSignOutFilter.setCasServerUrlPrefix(&quot;https://localhost:8443/cas&quot;);
        singleSignOutFilter.setIgnoreInitConfiguration(true);
        return singleSignOutFilter;
    }    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;각각 설명을 하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&lt;b&gt;casLogoutFilter&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;cas Server 로그아웃을 하기 위한 filter 이다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;기본 LogoutFilter 와는 별개로 존재 하고 FilterChain 순서 상 기본 LogoutFilter 앞에 위치한다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;/logout/cas URL 을 intercept 해서 Cas Server 로 redirection 한 뒤 전체 Logout 을 실행한다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;서브는 관련된 모든 서비스에 로그아웃 요청을 전송하고 서비스에서는 SingleSignOutFilter 에서 HTTP 세션을 무효하 시키면서&amp;nbsp;&lt;/span&gt;&lt;span&gt;로그아웃이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;securityContextLogoutHandler&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;logout Filter 가 실행(doFilter) 될 때 해당 Handler 가 사용 된다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;자세한 동작은 클래스 내부의 logout Method 를 확인 해보면 알겠지만&amp;nbsp;&lt;/span&gt;&lt;span&gt;session 을 만료 시키고, SecurityContextHolder 의 SecurityContext 를 가져온 뒤&amp;nbsp;&lt;/span&gt;&lt;span&gt;Authentication 객체를 null 로 만든다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;singleSignOutFilter&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;cas Server 에서 로그아웃이 완료되면 같은 ID로 로그인 되어 있는 모든 Client 서비스에 로그아웃 요청을 보낸다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;요청 URL 은 Cas Server 에 등록되어 있는 ServiceId 를 사용한다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;Cas Server 에서 Client 의 로그아웃 요청을 보낼 때, logoutRequest 라는 parameter 를 함께 보내는데,&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;SingSignOutFilter 에서는 logoutRequest parameter 가 존재할 경우에 서비스 로그아웃 처리를 하게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;cas Server 에서 로그인이 완료되면(또는 이미 로그인 되어 있으면) 로그인 요청을 했던 Client 서비스에 redirect 한다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;이때 ticket 이라는 parameter 를 함께 전달하는데, SingleSignOutHandler 에 ticket 을 저장한다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;저장 방식은 현재 ID_TO_SESSION_KEY_MAPPING(Map)에 &lt;b&gt;Key&lt;/b&gt; : SessionId, &lt;b&gt;Value&lt;/b&gt; : ticket 으로 저장하고&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;MANAGED_SESSIONS(Map)에 &lt;b&gt;key&lt;/b&gt; : ticket, &lt;b&gt;Value&lt;/b&gt; : Session 으로 저장 한다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;이렇게 저장하는 이유는 Cas Server 에서 각각의 Client 서비스에 로그아웃 요청을 보낼 때 사용하기 위해서다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;Cas Server 로 부터 로그아웃 요청을 받은 Client 는 함께 받은 xml 형식의 parameter 값에서 ticket 값을 얻는다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;ticket 값으로 MANAGED_SESSIONS 에서 Session 을 가져온 뒤 해당 session 을 만료 시킨다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;이렇게 되면 Cas Server 를 통해 인증 완료한 뒤 저장되어 있던 session 이 만료 되기 때문에 인증이 만료된다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;그리고 Security 설정에 다음과 같이 추가를 한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBdOMK/btqv3sSquSi/r0I4nKu59OmuSiiekhPPr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBdOMK/btqv3sSquSi/r0I4nKu59OmuSiiekhPPr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBdOMK/btqv3sSquSi/r0I4nKu59OmuSiiekhPPr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBdOMK%2Fbtqv3sSquSi%2Fr0I4nKu59OmuSiiekhPPr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;Bean으로 추가 했던 SingleSignOutFilter와 casLogoutFilter를 Filter에 추가 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;background-color: #ffe999;&quot;&gt;그리고 logoutSuccessUrl 을 '/logout/cas' 로 수정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;'/logout/cas'는&amp;nbsp;&lt;b&gt;casLogoutFilter&lt;/b&gt; 의 filterProcessesUrl 이다. 즉 '/logout/cas' 로 요청이 왔을 때 Filter가 동작하는 것이다.&lt;/p&gt;
&lt;p&gt;client에서 로그아웃 처리를 하고나서 바로 casLogoutFilter 가 동작하는 것이고 casLogoutFilter는 CAS 인증서버에 로그아웃 요청을 보내는 것이므로&amp;nbsp;&lt;b&gt;진짜 로그아웃 처리가 될 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;client를 다시 실행시켜서 '/secured' 로 이동한 뒤 logout 요청을 보내보자.&lt;/p&gt;
&lt;p&gt;그럼 다음과 같은 화면이 나올 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbMc4l/btqvYrOXCTn/4zqoK2ss8OruIwaL9OXnH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbMc4l/btqvYrOXCTn/4zqoK2ss8OruIwaL9OXnH1/img.png&quot; data-alt=&quot;;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbMc4l/btqvYrOXCTn/4zqoK2ss8OruIwaL9OXnH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbMc4l%2FbtqvYrOXCTn%2F4zqoK2ss8OruIwaL9OXnH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;CAS 인증서버로 redirect 된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;그리고 다시 Client의 '/secured' 로 이동해보면 다음과 같은 화면이 나올 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lWn3x/btqv0e80hqm/fAqvHfpFUFWFSydxYZoCB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lWn3x/btqv0e80hqm/fAqvHfpFUFWFSydxYZoCB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lWn3x/btqv0e80hqm/fAqvHfpFUFWFSydxYZoCB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlWn3x%2Fbtqv0e80hqm%2FfAqvHfpFUFWFSydxYZoCB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;CAS 인증서버의 로그인 화면으로 이동한 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&lt;b&gt;그렇다. Client 로그아웃이 잘 되었다!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번에는 단일로그아웃에&lt;span&gt;&amp;nbsp;&lt;/span&gt;대해서 알아보자.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot;&gt;&lt;b&gt;단일로그아웃&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이란&lt;/p&gt;
&lt;p&gt;여러개의 서비스가 하나의 인증서버를 통해 인증처리를 했을 경우, 하나의 서비스에서 로그아웃 했을 경우에 다른 서비스들 역시 로그아웃 처리가 되도록 하는 것을 말한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;CAS 인증서버 프로젝트에 json 파일로 Client를 등록했던 것을 기억할 것이다.&lt;/p&gt;
&lt;p&gt;해당 파일에 logoutUrl 을 추가하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qm2pI/btqv2IuOX9T/kkt67uEOm9Zv5KVUcYtrw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qm2pI/btqv2IuOX9T/kkt67uEOm9Zv5KVUcYtrw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qm2pI/btqv2IuOX9T/kkt67uEOm9Zv5KVUcYtrw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqm2pI%2Fbtqv2IuOX9T%2Fkkt67uEOm9Zv5KVUcYtrw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;사실 logoutUrl 을 지정하지 않으면 serviceId 에서 host 부분을 가져와서 사용하는데,&amp;nbsp;&lt;b&gt;localhost&lt;/b&gt;로 되어 있는 경우에는 logoutUrl을 추출하지 못한다.&lt;/p&gt;
&lt;p&gt;따라서 현재 테스트에서 처럼 &lt;b&gt;logoutUrl 을 별도로 추가하는 방법&lt;/b&gt;과&lt;/p&gt;
&lt;p&gt;정상적인 &lt;b&gt;domain으로 되어 있는 경우 logoutUrl이 자동으로 등록되는 방법&lt;/b&gt;이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Client 프로젝트 설정은 이미 되어 있고 테스트만 해보면 된다.&lt;/p&gt;
&lt;p&gt;시나리오는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot;&gt;
&lt;li&gt;두 개의 Client 서비스를 실행한다.&lt;/li&gt;
&lt;li&gt;하나의 서비스에서 로그인을 시도한다.&lt;/li&gt;
&lt;li&gt;다른 서비스에서 인증이 필요한 페이지에 요청을 보내고 로그인이 잘 되었는지 확인한다.&lt;/li&gt;
&lt;li&gt;하나의 Client 서비스에서 로그아웃을 시도한다.&lt;/li&gt;
&lt;li&gt;다른 서비스에서 로그아웃이 되었는지 확인한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;위 순서대로 테스트 하면 하나의 Client 서비스에서 로그아웃을 했을 때, 다른 서비스에서도 로그아웃이 되는 것을 확인 할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;샘플 프로젝트에서 설정한 것처럼 logoutSuccessUrl에 CAS 인증서버의 로그아웃 URL을 보내는 방법 보다는 logoutSuccessUrl에 로그아웃 성공 시 보여지는 화면을 하나 만들고 그 화면에서 CAS 인증서버 로그아웃 요청('/logout/cas')을 처리하는 것이 좋다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;이유는 단일로그아웃에 대한 자세한 설명글에서 다루겠다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>개발/[Spring] 인증</category>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/36</guid>
      <comments>https://authentication.tistory.com/36#entry36comment</comments>
      <pubDate>Thu, 13 Jun 2019 10:56:32 +0900</pubDate>
    </item>
    <item>
      <title>[SSO] CAS 클라이언트 (4) - 샘플 프로젝트 : 두 개의 Client 로그인 테스트</title>
      <link>https://authentication.tistory.com/35</link>
      <description>&lt;p&gt;이전 글에서는 Client에서 Cas 인증 서버로 Redirect 한 뒤 인증처리가 되는 것을 확인했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하나의 Client가 잘 동작하는 것은 확인 했다.&lt;/p&gt;
&lt;p&gt;이제 하나의 Client에서 인증이 완료 됐을 때, 다른 Client에서도 인증이 되는지 확인해 볼 필요가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;프로젝트를 하나 더 만들어도 되지만 어차피 거의 똑같은 구조이기에 PORT 만 변경해서 Client 가 실행될 수 있도록 &lt;br /&gt;Cas 인증 서버 프로젝트와 Client 프로젝트를 수정해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;Cas 인증서버 프로젝트&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;Cas 인증서버에서는 JSON 파일 하나만 추가하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TU1xH/btqvUyGsRBP/hNWJaTecRzWhELnvMS7YPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TU1xH/btqvUyGsRBP/hNWJaTecRzWhELnvMS7YPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TU1xH/btqvUyGsRBP/hNWJaTecRzWhELnvMS7YPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTU1xH%2FbtqvUyGsRBP%2FhNWJaTecRzWhELnvMS7YPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;그리고 실행 시켜보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F3Xte/btqvTG57JL4/MhRcXgrXgHjIKXKvPkVXyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F3Xte/btqvTG57JL4/MhRcXgrXgHjIKXKvPkVXyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F3Xte/btqvTG57JL4/MhRcXgrXgHjIKXKvPkVXyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF3Xte%2FbtqvTG57JL4%2FMhRcXgrXgHjIKXKvPkVXyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;새로 추가한 Client도 등록이 된 것을 확인할 수 있다.&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;Client 프로젝트&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;application.properties&lt;/b&gt; 파일에 server.port를 다음과 같이 수정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1559910993053&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server.port=${PORT:9000}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;환경 변수로 PORT를 사용하고 입력하지 않았을 경우에 '9000' 을 사용하는 설정이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;CasClientConfiguration 파일에 다음과 같이 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1559911239490&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Value(&quot;${server.port}&quot;)
    String port;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고&amp;nbsp;&lt;span&gt;&lt;b&gt;ServiceProperties&lt;/b&gt;,&amp;nbsp;&lt;b&gt;&lt;span&gt;CasAuthenticationProvider&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;Bean을 등록할 때 하드코딩 해놨던 포트 번호를 @Value로 추가한 port 변수로 변경한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1559911394730&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        serviceProperties.setService(&quot;http://localhost:&quot;+port+&quot;/login/cas&quot;);
        serviceProperties.setSendRenew(false);
        return serviceProperties;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1559911491814&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(ticketValidator());
        provider.setUserDetailsService(
                s -&amp;gt; new User(&quot;kennen&quot;, &quot;Mellon&quot;, true, true, true, true,
                        AuthorityUtils.createAuthorityList(&quot;ROLE_ADMIN&quot;)));
        provider.setKey(&quot;CAS_PROVIDER_LOCALHOST_&quot;+port);
        return provider;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;color: #f41a18;&quot;&gt;설명에서는 하드코딩으로 설정해놨지만 실제 소스는 Properties를 사용한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 Terminal 에서 실행시켜보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;포트 9000&lt;/p&gt;
&lt;pre id=&quot;code_1559912344218&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ mvn spring-boot:run -DPORT=9000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;포트 9100&lt;/p&gt;
&lt;pre id=&quot;code_1559912351929&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ mvn spring-boot:run -DPORT=9100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JzCjC/btqvWgkyDnD/hi37wg2pUI3kmvGA45KNh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JzCjC/btqvWgkyDnD/hi37wg2pUI3kmvGA45KNh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JzCjC/btqvWgkyDnD/hi37wg2pUI3kmvGA45KNh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJzCjC%2FbtqvWgkyDnD%2Fhi37wg2pUI3kmvGA45KNh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;포트가 다른 Client 두개가 잘 실행됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8NUck/btqvWrMUMQc/xdk70E16ahAiIkGt0U7Fik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8NUck/btqvWrMUMQc/xdk70E16ahAiIkGt0U7Fik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8NUck/btqvWrMUMQc/xdk70E16ahAiIkGt0U7Fik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8NUck%2FbtqvWrMUMQc%2Fxdk70E16ahAiIkGt0U7Fik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;9100 포트로 실행된 클라이언트에서 로그인을 시도해보자.&lt;/p&gt;
&lt;p&gt;로그인이 잘 성공되면, 9000번 포트로 실행한 Client의 URL Path를 /secured 로 변경해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxUNOz/btqvTV9JXLj/cdCCx6T6JCaVTsppH92rS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxUNOz/btqvTV9JXLj/cdCCx6T6JCaVTsppH92rS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxUNOz/btqvTV9JXLj/cdCCx6T6JCaVTsppH92rS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxUNOz%2FbtqvTV9JXLj%2FcdCCx6T6JCaVTsppH92rS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 이미지 처럼 9000번 포트의 Client에서는 로그인을 하지 않았지만 /secured 에 대한 응답을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 글에서는 Client의 로그아웃을 설정하고 Single Sign On의 로그아웃 버전인 Single Sign Out을 설정해보자.&lt;/p&gt;</description>
      <category>개발/[Spring] 인증</category>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/35</guid>
      <comments>https://authentication.tistory.com/35#entry35comment</comments>
      <pubDate>Thu, 13 Jun 2019 10:56:20 +0900</pubDate>
    </item>
    <item>
      <title>[SSO] CAS 클라이언트 (3) - 샘플 프로젝트 : CAS 설정 하기</title>
      <link>https://authentication.tistory.com/34</link>
      <description>&lt;p&gt;이전 글에서 CAS 인증 서버에 클라이언트를 등록했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 클라이언트의 인증처리를 CAS 인증 서버에서 동작하도록 클라이언트 프로젝트에 작업해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저, Spring Security의 CAS 모듈과 관련된 Bean을 설정한다. Spring Security와 Cas 인증 서버와 함께 동작하기 위함이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음과 같이 설정 클래스를 생성하고 Bean을 추가해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1559899626826&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class CasClientConfiguration {

    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        serviceProperties.setService(&quot;http://localhost:9000/login/cas&quot;);
        serviceProperties.setSendRenew(false);
        return serviceProperties;
    }

    @Bean
    @Primary
    public AuthenticationEntryPoint authenticationEntryPoint(ServiceProperties sP) {
        CasAuthenticationEntryPoint entryPoint
                = new CasAuthenticationEntryPoint();
        entryPoint.setLoginUrl(&quot;https://localhost:8443/cas/login&quot;);
        entryPoint.setServiceProperties(sP);
        return entryPoint;
    }

    @Bean
    public TicketValidator ticketValidator() {
        return new Cas30ServiceTicketValidator(
                &quot;https://localhost:8443/cas&quot;);
    }

    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(ticketValidator());
        provider.setUserDetailsService(
                s -&amp;gt; new User(&quot;kennen&quot;, &quot;Mellon&quot;, true, true, true, true,
                        AuthorityUtils.createAuthorityList(&quot;ROLE_ADMIN&quot;)));
        provider.setKey(&quot;CAS_PROVIDER_LOCALHOST_9000&quot;);
        return provider;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에 추가한 Bean을 하나씩 설명하면&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;ServiceProperties&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span&gt;[현재 서비스에 대한 정보]&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;ExceptionTranslationFilter 의 doFilter 동작 중에 AccessDeniedException 이 발생하면&amp;nbsp;&lt;/span&gt;&lt;span&gt;ExceptionTranslationFilter 에 있는 AuthenticationEntryPoint(CasAuthenticationEntryPoint)의&amp;nbsp;&lt;span&gt;commence 메소드가 실행 된다.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;이때 Cas 인증 서버로 이동해 로그인을 시도하게 되는데 Service 정보를 함께 전달 한다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;인증 서버에서 인증 시도 후에 다시 서비스로 Redirect 할 때 service 의 URL 로 사용하기 위해서 이다.&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;authenticationEntryPoint&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span&gt;Spring Security 에서 httpBasic 설정을 추가하게 되면 Default 로 BasicAuthenticationFilter 가 추가된다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;httpBasic().authenticationEntryPoint 를 이용해 AuthenticationEntryPoint 를 지정할 수 있는데,&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;CasAuthenticationEntryPoint 를 등록하기 위해 해당 Bean 을 추가한다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;인증이 필요한 페이지에 접근 시 AccessDeniedException 이 발생하면 CasAuthenticationEntryPoint 의 commence 가 실행된다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;Cas Server 의 로그인 페이지로 redirect 된다. (ServiceProperties Bean 참고)&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;&lt;span&gt;casAuthenticationProvider&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span&gt;CasAuthenticationFilter 가 동작할 때 실제로 인증을 처리하는 주체이다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;Filter 가 동작하는 중에 인증을 진행하게 된다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;Bean 으로 등록한 ServiceProperties, ticketValidator, UserDetailsService 가 이 때 사용되며&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;Key 는 CasAuthenticationToken 을 만들 때 인자로 쓰인다.&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;&lt;b&gt;&lt;span&gt;ticketValidator&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;font-size: 1.12em;&quot;&gt;&lt;span&gt;[티켓 유효성 체크]&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;실행 되기 전 순서&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 1. ExceptionTranslationFilter 진행 중에 AccessDeniedException 발생&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 2. CasAuthenticationEntryPoint.commence 실행&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 3. Cas 서버에서 인증 성공 후에 - CasAuthenticationFilter.doFilter 실행&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 4. CasAuthenticationFilter.attemptAuthentication 실행&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 5. AuthenticationManager.authenticate 실행&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 6. ProviderManager.authenticate 실행&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 7. AuthenticationProvider.authenticate 실행&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 8. CasAuthenticationProvider.authenticate 실행&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 9. 8번 실행 중에 authenticateNow 실행&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 10. authenticateNow 메소드 안에서 ticketValidator 사용&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;설명&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; Cas 서버에서 인증 성공을 하게 되면 CasAuthenticationFilter 로 들어오게 된다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 이 때 redirect 된 서비스가 인증요청을 보낸 서비스가 맞는지 확인하기 위해서 한번 더 체크를 한다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; redirect 될 때 받은 ticket 과 현재 서비스의 이름을 다시 Cas 서버로 보내고&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 일치 하면 XML 형식으로 응답을 받게 된다.&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt; 이후에는 해당 xml 응답을 parsing 해서 UserDetails 로 변환한 뒤 CasAuthenticationToken 을 만들게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;이제 특정 경로를 인증이 필요하도록 설정하고, 이 때 &lt;b&gt;CasAuthenticationEntryPoint&lt;/b&gt;가 사용 되도록 Spring Security를 설정해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;WebSecurityConfigurerAdapter를 상속받는 Configuration을 만든다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;또한 CasAuthenticationFilter를 Bean으로 추가하고,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; CasAuthenticationFilter에서 사용 할 AuthenticationManager 역시 Bean으로 추가한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1559905661524&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    ServiceProperties serviceProperties;
    @Autowired
    private AuthenticationProvider authenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().regexMatchers(&quot;/secured.*&quot;, &quot;/login&quot;).authenticated()
                .and().authorizeRequests().antMatchers(&quot;/favicon.ico&quot;, &quot;/static/**&quot;).permitAll()
                .and().authorizeRequests().antMatchers(&quot;/admin/**&quot;).hasAuthority(&quot;ADMIN&quot;)
                .and().authorizeRequests().antMatchers(&quot;/user/**&quot;).hasAuthority(&quot;USER&quot;)
                .and().authorizeRequests().regexMatchers(&quot;/&quot;).permitAll()
                .and().httpBasic().authenticationEntryPoint(authenticationEntryPoint);
    }
   
    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
        CasAuthenticationFilter filter = new CasAuthenticationFilter();
        filter.setServiceProperties(serviceProperties); // Bean 위치 : CasConfig.java
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(Arrays.asList(authenticationProvider));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;/secured, /login 에 대한 요청은 인증이 필요하도록 설정하고 '/' 에 대한 요청은 public 페이지로 설정했다.&lt;/p&gt;
&lt;p&gt;Controller를 생성하고 테스트 해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;HomeController&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1559906825741&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
public class HomeController {

    @GetMapping(&quot;/&quot;)
    public String home(){
        return &quot;home&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;SecuredController&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1559906863445&quot; class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
public class SecuredController {

    @GetMapping(&quot;/secured&quot;)
    public Authentication secured() {
        Authentication auth = SecurityContextHolder.getContext()
                .getAuthentication();
        return auth;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 HomeController에 요청을 보내보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEXyLr/btqvTV9GhxV/cTWeki76aZFFAePIKqbR9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEXyLr/btqvTV9GhxV/cTWeki76aZFFAePIKqbR9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEXyLr/btqvTV9GhxV/cTWeki76aZFFAePIKqbR9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEXyLr%2FbtqvTV9GhxV%2FcTWeki76aZFFAePIKqbR9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;그리고 /secured 또는 /login 요청을 보내보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFxJsU/btqvTWgqkPW/dgDEgDavdkcXzP2WPK44qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFxJsU/btqvTWgqkPW/dgDEgDavdkcXzP2WPK44qK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFxJsU/btqvTWgqkPW/dgDEgDavdkcXzP2WPK44qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFxJsU%2FbtqvTWgqkPW%2FdgDEgDavdkcXzP2WPK44qK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;URL을 확인해보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이전 요청과 포트 번호가 다른 것을 확인할 수 있다.&amp;nbsp;&lt;b&gt;인증서버로 Redirect 된 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;인증서버에서 인증 가능한 사용자 ID, Password를 입력하자.&lt;/p&gt;
&lt;p&gt;그리고 이 ID,Password는 위에서 설정한&amp;nbsp;CasAuthenticationProvider 에서 UserDetailsService 로 등록했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;로그인 하면 다음과 같은 결과를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qYnWG/btqvTfOnXGo/kj77KyhdcIDlXyk8SPOLjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qYnWG/btqvTfOnXGo/kj77KyhdcIDlXyk8SPOLjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qYnWG/btqvTfOnXGo/kj77KyhdcIDlXyk8SPOLjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqYnWG%2FbtqvTfOnXGo%2Fkj77KyhdcIDlXyk8SPOLjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;인증이 필요한 요청인 /secured URL을 확인 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또한 Controller에서 Return 했던 Authentication 내용을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;로그인은 성공했다.&lt;/p&gt;
&lt;p&gt;하지만 Single Sign On 은 다수의 Client Application이 한번의 로그인으로 인증처리 하는 것이 목적이기 때문에 Client Application을 하나 더 만들어서 테스트 해본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 글에서는 두 개의 Client가 한번의 로그인으로 인증처리가 되는지 확인해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;어떻게 인증이 진행 되는지에 대한 자세한 설명은 잠시 미루고 Single Sign On 그리고 Single Sign Out 이 잘 동작하는지를 먼저 확인해보자.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>개발/[Spring] 인증</category>
      <author>sheriff</author>
      <guid isPermaLink="true">https://authentication.tistory.com/34</guid>
      <comments>https://authentication.tistory.com/34#entry34comment</comments>
      <pubDate>Thu, 13 Jun 2019 10:56:11 +0900</pubDate>
    </item>
  </channel>
</rss>