개발가능구역

[ IntelliJ ] Spring Security 로그인 02 본문

SpringBoot/IntelliJ

[ IntelliJ ] Spring Security 로그인 02

oosomall 2025. 10. 1. 18:33
반응형

 

 

앞 전 유효성 검사 글에서는 회원가입을 함께 다뤘고,

이전 글에서는 초기 Security 로그인 창에서 원래 작업하던 웹 페이지로 이동하는 작업을 했다

 

 

[ IntelliJ ] Security 로그인 01

 

[ IntelliJ ] Security 로그인 01

로그인은 개인의 계정을 입력하여 권한을 얻는 행위이다앞전 유효성 검사는 그 계정을 만들기 위한 페이지를 구현하기 위함이었고, 이제부터는 로그인을 공부해보려 한다 아직 초심자라 모르

oosomall.tistory.com

 

 

이번 글에서는 회원가입한 계정으로 로그인을 실제로 하는 코드를 다룰 예정이다

 

 

 


 

 

🔴 목차

 

1. 작업 툴

2. 전체 흐름

3. 네비게이션 바 Thymeleaf Security 추가

4. 의존성 추가

5. SecurityConfig 클래스 업데이트

 - 이전 코드 / 현재 코드

 - 로그인 관련 설정

 - 로그아웃 관련 설정

 - Builder

6. CustomUserDetailsService 클래스 생성

 

 

 

 


 

 

 

🔴 작업 툴

 

IDE : IntelliJ

웹 서버 : Apache

UI : HTML5, Thymeleaf

사용 클래스 : Config, Service

사용 DB : Oracle

 

 

 

 


 

 

 

 

🔴 전체 흐름

 

1. 로그인 폼에서 ID, PW 입력 후 로그인 버튼 클릭 → 폼 전송

2. SecurityConfig - formLogin() 폼 전송으로 받은 ID, PW 매핑

3. CustomUserDetailsService 에서 DB 조회

4. User.builder 반환 → PasswordEncoder로 검증 → 성공 시 로그인 완료

 

 

 

 


 

 

 

 

🔴 Nav 바( top.html ) Thymeleaf Security 추가

html xmlns:th="http://www.thymeleaf.org" th:fragment="menu"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security"

 

기존에는 윗줄 th:fragment = "menu" 까지 있었는데 밑에 한 줄 추가해준다

 

  • xmlns:th ( Thymeleaf 선언 ) : 해당 문서가 Thymeleaf 템플릿 엔진을 사용함을 선언한다
    • th:접두사( th:text / th:each / th:action / th:fragment )
    • th:fragment=" menu " : menu 라는 이름을 다른 파일에서 페이지의 일부분으로 사용할 수 있다
  • xmlns:sec (Spring Security 선언 ) : Thymeleaf의 Spring Security 확장 기능을 사용함을 선언한다
    • sec:접두사 ( authorize / authentication ) : 사용자의 현재 로그인 및 권한 상태
    • 예 / sec:authorize = " isAuthenticated() " : 로그인 상태일 때만 메뉴를 보여주는 역할

 

<ul class="nav navbar-nav navbar-right">
    <li sec:authorize="!isAuthenticated()">
        <a href="sign_up"><span class="glyphicon glyphicon-user"></span> Sign Up</a>
    </li>
    <li sec:authorize="!isAuthenticated()">
        <a href="login"><span class="glyphicon glyphicon-log-in"></span> Login</a>
    </li>

    <li sec:authorize="isAuthenticated()">
        <a>
            <span class="glyphicon glyphicon-ok"></span>
            <span sec:authentication="name"></span> 님, 반갑습니다.
        </a>
    </li>
    <li sec:authorize="isAuthenticated()">
        <a href="/logout"><span class="glyphicon glyphicon-log-out"></span> Logout</a>
    </li>
</ul>

 

로그인 상태 [ "사용자 ID님, 반갑습니다" / 로그아웃 ]

로그인하지 않은 상태 [ 회원가입 / 로그인 ]

 

  • sec:authorize 속성
    • sec:authorize는 해당 태그가 렌더링될지 여부를 조건 제어한다
    • isAuthenticated() : 사용자 로그인 상태라면 true
    • !isAuthenticated() : 사용자 로그인하지 않은 상태라면 true
  • sec:authentication = " name " 
    •  현재 로그인한 사용자의 username을 가져온다
    • User.builder().username(member.getId()) 값이 가져와진다

 

 

 


 

 

 

🔴 의존성 추가

implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'

 

‼️문제 발생‼️

계속 네비게이션 바에서 [ 회원가입 / 로그인 / "id님, 반갑습니다" / 로그아웃 ]

모두 한꺼번에 출력되었어서 한참 헤맸다

해당 의존성을 추가 안 해서 Thymeleaf가 제대로 작동하지 않았던 것이다

build.gradle 에 해당 코드를 넣어준 뒤 오른쪽에 코끼리 클릭, 실행을 한다

 

 

 

 


 

 

 

 

🔴 SecurityConfig

