스프링 시큐리티 사용 전
스프링 시큐리티 사용 후
- Client의 요청은 모두 Spring Security를 거치게 됨
- Spring Security 역할
- 인증/인가
- 성공 시: Controller로 Client 요청 전달
- Client 요청 + 사용자 정보 (UserDetails)
- 실패 시: Controller로 Client 요청 전달되지 않음
- Client에게 Error Response 보냄
로그인 처리 과정
Client
a. 로그인 시도
b. 로그인 시도할 username, password 정보를 HTTP body로 전달(post 요청)
c. 로그인 시도 URL은 WebSecurityConfig 클래스에서 변경 가능
* 아래와 같이 설정 시 "POST/api/user/login"로 설정됨``` @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // CSRF 설정 http.csrf((csrf) -> csrf.disable()); http.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정 .anyRequest().authenticated() // 그 외 모든 요청 인증처리 ); // 로그인 사용 http.formLogin((formLogin) -> formLogin // 로그인 처리 (POST /api/user/login) .loginProcessingUrl("/api/user/login").permitAll() ); return http.build(); } ```
.loginPage()
- 우리가 만든 로그인 페이지 제공 가능
.loginProcessingUrl()
- Security 앞 단 쪽에서 처리
.loginSuccessUrl()
- 성공하면 메인 페이지로 이동
.failureUrl()
- 실패하면 ~?error로 반환
인증 관리자(Authentication Manager)
a. UserDetailsService에게 username을 전달하고 회원상세 정보 요청
- UserDetailsService
a. 회원 DB에서 회원 조회User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("Not Found " + username));
- 회원 정보가 존재하지 않을 시 ➡️ Error 발생
b. 조회된 회원 정보(user)를 UserDetails로 변화
UserDetails userDetails = new UserDetailsImpl(user)
c. UserDetails를 "인증 관리자"에게 전달
- "인증 관리자"가 인증 처리
a. 아래 2개의 username, password 일치 여부 확인c. 인증 성공 시 ➡️ 세션에 로그인 정보 저장
- Client가 로그인 시도한 username, password
- UserDetailsService가 전달해준 UserDetails의 username, password
b. password 비교 시
- Client가 보낸 password는 평문이고, UserDetails의 password는 암호문
- Client가 보낸 password를 암호화해서 비교
d. 인증 실패 시 ➡️ Error 발생
1. 로그인 구현
- 우리가 직접 Filter를 구현해서 URL 요청에 따른 인가를 설정한다면
- 코드가 매우 복잡해지고 유지보수 비용이 많이 들 수 있음
- Spring Security를 사용하면 이러한 인가 처리가 매우 편해짐
requestMatchers("/api/user/**").permitAll()
- 이 요청들은 로그인, 회원가입 관련 요청이기 때문에 비회원/회원 상관없이 누구나 접근 가능해야함
- 이렇게 인증이 필요 없는 URL들을 간편하게 허가 가능
anyRequest().authenticated()
- 인증이 필요한 URL들도 간편하게 처리 가능
2. DB의 회원 정보 조회 ➡️ Spring Security의 "인증 관리자"에게 전달
- UserDetailsService 구현
- UserDetailsService 인터페이스 ➡️ UserDetailsServiceImpl
package com.sparta.springauth.security; import com.sparta.springauth.entity.User; import com.sparta.springauth.entity.UserRoleEnum; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; public class UserDetailsImpl implements UserDetails { private final User user; public UserDetailsImpl(User user) { this.user = user; } public User getUser() { return user; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public Collection<? extends GrantedAuthority> >getAuthorities() { UserRoleEnum role = user.getRole(); String authority = role.getAuthority(); SimpleGrantedAuthority simpleGrantedAuthority = >new SimpleGrantedAuthority(authority); Collection<GrantedAuthority> authorities = new >ArrayList<>(); authorities.add(simpleGrantedAuthority); return authorities; } @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 구현
- UserDetails 인터페이스 ➡️ UserDetailsImpl
- UserDetailsService와 UserDetails를 직접 구현해서 사용하게 되면 Security의 default 로그인 기능을 사용하지 않겠다는 설정이 됨
- Security의 password를 더 이상 제공하지 않는 것 확인 가능!
- POST "/api/user/login"을 로그인 인증 URL로 설정했기 때문에 이제 해당 요청이 들어오면
- 우리가 직접 구현한 UserDetailsService를 통해 인증 확인 작업이 이뤄지고
- 인증 객체에 직접 구현한 UserDetails가 담기게 됨
@Controller
@RequestMapping("/api")
public class ProductController {
@GetMapping("/products")
public String getProducts(@AuthenticationPrincipal UserDetailsImpl userDetails) {
// Authentication 의 Principal 에 저장된 UserDetailsImpl 을 가져옵니다.
User user = userDetails.getUser();
System.out.println("user.getUsername() = " + user.getUsername());
return "redirect:/";
}
}
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails) {
// 페이지 동적 처리 : 사용자 이름
model.addAttribute("username", userDetails.getUser().getUsername());
return "index";
}
}