Programming/Web Projects

SPRING+REACT+MYSQL 프로젝트 (5, 6, 7, 8, 9)

서린이1 2024. 9. 7. 23:16

1. react 프로젝트 생성 (board-frontend)

  - npx create-app react --template typescript

  - App.tsx 초기화

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  - index.tsx 초기화

 

  - src 하위 폴더 구조 생성

 

 

2. Authentication 방식

1. Basic Authentication
- 사용자 이름 / 비밀번호를 Base64로 인코딩하여 Authorizaiton 헤더에 포함하여 전송
- 매우 안전하지 않음, SSL/TLS와 함께 사용됨

Ex: Authorization: Basic ~~~

2. Bearer Token Authentication
- 헤더에 토큰을 포함하여 전송 Authorization 헤더에 포함하여 전송
- JWT을 사용하여 인증
- 간단한 방식, 상태를 유지하지 않음, 확장성이 높음
- 단점 : 토큰 노출 위험, 토큰 관리가 힘듬, 귀찮음

Ex: Authorizaiton: Bearer ~~~~~

3. OAuth 방식
- 토큰기반 인증 방식, 사용자가 직접 자격을 증명 X 미리 인증 받아서 토큰을 발급 받고
- 이 토큰을 이용하여 API를 요청하는 방식, OAuth 2.0

Ex: Kakao, Naver, Git, Facebook login ..

4. API Key 방식
- 발급 받은 키를 요청

5. Session based Authentication
- Session ID를 생성하여 세션이나 쿠키에 포함하여 인증

- JWT (JSON Web Token) : 클레임이라고 불리는 정보를 JSON 형태로 안전하게 전송하기 위한 토큰
- 인증과 정보 교환에 사용, 서명이 되어 있어서 신뢰성 확보가 가능

1. Header : 토큰의 타입과 사용된 알고리즘 정보를 담고 있음, Base64 URL로 인코딩
2. Payload : 클레임 정보, 대상, 발행자, 만료 시간 등 다양한 정보가 포함, Base64 URL로 인코딩
3. Signature : Header와 Payload, Secret Key를 사용하여 생성된 서명

인증, 정보교환

장점 :
상태유지하지 않는다. = stateless, 서버가 클라이언트의 상태를 유지안한다
간단하고 자기 포함적
확장성이 높다, 토큰을 만들어 놓으면 여러 시스템에서 사용 가능

단점 : 
크기 : 클레임이 많을수록 토큰의 크기가 커짐
보안 : 서명은 되어있지만, 암호화는 되어있지 않음, 중요한 정보를 JWT에 포함할 수 없음
토큰 관리 : 만료 시간, 갱신 잘해줘야 함

 

 

3. Spring Initializr로 Gradle Project 생성 (Gradle 2.7.14)

 

  - src/main/java/test 하위 삭제및 src/main/java 하위 폴더 구조 잡기

 

  - applicaiton.properties 설정

 

  - JwtProvider.java 생성

package kr.co.sorin.board_backend.provider;

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

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

@Component
public class JwtProvider {

    private String secretKey = "S3cr3tK3y";

    public String create(String email) {
        Date expiredDate = Date.from(Instant.now().plus(1, ChronoUnit.HOURS));

        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, secretKey) // 수정: SignatureAlgorithm.ES256 ->
                .setSubject(email)
                .setIssuedAt(new Date())
                .setExpiration(expiredDate)
                .compact();

        return jwt;
    }

    public String validate(String jwt) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secretKey)
                    .parseClaimsJws(jwt)
                    .getBody();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

        return claims.getSubject();
    }
}

 

  - WebSecurityConfig.java 생성

package kr.co.sorin.board_backend.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.web.SecurityFilterChain;

import kr.co.sorin.board_backend.filter.JWTAuthenticationFilter;
import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {

    private final JWTAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    protected SecurityFilterChain configure(HttpSecurity HttpSecurity) throws Exception {
        HttpSecurity
                .cors().and()
                .csrf().disable()
                .httpBasic().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .anyRequest().permitAll();

    }
}

 

  - JwtAuthenticationFilter.java 생성

package kr.co.sorin.board_backend.filter;

import java.io.IOException;

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 kr.co.sorin.board_backend.provider.JwtProvider;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtProvider jwtProvider;

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

        String token = parseBearerToken(request);

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

            String email = jwtProvider.validate(token);

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

            // context에 등록
            AbstractAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, null,
                    AuthorityUtils.NO_AUTHORITIES);

            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 웹 인증정보 세부정보
                                                                                                        // 셋팅

            SecurityContext SecurityContext = SecurityContextHolder.createEmptyContext();
            SecurityContext.setAuthentication(authenticationToken);

            SecurityContextHolder.setContext(SecurityContext); // 외부에서 사용 가능
        } catch (Exception e) {
            e.printStackTrace();
        }

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

    }

    private String parseBearerToken(HttpServletRequest request) {

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

        boolean hasAuthorization = StringUtils.hasText(authorization);

        if (!hasAuthorization)
            return null;

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

        String token = authorization.substring(7);

        return token;

    }

}

 

  - WebSecurityConfig.java 작성

package kr.co.sorin.board_backend.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.web.SecurityFilterChain;

import kr.co.sorin.board_backend.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    protected SecurityFilterChain configure(HttpSecurity HttpSecurity) throws Exception {
        HttpSecurity
                .cors().and()
                .csrf().disable()
                .httpBasic().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .anyRequest().permitAll();

    }
}

 

- CorsConfig.java 작성

package kr.co.sorin.board_backend.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("*");
    }

}

 

 

출처 : https://www.youtube.com/@jiraynorprogramming1589