스프링 기반의 애플리케이션 보안을 담당하는 스프링 하위 프레임워크이다.
Spring Security는 인증과 권한에 대한 부분을 Filter 흐름에 따라 처리한다.
Filter는 Dispatcher Servlet으로 가기 전에 적용되기에 가장 먼저 URL 요청을 받지만, Interceptor는 Dispatcher와 Controller 사이에 위치한다는 점에서 적용 시기의 차이가 있다.
더 자세한 건 스프링 MVC - 필터, 인터셉터 을 확인하면 된다.
Spring Security의 흐름은 아래와 같다.

출처 : https://medium.com/@greekykhs/springsecurity-part-3-spring-security-flow-7da9cc3624ab

Spring Security는 기본적으로 인증 절차를 거친 후 인가 절차를 진행하며 인가 과정에서 해당 리소스에 대한 접근 권한이 있는지 확인하게 된다.
이런 인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하는 Credential 기반 인증 방식을 사용한다.
gradle 파일에
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
이 코드를 추가해준다.
이때 maven을 쓴다면
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>5.7.1</version>
</dependency>
이 코드를 추가한다.
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootTest
class PwdEncodeTest {
@Autowired
PasswordEncoder passwordEncoder; // DI
@Test
void pwdEnc() {
String pwd = "melong";
String encodedPwd = passwordEncoder.encode(pwd); //암호화
System.out.println(encodedPwd);
}
}
passwordEncoder의 DI를 받아서 passwordEncoder.encode(비밀번호)를 해주면 자동으로 암호화해준다.
이렇게 암호화된 비밀번호를 출력하면 우리가 입력한 값이 아닌 암호화된 값을 확인해볼 수 있다.
이때 암호화된 값이 항상 바뀌는데 이를 확인하기 위해서는 passwordEncodr.mathes()를 사용해 기존 비밀번호와 일치하는지 아닌지를 확인한다. 이때 이 메서드는 boolean타입을 반환한다.
일치하면 true를 반환하고 일치하지 않으면 false를 반환한다.
유저 아이디와 패스워드 사용자 정보를 넣고 실제 사용자인지 체크 후 인증에 성공하면 사용자의 principal, credential 정보를 Authentication 안에 담는다.
Spring Security에서 방금 담은 Authencation을 Security Context에 보관한다. 이 Security Context를 SecurityContextHolder에 담아 보관하게 된다. 구조는 위의 그림을 참고하면 된다.
Authentication 클래스는 현재 접근하는 주체의 정보와 권한을 담는 인터페이스이며 SecurityContext에 저장되고 SecurityContextHolder를 통해 SecurityContext에 접근할 수 있다.
이 클래스는 Authentication을 구현한 AbstractAuthenticationToken의 하위 클래스로 유저 아이디가 principal의 역할을 하고 유저의 패스워드가 credential 역할을 한다.
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
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<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
}
인증에 대한 부분은 AuthenticationManager을 통해 처리된다. 이때 AuthenticationManager는 인터페이스 이기 때문에 이를 구현한 AuthenticationProvider에 의해 처리된다.
이 클래스는 실제 인증에 대한 부분을 처리하는 작업을 치룬다.
인증 전, Authentication 객체를 받아 인증이 완료된 객체를 반환하는 역할을 한다.
그리고 AuthenticationManager에 등록하면 된다.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
AuthenticationManager를 구현한 ProviderManager는 AuthenticationProvider를 구성하는 리스트를 갖게된다.
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
public List<AuthenticationProvider> getProviders() {
return this.providers;
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
for (AuthenticationProvider provider : getProviders()) { ... }
}
}
UserDetails 객체를 반환하는 하나의 메서드만을 가진다. 보통 UserRepository를 주입받아 데이터베이스와 연결해 처리한다.
인증에 성공하여 생성된 UserDetails 클래스는 Authentication 객체를 구현한 UsernamePasswordAuthenticationToken을 생성하기 위해 사용된다.
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
Authentication을 보관하는 역할을 하며, 이를 통해 Authentication에 저장하거나 꺼내올 수 있다.