<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>pizza_1.log</title>
        <link>https://velog.io/</link>
        <description>https://github.com/sorrynthx/Spring-Boot/tree/main/springsecurity</description>
        <lastBuildDate>Tue, 19 Sep 2023 08:19:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>pizza_1.log</title>
            <url>https://images.velog.io/images/pizza_1/profile/fc86f65d-aceb-4a58-b4e0-4a81d7ce9aed/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. pizza_1.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/pizza_1" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Spring Security 6 튜토리얼 (5)]]></title>
            <link>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-5</link>
            <guid>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-5</guid>
            <pubDate>Tue, 19 Sep 2023 08:19:48 GMT</pubDate>
            <description><![CDATA[<h1 id="authentication-provider">Authentication Provider</h1>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/07bf5824-ae7e-4dbe-8131-7a493384195a/image.png" alt=""></p>
<p>authenticate() 메서드는 인증 개체를 수신하고 반환합니다. authenticate() 메서드 내부에 사용자 지정 인증 로직을 모두 구현할 수 있습니다.</p>
<p>AuthenticationProvider 인터페이스의 두 번째 방법은 지원(Class&lt;?&gt; Authentication)입니다. 현재 AuthenticationProvider가 제공된 Authentication 개체의 유형을 지원하는 경우 true를 반환하도록 이 방법을 구현합니다.</p>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/27aae663-b5e4-4e5a-bd3d-9f571c8e58ce/image.png" alt=""></p>
<p>CORS (Cross-Origin Resource Sharing, 교차 출처 리소스 공유)
CORS는 웹 리소스가 다른 도메인의 리소스에 접근할 수 있도록 허용하는 메커니즘이다. 예를 들어, 도메인 A에서 도메인 B의 API를 호출하려면 CORS 정책을 설정해야 할 수 있다.</p>
<p>CSRF (Cross-Site Request Forgery, 사이트 간 요청 위조)
CSRF는 공격자가 사용자의 세션을 이용해 악의적인 작업을 수행하는 공격 방법입니다. 사용자가 로그인한 상태에서 공격자가 준비한 페이지를 열면, 그 페이지에서는 사용자의 권한으로 서버에 요청을 보낼 수 있습니다.</p>
<h4 id="프론트-단-유튜버가-미리-만들어-둠">프론트 단 (유튜버가 미리 만들어 둠)</h4>
<p><a href="https://github.com/eazybytes/springsecurity6/tree/3.1.2/section6/bank-app-ui">https://github.com/eazybytes/springsecurity6/tree/3.1.2/section6/bank-app-ui</a></p>
<h4 id="필요한-테이블-쿼리">필요한 테이블 쿼리</h4>
<p><a href="https://github.com/eazybytes/springsecurity6/blob/3.1.2/section6/springsecsection6/src/main/resources/sql/scripts.sql">https://github.com/eazybytes/springsecurity6/blob/3.1.2/section6/springsecsection6/src/main/resources/sql/scripts.sql</a></p>
<h4 id="계정-저장">계정 저장</h4>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/ba7ea7e2-8529-4238-9948-b6c9163bd65f/image.png" alt=""></p>
<h4 id="cors-에러">CORS 에러</h4>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/8ea36558-90d9-49e9-b531-9009e5f63946/image.png" alt=""></p>
<h4 id="cors-란">CORS 란</h4>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/16ecb3cc-94e9-43b1-99ac-64c404eb54a6/image.png" alt="">
CORS는 브라우저 클라이언트에서 실행되는 스크립트가 다른 원본의 리소스와 상호 작용할 수 있도록 하는 프로토콜입니다.
예를 들어 UI 앱이 다른 도메인에서 실행 중인 API 호출을 원할 경우 CORS로 인해 기본적으로 차단됩니다. 대부분의 브라우저에서 도입한 WsC의 사양입니다.</p>
<p>따라서 CORS는 보안 문제/공격이 아니라 서로 다른 원본 간의 데이터/통신 공유를 중단하기 위해 브라우저가 제공하는 기본 보호 기능입니다.</p>
<h4 id="cors-다루기">CORS 다루기</h4>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/778d71bd-8bf8-49f9-86cd-af59aca4c822/image.png" alt=""></p>
<p>서버에 배치된 웹 APP UI가 다른 서버에 배치된 REST 서비스와 통신을 시도하는 유효한 시나리오가 있다면 @CrossOrigin 주석을 사용하여 이러한 종류의 통신을 허용할 수 있습니다. @CrossOrigin을 사용하면 모든 도메인의 클라이언트가 API를 사용할 수 있습니다.</p>
<h4 id="해결책">해결책</h4>
<p>웹 앱 내의 모든 컨트롤러에 @CrossOrigin 주석을 언급하는 대신 아래와 같은 Spring Security를 사용하여 CORS 관련 구성을 전역적으로 정의할 수 있습니다.
<img src="https://velog.velcdn.com/images/pizza_1/post/fcde421e-9858-4278-be46-9dddb63e2f98/image.png" alt=""></p>
<h4 id="코드">코드</h4>
<pre><code>import java.util.Collections;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

import jakarta.servlet.http.HttpServletRequest;

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

        // http://localhost:4200 에 대한 CORS 허용
        http.cors(corsCustomizer -&gt; corsCustomizer.configurationSource(new CorsConfigurationSource() {
            @Override
            public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                CorsConfiguration config = new CorsConfiguration();
                config.setAllowedOrigins(Collections.singletonList(&quot;http://localhost:4200&quot;));
                config.setAllowedMethods(Collections.singletonList(&quot;*&quot;));
                config.setAllowCredentials(true);
                config.setAllowedHeaders(Collections.singletonList(&quot;*&quot;));
                config.setMaxAge(3600L);
                return config;
            }
        }))
        // csrf 비활성화 (사이트 요청 위조) ---&gt; csrf 토큰이 없어도 서버는 응답
        // 스프링에서는 csrf 기본은 활성화 (보안 목적) ---&gt; csrf 토큰을 url에 포함해야 서버는 응답
        .csrf((csrf) -&gt; csrf.disable()) 
                .authorizeHttpRequests((requests)-&gt;requests
                        .requestMatchers(&quot;/myAccount&quot;,&quot;/myBalance&quot;,&quot;/myLoans&quot;,&quot;/myCards&quot;, &quot;/user&quot;).authenticated()
                        .requestMatchers(&quot;/notices&quot;,&quot;/contact&quot;,&quot;/register&quot;).permitAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }

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


}</code></pre><h4 id="성공">성공</h4>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/4642f05e-8eee-4858-8113-6dabb5395042/image.png" alt=""></p>
<h4 id="crsf-비활성화-시-주석-처리">crsf 비활성화 시, (주석 처리)</h4>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/ae5e39f3-0c40-4c7a-a0dd-089a74f89803/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/d16c5ffc-ac48-46f6-8f0b-c99db8c2eb1f/image.png" alt=""></p>
<p>status: 401 Unauthorized 확인</p>
<h4 id="cross-site-request-forgery-csrf-사이트-간-요청-위조">Cross-site Request Forgery (CSRF): 사이트 간 요청 위조</h4>
<p><img src="blob:https://velog.io/c50d89e4-72fb-4757-a1f9-8aa296070864" alt="업로드중.."></p>
<p>일반적으로 CSRF 또는 XSRF(Cross-Site Request Fuggement) 공격은 사용자의 명시적인 동의 없이 웹 어플리케이션에서 동작을 수행하는 것을 목적으로 하며, 일반적으로 사용자의 압흔을 직접적으로 훔치는 것이 아니라 사용자를 이용하여 자신의 의지 없이 동작을 수행하는 것을 목적으로 합니다.</p>
<p>netflix.com 웹사이트와 공격자의 웹사이트 evil.com 를 사용하고 있다고 생각합니다</p>
<p>1단계 : Netfilx.com 에 대한 넷플릭스 사용자 로그인과 넷플릭스의 백엔드 서버는 Netflix.com 도메인 이름에 대해 브라우저에 저장할 쿠키를 제공합니다</p>
<p>---&gt;사용자가 자격 증명을 제출하고 Netflix.com 에 로그인합니다</p>
<p>&lt;---넷플릭스 서버는 Netflix.com 도메인 이름을 기반으로 쿠키를 만들고 사용자 브라우저에 저장합니다.</p>
<p>2단계 : 같은 넷플릭스 사용자가 브라우저의 다른 탭에서 evil.com 웹사이트를 엽니다.</p>
<p>---&gt;사용자가 evil.com 에 호스팅된 악의적인 블로그/사이트에 액세스했습니다</p>
<p>&lt;---evil.com 은 넷플릭스 계정의 이메일을 변경하기 위한 악성 링크가 내장된 웹 페이지를 반환합니다. 그러나 링크는 아이폰에서 &quot;90%&quot; OFF와 같은 텍스트로 나타납니다.</p>
<p><img src="blob:https://velog.io/28a22cfc-9fcf-4fb5-9f30-93aea0298d69" alt="업로드중.."></p>
<p>3단계 : 사용자가 Netflix.com 에 요청하는 악성 링크를 유혹하여 클릭하였고, 동일한 브라우저에 이미 존재하는 로그인 쿠키와 동일한 도메인 Netflix.com 에 이메일 변경 요청이 이루어지므로 Netflix.com 의 백엔드 서버는 요청이 온 곳을 구별할 수 없습니다. 따라서 여기서 evil.com 은 Netflix.com UI 페이지에서 오는 것처럼 요청을 위조하였습니다.</p>
<p>---&gt;사용자는 아래와 같은 내용이 있는 evil.com 의 링크를 클릭합니다</p>
<p>&lt;---쾅!! 넷플릭스 계정 이메일이 바뀌었네요.</p>
<h4 id="csrf-공격에-대한-솔루션">CSRF 공격에 대한 솔루션</h4>
<p><img src="blob:https://velog.io/617df03f-6474-4d85-b89c-0b45fe5a9992" alt="업로드중.."></p>
<p>CSRF 공격을 물리치기 위해서는 응용 프로그램의 사용자 인터페이스를 통해 HTTP 요청이 합법적으로 생성되었는지 여부를 판단할 수 있는 방법이 필요합니다. 이를 달성하기 위한 가장 좋은 방법은 CSRF 토큰을 통해서입니다.</p>
<p>CSRF 토큰은 CSRF 공격을 방지하기 위해 사용되는 안전한 랜덤 토큰입니다. 토큰은 사용자 세션마다 고유해야 하며 추측하기 어렵도록 큰 랜덤 값이어야 합니다.</p>
<p>이것이 어떻게 CSRF 공격을 해결하는지 이전 넷플릭스의 예를 다시 들어보겠습니다.</p>
<p>1단계 : Netflix.com 에 대한 넷플릭스 사용자 로그인과 넷플릭스의 백엔드 서버는 이 특정 사용자 세션에 대해 무작위로 생성된 고유 CSRF 토큰과 함께 도메인 이름 Netflix.com 에 대해 브라우저에 저장할 쿠키를 제공합니다.
CSRF 토큰은 세션 쿠키에 노출되지 않도록 HTML 양식의 숨겨진 매개변수 내에 삽입됩니다.</p>
<p>2단계 : 같은 넷플릭스 사용자가 브라우저의 다른 탭에서 evil.com 웹사이트를 엽니다.</p>
<p><img src="blob:https://velog.io/15606fdc-d575-48e2-ba8b-03d48827cdaf" alt="업로드중.."></p>
<p>3단계 : 사용자가 Netflix.com 에 요청하는 악성 링크를 유혹하여 클릭하였습니다. 그리고 동일한 브라우저에 이미 존재하는 로그인 쿠키와 이메일 변경 요청이 Netflix.com 에 이루어졌기 때문에 이번에는 Netflix.com 백엔드 서버가 쿠키와 함께 CSRF 토큰을 기대합니다. CSRF 토큰은 로그인 작업 중에 생성된 초기 값과 동일해야 합니다.</p>
<p>CSRF 토큰은 애플리케이션 서버가 최종 사용 요청이 동일한 App UI에서 오는지 여부를 확인하는 데 사용됩니다. 애플리케이션 서버는 CSRF 토큰이 테스트와 일치하지 않으면 요청을 거부합니다.</p>
<h4 id="disable-csrf-protection-csrf-보호-비활성화">Disable CSRF PROTECTION (CSRF 보호 비활성화)</h4>
<p><img src="blob:https://velog.io/bad7a078-8088-49fd-ab64-87551cb47637" alt="업로드중.."></p>
<p>기본적으로 Spring Security는 웹 애플리케이션에 구현된 CSRF 솔루션이 없는 경우 오류 403으로 모든 HTTP POST, PUT, DELETE, PATCH 작업을 차단합니다. Spring Security에서 제공하는 CSRF 보호를 비활성화하여 이 기본 동작을 변경할 수 있습니다.</p>
<pre><code>
✅ 추가 해야 하는 코드!!!

// 핸들러는 CSRF 토큰을 요청 속성(attribute)으로 설정하는 역할
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();

// CSRF 토큰의 이름을 &quot;_csrf&quot;로 설정. 즉, 요청이 들어올 때 &quot;_csrf&quot;라는 이름의 요청 속성(attribute)에 CSRF 토큰 값이 저장
requestHandler.setCsrfRequestAttributeName(&quot;_csrf&quot;);

// CSRF 활성화 (조건)
.csrf((csrf) -&gt; csrf.csrfTokenRequestHandler(requestHandler)                            // CSRF 토큰을 요청 핸들러에 설정
                    .ignoringRequestMatchers(&quot;/contact&quot;, &quot;/register&quot;)                    // 특정 URL 경로(/contact, /register)에 대해서는 CSRF 검증을 무시하도록 설정
                    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))// CSRF 토큰을 어떻게 저장할지 설정하는 부분 (쿠키를 사용하며, HttpOnly 속성을 false로 설정 -&gt;  JavaScript에서 쿠키에 접근)

</code></pre><pre><code>전체 코드
package com.example.springsecurity65.config;

import java.util.Collections;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

import jakarta.servlet.http.HttpServletRequest;

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

        // 핸들러는 CSRF 토큰을 요청 속성(attribute)으로 설정하는 역할
        CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();

        // CSRF 토큰의 이름을 &quot;_csrf&quot;로 설정. 즉, 요청이 들어올 때 &quot;_csrf&quot;라는 이름의 요청 속성(attribute)에 CSRF 토큰 값이 저장
        requestHandler.setCsrfRequestAttributeName(&quot;_csrf&quot;);

        // http://localhost:4200 에 대한 CORS 허용
        http.cors(corsCustomizer -&gt; corsCustomizer.configurationSource(new CorsConfigurationSource() {
            @Override
            public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                CorsConfiguration config = new CorsConfiguration();
                config.setAllowedOrigins(Collections.singletonList(&quot;http://localhost:4200&quot;));
                config.setAllowedMethods(Collections.singletonList(&quot;*&quot;));
                config.setAllowCredentials(true);
                config.setAllowedHeaders(Collections.singletonList(&quot;*&quot;));
                config.setMaxAge(3600L);
                return config;
            }
        }))
        // csrf 비활성화 (사이트 요청 위조) ---&gt; csrf 토큰이 없어도 서버는 응답
        // 스프링에서는 csrf 기본은 활성화 (보안 목적) ---&gt; csrf 토큰을 url에 포함해야 서버는 응답 
        //.csrf((csrf) -&gt; csrf.disable()) ---&gt; CSRF 비활성화 

        // CSRF 활성화 (조건)
        .csrf((csrf) -&gt; csrf.csrfTokenRequestHandler(requestHandler)                            // CSRF 토큰을 요청 핸들러에 설정
                            .ignoringRequestMatchers(&quot;/contact&quot;, &quot;/register&quot;)                    // 특정 URL 경로(/contact, /register)에 대해서는 CSRF 검증을 무시하도록 설정
                            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))// CSRF 토큰을 어떻게 저장할지 설정하는 부분 (쿠키를 사용하며, HttpOnly 속성을 false로 설정 -&gt;  JavaScript에서 쿠키에 접근) 


                .authorizeHttpRequests((requests)-&gt;requests
                        .requestMatchers(&quot;/myAccount&quot;,&quot;/myBalance&quot;,&quot;/myLoans&quot;,&quot;/myCards&quot;, &quot;/user&quot;).authenticated()
                        .requestMatchers(&quot;/notices&quot;,&quot;/contact&quot;,&quot;/register&quot;).permitAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }

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


}</code></pre><h4 id="결과">결과</h4>
<p><img src="blob:https://velog.io/fc069413-6a05-4b70-ab0c-1ddb9dfe6837" alt="업로드중.."></p>
<p><img src="blob:https://velog.io/c03cfe43-3748-4e56-90ae-ae263c91efc8" alt="업로드중..">
<img src="blob:https://velog.io/f14fcefb-dfe0-4481-b4ae-9851e4f5c145" alt="업로드중.."></p>
<p>&quot;/regist&quot;, &quot;/contact&quot; 에 대한 접근 성공</p>
<h4 id="클라이언트에게-csrf-토큰-안전하게-전달">클라이언트에게 CSRF 토큰 안전하게 전달</h4>
<p>일반적으로 CSRF 토큰은 클라이언트와 서버 간의 요청에서 보안을 강화하기 위해 사용됩니다. 이 필터를 통해 서버는 응답 헤더에 CSRF 토큰 정보를 담아 클라이언트에게 전달하게 됩니다. 클라이언트는 이 토큰을 사용하여 이후의 요청에서 서버로 보내면, 서버는 이 토큰을 검증하여 요청이 유효한지 판단하게 됩니다.</p>
<pre><code>✅ 코드 추가 !!!

