728x90

🛠️ 초기 세팅

dependencies세팅
application.yml파일 설정

 

db 생성 해줘야 함.

 

 


📘 1. MemberController.java

@Controller
public class MemberController {

    @GetMapping("/signup")
    public String showSignupForm() {
        return "signup";
    }

    @GetMapping("/signin")
    public String showSigninForm() {
        return "signin";
    }
}

✅ 설명

  • @Controller: 이 클래스가 Spring MVC의 컨트롤러임을 나타냄. 요청을 처리하고 뷰(View)를 반환하는 역할을 한다.
  • @GetMapping("/signup"): /signup 주소로 GET 요청이 들어오면 signup.html을 반환한다. 즉, 회원가입 화면을 보여주는 역할.
  • @GetMapping("/signin"): /signin 주소로 GET 요청이 들어오면 signin.html을 반환. 즉, 로그인 화면을 보여주는 역할.

 

✏️ 주석으로 쓰자면:

// 회원가입 화면 요청 처리
// GET /signup 요청 시 signup.html 반환

 

 

 


📘 2. SecurityConfig.java

package io.shi.sec_form.config;

import lombok.extern.slf4j.Slf4j;
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.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

//public class SecurityConfig extends WebSecurityConfiguration { 이건 옛날 방식이니까 사용하지 마삼 빈 사용해
@Slf4j
@Configuration
public class SecurityConfig {

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

//    @Bean
//    public PasswordEncoder passwordEncoder() {
//        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
//    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //여기에 설정을 하나씩 넣어줘야됨
        return http
                .csrf(csrf -> csrf.disable() )
//                .formLogin(
//                        form -> {
//                            form.failureForwardUrl("/login?error=true")
//                                    //.successForwardUrl("/index") //로그인에 성공하면 페이지는 그대로 둠
//                                    //.defaultSuccessUrl("/happy")
//                                    .usernameParameter("loginId")
//                                    .passwordParameter("loginPwd")
//                                    .loginProcessingUrl("/signin")
//                                    .loginPage("/signin")
//                                    .permitAll();
//                        }
//                )
                .formLogin(Customizer.withDefaults())
//                .logout(Customizer.withDefaults())
                .logout( logout -> {
                    logout.logoutUrl("/signout")
                            .logoutSuccessUrl("/")
                            .clearAuthentication(true)
                            .invalidateHttpSession(true) //우리 서버에서 갖고있는 httpsession을 무효화 시키겠다
                            .deleteCookies("JSESSIONID");
                })
                .authorizeHttpRequests(
                        auth -> {
                            auth.requestMatchers("/signup", "/signin")
                                    .anonymous()
                                    .requestMatchers("/user/**")
                                    .hasRole("MEMBER")//hasRole로 권한검사를 할 수 있다, 이걸 가지고 있어야 인가를 가질 수 있음
                                    .anyRequest()
                                    //.denyAll(); //아예 접근 못하게 막고 싶을때 사용
                                     .authenticated();
                        }
                )
                .build();
    }

    //인가
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        String targetPwd = "1234";
        String encoded = passwordEncoder().encode(targetPwd);

        log.info("encoded = {}", encoded);

        //얘는 InMemoryUserDetailsManager 객체를 user을만들어줌
        manager.createUser(
                User.withUsername("user")
                        .password(encoded)
                        //.roles()
                        .build()
        );
        return manager;
    }

}

 

 

 

