{"id":8839,"date":"2024-03-23T20:29:44","date_gmt":"2024-03-23T11:29:44","guid":{"rendered":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=8839"},"modified":"2024-03-24T14:53:41","modified_gmt":"2024-03-24T05:53:41","slug":"spring-security-session-cookie","status":"publish","type":"post","link":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=8839","title":{"rendered":"Spring Security + Session\/Cookie"},"content":{"rendered":"<h1>Spring Security + Session\/Cookie<\/h1>\n<h2>\uae30\ubcf8 \ud750\ub984<\/h2>\n<ul>\n<li>\n<p>\uc138\uc158\uc744 \uc911\uc2ec\uc73c\ub85c \ub85c\uadf8\uc778 \uc0c1\ud0dc\ub97c \uad00\ub9ac\ud569\ub2c8\ub2e4.<\/p>\n<p>JWT \ubc29\uc2dd\uc740 \ud1a0\ud070\uc758 \uc720\ud6a8\uc131\uac80\uc0ac\ub97c \ud560 \uc218 \uc5c6\ub2e4\ub294 \ub2e8\uc810\uc774 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\ub9cc\uc57d \uc720\ud6a8\uc131\uac80\uc0ac\ub97c \ud574\uc57c \ud55c\ub2e4\uba74 DB \uc811\uc18d\uc744 \ud574\uc57c\ud558\uace0,<br \/>\n\uc774\ub807\uac8c \ub418\uba74 JWT \uc758 \uc7a5\uc810\uc774 \uc0ac\ub77c\uc9c0\ub294 \ub51c\ub808\ub9c8\uac00 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\uadf8\ub798\uc11c JWT \ub97c \uc0ac\uc6a9\ud558\ub354\ub77c\ub3c4 \uc138\uc158\uc744 \uc11c\ube0c\ub85c \uc0ac\uc6a9\ud558\uba74,<br \/>\n\uc138\uc158\uc758 \uc874\uc7ac\ub85c \ud1a0\ud070\uc758 \uc720\ud6a8\uc131\ub3c4 \uac80\uc99d\ud560 \uc218 \uc788\uace0,<br \/>\n\ud544\uc694\uc2dc \uc138\uc158\uc744 \uc0ad\uc81c\ud568\uc73c\ub85c \ud574\uc11c JWT \ud1a0\ud070\uc744 \ubb34\ud6a8\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<\/li>\n<li>\n<p>\uba54\ubaa8\ub9ac\ub514\ube44\uc5d0 \uc138\uc158\uc815\ubcf4\ub97c \uc800\uc7a5\ud569\ub2c8\ub2e4.<\/p>\n<p>\uc11c\ubc84 \uc2a4\ucf00\uc77c \uc544\uc6c3\uc5d0\ub3c4 \ub300\uc751\ub418\uace0,<br \/>\n\ub85c\uadf8\uc544\uc6c3\uc2dc \uc138\uc158\uc0ad\uc81c \ud558\ub294 \ubc29\uc548\ub3c4 \ub429\ub2c8\ub2e4.<\/p>\n<\/li>\n<li>\n<p>\uc138\uc158 \uc720\uc9c0\uc2dc\uac04\uc740 \uca1f\uac8c, \ucfe0\ud0a4 \uc720\uc9c0\uc2dc\uac04\uc740 \uae38\uac8c<\/p>\n<p>\ub85c\uadf8\uc778\uc2dc \ub098\ub9cc \uc0ac\uc6a9\ud558\ub294 PC \uc778\uc9c0 \uc544\ub2c8\uba74 PC\ubc29\uc778\uc9c0 \uc785\ub825\ubc1b\uc544 \uc0ac\ubb34\uc2e4 \uac19\uc740 \uacbd\uc6b0 \ucfe0\ud0a4 \uc2dc\uac04\uc744 \uae38\uac8c \uc124\uc815\ud574 \uc8fc\uba74 \ub9e4\ubc88 \ub85c\uadf8\uc778\ud574\uc57c \ud558\ub294 \ubd88\ud3b8\ud568\uc744 \uc904\uc77c \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<\/li>\n<\/ul>\n<h2>github<\/h2>\n<p><a href=\"https:\/\/github.com\/skyer9\/spring-boot-rest-api-example\/tree\/session\">https:\/\/github.com\/skyer9\/spring-boot-rest-api-example\/tree\/session<\/a><\/p>\n<p>\uae43\ud5c8\ube0c\uc5d0 \uc18c\uc2a4\ub97c \uc62c\ub838\uc73c\ub2c8\uae4c \uc911\uc694 \ud30c\uc77c\ub9cc \uc124\uba85\ud558\uace0 \ub098\uba38\uc9c0\ub294 \uc0dd\ub7b5\ud558\ub294 \ubc29\uc2dd\uc73c\ub85c \uc9c4\ud589\ud569\ub2c8\ub2e4.<\/p>\n<h2>Redis<\/h2>\n<p>\ub808\ub514\uc2a4\ub294 Entity &lt;-&gt; Json &lt;-&gt; Redis \ud615\uc2dd\uc73c\ub85c StringRedisSerializer \ub97c \uc774\uc6a9\ud558\ub294 \uac83\uc774 \uac00\uc7a5 \uac04\ud3b8\ud558\uace0 \uc88b\uc2b5\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-java\">@Configuration\n@RequiredArgsConstructor\n@EnableRedisRepositories\npublic class RedisConfig {\n    private final RedisProperties redisProperties;\n\n    @Bean\n    public RedisConnectionFactory redisConnectionFactory() {\n        return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());\n    }\n\n    @Bean\n    public RedisTemplate&lt;?, ?&gt; redisTemplate() {\n        RedisTemplate&lt;?, ?&gt; redisTemplate = new RedisTemplate&lt;&gt;();\n        redisTemplate.setConnectionFactory(redisConnectionFactory());\n        redisTemplate.setEnableTransactionSupport(true);\n\n        redisTemplate.setKeySerializer(new StringRedisSerializer());\n        redisTemplate.setValueSerializer(new StringRedisSerializer());\n\n        redisTemplate.setHashKeySerializer(new StringRedisSerializer());\n        redisTemplate.setHashValueSerializer(new StringRedisSerializer());\n\n        return redisTemplate;\n    }\n}<\/code><\/pre>\n<p>ObjectMapper \ub97c \uc774\uc6a9\ud574 Entity &lt;-&gt; Json &lt;-&gt; Redis \ubcc0\ud658\uc744 \uc9c4\ud589\ud569\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-java\">@Service\n@RequiredArgsConstructor\npublic class RedisService {\n    private final RedisTemplate&lt;String, Object&gt; redisTemplate;\n    private final ObjectMapper mapper;\n\n    public void putData(String key, Object value, Long expiredTime) {\n        try {\n            String jsonString = mapper.writeValueAsString(value);\n            redisTemplate.opsForValue().set(key, jsonString, expiredTime, TimeUnit.MILLISECONDS);\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(&quot;Invalid json format: &quot;, e);\n        }\n    }\n\n    public &lt;T&gt; Optional&lt;T&gt; getData(String key, Class&lt;T&gt; valueType) {\n        try {\n            String jsonString = (String) redisTemplate.opsForValue().get(key);\n            if (StringUtils.hasText(jsonString)) {\n                return Optional.ofNullable(mapper.readValue(jsonString, valueType));\n            }\n            return Optional.empty();\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(&quot;Invalid json format: &quot;, e);\n        }\n    }\n\n    public void remove(String key) {\n        redisTemplate.delete(key);\n    }\n}<\/code><\/pre>\n<h2>SecurityConfig<\/h2>\n<p>\ucfe0\ud0a4 \uc778\uc99d \ud544\ud130\uc640 \uc138\uc158 \uc778\uc99d \ud544\ud130\ub97c \uc138\ud305\ud574 \uc90d\ub2c8\ub2e4.<\/p>\n<p>SessionAuthenticationFilter -&gt; CookieAuthenticationFilter -&gt; UsernamePasswordAuthenticationFilter \uc21c\uc11c\ub85c \ud544\ud130(\uc778\uc99d) \uac00 \uc9c4\ud589\ub429\ub2c8\ub2e4.<\/p>\n<p>\uac01 \ud30c\uc77c\ubcc4\ub85c \uc0ac\uc774\uc988\uac00 \ub9ce\uc774 \uc791\uc73c\ub2c8\uae4c \uc77d\uc5b4\ubcf4\uc2dc\uba74 \uc27d\uac8c \uc774\ud574\ub418\ub9ac\ub77c \uc0dd\uac01\ud569\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-java\">@Configuration\n@EnableWebSecurity\n@RequiredArgsConstructor\npublic class SecurityConfig {\n    private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;\n    private final CustomAccessDeniedHandler customAccessDeniedHandler;\n    private final SessionManager sessionManager;\n    private final CookieManager cookieManager;\n    private final SessionAuthenticationProvider sessionAuthenticationProvider;\n\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http\n                .csrf(AbstractHttpConfigurer::disable)\n                .headers((headerConfig) -&gt;\n                        headerConfig.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)\n                )\n                .authorizeHttpRequests((authorizeRequests) -&gt;\n                        authorizeRequests\n                                .requestMatchers(PathRequest.toH2Console()).permitAll()\n                                .requestMatchers(&quot;\/api\/createAdminUser&quot;).permitAll()\n                                .requestMatchers(&quot;\/api\/signin&quot;).permitAll()\n                                .requestMatchers(&quot;\/api\/signup&quot;).permitAll()\n                                .requestMatchers(&quot;\/api\/reissue&quot;).permitAll()\n                                .requestMatchers(&quot;\/favicon.ico&quot;).permitAll()\n                                .requestMatchers(&quot;\/swagger-ui\/**&quot;).permitAll()\n                                .requestMatchers(&quot;\/v3\/api-docs\/**&quot;).permitAll()\n                                .requestMatchers(&quot;\/api\/user&quot;).hasAnyRole(&quot;ADMIN&quot;, &quot;USER&quot;)\n                                .requestMatchers(&quot;\/api\/user\/**&quot;).hasRole(&quot;ADMIN&quot;)\n                                .anyRequest().authenticated()\n                )\n                .exceptionHandling((exceptionConfig) -&gt;\n                        exceptionConfig\n                                .authenticationEntryPoint(customAuthenticationEntryPoint) \/\/ handle 401 Error\n                                .accessDeniedHandler(customAccessDeniedHandler)           \/\/ handle 403 Error\n                )\n                .addFilterBefore(new CookieAuthenticationFilter(cookieManager, sessionAuthenticationProvider), UsernamePasswordAuthenticationFilter.class)\n                .addFilterBefore(new SessionAuthenticationFilter(sessionManager, sessionAuthenticationProvider), CookieAuthenticationFilter.class);\n        return http.build();\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n}<\/code><\/pre>\n<h2>SessionAuthenticationFilter<\/h2>\n<p>\uc138\uc158\uc5d0\uc11c \uc778\uc99d\uc815\ubcf4\ub97c \ucc3e\uc544\uc11c \uc788\uc73c\uba74 Spring Authentication \uc744 \uc0dd\uc131\ud569\ub2c8\ub2e4.<br \/>\n\uc138\uc158\uc774 \uc5c6\uc73c\uba74 \ucfe0\ud0a4 \ud544\ud130\ub85c \ub118\uc5b4\uac11\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\npublic class SessionAuthenticationFilter extends GenericFilterBean {\n    private final SessionManager sessionManager;\n    private final SessionAuthenticationProvider sessionAuthenticationProvider;\n\n    @Override\n    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {\n        Object o = sessionManager.getSession((HttpServletRequest) request);\n        if (o instanceof MyUser myUser) {\n            Authentication authentication = sessionAuthenticationProvider.getAuthentication(myUser);\n            SecurityContextHolder.getContext().setAuthentication(authentication);\n        }\n        chain.doFilter(request, response);\n    }\n}<\/code><\/pre>\n<h2>CookieAuthenticationFilter<\/h2>\n<p>\uc138\uc158\uc5d0 \uc815\ubcf4\uac00 \uc5c6\uc73c\uba74 \ucfe0\ud0a4\uc758 \uc874\uc7ac\ub97c \ud655\uc778\ud558\uace0,<br \/>\n\ucfe0\ud0a4\uac00 \uc788\uc73c\uba74 DB \uc5d0\uc11c \ucfe0\ud0a4 \uc815\ubcf4\uac00 \uc788\ub294\uc9c0 \ud655\uc778\ud569\ub2c8\ub2e4.(\ucfe0\ud0a4 \ub3c4\ub09c \ub300\uc751)<br \/>\nDB \uc5d0 \uc720\ud6a8\ud55c \uc815\ubcf4\uac00 \uc788\uc73c\uba74 \uc138\uc158\ub3c4 \uac19\uc774 \uc0dd\uc131\ud574 \uc90d\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\npublic class CookieAuthenticationFilter extends GenericFilterBean {\n    private final CookieManager cookieManager;\n    private final SessionAuthenticationProvider sessionAuthenticationProvider;\n\n    @Override\n    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {\n        Object o = cookieManager.getSession((HttpServletRequest) request, (HttpServletResponse) response);\n        if (o instanceof MyUser myUser) {\n            Authentication authentication = sessionAuthenticationProvider.getAuthentication(myUser);\n            SecurityContextHolder.getContext().setAuthentication(authentication);\n        }\n        chain.doFilter(request, response);\n    }\n}<\/code><\/pre>\n<h2>RestController<\/h2>\n<p>\ub85c\uadf8\uc778\uc2dc \uc774\uc804 \ub85c\uadf8\uc778 \uc138\uc158\/\ucfe0\ud0a4 \uc815\ubcf4\ub97c \uc0ad\uc81c\ud574 \uc90d\ub2c8\ub2e4.(\ucfe0\ud0a4 \ud0c8\ucde8 \ub300\ube44)<\/p>\n<pre><code class=\"language-java\">@RestController\n@RequestMapping(&quot;\/api&quot;)\n@RequiredArgsConstructor\n@Tag(name = &quot;Auth&quot;, description = &quot;Auth API&quot;)\npublic class AuthController {\n    private final MyUserService myUserService;\n    private final SessionManager sessionManager;\n    private final CookieManager cookieManager;\n\n    @PostMapping(&quot;\/signin&quot;)\n    public ResponseEntity&lt;?&gt; signin(HttpServletRequest request, HttpServletResponse response, @RequestBody LoginDto loginDto) {\n        String username = loginDto.getUsername();\n        String password = loginDto.getPassword();\n\n        MyUser myUser = myUserService.login(request, username, password);\n        sessionManager.deleteSession(request, response);\n        cookieManager.deleteLoginCookie(request, response);\n        sessionManager.createSession(response, myUser);\n        cookieManager.createLoginCookie(myUser, response);\n\n        return ResponseEntity.ok(ResponseDto.res(HttpStatus.ACCEPTED, &quot;OK&quot;));\n    }\n\n    @PostMapping(&quot;\/signout&quot;)\n    public ResponseEntity&lt;?&gt; signout(HttpServletRequest request, HttpServletResponse response) {\n        sessionManager.deleteSession(request, response);\n        cookieManager.deleteLoginCookie(request, response);\n\n        return ResponseEntity.ok(ResponseDto.res(HttpStatus.ACCEPTED, &quot;OK&quot;));\n    }\n}<\/code><\/pre>\n<h2>JWT \ud1a0\ud070\uc744 \uc0dd\uc131\ud55c\ub2e4\uba74?<\/h2>\n<p>\uc138\uc158\uc815\ubcf4\uc5d0 AccessToken\/RefreshToken \ub123\uc5b4\uc8fc\uc5b4\uc57c \ud569\ub2c8\ub2e4.<br \/>\n\ub610\ud55c \ud1a0\ud070 \uc7ac\ubc1c\uae09\uc2dc \uc138\uc158\uc815\ubcf4\ub3c4 \uc218\uc815\ud574 \uc8fc\uc5b4\uc57c \ud569\ub2c8\ub2e4.<br \/>\n(Redis \uc5d0 \uc138\uc158\uc815\ubcf4\uac00 \uc800\uc7a5\ub418\ubbc0\ub85c \ubcc0\uacbd\uc774 \uc26c\uc6cc\uc9d1\ub2c8\ub2e4.)<br \/>\n\uadf8\ub798\uc57c JWT \ud1a0\ud070\uc774 \ub4e4\uc5b4\uc654\uc744 \ub54c DB \uac80\uc99d\uc744 \ud558\uc9c0 \uc54a\uace0 \ud1a0\ud070\uc758 \uc720\ud6a8\uc131\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Spring Security + Session\/Cookie \uae30\ubcf8 \ud750\ub984 \uc138\uc158\uc744 \uc911\uc2ec\uc73c\ub85c \ub85c\uadf8\uc778 \uc0c1\ud0dc\ub97c \uad00\ub9ac\ud569\ub2c8\ub2e4. JWT \ubc29\uc2dd\uc740 \ud1a0\ud070\uc758 \uc720\ud6a8\uc131\uac80\uc0ac\ub97c \ud560 \uc218 \uc5c6\ub2e4\ub294 \ub2e8\uc810\uc774 \uc788\uc2b5\ub2c8\ub2e4. \ub9cc\uc57d \uc720\ud6a8\uc131\uac80\uc0ac\ub97c \ud574\uc57c \ud55c\ub2e4\uba74 DB \uc811\uc18d\uc744 \ud574\uc57c\ud558\uace0, \uc774\ub807\uac8c \ub418\uba74 JWT \uc758 \uc7a5\uc810\uc774 \uc0ac\ub77c\uc9c0\ub294 \ub51c\ub808\ub9c8\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uadf8\ub798\uc11c JWT \ub97c \uc0ac\uc6a9\ud558\ub354\ub77c\ub3c4 \uc138\uc158\uc744 \uc11c\ube0c\ub85c \uc0ac\uc6a9\ud558\uba74, \uc138\uc158\uc758 \uc874\uc7ac\ub85c \ud1a0\ud070\uc758 \uc720\ud6a8\uc131\ub3c4 \uac80\uc99d\ud560 \uc218 \uc788\uace0, \ud544\uc694\uc2dc \uc138\uc158\uc744 \uc0ad\uc81c\ud568\uc73c\ub85c \ud574\uc11c JWT \ud1a0\ud070\uc744\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=8839\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-8839","post","type-post","status-publish","format-standard","hentry","category-spring-boot"],"_links":{"self":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/8839","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=8839"}],"version-history":[{"count":3,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/8839\/revisions"}],"predecessor-version":[{"id":8843,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/8839\/revisions\/8843"}],"wp:attachment":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=8839"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=8839"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=8839"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}