20250807 - 학습일지

창훈·2025년 8월 7일

form 방식 로그인과 로그아웃을 위한 SecurityConfig.java 등록

Spring Security http.formLogin() 메소드를 이용한 설정

 @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
                        .requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
                .csrf((csrf) -> csrf
                        .ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
                .headers((headers) -> headers
                        .addHeaderWriter(new XFrameOptionsHeaderWriter(
                                XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
                .formLogin((formLogin) -> formLogin
                        .loginPage("/user/login")
                        .defaultSuccessUrl("/"))
                .logout((logout) -> logout
                        .logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
                        .logoutSuccessUrl("/")
                        .invalidateHttpSession(true))
        ;
        return http.build();
    }
    
    @Bean
    AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
  • 람다식 미적용
http.formLogin()
    .loginPage("/user/login")	//
    .defaultSuccessUrl("/");
  • 람다식 적용
http.formLogin(formLogin -> 
    formLogin
    .loginPage("/user/login")               // 커스텀 로그인 페이지
    .loginProcessingUrl("/user/doLogin")    // 로그인 처리 URL
    .usernameParameter("email")             // 사용자명 파라미터 이름 변경
    .passwordParameter("passwd")            // 비밀번호 파라미터 이름 변경
    .defaultSuccessUrl("/dashboard")        // 로그인 성공 시 이동 경로
    .failureUrl("/user/login?error=true")   // 로그인 실패 시 이동 경로
);

안 쓰는 경우?
-OAuth2 로그인 (http.oauth2Login())
-JWT 기반 인증 (필터 커스터마이징)
-API 서버에서 토큰 기반 인증만 사용하는 경우

Spring Security http.formLogout() 메소드를 이용한 설정

  • logoutRequestMatcher : 로그아웃을 처리할 URL을 지정합니다. 기본은 POST /logout이지만, 여기서는 GET /user/logout도 허용하기 위해 AntPathRequestMatcher를 사용
  • logoutSuccessUrl("/") : 로그아웃 성공 후 리다이렉트할 경로입니다. 보통 홈이나 로그인 페이지로 설정
  • invalidateHttpSession(true) : 로그아웃 시 현재 HTTP 세션을 무효화합니다. 즉, 세션에 저장된 사용자 정보나 인증 정보가 모두 삭제.
.logout((logout) -> logout
    .logoutRequestMatcher(new AntPathRequestMatcher("/user/logout")) // get/post 방식 로그아웃 처리
    .logoutSuccessUrl("/")       //로그아웃 처리 후 url
    .invalidateHttpSession(true) // 로그아웃 후 세션 파기
    .deleteCookies("JSESSIONID")  // 쿠키 내 세션정보 파기
)

@Bean 생성

  • Spring에서 @Bean을 사용하는 이유는 객체를 직접 생성하지 않고, Spring이 대신 관리하도록 하기 위해서입니다. 이걸 "의존성 주입(DI, Dependency Injection)"이라고 함.
  • 관련 학습사항 : @Component, @Service, @Configuration 등

id/password 불일치 처리

        <div th:if="${param.error}">
            <div class="alert alert-danger">
                사용자ID 또는 비밀번호를 확인해 주세요.
            </div>
        </div>
  • 로그인 폼 양식과 같이 출력하는 경우. login?error 형식의 파라미디터 값이 전달되는 경우에만 화면출력됨.

로그인/아웃 상태 메뉴바 토글적용

        <li class="nav-item">
            <a class="nav-link" sec:authorize="isAnonymous()" th:href="@{/user/login}">로그인</a>
            <a class="nav-link" sec:authorize="isAuthenticated()" th:href="@{/user/logout}">로그아웃</a>
        </li>
  • Thymeleaf + Spring Security를 함께 사용할 때 로그인/로그아웃 링크를 사용자 ROLE 인증 상태에 따라 조건부로 보여주는 방식.

SecurityConfig.java - 보안설정과 DB조회

User Repository 내 사용자 정보 찾기 기능 추가

package com.mysite.sbb.user;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<SiteUser, Long> {
    Optional<SiteUser> findByusername(String username);
}
  • findByusername 메소드를 추가하여 SiteUser 정보 확인

com.mysite.sbb.user 패키지에 UserRole.java를 추가하여 관리자 유형관리

package com.mysite.sbb.user;

import lombok.Getter;

@Getter
public enum UserRole {
    ADMIN("ROLE_ADMIN"),
    USER("ROLE_USER");

    UserRole(String value) {
        this.value = value;
    }

    private String value;
}

enum 열거 자료형을 이용하여 ADMIN, USRER 라는 상수를 등록하고 각각의 값을 부여. 이때 ROLE_ 이라는 접두사를 반드시 적용. @Setter가 없는 이유는 상수 선언이기 때문

UserSecurityService 서비스 생성

package com.mysite.sbb.user;

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

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 lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class UserSecurityService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<SiteUser> _siteUser = this.userRepository.findByusername(username);
        if (_siteUser.isEmpty()) {
            throw new UsernameNotFoundException("사용자를 찾을수 없습니다.");
        }
        SiteUser siteUser = _siteUser.get();
        List<GrantedAuthority> authorities = new ArrayList<>();
        if ("admin".equals(username)) {
            authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.getValue()));
        } else {
            authorities.add(new SimpleGrantedAuthority(UserRole.USER.getValue()));
        }
        return new User(siteUser.getUsername(), siteUser.getPassword(), authorities);
    }
}
  • @Override 적용 이유는 기존 메소드를 수정 사용하기 위해 적용
  • 스프링 시큐리티가 로그인 시 사용할 UserSecurityService는 스프링 시큐리티가 제공하는 UserDetailsService 인터페이스를 구현(implements)해야 한다. 스프링 시큐리티의 UserDetailsService는 loadUserByUsername 메서드를 구현하도록 강제하는 인터페이스이다. loadUserByUsername 메서드는 사용자명(username)으로 스프링 시큐리티의 사용자(User) 객체를 조회하여 리턴하는 메서드. 사용자정보를 DB에서 조회한 이후 반환하는 기본값은 "UserDetails" 객체로서 Spring Security가 인증에 필요한 정보를 가지고 있음
// 일반적 적용단계
@Service // 서비스계층임을 명시하면서 Spring이 자동으로 Bean에 등록하고 의존성 주입 대상으로 등록하기 위해 사용
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + username));

        return new org.springframework.security.core.userdetails.User(
            user.getUsername(),
            user.getPassword(),
            getAuthorities(user)
        );
    }

    private Collection<? extends GrantedAuthority> getAuthorities(User user) {
        return List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
    }
}
profile
한줄소개불가

0개의 댓글