빙응의 공부 블로그
[Spring]Spring Security + JWT(1) 본문
📝JWT를 구현해보자!
JWT를 간단하게 구현해보자.
나는 2가지 토큰을 이용한 방식을 사용하지 않고
1가지 토큰만 사용하는 방식을 사용하겠다!
개발자 유미 - YouTube
개발자 유미
백엔드 개발자 유미 - 실습 위주 진행 (개념적인 부분은 공식 Docs 참조 및 개인 학습 바랍니다!) - 간혹 댓글 알림이 안오는 경우가 있습니다.
www.youtube.com
이분꺼 보고 학습했으니 구독박자
📝1. 회원가입 구현
회원가입은 통상의 Spring Security와 다른 점이 없다.
간단하게 중복 검사를 통한 회원 가입을 진행한다.
🚩JWT에 초점을 맞춰 우리는 비밀번호 암호화를 제외한 Validation은 진행하지 않는다!
@PostMapping("/join")
public String joinProcess(JoinDTO joinDTO) {
joinService.joinProcess(joinDTO);
return "ok";
}
@Service
@RequiredArgsConstructor
public class JoinService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public void joinProcess(JoinDTO joinDTO) {
String username = joinDTO.getUsername();
String password = joinDTO.getPassword();
Boolean isExist = userRepository.existsByUsername(username);
if (isExist) {
return;
}
UserEntity data = new UserEntity();
data.setUsername(username);
data.setPassword(bCryptPasswordEncoder.encode(password));
data.setRole("ROLE_ADMIN");
userRepository.save(data);
}
}
📝2. 로그인 구현
🚩기존 Spring Security 방식은 DispatcherServlet(Controller) 전에 기본으로 내장된 로그인 필터를 이용해서 로그인 로직을 구현했습니다.
- 그러나 우리는 Spring Security의 formlogin을 사용하지 않기 때문에 직접 로그인 필터를 구현해주어야 한다.!
/**
* 우리는 JWT로 인해 formLogin 방식을 사용하지 않기 때문에 UsernamePassweordAuthenticationFilter를 직접 커스텀으로 만들어 인증을 진행한다.
*/
@RequiredArgsConstructor
@Slf4j
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JWTUtil jwtUtil;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
// 사용자가 제출한 인증 정보로 Authentication 객체 생성
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null);
// AuthenticationManager에게 인증을 위임
return authenticationManager.authenticate(authToken);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {
//나중에 로그인 성공 시 토큰을 만들어줄 메소드
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
}
}
🚩 attemptAuthentication 메소드
- UsernamePasswordAuthenticationFilter에서 제공하는 로그인 시 자동으로 호출되는 메소드이다
- 사용자의 username, password 입력을 받아 인증 처리를 위한 위임을 한다.
🚩 successfulAuthentication 메소드
- attemptAuthentication에서 인증을 위임한 AuthenticationManager가 인증 성공 시 실행하는 메소드이다.
- 인증 성공 후 토큰을 생성하는 메소드를 적을 장소이다.
🚩 unsuccessfulAuthentication 메소드
- 로그인 인증에 실패 시 실행하는 메소드이다.
- 보통 사용자 오류 처리가 이루어진다.
📝3. Security Config 구현하기
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
//AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입
private final AuthenticationConfiguration authenticationConfiguration;
//AuthenticationManager Bean 등록
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf((auth) -> auth.disable());
http
.formLogin((auth) -> auth.disable());
http
.httpBasic((auth) -> auth.disable());
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login", "/", "/join").permitAll()
.anyRequest().authenticated());
//필터 추가 LoginFilter()는 인자를 받음 (AuthenticationManager() 메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요
http
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class);
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
🚩 우리가 만든 커스텀필터를 Security Config에 설정을 해주어야 한다.
- 또한 JWT는 무상태성이기 때문에 세션을 무상태성으로 설정해야한다.
📝4. 인증 객체 구현하기
🚩우리는 인증을 하기 위한 필터를 구현하였지만 AuthenticationManager가 인증을 위해 사용할 객체는 구현하지 않았다.
/**
* 스프링 시큐리티에서 사용자의 인증 정보를 로드하는데 사용
*/
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//DB에서 조회
UserEntity userData = userRepository.findByUsername(username);
if (userData != null) {
//UserDetails에 담아서 return하면 AutneticationManager가 검증 함
return new CustomUserDetails(userData);
}
return null;
}
}
🚩 해당 메소드는 UserDetailsService를 상속받는 것으로 AutneticationManager가 검증을 위해 사용할 객체를 반환한다.
- loadUserByUsername 메소드
- 입력으로 들어온 username을 통해 비교할 객체를 가져온다.
- 이를 통해 검증을 시작하는 메소드이다.
/**
* 스프링 시큐리티에서 사용자의 정보를 표현하는데 사용
*/
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {
private final UserEntity userEntity;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 사용자가 가진 권한을 담을 리스트
List<GrantedAuthority> authorities = new ArrayList<>();
// 사용자 엔티티로부터 권한 정보를 가져와서 권한 객체로 변환하여 리스트에 추가
String role = userEntity.getRole();
authorities.add(new SimpleGrantedAuthority(role));
return authorities;
}
@Override
public String getPassword() {
return userEntity.getPassword();
}
@Override
public String getUsername() {
return userEntity.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
🚩UserDatails는 Spring Security가 사용자 정보를 표현하기 위한 객체이다.
- 해당 인터페이스는 다음과 같은 역할을 가진다.
- 사용자 인증 정보 제공
- 사용자 정보 제공 : 로그인한 사용자의 정보를 제공하는 역할
[Spring]Spring Security + JWT(2) (tistory.com)
[Spring]Spring Security + JWT(2)
🚩 전 포스팅가기 [Spring]Spring Security + JWT(1) (tistory.com) [Spring]Spring Security + JWT(1) 📝JWT를 구현해보자! JWT를 간단하게 구현해보자. 나는 2가지 토큰을 이용한 방식을 사용하지 않고 1가지 토큰만 사
quddnd.tistory.com
'Spring > 개인공부_실습' 카테고리의 다른 글
[React + Spring]리액트와 스프링으로 API 받아서 처리하기 (0) | 2024.05.20 |
---|---|
[Spring]Spring - RestTemplate (0) | 2024.05.19 |
[React + Spring]리액트와 스프링 - 연동 (0) | 2024.05.17 |
[Spring]Spring Security + JWT(2) (1) | 2024.04.07 |
[Spring]스프링 시큐리티 - 간단한 실습 (0) | 2024.03.24 |