<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>아이니그마</title>
    <link>https://cordingmonster.tistory.com/</link>
    <description>수수께기를 풀기위해 
구글을 항해하는 선원</description>
    <language>ko</language>
    <pubDate>Sat, 25 Jul 2026 04:22:45 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>디비드킴</managingEditor>
    <item>
      <title>Spring 3.x이상 UserDetailsService  여러개 사용하기</title>
      <link>https://cordingmonster.tistory.com/323</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;출처&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;a href=&quot;https://velog.io/@thisisnine99/%EC%8A%A4%ED%94%84%EB%A7%81%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EB%91%90%EA%B0%9C%EC%9D%98-%EC%97%94%ED%8B%B0%ED%8B%B0%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@thisisnine99/%EC%8A%A4%ED%94%84%EB%A7%81%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EB%91%90%EA%B0%9C%EC%9D%98-%EC%97%94%ED%8B%B0%ED%8B%B0%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715081863796&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;스프링시큐리티 두개의 엔티티로 로그인&quot; data-og-description=&quot;스프링시큐리티를 통한 폼로그인 여러개의 엔티티(데이터)로 로그인하는 방법 &amp;gt; ### 스프링 시큐리티를 통한 폼로그인 스프링시큐리티의 설정 클래스인 SecurityConfig 클래스를 위처럼 설정해두면 &quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@thisisnine99/%EC%8A%A4%ED%94%84%EB%A7%81%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EB%91%90%EA%B0%9C%EC%9D%98-%EC%97%94%ED%8B%B0%ED%8B%B0%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8&quot; data-og-url=&quot;https://velog.io/@thisisnine99/스프링시큐리티-두개의-엔티티로-로그인&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/h8Tv6/hyVZlAR0kI/h7W1puElhMSODOkzv9uwS0/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/sVxSr/hyV2zxtvH9/VYroClsfudxl6xTQ1m3ev1/img.png?width=420&amp;amp;height=420&amp;amp;face=0_0_420_420&quot;&gt;&lt;a href=&quot;https://velog.io/@thisisnine99/%EC%8A%A4%ED%94%84%EB%A7%81%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EB%91%90%EA%B0%9C%EC%9D%98-%EC%97%94%ED%8B%B0%ED%8B%B0%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@thisisnine99/%EC%8A%A4%ED%94%84%EB%A7%81%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EB%91%90%EA%B0%9C%EC%9D%98-%EC%97%94%ED%8B%B0%ED%8B%B0%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/h8Tv6/hyVZlAR0kI/h7W1puElhMSODOkzv9uwS0/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/sVxSr/hyV2zxtvH9/VYroClsfudxl6xTQ1m3ev1/img.png?width=420&amp;amp;height=420&amp;amp;face=0_0_420_420');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;스프링시큐리티 두개의 엔티티로 로그인&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스프링시큐리티를 통한 폼로그인 여러개의 엔티티(데이터)로 로그인하는 방법 &amp;gt; ### 스프링 시큐리티를 통한 폼로그인 스프링시큐리티의 설정 클래스인 SecurityConfig 클래스를 위처럼 설정해두면&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 깔금하고 이해하기 쉽게 글을써놓셨다&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;package com.example.demo2.Config;

import com.example.demo2.Admin.Repo.AdminRepo;
import com.example.demo2.Admin.Service.AdminPrincipalDetailsService;
import com.example.demo2.User.Repo.UserRepo;
import com.example.demo2.User.Service.UserPrincipalDetailsService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration//빈등록: 스프링 컨테이너에서 객체에서 관리
@EnableWebSecurity/////필터를 추가해준다
@RequiredArgsConstructor
public class SecurityConfig {

