회원 도메인 개발과 JWT 인증/인가 처리
Spring Security, JWT
인증은 당신이 누구냐에 대한 것이다. 인가는 당신이 내 집에서 할 수 있는 것들, 즉 사용할 수 있는 자원을 정의한다. Spring Security는 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다.
이번 캡스톤디자인 프로젝트에서 만들게 될 플랫폼 서비스는 기본적으로 회원 도메인이 들어가는 서비스이기에 이에 대한 인증/인가 처리를 유연하게 할 수 있도록 Spring Security를 활용하고자 하였다.
또한 Frontend와 Backend가 분리된 구조로 팀 프로젝트를 진행하기 때문에, 다양한 방법 중 JWT Token 인증/인가 방식으로 회원가입/로그인 로직을 구현하였다.
// JWT 방식에 대한 자세한 사항은 다른 포스팅으로 게시할 예정
// 이미지 출처: jwt.io
구현했던 주요 로직은 다음과 같다.
- Email(ID)/Password 로그인 시도
- Email(ID)/Password 검증 후 Access Token, Refresh Token 발급
- API 요청 시 HTTP Header Authorization에 Access Token을 담아 요청
- API 응답 또는 Access Token 만료 응답 보내줌
- Access Token 만료 시 Request Body에 Access Token, Refresh Token을 담아 재발급 요청
- 토큰 검증 후 새로운 Access Token, Refresh Token을 발급
Backend 패키지 구조
전체적인 패키지 구조는 다음과 같다. 현재는 member 도메인과 모든 도메인에 공통적으로 들어갈 전체적인 뼈대만 작업했기 때문에 요구사항 변경 시 내부 내용물들은 바뀔 수도 있다.
WaitForm
├── src
│ ├── domain # 각 도메인의 내부 구조는 같다.
│ ├── member
│ ├── controller
│ ├── service
│ ├── repository
│ ├── exception
│ ├── entity
│ └── dto
│ ├── order
│ ├── chatting
│ └── like
│ └── global
│ ├── config
│ ├── jwt # JWT 설정 클래스들
│ └── SecurityConfig.java, 기타 설정 클래스
│ ├── error # 전역적인 Exception Handling을 위한 클래스들
│ └── result # 응답 데이터 통합을 위한 클래스들
│ └── test # Test Code
└──
JWT와 Security 설정
JWT를 적용하기 위한 큰 틀은 이 방법이 무조건 정답은 아니겠지만, 구글링했던 결과들을 종합하면 가장 나은 방법이라 생각했었다. 세부적인 코드는 크게 공개하지 않고 각 클래스들이 어떤 역할을 하는지만 언급하고 넘어가겠다.
// 정은구님의 Inflearn 강의 Spring Boot JWT Tutorial를 참고하자.
JWT 관련
TokenProvider
: 유저 정보로 JWT 토큰을 만들거나 토큰을 바탕으로 유저 정보를 가져온다.JwtFilter
: Spring Request 앞단에 붙일 Custom Filter
Spring Security 관련
JwtSecurityConfig
: JWT Filter를 추가JwtAccessDeniedHandler
: 접근 권한 없을 때 403 에러JwtAuthenticationEntryPoint
: 인증 정보 없을 때 401 에러SecurityConfig
: 스프링 시큐리티에 필요한 설정SecurityUtil
:SecurityContext
에서 전역으로 유저 정보를 제공하는 유틸 클래스
이 중 TokenProvider
, SecurityConfig
만 요약하자면 다음과 같다.
TokenProvider
JWT Token에 관련된 암호화, 복호화, 검증 로직은 모두 이 클래스에서 이루어진다.
generateTokenDto
- 유저 정보를 넘겨받아 Access Token과 Refresh Token을 생성
- 넘겨받은 유저 정보의
authentication.getName()
이 username을 가져온다. - Access Token에는 유저와 권한 정보를 담고 Refresh Token에는 아무 정보도 담지 않는다.
getAuthentication
- JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼낸다.
- Access Token에만 유저 정보를 담기에
accessToken
을 파리미터로 - Refresh Token에는 만료일자만 담는다.
validateToken
- 토큰 정보를 검증
Jwts
모듈이 알아서 Exception을 던져준다.
SecurityConfig
package me.ramos.WaitForm.global.config;
import lombok.RequiredArgsConstructor;
import me.ramos.WaitForm.global.config.jwt.JwtAccessDeniedHandler;
import me.ramos.WaitForm.global.config.jwt.JwtAuthenticationEntryPoint;
import me.ramos.WaitForm.global.config.jwt.JwtSecurityConfig;
import me.ramos.WaitForm.global.config.jwt.TokenProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
// Swagger 3.x 설정
private static final String[] PERMIT_URL_ARRAY = {
/* swagger v2 */
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
/* swagger v3 */
"/v3/api-docs/**",
"/swagger-ui/**"
};
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// CORS 설정
@Bean
public CorsConfigurationSource configurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOriginPattern("*");
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/h2-console/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.logout()
.disable();
http
.csrf().disable()
// exceptionHandling customizing
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
.and()
.authorizeRequests()
.antMatchers("/", "/auth/login", "/auth/signup").permitAll()
.antMatchers(PERMIT_URL_ARRAY).permitAll()
.anyRequest().authenticated()
// JwtFilter를 addFilterBefore로 등록했던 JwtSecurityConfig를 등록
.and()
.apply(new JwtSecurityConfig(tokenProvider));
}
}
Swagger 설정과, 몇 가지 커스터마이징 할 요소들이 있어 시큐리티 필터에서 제외해야 하거나 기타 설정이 필요한 내용들을 위와 같이 작업하였다.
Postman으로 JWT 로직 테스트
회원가입
Frontend에서 사용자가 입력한 email, password, nickname 값을 JSON 형식으로 요청하면 다음과 같이 정상 응답을 반환한다.
로그인
Frontend에서 사용자가 입력한 email, password 값을 JSON 형식으로 요청하면 다음과 같이 accessToken과 refreshToken과 함께 정상 응답을 반환한다.
테스트로 사용중인 H2 Database 내부를 보면 회원의 password는 BCryptPasswordEncoder
에 의해 암호화되어 저장되어있고, Refresh Token 역시 정상적으로 DB에 저장되어 있다.
다만, Access Token의 유효 기간과 이에 대한 재발급을 위한 용도이고, 현재 아키텍쳐 구조상 DB에서 불러오는 I/O가 성능 이슈를 발생할 여지가 있기 때문에 추 후 캐시 역할을 할 Redis로 토큰 저장소를 변경하고자 한다.
토큰 재발급
토큰 재발급의 경우, HTTP Header Authorization에 Bearer {accessToken}
으로 셋팅된 상태로 accessToken, refreshToken 값을 서버로 전송해야 한다.
정상 요청의 경우 다음과 같이 두 토큰이 재발급 된다.
다음 포스팅에선?
현재 Postman에서 보이는 Response Body의 값들을 보면, 응답 값들만 나타나있고 상태코드나 메시지에 대한 내용은 전혀 없으며 또한 에러 발생 시 이를 핸들링해서 일관된 형식으로 나타낼 수 없는 구조이다.
이에 대한 처리를 한 과정들을 다음 포스팅에 게시할 예정이다.
References
Author And Source
이 문제에 관하여(회원 도메인 개발과 JWT 인증/인가 처리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@songs4805/회원-도메인-개발과-JWT-인증인가-처리
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Author And Source
이 문제에 관하여(회원 도메인 개발과 JWT 인증/인가 처리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@songs4805/회원-도메인-개발과-JWT-인증인가-처리저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)