✅✅✅
http.securityContext((context) -&gt; context
                .requireExplicitSave(false))
                .sessionManagement(session -&gt; session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))


// CSRF 활성화 (조건)
.csrf((csrf) -&gt; csrf.csrfTokenRequestHandler(requestHandler)                            // CSRF 토큰을 요청 핸들러에 설정
                    .ignoringRequestMatchers(&quot;/contact&quot;, &quot;/register&quot;)                    // 특정 URL 경로(/contact, /register)에 대해서는 CSRF 검증을 무시하도록 설정
                    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))// CSRF 토큰을 어떻게 저장할지 설정하는 부분 (쿠키를 사용하며, HttpOnly 속성을 false로 설정 -&gt;  JavaScript에서 쿠키에 접근) 

                ✅✅✅ // CsrfCookieFilter 필터( 이 필터는 요청마다 한 번씩 실행되는 OncePerRequestFilter를 상속받아 구현) 추가 (Basic 인증이 처리된 후에 이 필터가 실행)
                .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)</code></pre><pre><code>전체 코드
package com.example.springsecurity65.config;

import java.util.Collections;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

import com.example.springsecurity65.filter.CsrfCookieFilter;

import jakarta.servlet.http.HttpServletRequest;

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

        // 핸들러는 CSRF 토큰을 요청 속성(attribute)으로 설정하는 역할
        CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();

        // CSRF 토큰의 이름을 &quot;_csrf&quot;로 설정. 즉, 요청이 들어올 때 &quot;_csrf&quot;라는 이름의 요청 속성(attribute)에 CSRF 토큰 값이 저장
        requestHandler.setCsrfRequestAttributeName(&quot;_csrf&quot;);

        // requireExplicitSave(false)는 보안 컨텍스트가 명시적으로 저장되어야 하는지 여부를 설정
        http.securityContext((context) -&gt; context

                // false로 설정하면, 명시적으로 저장하지 않아도 됨.
                .requireExplicitSave(false))

                // sessionCreationPolicy(SessionCreationPolicy.ALWAYS)는 항상 새로운 세션을 생성하도록 설정합니다. 
                .sessionManagement(session -&gt; session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))

            // http://localhost:4200 에 대한 CORS 허용
            .cors(corsCustomizer -&gt; corsCustomizer.configurationSource(new CorsConfigurationSource() {
            @Override
            public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                CorsConfiguration config = new CorsConfiguration();
                config.setAllowedOrigins(Collections.singletonList(&quot;http://localhost:4200&quot;));
                config.setAllowedMethods(Collections.singletonList(&quot;*&quot;));
                config.setAllowCredentials(true);
                config.setAllowedHeaders(Collections.singletonList(&quot;*&quot;));
                config.setMaxAge(3600L);
                return config;
            }
        }))
        // csrf 비활성화 (사이트 요청 위조) ---&gt; csrf 토큰이 없어도 서버는 응답
        // 스프링에서는 csrf 기본은 활성화 (보안 목적) ---&gt; csrf 토큰을 url에 포함해야 서버는 응답 
        //.csrf((csrf) -&gt; csrf.disable()) ---&gt; CSRF 비활성화 

        // CSRF 활성화 (조건)
        .csrf((csrf) -&gt; csrf.csrfTokenRequestHandler(requestHandler)                            // CSRF 토큰을 요청 핸들러에 설정
                            .ignoringRequestMatchers(&quot;/contact&quot;, &quot;/register&quot;)                    // 특정 URL 경로(/contact, /register)에 대해서는 CSRF 검증을 무시하도록 설정
                            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))// CSRF 토큰을 어떻게 저장할지 설정하는 부분 (쿠키를 사용하며, HttpOnly 속성을 false로 설정 -&gt;  JavaScript에서 쿠키에 접근) 

                            // CsrfCookieFilter 필터( 이 필터는 요청마다 한 번씩 실행되는 OncePerRequestFilter를 상속받아 구현) 추가 (Basic 인증이 처리된 후에 이 필터가 실행)
                            .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)

                .authorizeHttpRequests((requests)-&gt;requests
                        .requestMatchers(&quot;/myAccount&quot;,&quot;/myBalance&quot;,&quot;/myLoans&quot;,&quot;/myCards&quot;, &quot;/user&quot;).authenticated()
                        .requestMatchers(&quot;/notices&quot;,&quot;/contact&quot;,&quot;/register&quot;).permitAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }

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


}
</code></pre><p>sessionCreationPolicy(SessionCreationPolicy.ALWAYS)는 항상 새로운 세션을 생성하도록 설정합니다. 이 설정은 다음과 같은 경우에 유용할 수 있습니다:</p>
<p>1.사용자가 로그인할 때마다 새로운 세션을 생성하려는 경우
2.사용자가 애플리케이션에 접근할 때마다 세션 정보를 새로 생성하려는 경우
기본적으로, Spring Security는 필요할 때만 세션을 생성합니다. 하지만 이 설정을 ALWAYS로 하면, 요청이 들어올 때마다 새로운 세션을 생성하게 됩니다. 이것은 특별한 요구 사항이 있을 때 유용하게 사용될 수 있습니다.</p>
<h4 id="클래스-생성csrfcookiefilter">클래스 생성(CsrfCookieFilter)</h4>
<pre><code>package com.example.springsecurity65.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

