Programing/Springboot

SpringBoot + JWT + Security + JPA 인증 구현, JWT란?

리커니 2022. 11. 14.
반응형

해당 포스팅은 인프런의 무료강의를 참고하여 작성되었습니다.

Link: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-jwt/dashboard

 

이해하기 쉽게 설명되어 있으니 참고하시면 좋을 것 같습니다.

 

Link: https://jwt.io/

 

JWT.IO

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

jwt.io

 

1. JWT (Json Web Tokens) 란?

JWT는 RFC 7519 웹 표준으로 지정된 

JSON 객체를 사용해서 토큰 자체에 정보를 저장하는 Web Token 입니다.

다른 인증 방식들에 비해 가볍고 간편해서 유용한 인증방식 입니다.

 

2.JWT의 구조

JWT는 Header, Payload, Signature 로 구성되어 있습니다.

Header : Signature를 해싱하기위한 알고리즘 정보.

Payload : 서버와 클라이언트가 주고받는 시스템에서 실제 사용될 정보.

Signature : 토큰의 유효성 검증을 위한 문자열.

 

3. JWT의 장/단점

장점 

- 중앙의 인증서버, 데이터 스토어에 대한 의존성 없음. 시스템 수평 확장 용이.

 - Base64 URL Safe Encoding을 사용하기 때문에 URL, Cookie, Header 모두 사용 가능.

 

단점

 - Payload의 정보가 많아지면 네트워크 사용량 증가, 데이터 설계 고려 필요.

 - Token이 클리이언트에 저장되기 때문에 서버에서 클라이언트의 토큰을 조작할 수 없음.

4.JWT 적용

java
 - common
   - jwt
     JwtAccessDeniedHandler.java
     JwtAuthenticationEntryPoint.java
     JwtFilter.java
     JwtSecurityConfig.java
     TokenProvider.java
   - response
     ResVO.java
 - config
   SecurityConfig.java

위와 같은 파일을 작성할 예정입니다. 구조를 확인하세요.

4.1 dependency 추가

JWT를 사용하기 위해 JWT 와 Spring security 관련 Dependency를 추가해줍니다.

/**jwt */
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'

/**spring security*/
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '2.7.5'

4.2 yml 에 설정 추가

header, secret, token-validity-in-seconds를 설정합니다.

secret은 사용할 알고리즘에 따라 길이를 맞추어 base64로 인코딩된 임의 값을 설정하게 됩니다.

SignatureAlgorithm.HS512 알고리즘을 사용할 예정이기 때문에 512자 이상의 값을 입력합니다.

token-validity-in-seconds 는 발행한 키의 유효성 시간 설정 입니다.

jwt:
  header: Authorization
  secret: and0LXRlc3QtYWxqamFiYWVnaS1qd3QtdGVzdC1hbGpqYWJhZWdpLWp3dC10ZXN0LWFsamphYmFlZ2ktand0LXRlc3QtYWxqamFiYWVnaS1qd3QtdGVzdC1hbGpqYWJhZWdpLWp3dC10ZXN0LWFsamphYmFlZ2ktand0LXRlc3QtYWxqamFiYWVnaS1qd3QtdGVzdC1hbGpqYWJhZWdp
  token-validity-in-seconds: 86400

link : https://www.convertstring.com/ko/EncodeDecode/Base64Encode

 

Base64로 인코딩 - 온라인 Base64로 인코더

 

www.convertstring.com

4.3 SecurityConfig 클래스 생성

SpringSecurty 버전이 올라가면서 WebSecurityConfigurerAdapter 클래스가 Deprecated 되었습니다.

@Bean을 통해 추가하는 방식으로 변경되었고, 해당 내용에 맞춰 구현되었습니다.

 

[SecurityConfig]

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

import lombok.RequiredArgsConstructor;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {

    private final TokenProvider tokenProvider;
    private final JwtAuthenticationEntryPoint jwtAtuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer(){
        return (web) -> web.ignoring()
                    .antMatchers("/favicon.ico");
    }
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                    .csrf().disable()

                    /**401, 403 Exception 핸들링 */
                    .exceptionHandling()
                    .authenticationEntryPoint(jwtAtuthenticationEntryPoint)
                    .accessDeniedHandler(jwtAccessDeniedHandler)

                    /**세션 사용하지 않음*/
                    .and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                    /** HttpServletRequest를 사용하는 요청들에 대한 접근 제한 설정*/
                    .and()
                    .authorizeRequests()
                    .antMatchers("/authenticate").permitAll()

                    /**JwtSecurityConfig 적용 */
                    .and()
                    .apply(new JwtSecurityConfig(tokenProvider))

                    .and().build();
    }
}

