보라코딩

JWT(JSON Web Token), Filter, Config 본문

코딩/Spring

JWT(JSON Web Token), Filter, Config

new 보라 2023. 9. 30. 15:55
JWT (JSON Web Token) 

 

 

클레임이라고 불리는 정보를 JSON 형태로 안전하게 전송하기 위한 토큰

 - 인증과 정보 교환에 사용, 서명이 되어 있어서 신뢰성 확보가 가능

 

 

1. Header : 토큰의 타입과 사용된 알고리즘 정보를 담고 있음, Base64Url로 인코딩

2. Payload : 클레임 정보, 대상, 발행자, 만료 시간 등 다양한 정보가 포함됨, Base64Url로 인코딩

3. Signature : Header과 Payload, Secrey Key를 사용하여 생성된 서명

 

 

[ 장점 ]

- 상태 유지 X

- 간단하고 자기 포함적

- 확장성

 

[ 단점 ]

- 크기 : 클레임이 많을수록 토큰 크기가 커짐

- 보안 : 서명은 되어 있지만 암호화는 되어있지 않음. 중요 정보 JWT에 포함하면 안됨

- 토큰 관리 : 만료 시간, 갱신 필요

 

 

 

 

 

 

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

 

 

 

 

build.gradle
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'

 

 

provider 패키지에 JwtProvider.java 생성

jwt 생성과 검증

 

( 참고로 secret key는 application.properties에 입력 )

package com.react.spring.provider;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;

@Component
public class JwtProvider {

    @Value("${secret-key}")
    private String secretKey;

    // jwt 생성
    public String create(String email){

        Date expiredDate = Date.from(Instant.now().plus(1, ChronoUnit.HOURS)); // 1시간

        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.ES256, secretKey)
                .setSubject(email) // email을 (id를 email로 설정함)
                .setIssuedAt(new Date()) // 생성시간
                .setExpiration(expiredDate) // 만료시간
                .compact(); //압축

        return jwt;
    }

    // jwt 검증 (반환되는 것은 email)
    public String validate(String jwt){

        Claims claims = null;

        try {
            claims = Jwts.parser().setSigningKey(secretKey)
                    .parseClaimsJws(jwt).getBody();

        } catch (Exception exception){
            exception.printStackTrace();
            return null;
        }
        return claims.getSubject();
    }
}

 

 

 

filter 패키지에 JwtAuthenticationFilter.java
package com.react.spring.filter;

import com.react.spring.provider.JwtProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@RequiredArgsConstructor // 필수 생성자
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtProvider jwtProvider;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        try {
            String token = parseBearerToken(request);

            if (token == null){
                // 현재 필터가 아무 작업을 하지 않고 요청을 다음 필터로 전달하는 역할 (요청 계속 처리할 수 있도록)
                filterChain.doFilter(request, response);
                return;
            }

            String email = jwtProvider.validate(token);

            if (email == null){
                filterChain.doFilter(request, response);
                return;
            }

            AbstractAuthenticationToken abstractAuthenticationToken =
                    // (아이디, 비밀번호, 권한) 입력
                    new UsernamePasswordAuthenticationToken(email, null, AuthorityUtils.NO_AUTHORITIES);

            // 인증 요청에 대한 사용자 인증 요청에 대한 세부정보 설정
            abstractAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            // context에 등록 (사용자의 인증 정보 관리 목적, 인증된 사용자의 컨텍스트 유지)
            SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); // 비어있는 context 만들고
            securityContext.setAuthentication(abstractAuthenticationToken); // 현재 사용자의 인증정보 등록

            // 사용자의 인증 정보 유지, 인증된 사용자로서 역할 수행
            SecurityContextHolder.setContext(securityContext);

        } catch (Exception exception){
            exception.printStackTrace();
        }

        // 다음 필터로 넘기기
        filterChain.doFilter(request, response);

    }


    private String parseBearerToken(HttpServletRequest request){

        String authorization = request.getHeader("Authorization");

        // hasText : null 이거나 길이 0 이거나 공백으로만 채워져 있으면 false
        boolean hasAuthorization = StringUtils.hasText(authorization);
        if(!hasAuthorization) return null;

        boolean isBearer = authorization.startsWith("Bearer ");
        if (!isBearer) return null;

        // Bearer 토큰인 경우에만
        String token = authorization.substring(7);
        return token;
    }
}

 

 

 

config 패키지에 WebSecurityConfig.java
package com.react.spring.config;

import com.react.spring.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Configurable
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    protected SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception{

        httpSecurity
                .cors().and() // 기본 cors 정책
                .csrf().disable() // csrf 공격 비활성화
                .httpBasic().disable() // http basic 인증 비활성화
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() // 세션 생성 정책 stateless : 세션 사용하지 않고 상태 보존하지 않음
                .authorizeRequests() // 요청의 인증과 권한 부여 설정
                .antMatchers("/", "/api/v1/auth/**", "/api/v1/search/**", "/file/**").permitAll()
                .antMatchers(HttpMethod.GET, "/api/v1/board/**", "/api/v1/user/*").permitAll()
                .anyRequest().authenticated().and()
                .exceptionHandling().authenticationEntryPoint(new FailedAuthenticationEntryPoint());

        httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return httpSecurity.build();
    }
}

class FailedAuthenticationEntryPoint implements AuthenticationEntryPoint{

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {

        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("{\"code:\" : \"AF\", \"message\",\"Authorization Failed.\", \"}\"");

    }
}

 

 

 

 

config 패키지에 CorsConfig.java
package com.react.spring.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings (CorsRegistry corsRegistry){

        corsRegistry.addMapping("/**")
                .allowedMethods("*")
                .allowedOrigins("*");
    }
}

 

 

 


 

참고한 강의!

최신 강의라 따라하기 좋음 ㅎ.ㅎ