
@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를 통해 인증 진행.
Security가 로그인 성공 시 만드는 세션 구조:
Security Session
└── Authentication (인증 객체)
└── UserDetails (PrincipalDetails)
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 엔티티를 시큐리티가 인식할 수 있는 객체로 변환.
@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 반환.
public interface UserRepository extends JpaRepository<User, Integer> {
User findByUsername(String username);
}
규칙 기반 메서드 생성: findByUsername → select * from user where username=?
@Secured@EnableGlobalMethodSecurity(securedEnabled = true) // 활성화
@Secured("ROLE_ADMIN")
public String adminOnly() { return "admin"; }
@PreAuthorize@EnableGlobalMethodSecurity(prePostEnabled = true) // 활성화
@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
public String managerOrAdmin() { return "manager"; }
@Secured = 단일 권한, @PreAuthorize = 다중 권한 제어.
새 프로젝트 생성 → OAuth 동의 화면 외부 설정
OAuth 클라이언트 ID 발급 (웹 애플리케이션 선택)
승인된 리다이렉션 URI:
http://localhost:8080/login/oauth2/code/google
클라이언트 ID & Secret 발급
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
spring:
security:
oauth2:
client:
registration:
google:
client-id: 발급받은-client-id
client-secret: 발급받은-client-secret
scope:
- email
- profile
<a href="/oauth2/authorization/google">구글 로그인</a>
/oauth2/authorization/google 경로는 Spring Security OAuth2 Client가 자동 제공.
@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 페이지 사용
}