@Configuration
@Slf4j
public class SecurityConfig {
  • @Configuration: 이 클래스는 Spring 설정 클래스임을 의미.
  • @Slf4j: 로그를 출력할 수 있도록 도와주는 Lombok 애노테이션.

 

 


🔐 PasswordEncoder 설정

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
  • 비밀번호를 암호화할 때 사용하는 인코더. 실제 로그인 시 사용자의 비밀번호와 저장된 암호화된 비밀번호를 비교함.
  • Spring Security에서 가장 많이 사용하는 방식이 BCrypt.
    (보안성이 높고 내부적으로 솔팅(salting)과 반복 해시를 지원)
  • Spring Security는 로그인 시 사용자가 입력한 비밀번호를 DB에 저장된 암호화된 비밀번호와 비교하는데, 이때 이 BCryptPasswordEncoder가 필요함.
  • 로그인 검증 시 자동으로 사용됨.

 

 


✅ 주석 처리된 코드와 비교

//@Bean
//public PasswordEncoder passwordEncoder() {
//    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
//}

이건 BCrypt뿐 아니라 다양한 암호화 알고리즘을 함께 지원하는 인코더야.
예를 들어, bcrypt, pbkdf2, scrypt, argon2 등을 사용할 수 있도록 내부에서 선택해줘.

📌 보안적으로는 이 방법이 더 유연하지만, 지금처럼 간단한 예제에서는 new BCryptPasswordEncoder()로 충분함.

 

 


🔐 SecurityFilterChain 설정

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  • Spring Security의 필터 체인을 설정하는 메서드. 이 메서드 내에서 인증, 인가, 로그인/로그아웃 설정을 모두 할 수 있음.
  • Spring Security의 필터 체인을 커스터마이징함
  • 필터란 사용자의 요청을 가로채서 인증, 인가 여부를 판단하고, 그 결과에 따라 요청을 진행시킬지 차단할지 결정하는 역할이다.
 
 
.csrf(csrf -> csrf.disable())
  • CSRF(Cross Site Request Forgery) 공격 방지를 위한 설정. 현재는 개발 편의상 꺼둠.
  • CSRF (Cross-Site Request Forgery): 사용자가 인증된 상태에서 악성 요청을 보내는 공격.
  • 기본적으로 form POST 요청은 CSRF 토큰이 없으면 거절되는데, 지금은 로그인 화면 만들기 편하게 비활성화한 상태.
  • 추후에는 반드시 활성화하고, <input type="hidden" name="_csrf" ...>로 토큰을 포함해줘야 한다.

 

 


🔓 로그인 설정

.formLogin(Customizer.withDefaults())
  • 기본 로그인 설정을 사용. Spring Security가 제공하는 기본 로그인 화면을 사용함.
  • 만약 커스터마이징을 하고 싶다면 .formLogin(form -> { ... }) 방식으로 세부 설정 가능.
  • 이건 Spring Security 기본 제공 로그인 화면을 사용하는 설정이야. 즉, /login으로 POST 요청하면 로그인 동작함

 

💬 주석 처리된 formLogin 설정

//.formLogin(
//    form -> {
//        form.failureForwardUrl("/login?error=true")
//            //.successForwardUrl("/index")
//            //.defaultSuccessUrl("/happy")
//            .usernameParameter("loginId")
//            .passwordParameter("loginPwd")
//            .loginProcessingUrl("/signin")
//            .loginPage("/signin")
//            .permitAll();
//    }
//)

(지금은 주석 처리돼 있지만 예전 설정에서는 usernameParameter, passwordParameter 등을 설정하려고 했었음.)

✅ 역할 설명

설정 설명
loginPage("/signin") 사용자 정의 로그인 페이지 경로
loginProcessingUrl("/signin") 로그인 요청을 처리할 URL. 로그인 form의 action과 일치해야 함
usernameParameter("loginId") 로그인 폼에서 아이디 input의 name 속성 지정
passwordParameter("loginPwd") 비밀번호 input의 name 속성 지정
failureForwardUrl("/login?error=true") 로그인 실패 시 이동할 URL
.permitAll() 로그인 페이지 자체는 로그인 안 한 사용자도 접근 가능해야 하므로 허용

✔ 이 설정을 사용하면 기본 form 대신 내가 따로 만든 signin.html이 로그인 페이지가 됨.

 

 


🔐 로그아웃 설정

.logout(logout -> {
    logout.logoutUrl("/signout")
          .logoutSuccessUrl("/")
          .clearAuthentication(true)
          .invalidateHttpSession(true)
          .deleteCookies("JSESSIONID");
})
  • 로그아웃 요청 URL: /signout
  • 로그아웃 성공 시 이동할 URL: /
  • 인증 정보 제거: clearAuthentication(true)
  • 세션 무효화: invalidateHttpSession(true)
  • 쿠키 제거: deleteCookies("JSESSIONID") (로그인 상태 유지하는 세션 쿠키 제거)

 

 

설정 설명
logoutUrl("/signout") 로그아웃 처리할 URL (GET/POST 모두 가능)
logoutSuccessUrl("/") 로그아웃 후 이동할 URL
clearAuthentication(true) 현재 사용자의 인증 정보 제거
invalidateHttpSession(true) 세션 무효화 (로그인 상태 삭제)
deleteCookies("JSESSIONID") 세션 쿠키도 브라우저에서 삭제

 

 


🔐 인가(접근 권한) 설정

.authorizeHttpRequests(auth -> {
    auth.requestMatchers("/signup", "/signin")
        .anonymous()
  • /signup, /signin은 로그인하지 않은 사용자만 접근 가능하게 설정
  • 로그인 안 한 사용자만 접근 가능 (로그인된 사용자는 접근 불가)
 
    .requestMatchers("/user/**")
        .hasRole("MEMBER")
  • /user/** 경로(주소)는 ROLE_MEMBER 권한이 있어야(권한을 가진 사용자만) 접근 가능.

 

 
    .anyRequest()
        .authenticated();
  • 그 외 모든 요청은 로그인된 사용자만 접근 가능.

 

 


🔐 사용자 정보 등록

@Bean
public UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

    String targetPwd = "1234";
    String encoded = passwordEncoder().encode(targetPwd);
  • InMemoryUserDetailsManager는 메모리 기반으로 사용자 정보를 관리하는 구현체.
  • 실제 프로젝트에서는 DB와 연동하지만, 지금은 예제를 위해 메모리에서 사용자 등록.
  • 메모리 기반 사용자 저장소를 만들고, 1234라는 비밀번호를 암호화해서 사용자 정보 등록.
  • 평문 비밀번호 1234를 암호와하고, 로그로 출력해서 확인할 수 있도록 설정

 

 
    manager.createUser(
        User.withUsername("user")
            .password(encoded)
            //.roles()
            .build()
    );
    return manager;
}
  • username: user, password: 1234인 계정을 생성함. (지금은 roles() 주석처리 되어 있어 ROLE이 없어 인가 오류 날 수 있음)
  • user라는 username을 갖고, 1234라는 암호화된 비밀번호를 갖는 사용자 생성.
  • .roles("MEMBER") 주석이 해제되어야 /user/** 접근이 가능함.

 

 

 


📘 3. 로그인 폼 (signin.html)

<form action="/signin" method="post">
  • 로그인 정보를 서버에 보낼 때 POST 방식으로 /signin 경로로 전송함.
  • ⚠️ 하지만 현재 SecurityConfig에서는 이 경로가 로그인 처리 URL이 아님. (loginProcessingUrl("/signin")이 주석처리 되어 있어서 /login으로 동작할 수도 있음)

<input type="text" id="loginId" name="loginId">
<input type="password" id="loginPwd" name="loginPwd">​
  • 이 부분에서 주의할 점은 name 속성임. Spring Security는 기본적으로 name="username"name="password"를 요구함.
    따라서 이 코드는 현재 로그인 시 정상 동작하지 않을 가능성이 높음.

 

✅ 해결 방법:

  1. loginProcessingUrl("/signin"), usernameParameter("loginId"), passwordParameter("loginPwd") 설정 추가
  2. 또는 input name을 username, password로 변경

 

 

 


📘 4. 회원가입 폼 (signup.html)

<h1>회원가입 페이지</h1>
  • 현재는 단순한 HTML 페이지로 기능은 구현되어 있지 않음.

 

 

 


🔍 결론적으로 전체 흐름 요약

  1. /signin 접속 시 signin.html 반환됨 (로그인 화면)
  2. 로그인 요청은 /login (기본값), 또는 /signin (커스터마이징 시)
  3. 로그인 성공 시 기본 페이지로 이동 (설정 변경 가능)
  4. 로그아웃 시 /로 이동
  5. /user/**MEMBER 권한 필요
  6. 사용자 정보는 메모리 내에 user/1234 로 존재
  7. 비밀번호는 BCrypt로 암호화됨

 

 

 

 

 


 

728x90