ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Firebase 에서 OAuth2 , JWT 전환기(3)
    Project/TravelFeelDog 2023. 10. 14. 17:54

    FireBase Cloud Authentication 를 사용한 클라이언트에서 구글로 인증을 하는 방식에서

     

    SpringBoot 에서 구글 클라우드로 인증하는 방식으로 변경이 되었고 사용자를 Guest ,User 와같이 구분이

    가능한 기본 작업을 하였습니다.

     

    Firebase 에서 OAuth2 , JWT 로 전환기(2)

     

    https://chosunghyun18.tistory.com/manage/newpost/171?returnURL=https%3A%2F%2Fchosunghyun18.tistory.com%2F171&type=post

     

    chosunghyun18.tistory.com

     

     

    지금 까지 구성한 설계는 구글을 통한 사용자의 인증 이후  사용자의 정보를 데이터베이스에 저장을 하는 형태입니다.

     

    이때 사용자의 권한은 Guest 의 상태로 회원 가입 하기 이전의 상태를 뜻합니다.

    회원가입이 완료가 된 사용자는 USER 의 권한을 가집니다.

     

    접속하는 사용자의 증가가 있는 상황에서는 세션, 서버에 사용자의 정보를 저장하고 인증과 인가를 진행하는 방식은 병목현상을 야기하는 방향이 될수가 있습니다. 이러한 문제에 대하여 다음 두가지 방안으로 생각을 할 수 가 있습니다.

     

    1. statless 한 서버를 설계하는데 있어 세션을 사용하는 것이 아닌 토큰을 사용하여 사용자의 인증과 인가를 처리합니다.

    사용자의 정보를 이용하여 토큰을 가지고 서버가 아닌 클라이언트에게 전달하여 이후 인가에 사용합니다.

     

    토큰의 경우

    1.토큰을 사용한다라고하면 기존의 세션방식보다 네트워크 통신량이 많아지는 , 세션 id 보다 큰 토큰 사이즈를 감안해야합니다.

    2. 토큰 탈취 위협이 존재합니다 -> 유효기간 설정으로 어느정도 대응합니다

    3. 만료기간동안 사용자가 접근이 가능함으로 사용자를 임의로 차단이 불가능할 수 도 있습니다.

     

     

    2. 서버 인스턴스 외에 Redis를 사용한 세션을 사용합니다. scale out 에 대응을 할 수있으며 , 속도가 빠르고 만료기간을 설정하여 세션을 관리하기 적합합니다.

     

     

     

    최종 목표는 

    JWT Token 으로  인가 확인용 access toekn(ATK) 과  Refreh token(RTK)용을 각각 만들고 redis 에 RTK 저장을 하는 방식으로 사용하며 클라이언트에게 최초 로그인 시 두개의 토큰을 전달하는 형태로 설계를 합니다.

     

     

    JWT 적용 하기

     

    Oauth2 로그인 이후

    게스트로 들어온 사용자(Guest)의 대하여 회원가입이 완료가 된다면 Token 을 발급해줍니다.

    또는 회원가입이 완료가 된 사용자(User)의 로그인 요청의 대하여 Token 을 확인합니다.

    만료가된 Token 인 경우 Token 을 재발급 해줍니다.

     

    개발 단계이기 때문에 확인용으로 mysql 의 토큰 데이터를 저장합니다.

     

    JWT 토큰을 발급하는 기능과  JWT Filter 를 필수로 구현 하셔야 합니다. 

    JWT Filter 는 chain 에 꼭 등록 하여야합니다.

     

     

    Member 클라스 (엔티티)에 다음과 같이 추가해 줍니다.

        @Column(name = "member_atk")
        private String accessToken;
    
        @Column(name = "member_rtk")
        private String refreshToken;
        ...
        public void  register(String atk, String rtk) {
            this.level = 1; // 개인 프로젝트에서 사용하는 값입니다.
            this.accessToken = atk;
            this.refreshToken = rtk;
            this.role = Role.USER;
        }
        
        ...

     

     

    세션을 사용하지 않으니 코드를 변경해줍니다.

    Login 어노테이션의 Resolver - Extractor 는 다음과 같습니다.

    @RequiredArgsConstructor
    @Component
    public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
    
        private final JwtService jwtService;
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(LoginUser.class);
    
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            String token = AuthorizationExtractor.extract(Objects.requireNonNull(request));
            return jwtService.findEmailByToken(token);
        }
    }

     

    -> 로그인이 이미된 사용자의 토큰에서 @loginUser 어노테이션을 활용하여 이메일 정보를 가져오는 코드

    package travelfeeldog.global.auth;
    
    import jakarta.servlet.http.HttpServletRequest;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.Enumeration;
    
    @Slf4j
    public class AuthorizationExtractor {
        public static final String AUTHORIZATION = "Authorization";
        private AuthorizationExtractor() {
        }
    
        public static String extract(HttpServletRequest request) {
            Enumeration<String> headers =request.getHeaders(AUTHORIZATION);
            while (headers.hasMoreElements()) {
                String token = headers.nextElement();
                log.info("Header Value: '{}'", token);
                return token;
            }
            return null;
        }
    }

     

     

    다음 두개의 클라스들을 추가하여 비밀키의 설정을 스프링의 빈을 이용해 등록해 줍니다.

     

    스프링 빈등록은 두가지 방식이 있습니다.

     

    1 . @Configuration- @Bean  을 사용하는 방식.

    2 . @Component 를 사용하는 방식.

     

    두개의 차이점 및 스프링 빈등록의 특징은 다른 글에서 확인 해주시길 바랍니다.

     

    package travelfeeldog.global.auth.jwt;
    
    import io.jsonwebtoken.io.Decoders;
    import io.jsonwebtoken.security.Keys;
    import java.security.Key;
    import lombok.Getter;
    @Getter
    public class JwtSecretKey {
    
        private  String jwtSecretKey;
        private  Long jwtValidityAccessTime;
        private  Long jwtValidityRefreshTime;
        private final Key key;
        public JwtSecretKey(String jwtSecretKey, Long jwtValidityAccessTime, Long jwtValidityRefreshTime) {
            this.jwtSecretKey = jwtSecretKey;
            this.jwtValidityAccessTime = jwtValidityAccessTime;
            this.jwtValidityRefreshTime = jwtValidityRefreshTime;
            byte[] keyBytes = Decoders.BASE64.decode(jwtSecretKey);
            this.key = Keys.hmacShaKeyFor(keyBytes);
        }
    
    }

     

    JWT 의 SecretKey 의 경우 yml 을 통하여 설정을 해줍니다 .

    이때 사용하는 알고리즘에 따라 최소로 요구하는 문자열의 길이가 있습니다.

     

    package travelfeeldog.global.auth.jwt;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class JwtSecretKeyConfiguration {
        @Value("${security.jwt.secretKey}") 
        private String jwtSecretKey;
    
        @Value("${security.jwt.validityAccessTime}")
        private Long jwtValidityAccessTime;
    
        @Value("${security.jwt.validityRefreshTime}")
        private Long jwtValidityRefreshTime;
    
        @Bean
        public JwtSecretKey jwtSecretKey() {
            return new JwtSecretKey(jwtSecretKey, jwtValidityAccessTime, jwtValidityRefreshTime);
        }
    }

     

    다음은 토큰을 생성하거나 유효성을 확인하는  클라스인 Token provider Class 를 만들어 줍니다.

     

    JWT 의 Claim 은 식별값으로 꼭  unique 한 값 + 민감하지 않은 정보로 설정하여야 합니다. 

    저는 여기서 payload = email 을 사용하였습니다.

     

    @RequiredArgsConstructor
    @Service
    @Slf4j
    public class JwtProvider {
    
        private final JwtSecretKey jwtSecretKey;
    
        public Map<String, String> createToken(String payload, long validityDuration, String tokenKey) {
            Claims claims = Jwts.claims().setSubject(payload);
            Date now = new Date();
            Date validityTime = new Date(now.getTime() + validityDuration);
            String jwt = Jwts.builder()
                    .setClaims(claims)
                    .setIssuedAt(now)
                    .setExpiration(validityTime)
                    .signWith(jwtSecretKey.getKey(), SignatureAlgorithm.HS256)
                    .compact();
    
            Map<String, String> result = new HashMap<>();
            result.put(tokenKey, jwt);
            return result;
        }
    
        public Map<String, String> createAccessToken(String payload) {
            return createToken(payload, jwtSecretKey.getJwtValidityAccessTime(), "accessToken");
        }
    
        public Map<String, String> createRefreshToken(String payload) {
            Map<String, String> result = createToken(payload, jwtSecretKey.getJwtValidityRefreshTime(), "refreshToken");
    
            Date validityTime = new Date(new Date().getTime() + jwtSecretKey.getJwtValidityRefreshTime());
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
            String refreshTokenExpirationAt = simpleDateFormat.format(validityTime);
    
            result.put("refreshTokenExpirationAt", refreshTokenExpirationAt);
            return result;
        }
        public Claims extractClaims(String token) throws JwtException {
            try {
                return Jwts.parserBuilder()
                        .setSigningKey(jwtSecretKey.getKey())
                        .build()
                        .parseClaimsJws(token)
                        .getBody();
            } catch (JwtException e) {
                throw new InvalidTokenException("Failed to extract claims from token", e);
            }
        }
    
        public boolean validateToken(String token) {
            try {
                Jwts.parserBuilder()
                        .setSigningKey(jwtSecretKey.getKey())
                        .build()
                        .parseClaimsJws(token);
                return true;
            } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
                log.info("잘못된 JWT 서명입니다.");
            } catch (ExpiredJwtException e) {
                log.info("만료된 JWT 토큰입니다.");
            } catch (UnsupportedJwtException e) {
                log.info("지원되지 않는 JWT 토큰입니다.");
            } catch (IllegalArgumentException e) {
                log.info("JWT 토큰이 잘못되었습니다.");
            }
            return false;
        }
    
    }

     

     

    회원가입이 가능한 조건은 OAuth2 를 통한 인증을 이미 거친 GUEST_ROLE 을 가진 유저입니다.

     

    case 1)

    구글로 로그인시 회원의 정보가 서버의 연결된 데이터베이스에 저장이 되며 회원가입 이후 역할을 USER 로 올려줌과 동시에 클라이언트에게 토큰을 발급합니다.

     

     

    case 2)

    구글로 로그인시 사용자 정보를 저장하지 않고 회원가입으로 넘어가는 방식도 있습니다.

     

    구글 로그인을 통한 사용자 인증이 완료된 사용자에게 토큰을 발급해준 후,  Spring Sever로 회원가입 요청시 사용자 정보와 함께 토큰을 클라이언트에서 서버로 전달 해 줄 수 있을것 같습니다.

     

    여기서는 case1 을 따라 가겠습니다.

    구글 로그인 시마다 토큰 발급을 안한다라는 특징있습니다.

     

     

     

    Member Repository 구현체는 다음과 같습니다. 

    Jwt 사용전의 save 메서드를 오버로딩하여 토큰을 발급받았을때 회원가입 하는 메서드를 만들었습니다.

     

        @Override // 기존 회원가입시 사용했던 메서드
        public Optional<Member> save(Member member) {
            try {
                Assert.notNull(member, "member must not be null");
                Member existingMember = null;
                if (member.getId() != null) { //could be change shorter
                    existingMember = em.find(Member.class, member.getId());
                }
                if (existingMember == null) {
                    em.persist(member);
                } else {
                    em.merge(member);
                }
                return Optional.of(member);
            } catch (IllegalArgumentException e) {
                return Optional.empty();
            }
        }
    
    	@Override // 추가한 메서드
        public Optional<Member> save(String email, String atk, String rtk) {
            try {
                Member member = findByEmail(email).orElseThrow(() -> new IllegalStateException("No memberInfo"));
                if(member.getRole() != Role.GUEST){
                    throw new IllegalStateException("Wrong type of Member");
                }
                member.register(atk, rtk);
                em.merge(member);
                return Optional.of(member);
            } catch (IllegalStateException e) {
                return Optional.empty();
            }
        }

     

    구글 로그인을 한 사용자의 회원 가입 결과 ,  해더로 AccessToken 을 넘기지 않고 회원가입을 하는 케이스입니다.

     

     

     

    OAuth 2의 로그인의 확인 여부를 위하여 만든 웹페이지에 사용할 index 메서드는 다음과 같습니다.

     

    기본 홈페이지 웹의 파라미터를@loginUser 에서  @AuthenticationPrincipal로 변경해줍니다.  

     

    이렇게 되면 ,  최초 홈페이지를 방문하는 사용자를 받을 수 있고.

     

    홈페이지에서 구글 로그인을 한 후 redirection  URL 으로 들어온  화면에 사용자의 정보를 표시할 수 있습니다.

     

    @GetMapping("/")
        public String index(Model model,  @AuthenticationPrincipal OAuth2User oauth2User) {
            if (oauth2User != null) {
                String userName = oauth2User.getAttribute("name");
                Collection<? extends GrantedAuthority> authorities = oauth2User.getAuthorities();
                model.addAttribute("userName", userName);
                model.addAttribute("userRole",authorities);
            }
            return "index";
        }

     

     

    @AuthenticationPrincipal 을 사용하여 현제 인증한 사람의 구글 프로필 정보를 가져올수 있습니다.

     

    cf 사진URL의 경우 접속하면 구글 프로필 사진을 브라우저로 확인 가능합니다.

     

     Name: [110608823760018439751],
     Granted Authorities: [[ROLE_GUEST]], 
     User Attributes: [
     	{
        	sub=110608823760018439751,
            name=‍조성현[재학 / 컴퓨터.전자시스템공학전공],
    		given_name=조성현[재학 / 컴퓨터.전자시스템공학전공], 
    		family_name=‍, 
    		picture=https://lh3.googleusercontent.com/a/ACg8ocJ3iirOvsjr758h6q58OtB7GmllArmM0tCUN5d1ssDr=s96-c, 
     		email=krischo1204@hufs.ac.kr, 
     		email_verified=true, locale=ko, hd=hufs.ac.kr
         }   
           ]

     

     

     Granted Authorities: [[ROLE_GUEST]] 의 경우 구글 인증을 통한 구글 프로필에서 가져온것이 아닌 ,SpringSecurity 에서 가져온 정보로 LoadUser 에서 저장한 정보를 가져옵니다.

    @Transactional
    public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    
        private final MemberRepository memberRepository;
    
        @Override
        public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
            OAuth2UserService delegate = new DefaultOAuth2UserService();
            OAuth2User oAuth2User = delegate.loadUser(userRequest);
    
            String registrationId = userRequest.getClientRegistration().getRegistrationId();
            String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
                    .getUserInfoEndpoint().getUserNameAttributeName();
    
            OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName,
                    oAuth2User.getAttributes());
            Member member = getByEmail(attributes);
            member = saveOrUpdate(member);
            return new DefaultOAuth2User(
            ////////////////////////// 이부분 !
                    Collections.singleton(new SimpleGrantedAuthority(member.getRoleKey())),
                    attributes.getAttributes(),
                    attributes.getNameAttributeKey());
        }
    
        ....
    }

     

     

    화면 결과는 다음과 같습니다.

     

     

    로그인전 기본 홈페이지 접속

    로그인전

     

    로그인 후

     

    기본 홈페이지 접속

     

     

     

    OAuth2 로그인과 토큰의 생성에 대하여 작성을 하였으니 , 사용자가 Header 에 accessToken 을 넣고 요청하는 경우를 처리하는 기능을 만들어 봅니다.

     

    JWT token 을 사용하여 Spring Server 에 자원을 요청하였을때 

    Header 에 Authoriztion 과 token 의 값이 주어진다면 재로그인을 요청하지 않고 자원에 요청에 맞는 응답값을 반환해 줘야합니다.

     

    OncePerRequestFilter 와 Filter 그리고 genericfilterbean 을 구분하여 구성할 수 있습니다.

     

    여기서는  OncePerRequestFilter 를 사용합니다.

     

    우선 가장 중요한 JwtFilter 를 생성해줍니다. 

     

    (다연하게도 단순하게 토큰을 생성하는 단계에서는 필요없습니다.)

     

    "/api/v1" 의 경로를 가진 요청의 대하여 헤더에서 토큰을 가져와 검증합니다.

    @RequiredArgsConstructor
    public class JwtFilter extends OncePerRequestFilter {
        private final static String AUTHORIZATION_HEADER = "Authorization";
        private final JwtService jwtService;
        private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                FilterChain filterChain) throws ServletException, IOException {
            if (request.getRequestURI().contains("/api/v1")) {
                final String token = request.getHeader(AUTHORIZATION_HEADER);
                Member member = jwtService.findMemberByToken(token);
                saveAuthentication(member);
            }
            filterChain.doFilter(request, response);
        }
    
        public void saveAuthentication(Member myUser) {
            String password = myUser.getEmail() + myUser.getNickName();
            UserDetails userDetailsUser = org.springframework.security.core.userdetails.User.builder()
                    .username(myUser.getEmail())
                    .password(password)
                    .roles(myUser.getRole().name())
                    .build();
            Authentication authentication =
                    new UsernamePasswordAuthenticationToken(userDetailsUser, null,
                            authoritiesMapper.mapAuthorities(userDetailsUser.getAuthorities()));
    
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    }

     

    Security FilterChain 에 add Filter 로 적용합니다.

     

    Spring Security 의 다양한 필터는 순서가 있으며 순서에 맞게 추가를 해줘야합니다.

     

    저희는 다음과 같이 로그아웃 url 을 감지하고 처리하는 필터 다음에 추가해 줍니다.

            httpSecurity.addFilterAfter(new JwtFilter(jwtProvider),LogoutFilter.class);

     

    SecurityConfig 에 존재 하던 requestMatchers 설정은 삭제 해 줍니다.

     

    requestMatchers 를 설정을 하더라도 JWT FILTER 을 거치지 때문에 PermitALL() 의 기능이 필요가 없습니다.

     

                            .requestMatchers("/","/actuator/health"
                                    ,"/api/v1/redis/**"
                                    ,"/api/v1/member/**"
                                    ,"/swagger-ui/**","/usage" // for swagger
                                    ).permitAll()

     

    전체 코드

    package travelfeeldog.global.auth.secure;
    
    import jakarta.servlet.DispatcherType;
    import lombok.RequiredArgsConstructor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.SecurityFilterChain;
    import org.springframework.security.web.authentication.logout.LogoutFilter;
    import travelfeeldog.global.auth.jwt.JwtFilter;
    import travelfeeldog.global.auth.jwt.JwtService;
    import travelfeeldog.infra.oauth2.service.CustomOAuth2UserService;
    
    import static org.springframework.security.config.Customizer.withDefaults;
    
    @Configuration
    @EnableMethodSecurity
    @EnableWebSecurity
    @RequiredArgsConstructor
    public class SecurityConfig {
    
        private final CustomOAuth2UserService customOAuth2UserService;
        private final JwtService jwtService;
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
            httpSecurity
                    .csrf(
                            AbstractHttpConfigurer::disable
                    ).cors(
                            AbstractHttpConfigurer::disable
                    )
                    .logout(withDefaults())
                    .oauth2Login(request -> request.userInfoEndpoint(
                            userInfoEndpointConfig -> userInfoEndpointConfig.userService(
                                    customOAuth2UserService)));
            httpSecurity.addFilterAfter(new JwtFilter(jwtService), LogoutFilter.class);
    
            return httpSecurity.build();
        }
    }

     

     

    모든 설정을 맞쳤으니 JWT 를 확인하는  자체 test api를 호출 합니다.

     

    현제 test api 는 다음과 같은 경우 사용이 가능합니다.

     

    1. 만들어둔 웹페이지로 구글 로그인을 진행하여 DB 에 사용자 정보가 저장이 되어 있어야합니다.

     

    2. 회원 가입을 진행하여 GUEST 에서 USER 로 업데이트 한 사용자가 사용 할 수 있습니다.

     

    @RestController
    @RequestMapping("/api/v1/test")
    @AllArgsConstructor
    @Slf4j
    public class ConnectionCheckApi {
        @GetMapping(value="/{testNumber}")
        @ResponseStatus(HttpStatus.OK)
        public ResponseEntity<Long> getConnectTestNumber(@PathVariable Long testNumber ,@LoginUser String email){
            log.info("email{}",email);
            return ResponseEntity.ok(testNumber);
        }
    }

     

    3.  test api 호출을 진행합니다.

     

    회원 가입이 완려된 후  발급받은 AccessToekn 을 요청하는 API 의 해더 에 넣고 POSTMAN 으로 확인합니다.

     

     

    param으로 넘겨준 14 가 정상적으로 출력되는걸 확인 가능합니다.

     

     

    다음과 같이 @LoginUser 를 통해 JWT 에서 이메일을 추출한 결과가 정상적으로 나오는 것 도 확인이 가능합니다.

    ~ .infra.api.ConnectionCheckApi           : email : krischo1204@gmail.com

     

     

     

    ROLE 을 확인 해봅니다.  , GUEST 를 가지는 계정으로 ADMIN 을 요구하는 api 를 호출해봅니다.

     

    다음과 같이 ADMIN ROLE 을 설정하여 확인합니다.

    httpSecurity
                    .csrf(
                            AbstractHttpConfigurer::disable
                    ).cors(
                            AbstractHttpConfigurer::disable
                    )
                    .logout(withDefaults())
                    .authorizeHttpRequests(request -> request
                            .requestMatchers("/api/v1/**").hasRole(Role.ADMIN.name())
                            .anyRequest()
                            .authenticated()
                    )
                    .oauth2Login(request -> request.userInfoEndpoint(
                            userInfoEndpointConfig -> userInfoEndpointConfig.userService(
                                    customOAuth2UserService)));
            httpSecurity.addFilterAfter(new JwtFilter(jwtService), LogoutFilter.class);

     

    구글 로그인 안내 페이지 html 을 확인 할 수 있습니다.

     

    인가와 관련된 추가적인 예외 처리 및 사용자의 정보가 DB 없는 경우 구글 로그인을 요구하는 응답값이 나오는걸 확인할 수 있습니다.

     

     

    지금까지의 상황을 종합해 봅니다.

     

    1. 사용자는 홈페이지에 접속이 가능합니다.

     

    2. 사용자는 구글 로그인을 통한 인증을 받아야 합니다.

     

    3. 구글 로그인을 통한 인증이 완료된 사용자는 추가 적인 API 를 호출하여 회원가입을 진행 하여합니다.

     

     

     

     

    다음 필요한 작업입니다.

     

    1.  지금은 리소스 서버의 API 를 호출한 회원가입시에만 토큰을 갱신합니다.

     

       로그인을 하는 유저에게 토큰을 갱신 해주는 기능이 필요합니다.

     

    2. 회원 가입 API 의 경우 JWT 를 사용하여 호출을 하지 않고 있습니다.

     

    즉 , 회원가입 시나리오가 클라이언트에게 의존적인 구조를 가지고 있습니다.

     

    OAuth2 를 사용한 인증이 완료되면 인증 서버로 부터 받아온 임시 토큰을 활용하여 회원가입 요청을 하는 것으로  변경 하는 작업이 필요할 것 같습니다.

     

     

    2. 부족한 예외 처리를 해줍니다.

     

     

     

    3. Role 별로 (Admin,User,Guest) 접근 가능한 메서드를 정하기 위한 어노테이션과 경로 설정을 합니다.

     

    4. Redis 를 적용하여 토큰 갱신을 합니다. (욕심 입니다 ㅎㅎ)

     

     

     

    코드 :

    https://github.com/chosunghyun18/TravelFeelDog-Server/tree/develop

     

    GitHub - chosunghyun18/TravelFeelDog-Server: 여행필독서 앱과 웹을 위한 벡엔드 저장소

    여행필독서 앱과 웹을 위한 벡엔드 저장소 . Contribute to chosunghyun18/TravelFeelDog-Server development by creating an account on GitHub.

    github.com

     

     

     

    참고 레포)

     

    1. 

    https://github.com/sh111-coder/oauth2WithJwtLogin

     

    GitHub - sh111-coder/oauth2WithJwtLogin

    Contribute to sh111-coder/oauth2WithJwtLogin development by creating an account on GitHub.

    github.com

     

    2.

    https://github.com/speculatingwook/SpringBootLoginTemplate/tree/oauth_develop/src/main/java/com/speculatingwook/OauthSpringBoot/global

     

Designed by Tistory.