유잔씨가 Spring Security 정의나 필터는 잘 설명해놨기에 사용하는 클래스나 인터페이스, 그리고 제어하는 방법을 정리해보았다.
SecurityContextHolder
- 보안 주체의 세부 정보를 포함한 응용프로그램의 현재 보안 컨텍스트에 대한 세부정보 저장
SecurityContext
- Authentication을 보관하는 역할
- SecurityContext 를 통해서 Authentication 객체를 꺼내온다.
Authentication
- 현재 접근하는 주체의 정보와 권한을 담는 인터페이스
- SecurityContext에 담겨있어 접근하기 위해선 SecurityContextHolder > SecurityContext > Authentication순으로 접근해야한다.
public interface Authentication extends Principal, Serializable {
// 현재 사용자의 권한 목록을 가져옴
Collection getAuthorities();
// credentials(주로 비밀번호)을 가져옴
Object getCredentials();
Object getDetails();
// Principal 객체를 가져옴.
Object getPrincipal();
// 인증 여부를 가져옴
boolean isAuthenticated();
// 인증 여부를 설정함
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
UsernamePasswordAuthenticationToken
Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스로
User의 ID가 Principal 역할을 하고, Password가 Credential의 역할을 한다.
- 생성자
- 두번째 생성자 - 인증이 완료된 객체 생성
- 첫번째 생성자 - 인증 전 객체 생성
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
// 주로 사용자의 ID에 해당함
private final Object principal;
// 주로 사용자의 PW에 해당함
private Object credentials;
// 인증 완료 전의 객체 생성
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
// 인증 완료 후의 객체 생성
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
}
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
}
AuthenticationProvider
실제 인증에 대한 부분을 처리하며 Authenication객체를 받아 인증이 완료된 객체를 반환한다.
AuthenticationProvider 인터페이스를 구현해서 커스텀한 provider를 AuthenticationManager에 등록한다.
public interface AuthenticationProvider {
// 인증 전의 Authenticaion 객체를 받아서 인증된 Authentication 객체를 반환
Authentication authenticate(Authentication var1) throws AuthenticationException;
boolean supports(Class var1);
}
AuthenticationManager
인증은 매니저를 통해 처리되는데 실질적으론 매니저에 등록된 provider에 의해 처리된다.
usename~ 첫번째 생성자 실행 → provider 실행(매니저가 등록) → usename~ 두번째 생성자 실행
→ 인증이 성공한 객체 생성되어 Security Context에 저장되며 인증 상태 유지를 세션에 보관
! 단, 인증이 실패한 경우 AuthenticationException 발생한다.
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
ProviderManager
실제 인증 과정에 대한 로직을 가지고 있는 AuthenticaionProvider를 List로 가지고 있으며
ProividerManager는 for문을 통해 모든 provider를 조회하면서 authenticate 처리한다.
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
public List<AuthenticationProvider> getProviders() {
return providers;
}
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
//for문으로 모든 provider를 순회하여 처리하고 result가 나올 때까지 반복한다.
for (AuthenticationProvider provider : getProviders()) {
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
}
throw lastException;
}
}
ProviderManager에 구현한 CustomAuthentication을 등록하는 방법
WebSecurityConfigurerAdapter를 상속해 만든 SecurityConfig에서 진행
→ WebSecurityConfigurerAdapter의 상위 클래스에서는 AuthenticationManager를 가지고 있기 때문에 우리가 직접 만든 CustomAuthenticationProvider를 등록할 수 있다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public CustomAuthenticationProvider customAuthenticationProvider() throws Exception {
return new CustomAuthenticationProvider();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
}
UserDetails
- 인증에 성공하여 생성된 UserDetails 객체는 Authentication객체를 구현한 UsernamePasswordAuthenticationToken을 생성하기 위해 사용된다.
- 즉 사용자 정보를 불러오기 위한 인터페이스
- DB나 다른 사용자 저장소로부터 사용자 정보를 가져오는데 사용
- UserDetails 인터페이스를 살펴보면 아래와 같이 정보를 반환하는 메소드를 가지고 있다.
UserDetails 인터페이스의 경우 직접 개발한 UserVO 모델에 UserDetails를 implements하여 이를 처리하거나 UserDetailsVO에 UserDetails를 implements하여 처리할 수 있다.
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
UserDetailsService
- UserDetailsService 인터페이스는 UserDetails 객체를 반환하는 단 하나의 메소드를 가지고 있는데, 일반적으로 이를 구현한 클래스의 내부에 UserRepository를 주입받아 DB와 연결하여 처리한다.
- UserDetails 인터페이스는 아래와 같다.
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
Password Encoding
AuthenticationManagerBuilder.userDetailsService().passwordEncoder() 를 통해 패스워드 암호화에 사용될 PasswordEncoder 구현체를 지정할 수 있다.
→ BCryptPasswordEncoder가 일반적으로 사용
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
GrantedAuthority
GrantAuthority는 현재 사용자(principal)가 가지고 있는 권한을 의미한다.
ROLE_ADMIN나 ROLE_USER와 같이 ROLE_*의 형태로 사용하며, 보통 "roles" 이라고 한다.
GrantedAuthority 객체는 UserDetailsService에 의해 불러올 수 있고, 특정 자원에 대한 권한이 있는지를 검사하여 접근 허용 여부를 결정한다.
로그인 처리 과정
클래스나 인터페이스 설명을 쭉 읽고 로그인 처리하는 과정을 읽어보면 훨씬 이해가 잘된다.
- 사용자가 아이디, 패스워드를 입력해 넘기면 HTTPServletRequest에 아이디, 비밀번호 정보가 전달된다.
이때 AuthenticationFilter가 넘어온 아이디와 비밀번호의 유효성 검사를 실시한다. - 유효성 검사 후 실제 구현체인 UsernamePasswordAuthenticationToken을 만들어 넘겨준다.
- 인증용 객체인 UsernamePasswordAuthenticationToken을 AuthenticationManager에게 전달한다.
- AuthenticationManager는 UsernamePasswordAuthenticationToken을 AuthenticationProvider에게 전달한다.
- 사용자 아이디를 UserDetailsService로 보낸다.
UserDetailService는 사용자 아이디로 찾은 사용자의 정보를 UserDetails 객체로 만들어 AuthenticationProvider에게 전달한다. - DB에 있는 사용자 정보를 가져온다.
- 입력 정보와 UserDetails의 정보를 비교해 실제 인증 처리를 진행한다.
- 사진 속 10번까지 인증이 완료되면 SecurityContextHolder에 Authentication을 저장한다.
인증 성공 여부에 따라 성공 시 AuthenticationSuccessHandler, 실패 시 AuthenticationFailureHandler 핸들러를 실행한다.
보안 컨텍스트를 통해 사용자 정보 접근하기
만약 인증 성공 시 다음과 같이 인증된 사용자의 정보를 가져올 수 있다.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
권한 기반 접근 제어
인증된 사용자에게 권한 기반 접근 제어를 적용할 수 있다.
특히 Spring Security는 @PreAuthorize, @Secured 같은 애노테이션을 통해 특정 메서드에 대한 접근을 제한할 수 있다.
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void secureMethod() {
// 관리자인 사용자만 접근 가능
}
URL 기반 접근 제어
Spring Security 설정 파일 = WebSecurityConfigurerAdapter를 상속받은 클래스 에서 URL 기반으로 접근 제어를 할 수 있다.
@Configuration
@EnableWebSecurity //Spring Security를 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() //http.authorizeRequests(): 요청에 대한 인가(권한 부여) 규칙을 설정하기 시작
.antMatchers("/").permitAll() // 홈 페이지는 모든 사용자가 접근 가능
.anyRequest().authenticated() // 그 외 모든 요청은 인증된 사용자만 접근 가능
.and() //설정을 연결
.formLogin() //폼 기반 로그인을 설정
.loginPage("/login") // 사용자 정의 로그인 페이지를 설정
.permitAll() //로그인 페이지에 대한 접근을 모든 사용자에게 허용
.and() //설정을 연결
.logout() //로그아웃을 설정
.permitAll(); //로그아웃 URL에 대한 접근을 모든 사용자에게 허용
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); //비밀번호 암호화
}
}
'Spring' 카테고리의 다른 글
기몽수의 FCM 웹 알림 서비스 (0) | 2024.08.17 |
---|---|
[Spring Security] 다중 UserDetailsService 사용하기 (1) | 2024.08.04 |
스프링내맘대로정리하기 1-2편 (환경 구성, 연관 관계) (0) | 2024.07.28 |
몽수의 Spring Scheduler (0) | 2024.07.28 |
[Security] 유잔씨의 Spring Security에 대하여 (0) | 2024.07.21 |