4.4. JwtSecurityConfig 클래스 생성

아래 생성할 TokenProvider를 주입 받아서 JwtFilter 를 Securty 로직에 등록하기 위한 클래스

 

[JwtSecurityConfig]

import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    private final TokenProvider tokenProvider;

    @Override
    public void configure(HttpSecurity http){
        JwtFilter customFilter = new JwtFilter(tokenProvider);
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

4.5 JwtFilter 생성

Jwt의 인증정보를 SecurityContext에 저장하는 역할을 합니다.

JwtFilter의 doFilter 메소드에서 Reqeust가 들어올 때 SecurityContext에 Authentication 객체를 저장해 사용하게 됩니다.

 

[JwtFilter]

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class JwtFilter extends GenericFilterBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(JwtFilter.class);
    public static final String AUTHORIZATION_HEADER = "Authorization";

    private final TokenProvider tokenProvider;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String jwt = resolveToken(httpServletRequest);
        String requestURI = httpServletRequest.getRequestURI();
        if(StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)){
            Authentication authentication = tokenProvider.getAuthentication(jwt);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            LOGGER.info("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI);
        }else{
            LOGGER.info("유효한 JWT 토큰이 없습니다., uri: {}", requestURI);
        }
        chain.doFilter(httpServletRequest, response);
    }

    /**토큰 정보 추출 */
    private String resolveToken(HttpServletRequest request){
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){
            return bearerToken.substring(7);
        }
        return null;
    }
}

4.6 TokenProvider 클래스 생성

Token의 생성, 인증정보 조회, 유효성 검증, 암호화 설정 등의 역할을 하는 클래스 입니다.

InitializingBean을 implemnets 받아 afterPropertiesSet을 Override 하는 이유는 TokenProvider Bean이 생성되고, 주입을 

받은 후에 secret 값을 Base64 Decode해서 key 변수에 할당하기 위함입니다.

 

[TokenProvider]

import java.security.Key;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;

@Component
public class TokenProvider implements InitializingBean {
    private final Logger LOGGER = LoggerFactory.getLogger(TokenProvider.class);

    private static final String AUTHORITIES_KEY = "NeighborAPI";

    private final String secret;
    private final long tokenValidityInMilliseconds;
    private Key key;

    public TokenProvider(@Value("${jwt.secret}") String secret,
                         @Value("${jwt.token-validity-in-seconds}") long tokenValidityInMilliseconds){
        this.secret = secret;
        this.tokenValidityInMilliseconds = tokenValidityInMilliseconds;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(secret);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }
    
    /**token 생성 algorithm */
    public String createToken(Authentication authentication){
        String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));
        long now = (new Date()).getTime();
        Date validity = new Date(now + this.tokenValidityInMilliseconds);

        return Jwts.builder()
                .setSubject(authentication.getName())
                .claim(AUTHORITIES_KEY, authorities)
                .signWith(key, SignatureAlgorithm.HS512)
                .setExpiration(validity)
                .compact();
    }

    /**인증 정보 조회 */
    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parserBuilder()
                        .setSigningKey(key)
                        .build()
                        .parseClaimsJws(token)
                        .getBody();
        Collection<? extends GrantedAuthority> authorities = 
            Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        User principal =new User(claims.getSubject(), "", authorities);
        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }

    /**token 유효성 검증 */
    public boolean validateToken(String token){
        try{
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        }catch(io.jsonwebtoken.security.SecurityException | MalformedJwtException e){
            LOGGER.info("잘못된 JWT 서명입니다.");
        }catch(ExpiredJwtException e){
            LOGGER.info("만료된 JWT 토큰입니다.");
        }catch(UnsupportedJwtException e){
            LOGGER.info("지원하지 않는 JWT 토큰입니다.");
        }catch(IllegalArgumentException e){
            LOGGER.info("JWT 토큰이 잘못되었습니다.");
        }
        return false;
    }
}

4.7 JwtAccessDeniedHandler 생성

403 Fobidden Exception 처리를 위한 클래스 입니다.

공통적인 응답을 위한 ResVO 는 아래 작성하였고,

Object to Json을 위한 CmmnVar.GSON은 공통 스태틱 클래스에 생성해 놓은 Gson 입니다.

 