    private final AdminRepo adminRepo;
    private final UserRepo userRepo;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Order(1)
    public SecurityFilterChain adminConfigure(HttpSecurity http) throws Exception {
        http.csrf(csrf -&amp;gt; csrf.disable())
                .securityMatcher(new AntPathRequestMatcher(&quot;/admin/**&quot;))
                .authenticationProvider(adminDaoAuthenticationProvider())
                .authorizeHttpRequests(auth-&amp;gt;
                        auth.requestMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
                )
                .formLogin(login -&amp;gt;
                        login
                                .loginPage(&quot;/admin/login&quot;)
                                .loginProcessingUrl(&quot;/admin/login/pro&quot;)
                                .permitAll()
                                .passwordParameter(&quot;pwd&quot;)
                                .defaultSuccessUrl(&quot;/admin/index&quot;, true)
                                .failureUrl(&quot;/admin/login?error=true&quot;)
                )
                .logout(logout -&amp;gt;
                        logout
                                .logoutUrl(&quot;/admin/logout&quot;)
                                .permitAll()
                );
        return http.build();

    }

    @Bean
    @Order(2)
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {
        http.csrf(csrf -&amp;gt; csrf.disable())
                .authenticationProvider(userDaoAuthenticationProvider())
                .authorizeHttpRequests(auth-&amp;gt;
                        auth. requestMatchers(&quot;/auth/**&quot;).authenticated() // /auth/** URL에 대해 인증이 필요하도록 설정
                                .anyRequest().permitAll())
                .formLogin(login -&amp;gt;
                        login
                                .loginProcessingUrl(&quot;/login/pro&quot;)
                                .permitAll()
                                .passwordParameter(&quot;pwd&quot;)
                                .defaultSuccessUrl(&quot;/index&quot;, true)
                                .failureUrl(&quot;/login?error=true&quot;)
                )
                .logout(logout -&amp;gt;
                        logout
                                .logoutUrl(&quot;/logout&quot;)
                                .permitAll()
                );
        return http.build();

    }
    @Bean
    DaoAuthenticationProvider adminDaoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(new AdminPrincipalDetailsService(adminRepo));
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }
    @Bean
    DaoAuthenticationProvider userDaoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(new UserPrincipalDetailsService(userRepo));
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Springboot/Security</category>
      <category>spring boot 3.x</category>
      <category>UserDetailsService</category>
      <category>스프링부트</category>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/323</guid>
      <comments>https://cordingmonster.tistory.com/323#entry323comment</comments>
      <pubDate>Tue, 7 May 2024 20:38:48 +0900</pubDate>
    </item>
    <item>
      <title>Springboot jwt 로그인5(엑세스 토큰 재발급)</title>
      <link>https://cordingmonster.tistory.com/322</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1.재발급 로직 작성&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;public ResponseEntity&amp;lt;?&amp;gt; refreshAccessToken(HttpServletRequest request,HttpServletResponse response) {
    log.info(&quot;토큰재발급 로직 시작&quot;);
    String refreshToken = extractTokenFromCookie(request,&quot;refresh_token&quot;);
    Map&amp;lt;String, Object&amp;gt; refreshTokenInfo = new HashMap&amp;lt;&amp;gt;();
    try {
        refreshTokenInfo = getRefreshTokenInfo(refreshToken);
        if (refreshTokenInfo == null) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(&quot;리프레시 토큰을 찾을 수 없거나 만료되었습니다. 다시 로그인하세요.&quot;);
        }
    }catch (Exception e){
        e.printStackTrace();
        log.error(&quot;refresh 토큰 추출중 에러발생&quot;);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(&quot;리프레시 토큰을 찾을 수 없거나 만료되었습니다. 다시 로그인하세요.&quot;);
    }


    // 리프레시 토큰 정보에서 사용자 ID 추출
    String username = (String) refreshTokenInfo.get(&quot;id&quot;);

    // 새로운 엑세스 토큰 생성
    String newAccessToken = generateToken(username, TokenAbout.jwtExpirationInMs);
    // 리프레시 토큰 정보 업데이트
    saveRefreshToken(username, refreshToken, LocalDateTime.now(), Long.parseLong(refreshTokenInfo.get(&quot;issueCount&quot;).toString())+1);
    // 레디스에 저장된 리프레시 토큰 정보 확인
    logRefreshInfo(refreshToken,username);
    // 새로운 엑세스 토큰 쿠키 생성
    setAccessTokenAtCookie(newAccessToken,response);
    return ResponseEntity.ok().body(null);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 부가코드 작성&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;public String extractTokenFromCookie(HttpServletRequest request,String cookieName) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if (cookieName.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
    }
    return null;
}
public void setAccessTokenAtCookie(String jwtToken,HttpServletResponse response){
    ResponseCookie jwtCookie = ResponseCookie.from(&quot;access_token&quot;, jwtToken)
            .path(&quot;/&quot;)
            .httpOnly(true)
            .maxAge(TokenAbout.jwtExpirationInMs * 60)
            .sameSite(&quot;none&quot;)
            .secure(true)
            .build();
    response.addHeader(&quot;Set-Cookie&quot;, jwtCookie.toString());
}
public void setCookie(HttpServletResponse response,String jwtToken,String refreshToken){
    setAccessTokenAtCookie(jwtToken,response);
    ResponseCookie refreshCookie = ResponseCookie.from(&quot;refresh_token&quot;, refreshToken)
            .path(&quot;/&quot;)
            .httpOnly(true)
            .maxAge(TokenAbout.refreshExpirationInMs*60)
            .sameSite(&quot;none&quot;)
            .secure(true)
            .build();
    response.addHeader(&quot;Set-Cookie&quot;, refreshCookie.toString());
}
public void logRefreshInfo(String refreshToken,String username){
    // 레디스에 저장된 리프레시 토큰 정보 확인
    Map&amp;lt;String,Object&amp;gt; savedTokenInfo = getRefreshTokenInfo(refreshToken);
    if (savedTokenInfo != null) {
        // 레디스에 저장된 리프레시 토큰 정보가 있는 경우 로그 출력
        log.info(&quot;Refresh Token Info from Redis: {}&quot;, savedTokenInfo);
    } else {
        // 레디스에 저장된 리프레시 토큰 정보가 없는 경우 로그 출력
        log.info(&quot;Refresh Token Info not found in Redis for user: {}&quot;, username);
    }
}&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2357).png&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JZ87x/btsDrImCZhQ/OQpTACkib0akiisFk1UDX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JZ87x/btsDrImCZhQ/OQpTACkib0akiisFk1UDX1/img.png&quot; data-alt=&quot;엑세스토큰만료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JZ87x/btsDrImCZhQ/OQpTACkib0akiisFk1UDX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJZ87x%2FbtsDrImCZhQ%2FOQpTACkib0akiisFk1UDX1%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;559&quot; height=&quot;94&quot; data-filename=&quot;사본 -스크린샷(2357).png&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;엑세스토큰만료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2358).png&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3YiNP/btsDofe7P4t/ZaM2m3N3p3drxRRAlAOQuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3YiNP/btsDofe7P4t/ZaM2m3N3p3drxRRAlAOQuK/img.png&quot; data-alt=&quot;리프레시토큰 요청후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3YiNP/btsDofe7P4t/ZaM2m3N3p3drxRRAlAOQuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3YiNP%2FbtsDofe7P4t%2FZaM2m3N3p3drxRRAlAOQuK%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;614&quot; height=&quot;166&quot; data-filename=&quot;사본 -스크린샷(2358).png&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;리프레시토큰 요청후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2359).png&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nxqCs/btsDuTun1jM/uJgliFQxz36vh0Pf9Iqfm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nxqCs/btsDuTun1jM/uJgliFQxz36vh0Pf9Iqfm0/img.png&quot; data-alt=&quot;로그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nxqCs/btsDuTun1jM/uJgliFQxz36vh0Pf9Iqfm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnxqCs%2FbtsDuTun1jM%2FuJgliFQxz36vh0Pf9Iqfm0%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;790&quot; height=&quot;149&quot; data-filename=&quot;사본 -스크린샷(2359).png&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로그&lt;/figcaption&gt;
&lt;/figure&gt;
&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;a href=&quot;https://github.com/novb1492/login/tree/token-re&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/novb1492/login/tree/token-re&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705223490133&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - novb1492/login&quot; data-og-description=&quot;Contribute to novb1492/login development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/novb1492/login/tree/token-re&quot; data-og-url=&quot;https://github.com/novb1492/login&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxuZuz/hyU5SdS68T/uwBZZ9Q5BlV0iyLKrt1b2k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/novb1492/login/tree/token-re&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/novb1492/login/tree/token-re&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxuZuz/hyU5SdS68T/uwBZZ9Q5BlV0iyLKrt1b2k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - novb1492/login&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to novb1492/login development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>짧은 프로젝트</category>
      <category>refresh token 발급</category>
      <category>springboot jwt refresh</category>
      <category>springboot refresh token</category>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/322</guid>
      <comments>https://cordingmonster.tistory.com/322#entry322comment</comments>
      <pubDate>Sun, 14 Jan 2024 18:11:51 +0900</pubDate>
    </item>
    <item>
      <title>Springboot jwt로그인 4(인증필터)</title>
      <link>https://cordingmonster.tistory.com/321</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1.jwt서비스에 토큰 오픈 코드 작성&lt;/p&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;public String getValue(String jwtToken){
    try {
        // JWT 검증을 위한 알고리즘 설정
        Algorithm algorithm = Algorithm.HMAC512(jwtSecret);

        // JWT 검증기 생성
        JWTVerifier verifier = JWT.require(algorithm).build();

        return verifier.verify(jwtToken).getSubject();

    } catch (Exception e) {
        // 토큰이 유효하지 않거나 디코딩에 실패한 경우
        e.printStackTrace();
        log.error(&quot;토큰이 유요하지 않습니다&quot;);
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.인증필터 작성&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    log.info(&quot;인증필터 입장&quot;);
    // 현재 요청된 URL 확인
    String requestURI = request.getRequestURI();
    log.info(&quot;Request URI: {}&quot;, requestURI);

    // 허용되는 URL 패턴 정의
    String allowedPattern = &quot;/pass&quot;; // 예시로 &quot;/public/&quot;로 시작하는 URL은 인증 필터를 통과시킴

    // 허용되는 URL 패턴인 경우 통과
    if (requestURI.startsWith(allowedPattern)) {
        log.info(&quot;허용되는 URL, 필터 통과&quot;);
        chain.doFilter(request, response);
        return;
    }
    log.info(&quot;허용되는 되지않는 URL, 검증시작&quot;);
    // 쿠키에서 엑세스 토큰 추출
    String accessToken = extractAccessTokenFromCookie(request);
    log.info(&quot;accessToken:{}&quot;,accessToken);
    if (accessToken != null) {
        // 엑세스 토큰에서 사용자 ID 추출
        String username = tokenService.getValue(accessToken);
        log.info(&quot;username:{}&quot;,username);

        // 시큐리티 세션에 인증 주입
        PrincipalDetails principalDetails =new PrincipalDetails(username,null);
        SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(principalDetails, null, principalDetails.getAuthorities()));

        if(username==null){
            set403(response);
        }
        chain.doFilter(request, response);
        return;
    }
    log.info(&quot;accessToken을 찾을 수없습니다&quot;);
    set403(response);
}&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2354).png&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx57e6/btsDqm5qbvg/W75R2xPO7frMGr9eRR20E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx57e6/btsDqm5qbvg/W75R2xPO7frMGr9eRR20E1/img.png&quot; data-alt=&quot;/test,/pass,만료후 테스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx57e6/btsDqm5qbvg/W75R2xPO7frMGr9eRR20E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx57e6%2FbtsDqm5qbvg%2FW75R2xPO7frMGr9eRR20E1%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;751&quot; height=&quot;179&quot; data-filename=&quot;사본 -스크린샷(2354).png&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;/test,/pass,만료후 테스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2356).png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnQqUK/btsDqO8tvTa/Zq6AgFXoENjDvFNvJiubCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnQqUK/btsDqO8tvTa/Zq6AgFXoENjDvFNvJiubCk/img.png&quot; data-alt=&quot;/test,/pass,만료후 테스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnQqUK/btsDqO8tvTa/Zq6AgFXoENjDvFNvJiubCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnQqUK%2FbtsDqO8tvTa%2FZq6AgFXoENjDvFNvJiubCk%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;368&quot; height=&quot;266&quot; data-filename=&quot;사본 -스크린샷(2356).png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;/test,/pass,만료후 테스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;404/404/403 정상&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;a href=&quot;https://github.com/novb1492/login/tree/auth-filter&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/novb1492/login/tree/auth-filter&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705132416239&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - novb1492/login&quot; data-og-description=&quot;Contribute to novb1492/login development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/novb1492/login/tree/auth-filter&quot; data-og-url=&quot;https://github.com/novb1492/login&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LZFud/hyU2sudvg0/AJBEMPGFQkpAOo4kiRc20k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/novb1492/login/tree/auth-filter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/novb1492/login/tree/auth-filter&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LZFud/hyU2sudvg0/AJBEMPGFQkpAOo4kiRc20k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - novb1492/login&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to novb1492/login development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AuthorizationFilter</category>
      <category>BasicAuthenticationFilter</category>
      <category>JWT</category>
      <category>SpringBoot</category>
      <category>springboot security</category>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/321</guid>
      <comments>https://cordingmonster.tistory.com/321#entry321comment</comments>
      <pubDate>Sat, 13 Jan 2024 16:50:02 +0900</pubDate>
    </item>
    <item>
      <title>Springboot jwt 로그인3(redis 리프레시토큰)</title>
      <link>https://cordingmonster.tistory.com/320</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1.레디스 설정&lt;/p&gt;
&lt;pre id=&quot;code_1705060101884&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class RedisConfig {



    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
//        if(mode.equals(&quot;1&quot;)){
//            RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
//            clusterConfiguration.clusterNode(host, port);
//            LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
//                    .clientOptions(ClientOptions.builder()
//                            .socketOptions(SocketOptions.builder()
//                                    .build())
//                            .build()).build();
//            return new LettuceConnectionFactory(clusterConfiguration, clientConfiguration);
//        }
        return new LettuceConnectionFactory(&quot;localhost&quot;,6379);

    }
    @Bean
    public RedisTemplate&amp;lt;String,Object&amp;gt; redisTemplate() {
        RedisTemplate&amp;lt;String,Object&amp;gt;redisTemplate=new RedisTemplate&amp;lt;&amp;gt;();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer&amp;lt;&amp;gt;(Object.class));
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.jwt 서비스 작성&lt;/p&gt;
&lt;pre id=&quot;code_1705060255380&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class TokenService {

    private final RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate;
    private  String jwtSecret=&quot;jwtSecret&quot;;

    public String generateToken(String username, long expiration) {
        try {
            Algorithm algorithm = Algorithm.HMAC512(jwtSecret);
            Date expiryDate = new Date(System.currentTimeMillis() + expiration);

            return JWT.create()
                    .withSubject(username)
                    .withIssuedAt(new Date())
                    .withExpiresAt(expiryDate)
                    .sign(algorithm);
        } catch (JWTCreationException e) {
            throw new RuntimeException(&quot;Error creating JWT token&quot;, e);
        }
    }

    public void saveRefreshToken(String username, String refreshToken, LocalDateTime issueDate, long issueCount) {
        // TokenInfo 객체 생성
        Map&amp;lt;String,Object&amp;gt; tokenInfo = new HashMap&amp;lt;&amp;gt;();
        tokenInfo.put(&quot;id&quot;,username);
        tokenInfo.put(&quot;refreshToken&quot;,refreshToken);
        tokenInfo.put(&quot;issueDate&quot;,issueDate.toString());
        tokenInfo.put(&quot;issueCount&quot;,issueCount);
        // 여기에서 username을 사용하여 리프레시 토큰 정보를 객체로 직렬화하여 저장
        redisTemplate.opsForHash().put(&quot;refreshToken&quot;+username, username,tokenInfo);
        // 만료 시간 설정 (예: 1시간)
        redisTemplate.expire(&quot;refreshToken&quot; + username, 1, TimeUnit.HOURS);
    }

    public Map&amp;lt;String,Object&amp;gt; getRefreshTokenInfo(String username) {
        // 여기에서 username을 사용하여 리프레시 토큰 정보를 역직렬화하여 추출
        return (Map&amp;lt;String,Object&amp;gt;) redisTemplate.opsForHash().get(&quot;refreshToken&quot; + username,username);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.로그인 필터 추가 로직 작성&lt;/p&gt;
&lt;pre id=&quot;code_1705060280460&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        log.info(&quot;로그인 성공&quot;);

        // 로그인 성공 시 JWT 토큰 발급
        String jwtToken = tokenService.generateToken(&quot;access&quot;, jwtExpirationInMs);
        String refreshToken = tokenService.generateToken(&quot;refresh&quot;, refreshExpirationInMs);

        // JWT 토큰을 응답 쿠키에 추가
        ResponseCookie jwtCookie = ResponseCookie.from(&quot;access_token&quot;, jwtToken)
                .path(&quot;/&quot;)
                .httpOnly(true)
                .maxAge(jwtExpirationInMs / 1000)
                .sameSite(&quot;None&quot;)
                .secure(true)
                .build();
        response.addHeader(&quot;Set-Cookie&quot;, jwtCookie.toString());

        ResponseCookie refreshCookie = ResponseCookie.from(&quot;refresh_token&quot;, refreshToken)
                .path(&quot;/&quot;)
                .httpOnly(true)
                .maxAge(refreshExpirationInMs / 1000)
                .sameSite(&quot;None&quot;)
                .secure(true)
                .build();
        response.addHeader(&quot;Set-Cookie&quot;, refreshCookie.toString());

        // 로그인 성공 시 200 응답 코드만 반환
        response.setStatus(HttpServletResponse.SC_OK);

        // 응답 본문에 JSON 추가
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonResponse = objectMapper.writeValueAsString(Map.of(&quot;message&quot;, &quot;login done&quot;));
        response.getWriter().write(jsonResponse);
        response.getWriter().flush();

        // 로그인 성공한 유저의 이름 얻어내기
        SecurityContextHolder.getContext().setAuthentication(authResult);
        PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();
        String username = principalDetails.getUsername();
        LocalDateTime issueDate = LocalDateTime.now();
        long issueCount = 0;
        // 리프레시 토큰 저장
        tokenService.saveRefreshToken(username, refreshToken,issueDate,issueCount);

        // 시큐리티 세션에 인증 주입
        SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(principalDetails, null, principalDetails.getAuthorities()));

        // 레디스에 저장된 리프레시 토큰 정보 확인
        Map&amp;lt;String,Object&amp;gt; savedTokenInfo = tokenService.getRefreshTokenInfo(username);
        if (savedTokenInfo != null) {
            // 레디스에 저장된 리프레시 토큰 정보가 있는 경우 로그 출력
            log.info(&quot;Refresh Token Info from Redis: {}&quot;, savedTokenInfo);
        } else {
            // 레디스에 저장된 리프레시 토큰 정보가 없는 경우 로그 출력
            log.info(&quot;Refresh Token Info not found in Redis for user: {}&quot;, username);
        }
    }&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2352).png&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1F1ll/btsDqbpl0qp/WvaqBiCX62FvB46SOe0KF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1F1ll/btsDqbpl0qp/WvaqBiCX62FvB46SOe0KF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1F1ll/btsDqbpl0qp/WvaqBiCX62FvB46SOe0KF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1F1ll%2FbtsDqbpl0qp%2FWvaqBiCX62FvB46SOe0KF1%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;788&quot; height=&quot;163&quot; data-filename=&quot;사본 -스크린샷(2352).png&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;163&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;a href=&quot;https://github.com/novb1492/login/tree/set-auth&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/novb1492/login/tree/set-auth&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705060362841&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - novb1492/login&quot; data-og-description=&quot;Contribute to novb1492/login development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/novb1492/login/tree/set-auth&quot; data-og-url=&quot;https://github.com/novb1492/login&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lflFI/hyU5Vaj4qY/9NbPTz0Z2VcFITyRJPDtF0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/novb1492/login/tree/set-auth&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/novb1492/login/tree/set-auth&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lflFI/hyU5Vaj4qY/9NbPTz0Z2VcFITyRJPDtF0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - novb1492/login&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to novb1492/login development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그fix&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    log.info(&quot;로그인 성공&quot;);

    // 로그인 성공한 유저의 이름 얻어내기
    SecurityContextHolder.getContext().setAuthentication(authResult);
    PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();
    String username = principalDetails.getUsername();

    // 로그인 성공 시 JWT 토큰 발급
    String jwtToken = tokenService.generateToken(username, jwtExpirationInMs);
    String refreshToken = tokenService.generateToken(username, refreshExpirationInMs);

    // JWT 토큰을 응답 쿠키에 추가
    ResponseCookie jwtCookie = ResponseCookie.from(&quot;access_token&quot;, jwtToken)
            .path(&quot;/&quot;)
            .httpOnly(true)
            .maxAge(jwtExpirationInMs * 60)
            .sameSite(&quot;none&quot;)
            .secure(true)
            .build();
    response.addHeader(&quot;Set-Cookie&quot;, jwtCookie.toString());

    ResponseCookie refreshCookie = ResponseCookie.from(&quot;refresh_token&quot;, refreshToken)
            .path(&quot;/&quot;)
            .httpOnly(true)
            .maxAge(refreshExpirationInMs*60)
            .sameSite(&quot;none&quot;)
            .secure(true)
            .build();
    response.addHeader(&quot;Set-Cookie&quot;, refreshCookie.toString());


    // 로그인 성공 시 200 응답 코드만 반환
    response.setStatus(HttpServletResponse.SC_OK);

    // 응답 본문에 JSON 추가
    ObjectMapper objectMapper = new ObjectMapper();
    String jsonResponse = objectMapper.writeValueAsString(Map.of(&quot;message&quot;, &quot;login done&quot;));
    response.getWriter().write(jsonResponse);
    response.getWriter().flush();

    LocalDateTime issueDate = LocalDateTime.now();
    long issueCount = 0;
    // 리프레시 토큰 저장
    tokenService.saveRefreshToken(username, refreshToken,issueDate,issueCount);

    // 시큐리티 세션에 인증 주입
    SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(principalDetails, null, principalDetails.getAuthorities()));

    // 레디스에 저장된 리프레시 토큰 정보 확인
    Map&amp;lt;String,Object&amp;gt; savedTokenInfo = tokenService.getRefreshTokenInfo(username);
    if (savedTokenInfo != null) {
        // 레디스에 저장된 리프레시 토큰 정보가 있는 경우 로그 출력
        log.info(&quot;Refresh Token Info from Redis: {}&quot;, savedTokenInfo);
    } else {
        // 레디스에 저장된 리프레시 토큰 정보가 없는 경우 로그 출력
        log.info(&quot;Refresh Token Info not found in Redis for user: {}&quot;, username);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>짧은 프로젝트</category>
      <category>Redis</category>
      <category>SpringBoot</category>
      <category>springboot redis</category>
      <category>springboot redis jwt</category>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/320</guid>
      <comments>https://cordingmonster.tistory.com/320#entry320comment</comments>
      <pubDate>Fri, 12 Jan 2024 20:53:18 +0900</pubDate>
    </item>
    <item>
      <title>Springboot Jwt 로그인2(로그인 처리 후 쿠키 발급)</title>
      <link>https://cordingmonster.tistory.com/319</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1.로그인필터 작성&lt;/p&gt;
&lt;pre id=&quot;code_1705034107964&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Slf4j
public class LoginFilter  extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private  String jwtSecret=&quot;jwtSecret&quot;;
    private  long jwtExpirationInMs=1;
    private  long refreshExpirationInMs=30;
    public LoginFilter(AuthenticationManager authenticationManager){
        this.authenticationManager=authenticationManager;
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {
        log.info(&quot;로그인 필터 입장&quot;);
        ObjectMapper objectMapper=new ObjectMapper();
        JSONObject jsonObject=new JSONObject();
        try {
            jsonObject = objectMapper.readValue(request.getInputStream(), JSONObject.class);
            log.info(&quot;로그인시도 정보:{}&quot;,jsonObject);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(jsonObject.get(&quot;id&quot;),jsonObject.get(&quot;pwd&quot;)));
        } catch (IOException e) {
            log.error(&quot;요청에서 JSON 데이터를 읽는 중 오류 발생&quot;, e);
            throw new AuthenticationServiceException(&quot;요청에서 JSON 데이터를 읽는 중 오류 발생&quot;, e);
        }

    }
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        log.info(&quot;로그인 성공&quot;);

        // 로그인 성공 시 JWT 토큰 발급
        String jwtToken = generateToken(&quot;access&quot;, jwtExpirationInMs);
        String refreshToken = generateToken(&quot;refresh&quot;, refreshExpirationInMs);

        // JWT 토큰을 응답 쿠키에 추가
        ResponseCookie jwtCookie = ResponseCookie.from(&quot;access_token&quot;, jwtToken)
                .path(&quot;/&quot;)
                .httpOnly(true)
                .maxAge(jwtExpirationInMs / 1000)
                .sameSite(&quot;None&quot;)
                .secure(true)
                .build();
        response.addHeader(&quot;Set-Cookie&quot;, jwtCookie.toString());

        ResponseCookie refreshCookie = ResponseCookie.from(&quot;refresh_token&quot;, refreshToken)
                .path(&quot;/&quot;)
                .httpOnly(true)
                .maxAge(refreshExpirationInMs / 1000)
                .sameSite(&quot;None&quot;)
                .secure(true)
                .build();
        response.addHeader(&quot;Set-Cookie&quot;, refreshCookie.toString());

        // 로그인 성공 시 200 응답 코드만 반환
        response.setStatus(HttpServletResponse.SC_OK);

        // 응답 본문에 JSON 추가
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonResponse = objectMapper.writeValueAsString(Map.of(&quot;message&quot;, &quot;login done&quot;));
        response.getWriter().write(jsonResponse);
        response.getWriter().flush();
    }
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        log.info(&quot;로그인실패:{}&quot;,failed.getMessage());
        // 로그인 실패 응답 설정
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 예시로 401 Unauthorized 상태 코드를 설정

        // 실패 메시지를 JSON 형식으로 응답에 추가
        Map&amp;lt;String, String&amp;gt; errorResponse = new HashMap&amp;lt;&amp;gt;();
        errorResponse.put(&quot;error&quot;, &quot;Authentication failed&quot;);
        errorResponse.put(&quot;message&quot;, failed.getMessage());

        response.setContentType(&quot;application/json&quot;);
        response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));

        response.getWriter().flush();


    }
    private String generateToken(String username, long expiration) {
        try {
            Algorithm algorithm = Algorithm.HMAC512(jwtSecret);
            Date expiryDate = new Date(System.currentTimeMillis() + expiration);

            return JWT.create()
                    .withSubject(username)
                    .withIssuedAt(new Date())
                    .withExpiresAt(expiryDate)
                    .sign(algorithm);
        } catch (JWTCreationException e) {
            throw new RuntimeException(&quot;Error creating JWT token&quot;, e);
        }
    }&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2348).png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F5nBN/btsDpvgo7aV/8aqq6pMBfSLgzZN5b7HTd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F5nBN/btsDpvgo7aV/8aqq6pMBfSLgzZN5b7HTd1/img.png&quot; data-alt=&quot;성공시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F5nBN/btsDpvgo7aV/8aqq6pMBfSLgzZN5b7HTd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF5nBN%2FbtsDpvgo7aV%2F8aqq6pMBfSLgzZN5b7HTd1%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;668&quot; height=&quot;441&quot; data-filename=&quot;사본 -스크린샷(2348).png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;성공시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2349).png&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;89&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW9M91/btsDnu3Ak1G/0J1D71kK49rl3KFCkf9GDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW9M91/btsDnu3Ak1G/0J1D71kK49rl3KFCkf9GDk/img.png&quot; data-alt=&quot;성공시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW9M91/btsDnu3Ak1G/0J1D71kK49rl3KFCkf9GDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW9M91%2FbtsDnu3Ak1G%2F0J1D71kK49rl3KFCkf9GDk%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;667&quot; height=&quot;89&quot; data-filename=&quot;사본 -스크린샷(2349).png&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;89&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;성공시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2350).png&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SY1Yb/btsDogw7zR6/ZO7m0xZpfMOkKgSk5LTuH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SY1Yb/btsDogw7zR6/ZO7m0xZpfMOkKgSk5LTuH1/img.png&quot; data-alt=&quot;실패시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SY1Yb/btsDogw7zR6/ZO7m0xZpfMOkKgSk5LTuH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSY1Yb%2FbtsDogw7zR6%2FZO7m0xZpfMOkKgSk5LTuH1%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;341&quot; height=&quot;86&quot; data-filename=&quot;사본 -스크린샷(2350).png&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실패시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2351).png&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/81YlT/btsDlnjG9BO/k1CequQ0sWRard8K0HwFTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/81YlT/btsDlnjG9BO/k1CequQ0sWRard8K0HwFTK/img.png&quot; data-alt=&quot;로그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/81YlT/btsDlnjG9BO/k1CequQ0sWRard8K0HwFTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F81YlT%2FbtsDlnjG9BO%2Fk1CequQ0sWRard8K0HwFTK%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;396&quot; height=&quot;141&quot; data-filename=&quot;사본 -스크린샷(2351).png&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로그&lt;/figcaption&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;a href=&quot;https://github.com/novb1492/login/tree/action-login&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/novb1492/login/tree/action-login&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705034370088&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - novb1492/login&quot; data-og-description=&quot;Contribute to novb1492/login development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/novb1492/login/tree/action-login&quot; data-og-url=&quot;https://github.com/novb1492/login&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JZxCy/hyU2m8rEek/FKKs0j48HwwkTU4myVsTkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/novb1492/login/tree/action-login&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/novb1492/login/tree/action-login&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JZxCy/hyU2m8rEek/FKKs0j48HwwkTU4myVsTkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - novb1492/login&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to novb1492/login development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그fix&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.쿠키가 너무빨리 만료되는버그&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;// JWT 토큰을 응답 쿠키에 추가
ResponseCookie jwtCookie = ResponseCookie.from(&quot;access_token&quot;, jwtToken)
        .path(&quot;/&quot;)
        .httpOnly(true)
        .maxAge(jwtExpirationInMs * 60)
        .sameSite(&quot;none&quot;)
        .secure(true)
        .build();
response.addHeader(&quot;Set-Cookie&quot;, jwtCookie.toString());

ResponseCookie refreshCookie = ResponseCookie.from(&quot;refresh_token&quot;, refreshToken)
        .path(&quot;/&quot;)
        .httpOnly(true)
        .maxAge(refreshExpirationInMs*60)
        .sameSite(&quot;none&quot;)
        .secure(true)
        .build();
response.addHeader(&quot;Set-Cookie&quot;, refreshCookie.toString());


// 로그인 성공 시 200 응답 코드만 반환
response.setStatus(HttpServletResponse.SC_OK);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.토큰 값에 잘못들어가는 버그&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;// 로그인 성공한 유저의 이름 얻어내기
SecurityContextHolder.getContext().setAuthentication(authResult);
PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();
String username = principalDetails.getUsername();

// 로그인 성공 시 JWT 토큰 발급
String jwtToken = tokenService.generateToken(username, jwtExpirationInMs);
String refreshToken = tokenService.generateToken(username, refreshExpirationInMs);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.토큰이 너무 빨리 만료되는 버그&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-darkreader-inline-bgcolor=&quot;&quot; data-darkreader-inline-color=&quot;&quot;&gt;&lt;code&gt;public String generateToken(String username, long expiration) {
    try {
        Algorithm algorithm = Algorithm.HMAC512(jwtSecret);
        Date expiryDate = new Date(System.currentTimeMillis() + 1000*expiration*60);

        return JWT.create()
                .withSubject(username)
                .withIssuedAt(new Date())
                .withExpiresAt(expiryDate)
                .sign(algorithm);
    } catch (JWTCreationException e) {
        throw new RuntimeException(&quot;Error creating JWT token&quot;, e);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>짧은 프로젝트</category>
      <category>springboot jwt</category>
      <category>스프링부트 시큐리티 jwt</category>
      <category>스프링부트 시큐리티 인증 토큰발급</category>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/319</guid>
      <comments>https://cordingmonster.tistory.com/319#entry319comment</comments>
      <pubDate>Fri, 12 Jan 2024 13:38:23 +0900</pubDate>
    </item>
    <item>
      <title>Spring boot jwt 로그인 하기1 (기본세팅)</title>
      <link>https://cordingmonster.tistory.com/318</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1.필요 필터 작성&lt;/p&gt;
&lt;pre id=&quot;code_1704945856886&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsfilter() {
        UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
        CorsConfiguration configuration=new CorsConfiguration();
        configuration.setAllowCredentials(true);
        configuration.addAllowedOriginPattern(&quot;*&quot;);
        configuration.addAllowedHeader(&quot;*&quot;);
        configuration.addAllowedMethod(&quot;*&quot;);
        source.registerCorsConfiguration(&quot;/**&quot;, configuration);
        return new CorsFilter(source);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1704945879350&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
public class LoginFilter  extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    public LoginFilter(AuthenticationManager authenticationManager){
        this.authenticationManager=authenticationManager;
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {
        log.info(&quot;로그인 필터 입장&quot;);
        ObjectMapper objectMapper=new ObjectMapper();
        JSONObject jsonObject=new JSONObject();
        try {
            jsonObject = objectMapper.readValue(request.getInputStream(), JSONObject.class);
            log.info(&quot;로그인시도 정보:{}&quot;,jsonObject);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(jsonObject.get(&quot;id&quot;),jsonObject.get(&quot;pwd&quot;)));
        } catch (IOException e) {
            log.error(&quot;요청에서 JSON 데이터를 읽는 중 오류 발생&quot;, e);
            throw new AuthenticationServiceException(&quot;요청에서 JSON 데이터를 읽는 중 오류 발생&quot;, e);
        }

    }
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        log.info(&quot;로그인 성공&quot;);
    }
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        log.info(&quot;로그인실패:{}&quot;,failed.getMessage());

    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1704945893557&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
public class AuthorizationFilter extends BasicAuthenticationFilter {

    public AuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info(&quot;인증필터 입장&quot;);
        chain.doFilter(request, response);
    }

}&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;2.시큐리티설정&lt;/p&gt;
&lt;pre id=&quot;code_1704945993741&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration//빈등록: 스프링 컨테이너에서 객체에서 관리
@EnableWebSecurity/////필터를 추가해준다
@RequiredArgsConstructor
public class SecurityConfig {

    private final CorsConfig corsConfig;
    private final AuthenticationConfiguration authenticationConfiguration;

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public BCryptPasswordEncoder pwdEncoder() {
        return  new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable()
                //.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                //.and()
                .addFilter(corsConfig.corsfilter())
                .formLogin().disable().httpBasic().disable()
                .authorizeRequests()
                .antMatchers(&quot;/pass/**&quot;,&quot;/login&quot;).permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilter(new LoginFilter(authenticationManager(authenticationConfiguration)))
                .addFilter(new AuthorizationFilter(authenticationManager(authenticationConfiguration)))
        ;

        return http.build();

    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.그외 로그인 정보 로직 추가&lt;/p&gt;
&lt;pre id=&quot;code_1704946026654&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
@Slf4j
public class PrincipalDetails implements UserDetails {

    private String email;
    private String pwd;

    public PrincipalDetails(String email,String pwd){
        this.email=email;
        this.pwd=pwd;
    }
    @Override
    public Collection&amp;lt;? extends GrantedAuthority&amp;gt; getAuthorities() {
        String role = &quot;ROLE_USER&quot;;
        Collection&amp;lt;GrantedAuthority&amp;gt; roles = new ArrayList&amp;lt;&amp;gt;();
        roles.add(new SimpleGrantedAuthority(role));
        return roles;
    }

    @Override
    public String getPassword() {
        return pwd;
    }

    @Override
    public String getUsername() {
        return email;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1704946040206&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
@Slf4j
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List&amp;lt;String&amp;gt; tempInfo = tempInfo(username);
        return new PrincipalDetails(username, tempInfo.get(1));
    }
    private List&amp;lt;String&amp;gt;tempInfo(String username){
        List&amp;lt;String&amp;gt; tempInfo = new ArrayList&amp;lt;&amp;gt;();
        if(username.equals(&quot;kim&quot;)){
            tempInfo.add(&quot;kim&quot;);
            tempInfo.add(&quot;$2a$12$qMfj1gc1MQnJReMJwq1zH.u85uqp/0V3Ij/LqBdeITiJmNcwyjwfS&quot;);
        }else{
            tempInfo.add(&quot;kim2&quot;);
            tempInfo.add(&quot;$2a$12$KxYh2x/YI4TY5qYuwicYKe4Kr7fPlA7aLqvmE18bJaJ/2fRIPa95.&quot;);
        }
        return tempInfo;
    }
}&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2347).png&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5pd35/btsDlo2MqgT/hczZ1ClMP11XvVb7XwVazk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5pd35/btsDlo2MqgT/hczZ1ClMP11XvVb7XwVazk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5pd35/btsDlo2MqgT/hczZ1ClMP11XvVb7XwVazk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5pd35%2FbtsDlo2MqgT%2FhczZ1ClMP11XvVb7XwVazk%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;829&quot; height=&quot;118&quot; data-filename=&quot;사본 -스크린샷(2347).png&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;118&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;a href=&quot;https://github.com/novb1492/login/tree/basic-setting&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/novb1492/login/tree/basic-setting&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704946237675&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - novb1492/login&quot; data-og-description=&quot;Contribute to novb1492/login development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/novb1492/login/tree/basic-setting&quot; data-og-url=&quot;https://github.com/novb1492/login&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cjDwHf/hyU2fBhKbx/XB8KYIPLuy5MjudrqD4viK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/novb1492/login/tree/basic-setting&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/novb1492/login/tree/basic-setting&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cjDwHf/hyU2fBhKbx/XB8KYIPLuy5MjudrqD4viK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - novb1492/login&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to novb1492/login development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>짧은 프로젝트</category>
      <category>jwt 로그인</category>
      <category>springboot security jwt</category>
      <category>스프링부트</category>
      <category>시큐리티 jwt 로그인</category>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/318</guid>
      <comments>https://cordingmonster.tistory.com/318#entry318comment</comments>
      <pubDate>Thu, 11 Jan 2024 13:09:28 +0900</pubDate>
    </item>
    <item>
      <title>Springboot 배달 시스템 만들기 3(위치정보 전송 및 표시하기 카카오지도)</title>
      <link>https://cordingmonster.tistory.com/317</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1.어드민 위치전송 코드 작성&lt;/p&gt;
&lt;pre id=&quot;code_1704884520199&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
        let currentUrl = window.location.href;
        // URL을 &quot;/&quot;로 분할
        let urlParts = currentUrl.split(&quot;/&quot;);
        // Path variable 위치에서 값을 가져옴 (예: /deliver/1/admin에서 2번째 요소)
        let pathVariableValue = urlParts[4]; // index는 0부터 시작하므로, 4는 세 번째 요소를 나타냄
        // 가져온 값을 콘솔에 출력 또는 필요한 작업 수행
        let socket = null;
        const btn = document.getElementById('btn');
        function startDeliver() {
            // 웹소켓 생성
            socket = new WebSocket(`ws://localhost:8080/ws/deliver?did=${pathVariableValue}&amp;amp;role=admin`);

            // 커넥션이 제대로 생성되었을 때
            socket.onopen = function (e) {
                // Disable the button
                btn.disabled = true;
                if(navigator.geolocation){
                    navigator.geolocation.watchPosition(success,null,null);
                }
            };
            // 에러가 발생했을 때
            socket.onerror = function (e) {
                alert('웹소켓 연결 실패');
                console.log(e);
                btn.disabled = false;

            };
        }
        function success(position) {
            let data = {
                action: &quot;send&quot;,
                did: pathVariableValue,
                lat:position.coords.latitude,// 위도
                lon:position.coords.longitude // 경도
            };
            socket.send(JSON.stringify(data));
        };

    &amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.고객 페이지 메세지 받는 코드 및 카카오지도 표시 코드 작성&lt;/p&gt;
&lt;pre id=&quot;code_1704884967279&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    &amp;lt;script&amp;gt;
        let currentUrl = window.location.href;
        // URL을 &quot;/&quot;로 분할
        let urlParts = currentUrl.split(&quot;/&quot;);
        // Path variable 위치에서 값을 가져옴 (예: /deliver/1/admin에서 2번째 요소)
        let pathVariableValue = urlParts[4]; // index는 0부터 시작하므로, 4는 세 번째 요소를 나타냄
        // 가져온 값을 콘솔에 출력 또는 필요한 작업 수행
        // 웹소켓 생성
        const socket = new WebSocket(`ws://localhost:8080/ws/deliver?did=${pathVariableValue}&amp;amp;role=user`);
        let map =null;
        let marker=null;
        // 커넥션이 제대로 생성되었을 때
        socket.onopen = function (e) {
            console.log('유저배달방입장');
            let container = document.getElementById('map');
            let options = {
                center: new kakao.maps.LatLng(33.450701, 126.570667),
                level: 3
            };
            map = new kakao.maps.Map(container, options);
        };

        // 데이터를 수신 받았을 때
        socket.onmessage = async function (e) {
            console.log(e);
            const data = JSON.parse(e.data);
            // &quot;let&quot; 및 &quot;lon&quot; 값을 가져와서 표시
            const letValue = data.lat; // 데이터에서 &quot;let&quot;의 값을 가져옴
            const lonValue = data.lon; // 데이터에서 &quot;lon&quot;의 값을 가져옴
            // 가져온 값을 화면에 표시 (예: HTML 요소에 추가)
            document.getElementById('lat').innerText = `lat: ${letValue}`;
            document.getElementById('lon').innerText = `lon: ${lonValue}`;
            // 이전에 생성된 마커가 있으면 제거
            console.log(marker);
            if (marker!==null) {
                marker.setMap(null);
            }
            // 마커 위치 생성
            let markerPosition = new kakao.maps.LatLng(letValue, lonValue);
            // 위치를 사용하여 마커 생성
            marker = new kakao.maps.Marker({
                position: markerPosition,
            });
            // 마커를 지도에 추가
            marker.setMap(map);
            // 지도 중심 이동
            map.setCenter(markerPosition);
        };

        // 에러가 발생했을 때
        socket.onerror = function (e) {
            console.log(e);
        };
    &amp;lt;/script&amp;gt;&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-filename=&quot;사본 -스크린샷(2342).png&quot; data-origin-width=&quot;1511&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpWfdT/btsDkQLzG7v/OfKYfE2zhk4C6W1ddB9cp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpWfdT/btsDkQLzG7v/OfKYfE2zhk4C6W1ddB9cp0/img.png&quot; data-alt=&quot;어드민 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpWfdT/btsDkQLzG7v/OfKYfE2zhk4C6W1ddB9cp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpWfdT%2FbtsDkQLzG7v%2FOfKYfE2zhk4C6W1ddB9cp0%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;1511&quot; height=&quot;324&quot; data-filename=&quot;사본 -스크린샷(2342).png&quot; data-origin-width=&quot;1511&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어드민 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2344).png&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB0Nkf/btsDfPVkbTx/22XI9Me6tDgq2nlNGqrkI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB0Nkf/btsDfPVkbTx/22XI9Me6tDgq2nlNGqrkI0/img.png&quot; data-alt=&quot;고객페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB0Nkf/btsDfPVkbTx/22XI9Me6tDgq2nlNGqrkI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB0Nkf%2FbtsDfPVkbTx%2F22XI9Me6tDgq2nlNGqrkI0%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;1231&quot; height=&quot;386&quot; data-filename=&quot;사본 -스크린샷(2344).png&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;고객페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&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-filename=&quot;사본 -스크린샷(2346).png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFJYcF/btsDfnqSpuc/Mtnlu4Rf6d0pULstTpS1dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFJYcF/btsDfnqSpuc/Mtnlu4Rf6d0pULstTpS1dK/img.png&quot; data-alt=&quot;고객페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFJYcF/btsDfnqSpuc/Mtnlu4Rf6d0pULstTpS1dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFJYcF%2FbtsDfnqSpuc%2FMtnlu4Rf6d0pULstTpS1dK%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;1382&quot; height=&quot;387&quot; data-filename=&quot;사본 -스크린샷(2346).png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;고객페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&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;
&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;&lt;a href=&quot;https://github.com/novb1492/deliver/tree/room-send&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/novb1492/deliver/tree/room-send&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704885188703&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - novb1492/deliver&quot; data-og-description=&quot;Contribute to novb1492/deliver development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/novb1492/deliver/tree/room-send&quot; data-og-url=&quot;https://github.com/novb1492/deliver&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkcSC7/hyU2hTby8y/KgrHVE3SwvVQPjJi8dRBkk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/novb1492/deliver/tree/room-send&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/novb1492/deliver/tree/room-send&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkcSC7/hyU2hTby8y/KgrHVE3SwvVQPjJi8dRBkk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - novb1492/deliver&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to novb1492/deliver development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>짧은 프로젝트</category>
      <category>navigator.geolocation.watchPosition</category>
      <category>배달시스템</category>
      <category>스프링부트 배달</category>
      <category>자바스크립트</category>
      <category>자바스크립트 위치 추적</category>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/317</guid>
      <comments>https://cordingmonster.tistory.com/317#entry317comment</comments>
      <pubDate>Wed, 10 Jan 2024 20:11:28 +0900</pubDate>
    </item>
    <item>
      <title>Springboot 배달 시스템 만들기 2(배달방 생성,삭제,메세지전송)</title>
      <link>https://cordingmonster.tistory.com/316</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1.웹소켓 입장시 배달방 생성로직 작성&lt;/p&gt;
&lt;pre id=&quot;code_1704869653537&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override//연결이되면 자동으로 작동하는함수
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info(&quot;배달 웹소켓이 연결되었습니다&quot;);
        URI uri = session.getUri();
        Map&amp;lt;String, String&amp;gt; params = extractQueryParameters(uri.getQuery());
        String did = params.get(&quot;did&quot;);
        // list에 방 정보 저장
        String roomKey = &quot;ROOM:&quot; + did;
        if (!roomList.containsKey(roomKey)) {
            // 방이 존재하지 않으면 새로운 방 생성
            List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; infos = new ArrayList&amp;lt;&amp;gt;();
            Map&amp;lt;String, Object&amp;gt; info = new HashMap&amp;lt;&amp;gt;();
            info.put(&quot;role&quot;, params.get(&quot;role&quot;));
            info.put(&quot;did&quot;, did);
            info.put(&quot;sessionId&quot;, session.getId());
            info.put(&quot;session&quot;, session);
            infos.add(info);

            // 새로운 방 정보를 추가하고 로그 출력
            log.info(&quot;새로운 방 생성: {}&quot;, roomKey);
            log.info(&quot;배달방 입장: {}&quot;, infos);

            roomList.put(roomKey, infos);
        } else {
            // 방이 이미 존재하면 기존 방에 입장
            List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; infos = roomList.get(roomKey);
            Map&amp;lt;String, Object&amp;gt; info = new HashMap&amp;lt;&amp;gt;();
            info.put(&quot;role&quot;, params.get(&quot;role&quot;));
            info.put(&quot;did&quot;, did);
            info.put(&quot;sessionId&quot;, session.getId());
            info.put(&quot;session&quot;, session);
            infos.add(info);

            // 기존 방에 입장하고 로그 출력
            log.info(&quot;기존 방 입장: {}&quot;, roomKey);
            log.info(&quot;배달방 입장: {}&quot;, infos);

            roomList.put(roomKey, infos);
        }

        // 로그에 전체 방 정보 출력
        log.info(&quot;전체 배달방 정보: {}&quot;, roomList);

    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.새로고침 배달추적 이탈등 세션이 바뀌면서 연결 끊길시 배달방에서 자동탈퇴로직 작성&lt;/p&gt;
&lt;pre id=&quot;code_1704869669616&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override //연결이끊기면 자동으로 작동하는함수
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        //권한에 따라
        log.info(&quot;연결종료&quot;);
        URI uri = session.getUri();
        Map&amp;lt;String, String&amp;gt; params = extractQueryParameters(uri.getQuery());
        String did = params.get(&quot;did&quot;);
        // list에 방 정보 저장
        String roomKey = &quot;ROOM:&quot; + did;
        if (roomList.containsKey(roomKey)) {
            List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; infos = roomList.get(roomKey);

            // 해당 세션 아이디를 찾아 제거
            for (Iterator&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; iterator = infos.iterator(); iterator.hasNext();) {
                Map&amp;lt;String, Object&amp;gt; room = iterator.next();
                String sessionId = (String) room.get(&quot;sessionId&quot;);

                if (sessionId.equals(session.getId())) {
                    // 세션 아이디가 일치하면 해당 세션 제거
                    iterator.remove();
                    log.info(&quot;배달방에서 세션 제거: {}&quot;, session.getId());
                    break;  // 해당 세션을 찾았으므로 루프 종료
                }
            }

            // 만약 방에 더 이상 세션이 없다면 방도 제거
            if (infos.isEmpty()) {
                roomList.remove(roomKey);
                log.info(&quot;배달방 제거: {}&quot;, roomKey);
            }

            // 로그에 현재 배달방 정보 출력
            log.info(&quot;현재 배달방 정보: {}&quot;, roomList);
        } else {
            log.error(&quot;배달방이 존재하지 않습니다. 방 정보를 제거할 수 없습니다.&quot;);
        }

    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.프론트에서 받을 배달원 위치 전송로직 작성&lt;/p&gt;
&lt;pre id=&quot;code_1704869716167&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override//메세지가오는함수
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info(&quot;받은메세지:{}&quot;,message.getPayload());
        ObjectMapper objectMapper = new ObjectMapper();
        Map&amp;lt;String, Object&amp;gt; payloadMap = new HashMap&amp;lt;&amp;gt;();
        try {
            // payload를 Map으로 파싱합니다.
           payloadMap = objectMapper.readValue(message.getPayload(), new TypeReference&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt;() {});
        } catch (JsonProcessingException e) {
            // JSON 처리 예외를 처리합니다.
            e.printStackTrace();
            log.error(&quot;변환실패&quot;);
        }
        Object didObject = payloadMap.get(&quot;did&quot;);
        String did = didObject.toString();
        String roomKey = &quot;ROOM:&quot; + did;
        for(Map&amp;lt;String,Object&amp;gt;room:roomList.get(roomKey)){
            try {
                //보내기만 하면됨 n번방 세션 들 다꺼내기
                WebSocketSession wss = (WebSocketSession) room.get(&quot;session&quot;);
                wss.sendMessage(new TextMessage(message.getPayload()));
            } catch (Exception e) {
                e.printStackTrace();
                log.error(&quot;배달 위치 전송실패&quot;);
            }
        }
    }&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트 간단 한 테스트 코드 작성&lt;/p&gt;
&lt;pre id=&quot;code_1704869785498&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 커넥션이 제대로 생성되었을 때
        socket.onopen = function (e) {
            let data = {
                action: &quot;send&quot;,
                did: pathVariableValue,
                location:&quot;12,14&quot;
            };
            socket.send(JSON.stringify(data))
        };&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;admin/user입장&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2339).png&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MWQxd/btsDdE0tPRd/KDk2SESf8MVuEnWZLTSM91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MWQxd/btsDdE0tPRd/KDk2SESf8MVuEnWZLTSM91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MWQxd/btsDdE0tPRd/KDk2SESf8MVuEnWZLTSM91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMWQxd%2FbtsDdE0tPRd%2FKDk2SESf8MVuEnWZLTSM91%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;710&quot; height=&quot;174&quot; data-filename=&quot;사본 -스크린샷(2339).png&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;admin/user 새로 고침또는 이탈 발생&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2341).png&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y4bMO/btsDjX5irCN/iS193TYlKv9usia5bObd5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y4bMO/btsDjX5irCN/iS193TYlKv9usia5bObd5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y4bMO/btsDjX5irCN/iS193TYlKv9usia5bObd5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY4bMO%2FbtsDjX5irCN%2FiS193TYlKv9usia5bObd5K%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;704&quot; height=&quot;282&quot; data-filename=&quot;사본 -스크린샷(2341).png&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;a href=&quot;https://github.com/novb1492/deliver/tree/room-into-out&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/novb1492/deliver/tree/room-into-out&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704870237651&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - novb1492/deliver&quot; data-og-description=&quot;Contribute to novb1492/deliver development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/novb1492/deliver/tree/room-into-out&quot; data-og-url=&quot;https://github.com/novb1492/deliver&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XyknD/hyU2jJ9uHV/xDCzqajjqkQX3zPeU0kTX0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/novb1492/deliver/tree/room-into-out&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/novb1492/deliver/tree/room-into-out&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XyknD/hyU2jJ9uHV/xDCzqajjqkQX3zPeU0kTX0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - novb1492/deliver&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to novb1492/deliver development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>짧은 프로젝트</category>
      <category>springboot websocket</category>
      <category>springboot 배달</category>
      <category>websocket</category>
      <category>배달 추적 시스템</category>
      <category>스프링부트</category>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/316</guid>
      <comments>https://cordingmonster.tistory.com/316#entry316comment</comments>
      <pubDate>Wed, 10 Jan 2024 16:01:12 +0900</pubDate>
    </item>
    <item>
      <title>Springboot 배달 시스템 만들기 1(웹소켓 기본세팅)</title>
      <link>https://cordingmonster.tistory.com/315</link>
      <description>&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;&lt;span style=&quot;color: #c8c3bc; text-align: left;&quot; data-darkreader-inline-color=&quot;&quot;&gt;1.배달 웹소캣 핸들러 작성&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704782060086&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@Slf4j
@RequiredArgsConstructor
public class DeliverPositionHandler extends TextWebSocketHandler {

    @Override//메세지가오는함수
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

    }


    @Override//연결이되면 자동으로 작동하는함수
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info(&quot;배달 웹소켓이 연결되었습니다&quot;);
    }



    @Override //연결이끊기면 자동으로 작동하는함수
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        //권한에 따라
    }


}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.웹소켓 설정&lt;/p&gt;
&lt;pre id=&quot;code_1704782010965&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@RequiredArgsConstructor
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    private final DeliverPositionHandler deliverPositionHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler((WebSocketHandler) deliverPositionHandler, &quot;/ws/deliver&quot;).setAllowedOrigins(&quot;*&quot;);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.프론트 연결코드작성&lt;/p&gt;
&lt;pre id=&quot;code_1704782119446&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    &amp;lt;script&amp;gt;
        // 웹소켓 생성
        const socket = new WebSocket(&quot;ws://localhost:8080/ws/deliver&quot;);

        // 커넥션이 제대로 생성되었을 때
        socket.onopen = function (e) {
            
        };
        // 데이터를 수신 받았을 때
        socket.onmessage = async function (e) {
         
        };
        // 에러가 발생했을 때
        socket.onerror = function (e) {
            console.log(e);
        };
    &amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.결과&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사본 -스크린샷(2337).png&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BXbJk/btsDhNakowD/Wlg3So2M4GsGqff8KYQiJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BXbJk/btsDhNakowD/Wlg3So2M4GsGqff8KYQiJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BXbJk/btsDhNakowD/Wlg3So2M4GsGqff8KYQiJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBXbJk%2FbtsDhNakowD%2FWlg3So2M4GsGqff8KYQiJK%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;833&quot; height=&quot;254&quot; data-filename=&quot;사본 -스크린샷(2337).png&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;깃허브페이지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/novb1492/deliver/tree/basic-setting&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/novb1492/deliver/tree/basic-setting&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704782357982&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - novb1492/deliver&quot; data-og-description=&quot;Contribute to novb1492/deliver development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/novb1492/deliver/tree/basic-setting&quot; data-og-url=&quot;https://github.com/novb1492/deliver&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/By1qD/hyU2r17u2l/oo8eFuQV8yOvZwWfRtrfN1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/novb1492/deliver/tree/basic-setting&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/novb1492/deliver/tree/basic-setting&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/By1qD/hyU2r17u2l/oo8eFuQV8yOvZwWfRtrfN1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - novb1492/deliver&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to novb1492/deliver development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>짧은 프로젝트</category>
      <category>SpringBoot</category>
      <category>springboot websocket</category>
      <category>springboot 배달</category>
      <category>스프링부트</category>
      <category>스프링부트 배달</category>
      <category>스프링부트 배달서비스</category>
      <category>웹소켓</category>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/315</guid>
      <comments>https://cordingmonster.tistory.com/315#entry315comment</comments>
      <pubDate>Tue, 9 Jan 2024 15:39:56 +0900</pubDate>
    </item>
    <item>
      <title>에듀윌 공인중개사 시험 후기</title>
      <link>https://cordingmonster.tistory.com/314</link>
      <description>&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;선생님들 후기&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;-수업 끝날때마다 좋은 글귀 소개 해주는게 너무 좋음&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;임선정(중개실무)&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;-과목 특성상 때문에 억울한 항의를 많이 받는걸로 보임 ㅋㅋ&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;-위트있고 수업 재밌음&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;
&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;정경택(공법)&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;
&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;&amp;nbsp;&lt;/p&gt;</description>
      <author>디비드킴</author>
      <guid isPermaLink="true">https://cordingmonster.tistory.com/314</guid>
      <comments>https://cordingmonster.tistory.com/314#entry314comment</comments>
      <pubDate>Wed, 29 Nov 2023 12:49:58 +0900</pubDate>
    </item>
  </channel>
</rss>