[Spring Security] SecurityConfig

SeungWoo Cha·2025년 9월 23일

Spring_Security

목록 보기
2/4

Spring Security


1. 커스텀 로그인 & UserDetailsService

1.1. SecurityConfig 확장

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeRequests()
            .antMatchers("/user/**").authenticated()
            .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
            .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
            .anyRequest().permitAll()
        .and()
        .formLogin()
            .loginPage("/loginForm")          // 사용자 정의 로그인 페이지
            .loginProcessingUrl("/login")     // login 요청을 시큐리티가 낚아채서 로그인 처리
            .defaultSuccessUrl("/");          // 로그인 성공 시 이동 경로
}

핵심: loginProcessingUrl("/login") 등록 시 컨트롤러에 /login을 직접 만들 필요가 없다.
Spring Security 필터가 낚아채서 AuthenticationManager를 통해 인증 진행.


1.2. 인증 객체 구조

  • Security가 로그인 성공 시 만드는 세션 구조:

    Security Session
     └── Authentication (인증 객체)
          └── UserDetails (PrincipalDetails)

1.3. PrincipalDetails (UserDetails 구현체)

public class PrincipalDetails implements UserDetails {
    private User user;

    public PrincipalDetails(User user) { this.user = user; }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add(() -> user.getRole()); // 권한 반환
        return collect;
    }

    @Override public String getPassword() { return user.getPassword(); }
    @Override public String getUsername() { return user.getUsername(); }
    @Override public boolean isAccountNonExpired() { return true; }
    @Override public boolean isAccountNonLocked() { return true; }
    @Override public boolean isCredentialsNonExpired() { return true; }
    @Override public boolean isEnabled() { return true; }
}

UserDetails 인터페이스를 구현하여 User 엔티티를 시큐리티가 인식할 수 있는 객체로 변환.


1.4. PrincipalDetailsService (UserDetailsService 구현체)

@Service
public class PrincipalDetailsService implements UserDetailsService {
    @Autowired private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userEntity = userRepository.findByUsername(username);
        if (userEntity != null) {
            return new PrincipalDetails(userEntity);
        }
        return null;
    }
}

시큐리티가 /login 요청을 받으면 내부적으로 loadUserByUsername 실행 → DB 조회 후 PrincipalDetails 반환.


1.5. UserRepository 확장

public interface UserRepository extends JpaRepository<User, Integer> {
    User findByUsername(String username); 
}

규칙 기반 메서드 생성: findByUsernameselect * from user where username=?



2. 메서드 단위 권한 처리

2.1. @Secured

  • 특정 메서드에 단일 권한만 허용
@EnableGlobalMethodSecurity(securedEnabled = true) // 활성화
@Secured("ROLE_ADMIN") 
public String adminOnly() { return "admin"; }

2.2. @PreAuthorize

  • 두 개 이상의 권한 허용 가능
@EnableGlobalMethodSecurity(prePostEnabled = true) // 활성화
@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
public String managerOrAdmin() { return "manager"; }

@Secured = 단일 권한, @PreAuthorize = 다중 권한 제어.



3. OAuth2 로그인 준비 (구글 예시)

3.1. 구글 API Console 설정

  • 새 프로젝트 생성 → OAuth 동의 화면 외부 설정

  • OAuth 클라이언트 ID 발급 (웹 애플리케이션 선택)

  • 승인된 리다이렉션 URI:

    http://localhost:8080/login/oauth2/code/google
  • 클라이언트 ID & Secret 발급


3.2. dependency 추가

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

3.3. application.yml 설정

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 발급받은-client-id
            client-secret: 발급받은-client-secret
            scope:
              - email
              - profile

3.4. 로그인 링크 추가

<a href="/oauth2/authorization/google">구글 로그인</a>

/oauth2/authorization/google 경로는 Spring Security OAuth2 Client가 자동 제공.


3.5. SecurityConfig 확장

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeRequests()
            .antMatchers("/user/**").authenticated()
            .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
            .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
            .anyRequest().permitAll()
        .and()
        .formLogin()
            .loginPage("/loginForm")
            .loginProcessingUrl("/login")
            .defaultSuccessUrl("/")
        .and()
        .oauth2Login()
            .loginPage("/loginForm"); // OAuth2 로그인도 동일한 loginForm 페이지 사용
}
profile
한 발자국씩

0개의 댓글