이전 코드

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/", "/sign_up", "/css/**", "/js/**").permitAll()
                        .anyRequest().authenticated()
                )
                .formLogin(form -> form
                        .loginPage("/login")
                        .defaultSuccessUrl("/")
                        .permitAll()
                );
        return http.build();
    }

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

}

 

 

바뀐 코드

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/", "/sign_up", "/signup_save",
                                "/check/**", "/image/**", "/css/**", "/js/**").permitAll()
                        .anyRequest().authenticated()
                )
                .formLogin(form -> form
                        .loginPage("/login")
                        .loginProcessingUrl("/go_login")
                        .usernameParameter("id")
                        .passwordParameter("pw")
                        .defaultSuccessUrl("/")
                        .permitAll()
                )
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .logoutSuccessUrl("/login")
                        .permitAll()
                );

        return http.build();
    }

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

}

 

이전 글에서 다뤘다 지금은 현재 바뀐 내용 위주로 글을 써보려 한다

csrf().disable()  →  csrf( AbstracHttpConfigurer :: disable ) : 버전 문제가 생겨 이렇게 바꿨다 

 

1️⃣ 로그인 관련 설정

.formLogin(form -> form
        .loginPage("/login")
        .loginProcessingUrl("/go_login")
        .usernameParameter("id")
        .passwordParameter("pw")
        .defaultSuccessUrl("/")
        .permitAll()
)

 

  • loginProcessingUrl( " 주소 " ) : 로그인 폼에서 ID와 PW 값을 제출할 URL 주소
  • usernameParameter( " ID " ) : 로그인 폼에서 name="ID" 와 연결되어 값을 가져온다
  • passwordParameter( " PW " ) : 로그인 폼에서 name="PW" 와 연결되어 값을 가져온다

 

2️⃣ 로그아웃 관련 설정

.logout(logout -> logout
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login")
        .permitAll()
);

 

  • logoutUrl( " URL " ) : 로그아웃 요청할 때 사용하는 URL 지정
  • logoutSuccessUrl( " URL " ) : 로그아웃 성공 시 이동할 URL

 

3️⃣ Builder

return http.build();

 

http.build()를 반환하며 사용자 인증을 하는 것이다

여기서 build는 무엇인가 하면 빌더 패턴( Builder Pattern )이다

 

시작은 🔴CustomUserDetailsService 에서 시작하여

"USER"라는 틀에 담긴 설정을 build()로 가져오는 것이다

 

  • http
    • HttpSecurity 타입이며 내부적으로 빌더 패턴을 사용해서 웹 보안 필터 체인을 설정한다
    • build()가 호출되면 이전 설정된 모든 내용이 적용된 보안 설정 객체가 완성되어 Spring 컨테이너에 등록된
  • Builder Pattern
    • 객체를 한 번에 만드는 대신, 필요한 설정을 단계적으로 수행한 후, 마지막에 완성된 객체를 얻는 방식이다
    • build() : 빌더 패턴의 마지막 단계이고, builder()와 설정 메서드를 이용해 앞서 설정한 모든 상태를 사용하여 원래의 최종 객체를 생성한다
      1. 역할 : 앞에 csrf, authorizeHttpRequests, formLogin 등이 완료된 후 설정된 내용에 따라 동작하는 SecurityFilterChain 객체를 생성하여 반환한다

 

 


 

 

 

🔴 CustomUserDetailsService

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final MemberRepository memberRepository;

    public CustomUserDetailsService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        MemberEntity member = memberRepository.findById(username)
                .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
        return User.builder()
                .username(member.getId())
                .password(member.getPw())
                .roles("USER")
                .build();
    }
}

 

CustomUserDetailsService 라는 서비스 클래스 파일을 만든다

사용자 인증 과정 중 사용자의 핵심정보 ( ID, 비밀번호, 권한 )를 DB에서 조회하고 로드하는 역할이다

 

  • Builder Pattern 
    • builder() : 빌더 패턴의 시작점이고, 객체를 대신 만들어줄 빌더 객체를 반환한다
      1. 역할 : 최종적으로 만들고자 하는 객체와 동일한 필드를 가지는 내부 클래스(빌더)의 인스턴스를 만든다
      2. User.builder() : DB 조회 결과를 기반으로 Security가 사용할 UserDetails 객체를 만드는 것
    • 설정 메서드( username(), password(), roles() )를 호출
      1. 역할 : 빌더 객체의 필드값 설정 후 다시 빌더 객체 자신을 반환한다
      2. 이를 통해 메서드를 연속적으로 호출하는 메서드 체이닝(Method Chaining)이 가능해진다
  • @Service
    • 비즈니스 로직을 담당하는 클래스를 명시하는 것이다. 해당 클래스가 서비스 계층에 속하며 핵심 기능을 수행한다
    • Bean 자동 등록
      1. @Service는 기본적으로 @Component 어노테이션을 포함하고 있다
      2. 역할 : Spring을 시작할 때, @Service 가 붙은 클래스를 자동 스캔하여 Spring Bean으로 등록한다
      3. 결과 : 개발자가 별도로 XML 설정이나 @Configuraion 클래스에 @Bean 메서드를 작성하지 않아도 SpringContainer가 이 클래스의 인스턴스를 관리하고 필요한 곳에 주입할 수 있게 된다
      4. @Component 특성을 상속받아 Spring Bean이 된다. 역할 분담과 가독성 때문에 @Service, @Controller, 기타 등을 쓴다
반응형