Spring Security 의 구조
1. 클라이언트의 요청이 들어오면 AuthenticationFilter에서 이를 가로챈다.
2-3. 전달받은 클라이언트의 아이디 / 비밀번호 를 UsernamePasswordAuthenticationToken에 담는다. (인증 객체 생성)
4. 이를 AutenticationManager를 이용하여 실제 인증을 구현하는 ProviderManager에 전달한다.
5. ProviderManager에 있는 AuthenticationProvider의 여러 인증 메서드 중 적절한 것을 사용 ( 별도 설정이 없을땐 Spring Security의 기본 설정에 의해 DaoAuthenticationProvider를 사용한다.)
6. DaoAuthenticationProvider가 UserDetailsService를 이용하여 전달받은 사용자 정보를 이용해서 DB에서 사용자 정보를 가져와 UserDetails 객체로 반환한다.
7. 이때 DaoAuthenticationProvider가 UserDetails객체에서 비밀번호를 가져와 사용자가 입력한 비밀번호와 비교합니다.
일치하면 사용자의 인증정보와 권한 정보가 담긴 Authentication 객체를 반환하여 Security Context에 저장합니다.
□ build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-web'
// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'
// JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// JWT
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
build.gradle에 필요한 의존성들을 추가해주었습니다.
□ LoginRequest
@Getter
public class LoginRequest {
private String loginId;
private String password;
}
□ UserDetailsServiceImpl
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String loginId) throws UserNotFoundException {
User user = userRepository.findByLoginId(loginId)
.orElseThrow(() -> new UsernameNotFoundException(loginId + "를 찾을 수 없습니다."));
/**
* 유저를 찾지 못해 UsernameNotFoundException가 발생하면 JwtAuthenticationEntryPoint를 호출 */
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(() -> user.getRole().getKey());
return new org
.springframework
.security
.core
.userdetails
.User(user.getLoginId(), user.getPassword(), authorities);
}
}
□ UserService
// 로그인
public LoginResponse login(LoginRequest loginRequest) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginRequest.getLoginId(), loginRequest.getPassword());
Authentication authenticate = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
TokenResponse tokenResponse = jwtProvider.generateTokenDto(authenticate);
LoginResponse loginResponse = new LoginResponse(tokenResponse);
return loginResponse;
}
- 전달받은 사용자의 아이디 / 비밀번호를 UsernamePasswordAuthenticationToken에 담습니다.
- 이 토큰을 authenticationManagerBuilder.getObject()를 이용하여 authenticationManager객체를 가져옵니다.
그리고 .authenticate() 메서드를 실행합니다. 이때 UserDetailsService의 loadUserByUsername메서드가 실행됩니다.
- loadUserByUSername메서드를 통해 LoginId를 이용하여 db에서 사용자 정보를 가져옵니다.
(일치하는 사용자 정보가 없다면 UsernameNotFoundException이 발생하고 Spring Security는 사용자가 인증되지 않은 상태로 리소스에 접근하려고 했기 때문에 해당 요청을 인터셉트하여 JwtAuthenticationEntryPoint를 호출합니다.)
- 권한을 부여한 후 UserDetails 객체로 반환하여 전달받은 비밀번호와 DB에서 가져온 비밀번호가 일치한다면 Authentication 객체에 담습니다.
( 회원가입 시 비밀번호를 BCryptPasswordEncoder를 이용하여 암호화를 하였습니다. DaoAuthenticationProvider는 db에서 가져온 비밀번호와 비교할때 전달 받은 비밀번호를 암호화된 형태로 비교하기 때문입니다.)
JWT 발급 부분은 다음에 이어서 적도록 하겠습니다.
혹시 잘못된게 있다면 알려주시면 정말 감사하겠습니다!!