[JwtAccessDeniedHandler]

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler{

    private final Logger LOGGER = LoggerFactory.getLogger(JwtAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException accessDeniedException) throws IOException, ServletException {
        PrintWriter writer = response.getWriter();
        ErrorCode errorCode = CommonErrorCode.FORBIDDEN;
        ResVO res = ResVO.builder()
                    .status(errorCode.getResultCode())
                    .message(errorCode.getResultMsg()).build();
        try{
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            writer.write(CmmnVar.GSON.toJson(res));
        }catch(NullPointerException e){
            LOGGER.error("응답 메시지 작성 에러", e);
        }finally{
            if(writer != null) {
                writer.flush();
                writer.close();
            }
        }
        response.getWriter().write(CmmnVar.GSON.toJson(res));
    }
}

4.8 JwtAuthenticationEntryPoint 생성

401 Unauthorized Exception 처리를 위한 클래스

 

[JwtAthenticationEntryPoint]

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        PrintWriter writer = response.getWriter();
        ErrorCode errorCode = CommonErrorCode.UNAUTHORIZED;
        ResVO res = ResVO.builder()
                    .status(errorCode.getResultCode())
                    .message(errorCode.getResultMsg()).build();
        try{
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            writer.write(CmmnVar.GSON.toJson(res));
        }catch(NullPointerException e){
            LOGGER.error("응답 메시지 작성 에러", e);
        }finally{
            if(writer != null) {
                writer.flush();
                writer.close();
            }
        }
        response.getWriter().write(CmmnVar.GSON.toJson(res));
    }
}

4.9 기타 ResVO

공통 응답 처리를 위한 클래스 입니다.

import java.util.ArrayList;

import lombok.Builder;
import lombok.ToString;

@ToString
public class ResVO {
    private final int status;
    private final String message;
    private final Integer size;
    private final ArrayList<?> items;

    @Builder
    public ResVO(int status, String message, Integer size, ArrayList<?> items) {
        this.status = status;
        this.message = message;
        this.size = size;
        this.items = items;
    }
}

5. 인증 구현

사용자 인증 요청에 대해 저장된 사용자 정보와 비교해 각 로직에 따른 처리를 위한 클래스들을 구현합니다.

 

5.1 AuthController

/authntication 요청이 왔을 때 인증 처리를 하는 클래스 입니다.

전달받은 정보로 권한을 조회하고 SpringContext에 해당 권한정보를 저장합니다.

그리고 이상없이 진행될 경우 Token을 생성하여 전달합니다.

import javax.validation.Valid;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class AuthController {

    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;

    @PostMapping("/authenticate")
    public ResponseEntity<TokenDTO> authorize(@Valid @RequestBody OperatorDTO operatorDTO) {
        UsernamePasswordAuthenticationToken authenticationToken = 
            new UsernamePasswordAuthenticationToken(operatorDTO.getLoginId(), operatorDTO.getOprrPswd());

        //authenticationManagerBuilder가 호출되면서 CustomUserDetailService가 로드됨.
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = tokenProvider.createToken(authentication);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);

        return new ResponseEntity<>(new TokenDTO(jwt), httpHeaders, HttpStatus.OK);
    }
}

5.2 CustomUserDetailsService 구현

spring security의 UserDetailService를 implements 받아 구현합니다.

전달받은 User 정보로 권한을 조회하고 존재여부, 있다면 활성화 여부에 따라 User 객체를 생성하여 전달합니다.

import java.util.List;
import java.util.stream.Collectors;

import javax.transaction.Transactional;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService{

    private final UserRepository userRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
        return userRepository.findOneWithAuthoritiesByLoginId(loginId)
                .map(user -> createUser(loginId, user))
                .orElseThrow(() -> new UsernameNotFoundException(loginId + " -> 존재 하지 않음."));
    }

    /**Security User 정보를 생성한다. */
    private User createUser(String loginId, MOpOperator operatorDTO) {
        System.out.println(operatorDTO.getIsUse());
        if(!"Y".equals(operatorDTO.getIsUse())){
            throw new BadCredentialsException(loginId + " -> 활성화 되어있지 않습니다.");
        }
        List<GrantedAuthority> grantedAuthorities = operatorDTO.getAuthorities().stream()
                .map(authority -> new SimpleGrantedAuthority(authority.getAthrNm()))
                .collect(Collectors.toList());
        return new org.springframework.security.core.userdetails.User(
                    operatorDTO.getLoginId(),
                    operatorDTO.getOprrPswd(),
                    grantedAuthorities);
    }
    
}

5.3 OperatorRespository

운영자 정보와 권한 정보를 조회하는 Repository.

EntityGraph는 쿼리가 수행이 될 때 Lazy 조회가 아니고, 호출되는 시점에 인증정보를 조회하기 위해 사용합니다.