public class CsrfCookieFilter extends OncePerRequestFilter {

    // doFilterInternal 메서드를 오버라이드하여 실제 필터의 로직을 구현
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // CsrfToken 클래스의 인스턴스를 요청 속성에서 가져오기 (현재 요청에 대한 CSRF 토큰 정보를 가져오기)
        CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());

        // 응답 헤더에 CSRF 토큰을 설정
        if(null != csrfToken.getHeaderName()){
            // CSRF 토큰의 이름(헤더 이름)과 값을 응답 헤더에 설정 -&gt; 클라이언트 측에서 이 헤더를 읽어 CSRF 토큰을 알 수 있게 됨.
            response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
        }

        // 다음 필터로 요청과 응답을 전달
        filterChain.doFilter(request, response);
    }

}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 6 튜토리얼 (4)]]></title>
            <link>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-4</link>
            <guid>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-4</guid>
            <pubDate>Wed, 23 Aug 2023 02:24:36 GMT</pubDate>
            <description><![CDATA[<h2 id="암호관리-방식">암호관리 방식</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/e4bea32f-888e-4c6b-9bf1-7fef5441f743/image.png" alt=""></p>
<p>🟢 [인코딩 (Encoding)]
인코딩은 데이터를 한 형태에서 다른 형태로 변환하는 과정으로 정의되며, 암호학과는 관련이 없습니다.</p>
<p>이 과정은 비밀이 없으며 완전히 되돌릴 수 있습니다.</p>
<p>인코딩은 데이터를 보호하는 데 사용될 수 없습니다. 다음은 인코딩에 사용되는 다양한 공개적으로 사용 가능한 알고리즘입니다.</p>
<p>예: ASCII, BASE64, UNICODE</p>
<p>🟢 [암호화 (Encryption)]
암호화는 기밀성을 보장하는 방식으로 데이터를 변환하는 과정으로 정의됩니다.</p>
<p>기밀성을 달성하기 위해, 암호화는 암호학 용어로 &quot;키&quot;라고 부르는 방식을 사용해야 합니다.</p>
<p>&quot;키&quot;의 도움으로 복호화를 사용하면 암호화를 되돌릴 수 있습니다. &quot;키&quot;가 기밀인 한, 암호화는 안전한 것으로 간주될 수 있습니다.</p>
<p>🟢 [해싱 (Hashing)]
해싱에서는 해싱 함수를 사용하여 데이터를 해시 값으로 변환합니다.</p>
<p>한 번 해싱된 데이터는 되돌릴 수 없습니다. 생성된 해시 값에서 원래 데이터를 알 수 없습니다.</p>
<p>임의의 데이터와 해싱 알고리즘의 출력이 주어지면 원래 입력 데이터와 일치하는지 확인할 수 있으며 원래 데이터를 볼 필요가 없습니다.</p>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/c888a6ad-3e34-4a9f-ac89-ecfd5b964c61/image.png" alt=""></p>
<p>노출 되도 상관없는 데이터 -&gt; 인코딩 (ex. 카테고리)
DB에서 꺼낸 뒤 다시 볼 데이터 -&gt; 암호화 (ex. 핸드폰, 이메일)
다시 볼 필요 없고, 확인만 필요한 데이터 -&gt; 해싱 (비밀번호)</p>
<h2 id="암호화-프로세스">암호화 프로세스</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/af664f88-815d-4ce3-adfd-86d4aab30928/image.png" alt=""></p>
<h5 id="해싱된-비밀번호를-db에서-가져온-뒤-맞는지만-확인">해싱된 비밀번호를 DB에서 가져온 뒤, 맞는지만 확인.</h5>
<h2 id="passwordencoder-메소드">PasswordEncoder 메소드</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/9cb0aed8-902f-43fd-8edb-57cfc6a79c9c/image.png" alt=""></p>
<h4 id="보통-bcryptpasswordencoder-많이-사용">보통 BCryptPasswordEncoder 많이 사용</h4>
<h5 id="encode--암호화-메소드">encode : 암호화 메소드</h5>
<h5 id="mathces-일치-여부-확인">mathces: 일치 여부 확인</h5>
<h5 id="upgradeencoding-더-강력하게-업그레이드-해야-하는지-확인-시간이-지남에-따라-덜-안전해지는-경우">upgradeEncoding: 더 강력하게 업그레이드 해야 하는지 확인 (시간이 지남에 따라 덜 안전해지는 경우)</h5>
<h5 id="upgradeencoding-예시">upgradeEncoding 예시</h5>
<pre><code>@Override
public boolean upgradeEncoding(String encodedPassword) {
    // Check the encoding algorithm used for the password
    // Return true if an upgrade is needed, false otherwise
    return !encodedPassword.startsWith(&quot;{bcrypt}&quot;);
}
</code></pre><h2 id="🟣-코드">🟣 코드</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/0f523b6c-848b-4d1b-b37e-62b82513fce0/image.png" alt=""></p>
<p>스프링부트 3.1.2
자바: OpenJDK 17
Gradle: 8.3</p>
<p>🟢 ProjectSecurityConfig.java</p>
<pre><code>import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

        // csrf 비활성화 (사이트 요청 위조) ---&gt; csrf 토큰이 없어도 서버는 응답
        // 스프링에서는 csrf 기본은 활성화 (보안 목적) ---&gt; csrf 토큰을 url에 포함해야 서버는 응답
        http.csrf((csrf) -&gt; csrf.disable()) 
                .authorizeHttpRequests((requests)-&gt;requests
                        .requestMatchers(&quot;/myAccount&quot;,&quot;/myBalance&quot;,&quot;/myLoans&quot;,&quot;/myCards&quot;).authenticated()
                        .requestMatchers(&quot;/notices&quot;,&quot;/contact&quot;,&quot;/register&quot;).permitAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }

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


}</code></pre><p>🟢 LoginController.java</p>
<pre><code>import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.example.springsecurity63.model.Customer;
import com.example.springsecurity63.repository.CustomerRepository;

@RestController
public class LoginController {

    @Autowired
    private CustomerRepository customerRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostMapping(&quot;/register&quot;)
    public ResponseEntity&lt;String&gt; registerUser(@RequestBody Customer customer) {

        Customer savedCustomer = null;
        ResponseEntity response = null;

        try {
            // encrypt
            String hashPwd = passwordEncoder.encode(customer.getPwd());
            customer.setPwd(hashPwd);

            // save
            savedCustomer = customerRepository.save(customer);

            if (savedCustomer.getId() &gt; 0) {
                response = ResponseEntity
                        .status(HttpStatus.CREATED)
                        .body(&quot;Given user details are successfully registered&quot;);
            }
        } catch (Exception ex) {
            response = ResponseEntity
                    .status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(&quot;An exception occured due to &quot; + ex.getMessage());
        }
        return response;
    }

}</code></pre><p>결과
<img src="https://velog.velcdn.com/images/pizza_1/post/124488d4-943a-4595-b0cb-a137e7105484/image.png" alt=""></p>
<p>만약 id 2번으로 로그인 시도 시,
<img src="https://velog.velcdn.com/images/pizza_1/post/85e5fc20-f59c-465b-900d-1a7d84f7b696/image.png" alt="">
자격 증명 실패
<img src="https://velog.velcdn.com/images/pizza_1/post/90ecc2a1-c5f3-48de-826a-fd6954ff1132/image.png" alt=""></p>
<p>소스코드:
<a href="https://github.com/sorrynthx/Spring-Boot">https://github.com/sorrynthx/Spring-Boot</a></p>
<p>그 다음에는 Implement and Customize the Authentication Provider에 대해서 작성.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 6 튜토리얼 (3)]]></title>
            <link>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-3</link>
            <guid>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-3</guid>
            <pubDate>Mon, 21 Aug 2023 01:55:50 GMT</pubDate>
            <description><![CDATA[<h2 id="🍃-스프링-시큐리티-api-테스트">🍃 스프링 시큐리티 API 테스트</h2>
<h3 id="securityconfigjava-수정">SecurityConfig.java 수정</h3>
<h4 id="🟢-csrf-사이트간-요청-위조-비활성화">🟢 csrf (사이트간 요청 위조) 비활성화</h4>
<p>csrf   활성화 -&gt; crsf 토큰  필요 (기본)
csrf 비활성화 -&gt; crsf 토큰 불필요 (옵션)</p>
<p>** 더 자세한 설명은 여기서 
<a href="https://junhyunny.github.io/information/security/spring-boot/spring-security/cross-site-reqeust-forgery/">https://junhyunny.github.io/information/security/spring-boot/spring-security/cross-site-reqeust-forgery/</a></p>
<p>🟣 코드</p>
<h4 id="crsf-비활성화-register-추가-등록">crsf 비활성화, /register 추가 등록</h4>
<pre><code>package com.example.springsecurity62.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

        // csrf 비활성화 (사이트 요청 위조) ---&gt; csrf 토큰이 없어도 서버는 응답
        // 스프링에서는 csrf 기본은 활성화 (보안 목적) ---&gt; csrf 토큰을 url에 포함해야 서버는 응답
        http.csrf((csrf) -&gt; csrf.disable()) 
                .authorizeHttpRequests((requests)-&gt;requests
                        .requestMatchers(&quot;/myAccount&quot;,&quot;/myBalance&quot;,&quot;/myLoans&quot;,&quot;/myCards&quot;).authenticated()
                        .requestMatchers(&quot;/notices&quot;,&quot;/contact&quot;,&quot;/register&quot;).permitAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
        return http.build();
    }

     /**
     * NoOpPasswordEncoder is not recommended for production usage.
     * Use only for non-prod.
     *
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    /*
     * @Bean public UserDetailsService userDetailsService(DataSource dataSource) {
     * return new JdbcUserDetailsManager(dataSource); }
     */

}
</code></pre><h4 id="로그인-컨트롤러-만들기-사용자-등록">로그인 컨트롤러 만들기 (사용자 등록)</h4>
<pre><code>import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.example.springsecurity62.model.Customer;
import com.example.springsecurity62.repository.CustomerRepository;

@RestController
public class LoginController {

    @Autowired
    private CustomerRepository customerRepository;

    @PostMapping(&quot;/register&quot;)
    public ResponseEntity&lt;String&gt; registerUser(@RequestBody Customer customer) {
        System.out.println(&quot;sssss&quot;);
        Customer savedCustomer = null;
        ResponseEntity response = null;
        try {
            savedCustomer = customerRepository.save(customer);
            if (savedCustomer.getId() &gt; 0) {
                response = ResponseEntity
                        .status(HttpStatus.CREATED)
                        .body(&quot;Given user details are successfully registered&quot;);
            }
        } catch (Exception ex) {
            response = ResponseEntity
                    .status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(&quot;An exception occured due to &quot; + ex.getMessage());
        }
        return response;
    }

}</code></pre><p>🟢 구조
<img src="https://velog.velcdn.com/images/pizza_1/post/427c54fd-e6c7-4e40-af94-f6ef6256b1e4/image.png" alt=""></p>
<p>🟢 테스트
<img src="https://velog.velcdn.com/images/pizza_1/post/e5cee311-3e70-44a5-90c8-303963a4af9c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/cb590d3e-5985-449b-b3fc-c6e2a7041263/image.png" alt=""></p>
<h2 id="플로우">플로우</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/22b5d1a2-dd3f-4085-940f-98fb1537d57d/image.png" alt=""></p>
<h3 id="해석">해석</h3>
<ol>
<li><p>사용자가 처음으로 보안 페이지에 액세스를 시도합니다.
사용자가 처음으로 보안된 페이지에 접근을 시도하면, 내부적으로 몇 가지 필터가 작동합니다.</p>
</li>
<li><p>AuthorizationFilter, DefaultLoginPageGeneratingFilter 등의 필터가 사용자가 로그인하지 않았음을 감지하고 로그인 페이지로 리다이렉트합니다.
이러한 필터들은 사용자가 로그인하지 않았다는 것을 확인하고 로그인 페이지로 사용자를 리다이렉트합니다.</p>
</li>
<li><p>사용자가 자신의 자격 증명을 입력하고, 요청이 필터에 의해 가로채집니다.
사용자가 로그인 정보를 입력하면, 이 요청은 Spring Security 필터에 의해 처리됩니다.</p>
</li>
<li><p>UsernamePasswordAuthenticationFilter와 같은 필터는 요청에서 사용자 이름과 비밀번호를 추출하고, Authentication 인터페이스의 구현체인 UsernamePasswordAuthenticationToken 객체를 생성합니다. 그리고 ProviderManager의 authenticate() 메서드를 호출합니다.
해당 필터는 로그인 정보를 추출하고 인증을 위한 객체를 생성하여 인증 과정을 시작합니다.</p>
</li>
<li><p>ProviderManager는 AuthenticationManager의 구현체로, 주어진 인증 객체 스타일을 지원하는 사용 가능한 AuthenticationProvider 목록을 식별합니다. 기본적으로 ProviderManager가 DaoAuthenticationProvider의 authenticate() 메서드를 호출합니다.
이 단계에서는 적절한 인증 제공자를 선택하여 인증 과정을 계속 진행합니다.</p>
</li>
<li><p>DaoAuthenticationProvider는 EazyBankUserDetails의 loadUserByUsername() 메서드를 호출하여 사용자 세부 정보를 로드합니다. 사용자 세부 정보가 로드되면 기본 비밀번호 인코더 구현을 사용하여 비밀번호를 비교하고 사용자가 정통한지 여부를 검증합니다.
이 단계에서는 사용자의 세부 정보를 로드하고 비밀번호를 검증하여 인증 과정을 완료합니다.</p>
</li>
<li><p>마지막으로 인증 성공 여부와 관련된 Authentication 객체를 ProviderManager에 반환합니다.</p>
</li>
<li><p>ProviderManager는 인증이 성공했는지 확인합니다. 그렇지 않으면 다른 사용 가능한 AuthenticationProvider로 시도합니다. 그렇지 않으면 필터에 인증 세부 정보를 반환합니다.
인증의 성공 여부를 확인하고 처리합니다.</p>
</li>
<li><p>인증 객체는 필터에 의해 SecurityContext 객체에 저장되어 향후 사용되고, 응답이 최종 사용자에게 반환됩니다.
인증 정보는 나중에 사용할 수 있도록 저장되며, 응답이 사용자에게 전달됩니다.</p>
</li>
</ol>
<h2 id="플로우-1">플로우</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/53bc02e9-3efb-4f44-b987-83dc54cce30a/image.png" alt=""></p>
<h2 id="비밀번호-일치-확인">비밀번호 일치 확인</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/defd8061-0222-4665-8b91-7a217b0c9721/image.png" alt=""></p>
<p>현재는 plain text 으로 비밀번호가 저장.</p>
<p>다음에는 암호화 해서 저장하고 비밀번호가 일치하는지 확인하는 프로세스를 추가 해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 6 튜토리얼 (2)]]></title>
            <link>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-2</link>
            <guid>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-2</guid>
            <pubDate>Fri, 18 Aug 2023 07:57:04 GMT</pubDate>
            <description><![CDATA[<h1 id="스프링-시큐리티-사용자-관리-inmemoryuserdetailsmanager-jdbcuserdetailsmanager-ldapuserdetailsmanager">스프링 시큐리티 사용자 관리 (InMemoryUserDetailsManager, JdbcUserDetailsManager, LdapUserDetailsManager)</h1>
<p>❗LdapUserDetailsManager는 회사에서 사용 안하기 때문에 튜토리얼 제외</p>
<h2 id="inmemoryuserdetailsmanager-withdefaultpasswordencoder-방식">InMemoryUserDetailsManager (withDefaultPasswordEncoder 방식)</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/3cc9fc51-a015-4971-ab40-e7965219cd55/image.png" alt=""></p>
<p> application.properties에 단일 사용자를 정의하는 대신, InMemoryUserDetailsManager를 이용하여 여러 사용자 정의해보기</p>
<p>🟣 코드</p>
<pre><code>package com.example.springsecurity61.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(
                (requests) -&gt; requests.requestMatchers(&quot;/myAccount&quot;, &quot;/myBalance&quot;, &quot;/myLoans&quot;, &quot;/myCards&quot;).authenticated()
                                      .requestMatchers(&quot;/notices&quot;, &quot;/contact&quot;).permitAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());

        return http.build();
    }

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {

        // Approach1 passwordEncoder 없이 테스트 (deprecated 뜸)

        UserDetails admin = User.withDefaultPasswordEncoder()
                                .username(&quot;admin&quot;)
                                .password(&quot;123123&quot;)
                                .authorities(&quot;admin&quot;)
                                .build();

        UserDetails user = User.withDefaultPasswordEncoder()
                               .username(&quot;user&quot;)
                               .password(&quot;123123&quot;)
                               .authorities(&quot;read&quot;)
                               .build();

        return new InMemoryUserDetailsManager(admin, user);
    }



}</code></pre><h2 id="inmemoryuserdetailsmanager-passwordencorder-방식">InMemoryUserDetailsManager (PasswordEncorder 방식)</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/b53881d6-41ad-439e-a603-73a5f2c0bddf/image.png" alt=""></p>
<pre><code>package com.example.springsecurity61.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(
                (requests) -&gt; requests.requestMatchers(&quot;/myAccount&quot;, &quot;/myBalance&quot;, &quot;/myLoans&quot;, &quot;/myCards&quot;).authenticated()
                                      .requestMatchers(&quot;/notices&quot;, &quot;/contact&quot;).permitAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());

        return http.build();
    }

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {

        // Approach 2 NoOpPasswordEncoder Bean 사용
        UserDetails admin = User.withUsername(&quot;admin&quot;)
                                .password(&quot;123132&quot;)
                                .authorities(&quot;admin&quot;)
                                .build();
        UserDetails user = User.withUsername(&quot;user&quot;)
                                .password(&quot;123123&quot;)
                                .authorities(&quot;read&quot;)
                                .build();

        return new InMemoryUserDetailsManager(admin, user);
    }

     /**
     * NoOpPasswordEncoder is not recommended for production usage.
     * Use only for non-prod.
     *
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

}</code></pre><h2 id="user-management-사용자-관리-interface">User Management (사용자 관리 Interface)</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/55db5ffc-7799-4926-9600-23eafde7459c/image.png" alt=""></p>
<p>🟢 UserDetailsService (인터페이스)</p>
<p>목적: 특정 사용자의 정보를 로드하는 핵심 인터페이스입니다.
메서드: loadUserByUsername(String username)
설명: 사용자 이름을 기반으로 사용자의 세부 정보를 로드합니다. 일반적으로 인증 과정에서 사용됩니다.</p>
<p>🟢 UserDetailsManager (인터페이스)</p>
<p>목적: UserDetailsService의 확장으로, 새 사용자를 생성하고 기존 사용자를 업데이트하는 기능을 제공합니다.
메서드:
createUser(UserDetails user): 새 사용자 생성
updateUser(UserDetails user): 기존 사용자 업데이트
deleteUser(String username): 사용자 삭제
changePassword(String oldPwd, String newPwd): 비밀번호 변경
userExists(String username): 사용자 존재 여부 확인
설명: 사용자 관리 기능을 위한 중요한 인터페이스로, 사용자 생성, 업데이트, 삭제 등의 작업을 수행할 수 있습니다.</p>
<p>🟢 InMemoryUserDetailsManager (클래스)
🟢 JdbcUserDetailsManager (클래스)
🟢 LdapUserDetailsManager (클래스)</p>
<p>목적: Spring Security 팀에서 제공하는 UserDetailsManager의 구현 예제 클래스들입니다.
설명:
InMemoryUserDetailsManager: 메모리 내에서 사용자 정보를 관리합니다. 주로 개발 및 테스트에 사용됩니다.
JdbcUserDetailsManager: 관계형 데이터베이스를 통해 사용자 정보를 관리합니다.
LdapUserDetailsManager: LDAP 데이터베이스를 통해 사용자 정보를 관리합니다.</p>
<h2 id="userdetails--authentication-관계-사용자-세부-정보와-인증">UserDetails &amp; Authentication 관계 (사용자 세부 정보와 인증)</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/33cec971-7308-4c73-8118-2861e21eef07/image.png" alt=""></p>
<p>🟢 Principal (인터페이스) ---&gt; Authentication (인터페이스) ---&gt; UsernamePasswordAuthenticationToken (클래스)</p>
<p>메서드:
getName(): 사용자 이름 반환
getPrincipal(): 인증 주체 반환
getAuthorities(): 사용자 권한 반환
getCredentials(): 사용자 자격 증명 반환
getDetails(): 인증 세부 정보 반환
isAuthenticated(): 인증 여부 확인
setAuthenticated(): 인증 상태 설정
eraseCredentials(): 자격 증명 지우기</p>
<p>설명: 인증은 사용자가 자신이 주장하는 사람인지 확인하는 과정입니다. Authentication 인터페이스는 인증 정보를 나타내며, UsernamePasswordAuthenticationToken 클래스는 사용자 이름과 비밀번호를 기반으로 한 인증을 나타냅니다.</p>
<p>사용처: Authentication은 인증이 성공적인지 여부를 판단하는 모든 시나리오에서 반환 타입입니다. 예를 들어, AuthenticationProvider 및 AuthenticationManager 내부에서 사용됩니다.</p>
<p>🟢 UserDetails (인터페이스) ---&gt; User (클래스)</p>
<p>메서드:
getPassword(): 비밀번호 반환
getUsername(): 사용자 이름 반환
getAuthorities(): 사용자 권한 반환
isAccountNonExpired(): 계정 만료 여부 확인
isAccountNonLocked(): 계정 잠금 여부 확인
isEnabled(): 계정 활성화 여부 확인
isCredentialsNonExpired(): 자격 증명 만료 여부 확인
eraseCredentials(): 자격 증명 지우기</p>
<p>설명: UserDetails 인터페이스는 사용자의 세부 정보를 나타냅니다. 이 정보는 인증 과정에서 사용되며, User 클래스는 이를 구현한 예제입니다.</p>
<p>사용처: UserDetails는 저장 시스템에서 사용자 정보를 로드하는 모든 시나리오에서 반환 타입입니다. 예를 들어, UserDetailsService 및 UserDetailsManager 내부에서 사용됩니다.</p>
<p><strong>결론</strong>
UserDetails와 Authentication은 Spring Security에서 사용자의 세부 정보와 인증 정보를 나타내는 중요한 구성 요소입니다. _<strong>UserDetails는 사용자의 세부 정보를 관리</strong>_하며, _<strong>Authentication은 인증 과정을 담당</strong>_합니다. 이 두 요소는 사용자 인증 및 권한 관리의 핵심 역할을 하며, 개발자는 이를 이해하고 적절히 활용해야 합니다.</p>
<p>🟣 코드</p>
<pre><code>-- MYSQL DB를 이용하여 스프링 시큐리티 인증 테스트 

-- test.users definition

CREATE TABLE `users` (
  `id` int DEFAULT NULL,
  `username` varchar(100) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  `enabled` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO test.users (id, username, password, enabled) VALUES(1, &#39;happy&#39;, &#39;12345&#39;, 1);

-- test.authorities definition

CREATE TABLE `authorities` (
  `id` int DEFAULT NULL,
  `username` varchar(100) DEFAULT NULL,
  `authority` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO test.authorities (id, username, authority) VALUES(1, &#39;happy&#39;, &#39;read&#39;);


-- customer  (JPA 설치 + 앱 실행 후)
INSERT INTO `customer` (`email`, `pwd`, `role`)
 VALUES (&#39;johndoe@example.com&#39;, &#39;54321&#39;, &#39;admin&#39;);
</code></pre><pre><code># application.properties

## spring JDBC
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=#testdb123!
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver</code></pre><pre><code>#build.gradle

dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    developmentOnly &#39;org.springframework.boot:spring-boot-devtools&#39;
    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;
    testImplementation &#39;org.springframework.security:spring-security-test&#39;

    // spring jdbc
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jdbc&#39;
    implementation &#39;mysql:mysql-connector-java:8.0.26&#39;

    // spring ldap (optional)
      implementation &#39;org.springframework.boot:spring-boot-starter-data-ldap&#39;

    // JPA
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
}</code></pre><pre><code>import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(
                (requests) -&gt; requests.requestMatchers(&quot;/myAccount&quot;, &quot;/myBalance&quot;, &quot;/myLoans&quot;, &quot;/myCards&quot;).authenticated()
                                      .requestMatchers(&quot;/notices&quot;, &quot;/contact&quot;).permitAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());

        return http.build();
    }

         /**
     * NoOpPasswordEncoder is not recommended for production usage.
     * Use only for non-prod.
     *
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Bean
    public UserDetailsService userDetailsService(DataSource dataSource) {
        return new JdbcUserDetailsManager(dataSource);
    }

}</code></pre><pre><code>package com.example.springsecurity61.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import org.hibernate.annotations.GenericGenerator;

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO,generator=&quot;native&quot;)
    @GenericGenerator(name = &quot;native&quot;,strategy = &quot;native&quot;)
    private int id;
    private String email;
    private String pwd;
    private String role;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}</code></pre><pre><code>// Repository

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.example.springsecurity61.model.Customer;

import java.util.List;

@Repository
public interface CustomerRepository extends CrudRepository&lt;Customer,Long&gt; {

    List&lt;Customer&gt; findByEmail(String email);

}</code></pre><pre><code>// UserDetail 추가
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.example.springsecurity61.model.Customer;
import com.example.springsecurity61.repository.CustomerRepository;

import java.util.ArrayList;
import java.util.List;

@Service
public class EazyBankUserDetails implements UserDetailsService {

    @Autowired
    private CustomerRepository customerRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String userName, password;
        List&lt;GrantedAuthority&gt; authorities;
        List&lt;Customer&gt; customer = customerRepository.findByEmail(username);
        if (customer.size() == 0) {
            throw new UsernameNotFoundException(&quot;User details not found for the user : &quot; + username);
        } else{
            userName = customer.get(0).getEmail();
            password = customer.get(0).getPwd();
            authorities = new ArrayList&lt;&gt;();
            authorities.add(new SimpleGrantedAuthority(customer.get(0).getRole()));
        }
        return new User(userName,password,authorities);
    }

}</code></pre><p>🟢 구조
<img src="https://velog.velcdn.com/images/pizza_1/post/415851ac-ba28-4eb4-b5af-ae9714a8290a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 6 튜토리얼 (1)]]></title>
            <link>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-1</link>
            <guid>https://velog.io/@pizza_1/Spring-Security-6-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-1</guid>
            <pubDate>Thu, 17 Aug 2023 07:16:59 GMT</pubDate>
            <description><![CDATA[<h1 id="🍃-spring-security-6">🍃 Spring Security 6</h1>
<p>망할. 예전 코드가 또 deprecated 됐다.
스프링 부트가 버전 업 하면서 코드가 바뀌고, Spring Security가 버전 업 하면서 코드가 또 바뀐다.</p>
<p>스택오버플로우, 블로그에서 코드 복사해서 붙여넣고 수정하는 것도 힘들다. 내 유일한 선임 개발자이자 선생님인 ChatGPT에게 물어보니 2021년 예전 코드만 알려준다.</p>
<p>나에게 남은 선택은 기초부터 파악하기! 게임 스테이지 분석처럼 스프링 시큐리티도 한번 까 보자.</p>
<h2 id="✅-spring-security-internal-flow">✅ Spring Security Internal Flow</h2>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/4c74ba2c-03b8-4758-95ba-2adf6f2a6782/image.png" alt="">
출처: <a href="https://www.youtube.com/@jgeek-xr6sy">https://www.youtube.com/@jgeek-xr6sy</a></p>
<p>이미지에 나온 스텝을 하나씩 해석해 보았다. (ChatGPT 선생님 번역본)</p>
<p>✅<strong>STEP 1</strong>: (<strong>사용자가 자격 증명 입력)</strong></p>
<p>사용자가 로그인을 위해 자격 증명(예: 사용자 이름과 비밀번호)을 제출합니다.</p>
<p>✅<strong>STEP 2</strong>: (Spring Security Filters - Authentication / 스프링 보안 필터 - 인증)</p>
<p>자격 증명이 포함된 요청이 스프링 보안의 필터 체인에 의해 가로채져 인증 프로세스가 시작됩니다.</p>
<p>✅<strong>STEP 3:</strong> (Authentication Manager / 인증 관리자)</p>
<p><strong><code>AuthenticationManager</code></strong>가 인증 프로세스를 관리하며, 올바른 인증 제공자(<code>AuthenticationProvider</code>)로 요청을 전달합니다.</p>
<p>✅<strong>STEP 4:</strong> (Authentication Providers / 인증 제공자<strong>)</strong> </p>
<p><strong><code>AuthenticationManager</code></strong>는 제공된 자격 증명으로 요청을 인증하는 책임이 있는 하나 이상의 <code>AuthenticationProvider</code> 구현체에게 위임합니다.</p>
<p>✅<strong>STEP 5:</strong> (UserDetails Manager/Service<strong>)</strong> </p>
<p><strong><code>UserDetailsService</code></strong>가 호출되어 사용자의 세부 정보(예: 역할 및 권한)를 데이터베이스 또는 기타 데이터 소스에서 로드합니다.</p>
<p>✅<strong>STEP 6:</strong> (PasswordEncoder / 비밀번호인코더<strong>)</strong> </p>
<p><strong><code>PasswordEncoder</code></strong>는 제공된 비밀번호가 사용자에 대해 저장된 비밀번호와 일치하는지 확인하거나 인코딩하는 데 사용됩니다.</p>
<p>✅<strong>STEP 7:</strong> (인증 관리자로 돌아감<strong>)</strong> </p>
<p><code>AuthenticationProvider</code>** 는 인증된 객체를 **<code>AuthenticationManager</code>에 반환하여 인증이 성공했음을 나타냅니다.</p>
<p>✅<strong>STEP 8:</strong> (스프링 보안 필터로 돌아감<strong>)</strong> </p>
<p><strong><code>AuthenticationManager</code></strong>는 사용자가 인증되었음을 나타내어 스프링 보안 필터 체인에 제어를 반환합니다.</p>
<p>✅<strong>STEP 9:</strong> (보안 컨텍스트<strong>)</strong> </p>
<p>인증된 사용자의 세부 정보가 세션의 보안 컨텍스트를 보유하고 있는 <strong><code>SecurityContextHolder</code></strong>에 저장됩니다.</p>
<p>✅<strong>STEP 10:</strong> (사용자<strong>)</strong> </p>
<p>요청이 대상 리소스(예: 컨트롤러 메서드)로 전달되고 사용자는 애플리케이션의 보안 부분에 접근할 수 있는 권한을 부여받습니다.</p>
<h2 id="😤그래서-뭐하는-놈들">😤그래서 뭐하는 놈들?</h2>
<h3 id="🟢-스프링-보안-필터-spring-security-filters">🟢 <strong>스프링 보안 필터 (Spring Security Filters)</strong></h3>
<p>스프링 보안 필터들은 각 요청을 가로채어 인증이 필요한지 확인합니다. 인증이 필요하면 사용자를 로그인 페이지로 이동시키거나 초기 인증 중 저장된 정보를 사용합니다.</p>
<h3 id="🟢-인증-authentication">🟢 <strong>인증 (Authentication)</strong></h3>
<p><strong><code>UsernamePasswordAuthenticationFilter</code></strong>와 같은 필터는 HTTP 요청에서 사용자 이름/비밀번호를 추출하여 인증 객체를 준비합니다. 인증은 스프링 보안 프레임워크 내에서 인증된 사용자 정보를 저장하는 핵심 표준입니다.</p>
<h3 id="🟢-인증-관리자-authenticationmanager">🟢 <strong>인증 관리자 (AuthenticationManager)</strong></h3>
<p>필터에서 요청을 받으면, 사용자 정보의 유효성 검사를 사용 가능한 인증 제공자들에게 위임합니다. 앱 내에 여러 제공자가 있을 수 있으므로, 인증 관리자가 모든 인증 제공자를 관리하는 책임이 있습니다.</p>
<h3 id="🟢-인증-제공자-authenticationprovider">🟢 <strong>인증 제공자 (AuthenticationProvider)</strong></h3>
<p>인증 제공자는 사용자 정보를 인증하기 위한 핵심 로직을 가지고 있습니다.</p>
<h3 id="🟢-userdetailsmanageruserdetailsservice">🟢 <strong>UserDetailsManager/UserDetailsService</strong></h3>
<p><strong><code>UserDetailsManager</code></strong>와 <strong><code>UserDetailsService</code></strong>는 DB/저장 시스템에서 사용자 정보를 검색, 생성, 업데이트, 삭제하는 데 사용이 됩니다.</p>
<h3 id="🟢-passwordencoder">🟢 <strong>PasswordEncoder</strong></h3>
<p>비밀번호를 인코딩하고 해싱하는 데 도움이 되는 서비스 인터페이스입니다. 그렇지 않으면 평문 비밀번호로 사용해야 할 수도 있습니다.</p>
<h3 id="🟢-보안-컨텍스트-securitycontext">🟢 <strong>보안 컨텍스트 (SecurityContext)</strong></h3>
<p>요청이 인증되면 인증 정보는 일반적으로 <strong><code>SecurityContextHolder</code></strong>에 의해 관리되는 스레드 로컬 보안 컨텍스트에 저장됩니다. 이는 동일한 사용자로부터의 다가오는 요청을 처리하는 데 사용이 됩니다.</p>
<h2 id="실습">실습</h2>
<p>🟡 시나리오
/contact, /notics 는 누구나 접근 가능
/myAccount, /myBalance, /myLoans, /myCards는 시큐리티 적용</p>
<p>🔵 구조
<img src="https://velog.velcdn.com/images/pizza_1/post/52985ddc-a3c2-43e4-9b4e-e42f599fc4b1/image.png" alt=""></p>
<p>스프링 부트 : 3.1.2 버전 + Gradle (8.2.x)
자바 : 17 (OpenJDK)</p>
<p>🔵 코드</p>
<pre><code>config - ProjectSecurityConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

        /**
         *  *authorizeHttpRequests 설정*
         *  my 으로 시작하는 URL은 접근 권한 필요
         *  notices, contact는 누구나 접근 가능
         *  
         */

          http.authorizeHttpRequests( (requests) -&gt;
                                          requests.requestMatchers(&quot;/myAccount&quot;,&quot;/myBalance&quot;,&quot;/myLoans&quot;,&quot;/myCards&quot;).authenticated() 
                                                  .requestMatchers(&quot;/notices&quot;,&quot;/contact&quot;).permitAll() )
                                      .formLogin(Customizer.withDefaults()) 
                                      .httpBasic(Customizer.withDefaults());
        return http.build();

        /**
         *  모든 request 거절
         *  Configuration to deny all the requests
         */
        /*http.authorizeHttpRequests(requests -&gt; requests.anyRequest().denyAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
        return http.build();*/

        /**
         *     모든 request 허락
         *  Configuration to permit all the requests
         */
        /*http.authorizeHttpRequests(requests -&gt; requests.anyRequest().permitAll())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults());
        return http.build();*/

    }




}
</code></pre><pre><code>application.properties

