Spring Security - 세션 기반 회원가입 / 로그인 방식 구현

TopOfTheHead·2026년 5월 24일

Spring Security

목록 보기
25/26

세션 기반의 로그인 로직 수행 및 인증 정보Security Context Holder에 저장되는 과정

Authentication Manager, Authentication Provider, UserDetailsService, UserDetails, SecurityContextHolder


1. 로그인 시 제공된 Authentication 구현체인증정보 획득 및 AuthenticationManager에 의해 해당 인증정보에 적합한 AuthenticationProvider 식별
。해당 Authentication 구현체credentials 정보ID / PW만 포함하는 미완성 상태


2. AuthenticationProviderUserDetailsService를 호출하여 해당 ID를 기반으로 DB에서 찾은 사용자 정보를 통해 UserDetails 구현체를 생성한 후 반환
Spring Security에 의한 세션 로그인 인증을 수행하기 위해 반드시 UserDetailsServiceloadUserByUsername() 메서드를 통해 UserDetails 구현체로 반환하도록 설정해야한다.


3. 해당 UserDetails 구현체Password와 입력된 Authentication 구현체Password를 서로 비교검증을 수행
Authentication 구현체 Password : 평문, UserDetails 구현체 Password : 암호문이며, PasswordEncoder.matches(raw, encoded)를 통해 암호화 후 비교

검증이 성공할 경우, principal, authorities 정보를 추가로 포함한 완전한 Authentication 구현체가 반환되어 SecurityContextHolder현재 스레드SecurityContext로 등록 후 인증상태 유지
principalUserDetails 구현체가 저장됨.
검증 이후 Authentication 구현체credentialsnull로 설정

사전 설정

  • Spring Starter 정의
  • application.yml 정의
spring:
  application:
    name: session
  datasource:
    url: jdbc:mysql://localhost:3306/bblog
    username: root
    password: wjd747
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: create
  • 암호 저장용 PasswordEncoderSpring Bean으로 등록
@Configuration
public class PasswordConfiguration {
    @Bean
    public PasswordEncoder passwordEncoderB(){
        return new BCryptPasswordEncoder(10);
    }
}
  • SecurityFilterChain 설정 및 Spring Bean으로 등록
@Configuration
public class SecurityConfiguration {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity){
        return httpSecurity
                .cors(cors -> cors.disable())
                .csrf(csrf->csrf.disable())
                .formLogin(Customizer.withDefaults())
                .logout(Customizer.withDefaults())
                .authorizeHttpRequests(
                        auth->
                        {
                                auth
                                        .requestMatchers("/sign-up")
                                        .permitAll()
                                        .requestMatchers("/login")
                                        .anonymous()
                                        .requestMatchers("/user/**")
                                        .hasAuthority("USER")
                                        .anyRequest()
                                        .denyAll();
                        } 
                )
                .build();
    }
}
  • DB Entity Class 정의
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member   {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String email;
    private String role = "USER";
    @Builder
    public Member(String userName, String password, String email){
        this.email = email;
        this.password = password;
        this.userName = userName;
    }
}
  • DTO 역할UserDetails 구현클래스 정의
    세션 로그인UserDetailsService.loadUserByUsername("username")에 의해 DB에서 가져온 사용자 정보를 포함한 UserDetails DTO를 생성 후 AuthenticationProvider에게 반환
@Getter
@Builder
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {
    private final String username;
    private final String password;
    private final String role;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(
        new SimpleGrantedAuthority("ROLE_" + this.role.name())
        );
    }
}

Lombok을 사용 시 getUsername()getPassword()를 생략가능

  • Member Repository 정의
@Repository
public interface MemberRepository extends JpaRepository<Member,Long> {
    Optional<Member> findMemberByUsername(String username);
    default Member findMemberByUsernameOrThrow(String username){
        return findMemberByUsername(username).orElseThrow(
                () -> new RuntimeException("멤버 없음")
        );
    }
}
  • 회원가입 DTO 정의
@Getter
@AllArgsConstructor
public class SignUpRequest {
    private String username;
    private String password;
    private String email;
}
  • Member Service 정의
    세션 로그인@Service Class에서 해당 UserDetailsService 인터페이스를 구현 및 loadUserByUsername() 구현 메서드를 구현해야한다.
    JWT 로그인 시는 구현할 필요가 없음.

    회원가입을 위한 Member Entity 생성 시 반드시 PasswordEncoder를 통한 암호화를 수행 후 DB에 저장

    UserDetailsServiceloadUserByUsername(String username)에서 DB에서 username에 해당하는 Entity를 가져온 후 UserDetails 구현체로서 생성 후 Authentication Provider에게 반환
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemberService implements UserDetailsService {
    private final MemberRepository memberRepository;
    private final PasswordEncoder passwordEncoder;
    @Transactional
    public void saveMember(SignUpRequest signUpRequest){
        memberRepository.save(
                Member.builder()
                        .email(signUpRequest.getEmail())
                        .password(passwordEncoder.encode(signUpRequest.getPassword()))
                        .userName(signUpRequest.getUsername())
                        .build()
        );
    }
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Member founded = memberRepository.findMemberByUsernameOrThrow(username);
        return CurrentUser.builder()
                .username(founded.getUsername())
                .password(founded.getPassword())
                .role(founded.getRole())
                .build();
    }
}
  • Controller 정의
    GET 호출회원가입 페이지 도출

    POST 호출회원가입 로직 수행
@Controller
@RequiredArgsConstructor
public class MemberController {
    private final MemberService memberService;
    @GetMapping("/sign-up")
    public String showSignUpPage(){
        return "sign_up";
    }
    @PostMapping("/sign-up")
    public String doSignUpPage(
            SignUpRequest signUpRequest
    ){
        memberService.saveMember(signUpRequest);
        return "redirect:/login";
    }
}
  • VIEW 정의
    src/main/resources/templatessign_up.html로서 저장
<!doctype html>
<html lang="ko">
<head>
    <title>Document</title>
</head>
<body>
<form action="sign-up" method="post">
    <div>
        <div>
            <label for="username">
                사용자 ID
            </label>
            <input type="text" name="username" id = "username">
        </div>
        <div>
            <label for="username">
                사용자 email
            </label>
            <input type="email" name="email" id = "email">
        </div>
        <div>
            <label for="username">
                사용자 password
            </label>
            <input type="password" name="password" id = "password">
        </div>
        <div>
            <button type="submit">가입</button>
            <button type="reset">취소</button>
        </div>
    </div>
</form>
</body>
</html>

<form method="post">회원가입 APIPOST 전송 시 <form 태그> 내 각 <input> 태그들은 name 속성을 기준으로 @ModelAttribute에 의해 ControllerDTO바인딩
<input name="username">SignUpRequest DTOprivate String username바인딩
<input name="email">SignUpRequest DTOprivate String email바인딩
<input name="password">SignUpRequest DTOprivate String password바인딩

  • 회원가입로그인 수행



    회원가입 후 성공적으로 생성된 계정에 대해 로그인이 수행
    ▶ 이후 인가검증필터체인에서 URL : /login에 지정된 anonymous()로 인해 권한 에러 발생

    Spring Securityform-base Login을 통해 로그인 시 Authentication 구현체 Password : 평문, UserDetails 구현체 Password : 암호문으로서 PasswordEncoder.matches(raw, encoded) 에 의해 검증이 수행.
profile
공부기록 블로그

0개의 댓글