loginId로 권한정보를 조회하여 Entity를 리턴합니다.

import java.util.Optional;

import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<MOpOperator, MOpOperatorKey>{

    @EntityGraph(attributePaths = "authorities")
    Optional<MOpOperator> findOneWithAuthoritiesByLoginId(String loginId);
    
}

5.4 OperatorDTO

운영자 정보 DTO 입니다.

import java.util.Set;
import lombok.Data;

@Data
public class OperatorDTO {
	private String oprrId;
	private String loginId;
	private String oprrNm;
	private String oprrPswd;
	private String isUse;
	private Set<AuthDTO> authorities;
}

5.5  AuthDTO

권한정보 DTO입니다.

import lombok.Data;

@Data
public class AuthDTO {
    private String athrId;
    private String athrNm;
}

5.6 TokenDTO

토큰정보 DTO 입니다.

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class TokenDTO {
    private String token;
}

6. 테스트

이제 서버로 인증정보를 요청해 보겠습니다.

Postman을 사용합니다.

6.1 활성화가 안되어 있을 경우

우선 활성화 되어 있지 않은, CustomUserDetailsService에서 아래 조건에 걸리는 결과 입니다.

isUse가 "N" 일 때 겠죠?

if(!"Y".equals(operatorDTO.getIsUse())){
    throw new BadCredentialsException(loginId + " -> 활성화 되어있지 않습니다.");
}

사용자에겐 "자격 증명에 실패하였습니다." 를 전달하고 Log에는 활성화 에러 로그를 추가합니다.

[2022-11-14] [09:38:09.682] [ERROR] : geonlee -> 활성화 되어있지 않습니다.
org.springframework.security.authentication.InternalAuthenticationServiceException: 
geonlee -> 활성화 되어있지 않습니다.

6.2 로그인정보가 잘못 되었을 경우

id나 pw가 다를 경우입니다.

사용자에겐 같은 자격 증명 실패 메시지를 전달합니다.

[2022-11-14] [09:42:25.779] [ERROR] : 자격 증명에 실패하였습니다.
org.springframework.security.authentication.BadCredentialsException: 
자격 증명에 실패하였습니다.

Log는 BadCredenticalsException 이 출력됩니다.

6.3 정상 처리의 경우

정상적으로 수행된 경우 token 정보가 리턴됩니다.

이제 다른 요청에는 해당 token 정보를 인증 정보에 넣어 전달해 줍니다.

 

Bearer Token을 선택하고 Token에 전달받은 Token 정보를 입력하여 요청합니다.

정상적으로 데이터를 받을 수 있게 됩니다.

 

공통 Exception 처리와 관련해서는 아래의 Link를 참고하세요.

 

Link : https://aljjabaegi.tistory.com/657

 

SpringBoot RestAPI 404 Not Found message Custom, @ControllerAdvice @ExceptionHandler

SpringBoot 로 API를 개발할 때 RequestMapping 되어있지 않은 주소로 요청하면 아래와 같은 에러 메시지를 전송합니다. { "timestamp": "2022-11-09T09:35:14.441+00:00", "status": 404, "error": "Not Found", "message": "No message

aljjabaegi.tistory.com

 

Refresh Token 관련 내용은 아래의 Link를 참고하세요.

https://aljjabaegi.tistory.com/708

 

JWT Access Token, Refresh Token 사용 방식 정리, 장단점, 보안

JWT 에 대한 설명과 설정 방식은 아래의 링크를 확인하세요. Link : https://aljjabaegi.tistory.com/659 SpringBoot + JWT + Security + JPA 인증 구현, JWT란? 해당 포스팅은 인프런의 무료강의를 참고하여 작성되었습

aljjabaegi.tistory.com

 

7. 정리

간단하게 로직을 정리하자면,

Spring Security에 TokenProvider를 주입받은 JwtFilter를 등록합니다.

사용자가 사용자정보(loginId, oprrPswd) 로 인증을 요청(/authenticate) 하면 JwtFilter에서는 토큰이 있는지, 유효한지를 판단하여, 유효하다면 SpringContext에 인증정보를 저장하여 사용합니다.

유효하지 않다면(인증정보가 없거나 만료, 잘못된 정보일 경우) CustomUserDetailsService에서 사용자정보(loginId, oprrPswd)로 다시 확인을 하고, 유효하다면 User 정보를 조회하여 새로운 토큰정보를 생성하여 전달을 하고, 유요하지 않다면 Exception을 발생시키게 됩니다.

 

반응형

댓글

💲 추천 글