Spring Boot + React 프로젝트 / jwt 토큰 발급 및 쿠키 저장 (로그인)

fever·2024년 4월 9일
0

jwt란?

JWT(토큰 기반 인증)는 JSON Web Token의 약자로, 웹 표준인 RFC 7519에서 정의된 개방형 표준

  • JSON 형식을 사용하여 정보를 안전하게 전달하기 위한 컴팩트하고 자가수용적인 방법을 제공
  • 주로 인증 및 정보 교환에 사용되며, 클레임(claim) 기반으로 정보를 저장하고 전송

✔️ JWT 구성
Header(헤더): 토큰의 유형 및 해싱 알고리즘 등의 메타데이터를 포함
Payload(페이로드): 토큰에 포함될 클레임(사용자 정보, 권한 등)
Signature(서명): 헤더와 페이로드를 결합하고, 비밀 키를 사용하여 서명된 값으로, 토큰의 유효성 검증

💻 구현 기능

  1. 로그인 정보로 스프링 시큐리티로 검사
  2. 검사가 통과되면 jwt 토큰 발급
  3. 해당 토큰 쿠키로 저장

✏️ 기능 설계

  1. 리액트에서 유저 정보(이메일, 비밀번호)를 스프링부트로 전달
  2. 시큐리티 활용해서 사용자 인증 정보 생성 및 저장
  3. db에서 한 번 더 email과 pw비교 검사
  4. jwt 토큰 발급
  5. 해당 토큰 쿠키로 저장

📌 구현 과정

  • 그래들 라이브러리

1. 리액트 설정

  • user 객체 생성 후, 인풋에 핸들 달아서 값이 바뀔 때마다 해당 값 user에 저장시킴

  • 입력 폼은 로그인 누르면 submit해서 form에서 함수 실행

  • 해당 값 스프링부트 컨트롤러로 전달

2. Auth 설정

2-1. AuthController

2-2. AuthService


3. security 설정

3-1. SecurityConfig


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // CORS 설정
                .cors().and()
                // CSRF 보호 비활성화
                .csrf().disable()
                // 인증 필터 설정
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                // URL 규칙 및 권한 설정
                .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/myPage").authenticated()
                .anyRequest().permitAll();
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); // 쿠키 사용 허용
        config.addAllowedOrigin("http://localhost:3000");// 리액트 서버
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

3-2. SecurityUser

  • jwt와 연동할 유저 객체 생성
  • User 클래스는 스프링 시큐리티에서 제공하는 UserDetails 인터페이스를 구현한 클래스로 사용자 정보를 나타냄
  • super (식별자, 비밀번호, 권한목록)

3-3. SecurityUserDetailService

  • 사용자의 인증 정보를 데이터베이스에서 조회

4. jwt 설정

4-1. JwtTokenProvider

  • jwt로 쓸 수 있는 기능들
  • jwt 생성, 인증정보 조회, 토큰으로 회원정보, 토큰으로 닉네임, 토큰 유효성 체크 기능 만들어둠 (추후에 사용)
@RequiredArgsConstructor
@Component
public class JwtTokenProvider {

    // 임의의 숫자, 문자 아무거나 (Base64로 인코딩 할거라 특수문자 안됨)
    private String secretKey = "3d12j3l1k2j3l1k2j3lmdlaskjasdasdqwlkejlk12j3lk123dl23asQWEYZAA";

    // 토큰 유효시간 30분
    private long tokenValidTime = 30 * 60 * 1000L;

    private final UserDetailsService userDetailsService;

    // 객체 초기화, secretKey를 Base64로 인코딩
    protected void init() {
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
    }

    // JWT 토큰 생성
    public String createToken(String userEmail, String nickname, UserRole roles) {
        // claims: JWT payload에 저장되는 정보 단위 (여기서는 주제(subject) 클레임으로 사용)
        Claims claims = Jwts.claims().setSubject(userEmail);
        // 원하는 정보 put
        claims.put("nickname", nickname); // 닉네임 정보 추가
        claims.put("roles", roles); // 롤 추가
        Date now = new Date();
        return Jwts.builder()
                .setClaims(claims)// 정보 저장
                .setIssuedAt(now) // 토큰 발행 시간 정보
                .setExpiration(new Date(now.getTime() + tokenValidTime)) // set Expire Time
                .signWith(SignatureAlgorithm.HS256, secretKey) // 사용할 암호화 알고리즘과 signature에 들어갈 secret 값 세팅
                .compact();
    }

    // JWT 토큰에서 인증 정보 조회
    public Authentication getAuthentication(String token) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserPK(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }

    // 토큰에서 회원 정보 추출
    public String getUserPK(String token) {
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
    }

    // 토큰에서 닉네임만 추출
    public String getUserNickname(String token) {
        return (String) Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().get("nickname");
    }

    // 토큰의 유효성 + 만료일자 확인
    public boolean validateToken(String jwtToken) {
        try {
            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken);
            return !claims.getBody().getExpiration().before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

}

5. 결과

🤔 다음 목표와 고민들

  1. 스프링시큐리티 쪽에 대한 이해가 부족해서 설정할 때 어려움이 많았다. 특히 코드 자체가 낯설어서 어려웠는데, 해당 부분 공부를 더 할 예정.
  2. 최신 버전에 맞춰서 코드 업데이트 필요함 (특히 시큐리티 설정 쪽)
  3. 서비스에서 기능별 모듈화 필요 (지금 깊은 생각없이 한번에 때려넣어서 보기 불편함, jwt 필터를 만들어서 시큐리티 인증부분을 분리할 예정)
profile
선명한 삶을 살기 위하여

0개의 댓글