spring.security.user.name = test1
spring.security.user.password = 123123</code></pre><pre><code>controller - NoticesController

package com.example.springsecurity6.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class NoticesController {

    @GetMapping(&quot;/notices&quot;)
    public String getNotices() {
        return &quot;Here are the notices details from the DB&quot;;
    }

}</code></pre><pre><code>controller - AccountController

package com.example.springsecurity6.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AccountController {

    @GetMapping(&quot;/myAccount&quot;)
    public String getAccountDetails() {
        return &quot;Here are the account details from the DB&quot;;
    }

}
</code></pre><pre><code>build.gradle

plugins {
    id &#39;java&#39;
    id &#39;org.springframework.boot&#39; version &#39;3.1.2&#39;
    id &#39;io.spring.dependency-management&#39; version &#39;1.1.2&#39;
}

group = &#39;com.example&#39;
version = &#39;0.0.1-SNAPSHOT&#39;

java {
    sourceCompatibility = &#39;17&#39;
}

repositories {
    mavenCentral()
}

dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    developmentOnly &#39;org.springframework.boot:spring-boot-devtools&#39;
    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;

    // spring security basic
    implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;
    testImplementation &#39;org.springframework.security:spring-security-test&#39;
}

tasks.named(&#39;test&#39;) {
    useJUnitPlatform()
}
</code></pre><p>🟣 결과</p>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/5a578766-ee93-4ee3-bb45-55bc13e41c84/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/0b47640e-f91e-4372-932d-ba809ec3d43b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/e67b7c5d-c0fb-448a-a880-ca5db6903125/image.png" alt=""></p>
<p>/myAccount 접속 시, /login 으로 이동</p>
<p><img src="https://velog.velcdn.com/images/pizza_1/post/a3bad5e1-7445-476a-a40b-b91c571adf04/image.png" alt=""></p>
<p>test1 / 123123 입력 후
<img src="https://velog.velcdn.com/images/pizza_1/post/e46cdf10-dcee-4418-a52c-c0302ed53142/image.png" alt=""></p>
<p>스프링 시큐리티 6 기본 셋팅을 해보았다.
다음에는 JPA, MySQL을 이용해서 설정을 해보자.</p>
]]></description>
        </item>
    </channel>
</rss>