이번에는 스프링 시큐리티의 로그인 로직에 대해서 살펴보고자 한다.
사실 해당 부분 과정이 나에게는 어렵게 느껴져서, 아직도 긴가민가 하다,,,
우선 내가 이해해본대로 설명해보겠다.
01. 전체 구조
위의 이미지는 스프링 시큐리티의 로그인 로직을 보여준다. 하나씩 과정을 살펴보자.
- 1. 클라이언트의 요그인 요청이 날아오면
AuthenticationFilter(UsernamePasswordAuthenticationFilter)
가 요청을 가로챈다. - 2.
AuthenticationFilter
가UsernamePasswordAuthenticationToken
객체를 생성한다.
이때 사용된 토큰은 사용자가 입력한 로그인 정보를 담고있으며, 해당 객체는 로그인 검증에 사용된다. - 3. 생성한 토큰 객체를
AuthenticationManager
에게 전송하면서, 인증 과정을 위임한다. - 4.
AuthentictionManager
는 실제로 인증을 처리할AuthenticationProvider
객체를 선택한 후, 인증 과정을 다시 위임한다. - 5.
AuthenticationProvider
가 로그인 정보를 검증하기 위해, DB에 저장되어있는 로그인 정보를 가져온다.
이때, DB 정보는UserDetailsService
객체를 사용하여 가져온다. 가져오는 정보는UserDetails
형태이다. - 6. DB 정보를 가져온다.
- 7. DB 정보를
AuthenticationProvider
에게 넘겨준다. 이때 정보는UserDetails
형태로 넘어간다. - 8. 만약 로그인 정보가 성공적으로 검증 되었으면
AuthenticationManager
객체에게 유저 정보를 건내준다. 이때 정보는Authentication
객체에 담아서 전달한다.
만약 인증이 실패한다면Exception
을 던진다. - 9. 정보 객체를
AuthenticationFilter
에 다시 전달한다. - 10. 인증 객체를
SecurityContext
에 저장한다. 이후 저장된 객체는세션-쿠기 기반 인증 방식
에 사용된다.
02. 구현
위의 과정 중 많은 부분을 스프링 시큐리티가 알아서 처리해주기 때문에, 실제 구현은 매우 간단하다.
하나씩 살펴보자.
View - login.mustache
resources/templates/login.mustache
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
login page
<hr>
<form action="/loginProc" method="post" name="loginForm">
<input id="username" type="text" name="username" placeholder="id"/>
<input id="password" type="password" name="password" placeholder="password"/>
<input type="submit" value="login"/>
</form>
</body>
</html>
/login
경로의 페이지- 폼 정보를 입력 후
/loginProc
로 정보를 전달한다.
SecurityConfig
SecurityConfig.java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
// 커스텀 로그인 페이지 설정
/**
* loginPage(): formLogin 설정
* formLogin 동작 방식:
* 1. 권한이 필요한 페이지 접근
* 2. 로그인 페이지로 redirect
* 3. 로그인 정보 post
* 4. 해당 정보가 일치하면, 인증
* loginPage: 커스텀 로그인 페이지 설정
* loginProcessingURL: 로그인 정보를 받을 url(form action)
*/
http.formLogin((auth)->{
auth.loginPage("/login")
.loginProcessingUrl("/loginProc")
.permitAll();
});
http.csrf((auth)->auth.disable());
return http.build();
}
위와 같은 필터를 추가해준다.
- formLogtin(): 로그인 검증 과정을 추가함
- loginPage(): 로그인 페이지의 URL을 입력한다.
만약 클라이언트가 권한이 필요한 페이지를 요청시 해당 페이지로 자동으로 redirect해준다. - loginProcessingUrl(): 로그인 정보를 담은 폼을 전송할 URL을 입력한다.
해당 URL로 로그인 정보를 보내면 스프링 시큐리티 내부에서 검증을 처리해준다. - csrf(): 해당 옵션을
disable
하지 않으면 별도의 과정을 처리해야한다. 해당 내용은 추후에 포스팅 예정이다.
Controller - LoginController
LoginController.java
package com.example.jwt.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String loginPage(){
return "login";
}
}
로그인 페이지 경로와 템플릿을 매핑해준다.
Service - CustomUserDetailsServices
CustomUserDetailsServices.java
package com.example.jwt.service;
import com.example.jwt.dto.CustomUserDetails;
import com.example.jwt.entity.UserEntity;
import com.example.jwt.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailsService implements UserDetailsService {
UserRepository userRepository;
@Autowired
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userData = userRepository.findByUsername(username);
if (userData != null){
return new CustomUserDetails(userData);
}
return null;
}
}
- UserDetailsService를 구현하는 클래스이다.
- loadUserByUsername(): username으로 사용자 정보를 담은 UserDetails객체를 리턴하는 메소드이다. 꼭 구현해야한다.
- UserRepository를 사용하여 유저 정보를 가져온다.
Repository - UserRepository
UserRepository.java
package com.example.jwt.repository;
import com.example.jwt.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> { // JPA Repo 사용<entity객체, pk 자료형>
boolean existsByUsername(String username);
UserEntity findByUsername(String username);
}
JPA를 사용해 간단하게 구현했다.
CustomUserDetails
CUstomUserDetails.java
package com.example.jwt.dto;
import com.example.jwt.entity.UserEntity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
public class CustomUserDetails implements UserDetails {
private UserEntity userEntity;
public CustomUserDetails(UserEntity userEntity) {
this.userEntity = userEntity;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
/**
* GrantedAuthority 는 구현체
* getAuthority() 메소드를 구현해주어야함
* getAuthority()에서는 권한값(문자열) 반환
*/
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return userEntity.getRole();
}
});
return collection;
}
@Override
public String getPassword() {
return userEntity.getPassword();
}
@Override
public String getUsername() {
return userEntity.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return UserDetails.super.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return UserDetails.super.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return UserDetails.super.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return UserDetails.super.isEnabled();
}
}
UserService가 리턴하는 데이터의 형테이다. 이는 검증 과정에서 사용된다.
- getAuthorities(): 유저의 권한 목록을 담은 리스트를 반환하는 메소드. 이때 유저의 권한은
GrantedAuthory
에 담겨진다.
Reference
'Back End > Spring && Spring Boot' 카테고리의 다른 글
[Spring Boot] 스프링 부트에서 이미지 저장하기 (0) | 2024.06.05 |
---|---|
[Spring Security] 세션 관련 설정하기 - 세션 만료 시간, 최대 세션 갯수, 세션 고정 설정 (0) | 2024.06.04 |
[Spring Security] 회원 가입 로직 (0) | 2024.06.02 |
[Spring Boot] Properties 파일 따로 분리하기 (0) | 2024.06.02 |
[Spring Security] Security 인가 작업 구현 하기 (0) | 2024.06.01 |