Spring Security jwt 방식 - access_token(1)

2025. 3. 6. 11:06Springboot/security

전체코드 : https://github.com/gks930620/spring_securty_all

 

프로젝트 세팅

https://start.spring.io/ 에서 프로젝트를 생성합니다. 

필요한 library는
Spring Data JDBC,H2 Database,  Spring Data JPA,  Spring Web, Thymeleaf,

Spring Boot Devtools ,  Lombok,  Spring security 입니다.

 

jwt,  dotenv(.env 환경변수 설정)  라이브러리는 buuild.gradle에 직접 추가합니다.

implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'

implementation 'io.github.cdimascio:dotenv-java:2.2.0'

 

 

설정파일

application.yml

spring:
  datasource:
    url: jdbc:h2:mem:security
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        default_batch_fetch_size: 100
    open-in-view: false
  devtools:
    livereload:
      enabled: true
    freemarker:
      cache: false
    restart:
      enabled: true
  thymeleaf:
    cache: false


jwt:
  secret : ${JWT_SECRET_KEY}
    #키는 길이만 충분하고 노출되지만 않으면 됨. gpt한테 만들어하던가 내가 막 타자 아무렇게 해도됨
  expiration_access: 60000  #테스트용 1분


logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.type: trace
    org.springframework.security : DEBUG

 

 

SecurityConfig       

설명이 필요한 부분은 JWT를 구현하면서  설명할 것이지만,
마지막에 exceptionHandling.authenticationEntryPoint는 잠깐 살펴보자. 

여기는  다음과 같은 상황에 오게된다.

  • 인증없이(access token없이) 인증 필요한 url에 접근했을 때 
  • 토큰만료
  • 로그인실패

이후 구현과정에서  이 3가지 상황에 대한 코드부분에서 request.setAttribute를 하게 될 것이다. 

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtUtil jwtUtil;
    private final CustomUserDetailsService customUserDetailsService;  //내가 빈으로 등록한것들

    private final AuthenticationConfiguration authenticationConfiguration;  //authenticationManger를 갖고있는 빈.

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


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http  //내부H2DB  확인용.  진짜 1도 안중요함.
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/h2-console/**").permitAll() // H2 콘솔 접근 허용
            )
            .csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**")) // H2 콘솔 CSRF 비활성화
            .headers(
                headers -> headers.frameOptions(frame -> frame.disable())); // H2 콘솔을 iframe에서 허용

        http    //기본 session방식관련 다 X
            .csrf(csrf -> csrf.disable())
            .sessionManagement(
                session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .formLogin(form -> form.disable())
            .httpBasic(basic -> basic.disable());

        http   //경로와 인증/인가 설정.
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login", "/api/join").permitAll() //login필터는 기본적으로  /login 일 때 동작
                .requestMatchers("/api/my/info").authenticated()
            );

        http          //필터
            .userDetailsService(customUserDetailsService)
            .addFilterAt(
                new JwtLoginFilter(authenticationConfiguration.getAuthenticationManager(), jwtUtil),
                UsernamePasswordAuthenticationFilter.class)  //기존 세션방식의 로그인 검증필터 대체.
            .addFilterBefore(
                new JwtAccessTokenCheckAndSaveUserInfoFilter(jwtUtil, customUserDetailsService),
                UsernamePasswordAuthenticationFilter.class);

        // .authenticated()  url 부분 로그인 안하고 접근하면  기본적으로는 로그인페이지 redirect하기
        //  근데 formLogin을 disable했는데 이러면 security는 기본적으로 403보냄.
        http
            .exceptionHandling(ex -> ex
                .authenticationEntryPoint((request, response, authException) -> {
                    String errorCause =
                        request.getAttribute("ERROR_CAUSE") != null ? (String) request.getAttribute(
                            "ERROR_CAUSE") : null;
                    //인증없이(access token없이) 인증필요한 곳에 로그인했을 떄.
                    if (errorCause == null) {
                        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                        response.setContentType("application/json;charset=UTF-8");
                        response.getWriter().write("{\"error\": \"인증이 필요합니다.\"}");
                        return;
                    }

                    // JwtAccessTokenCheckAndSaveUserInfoFilter  토큰체크하는부분
                    if (errorCause.equals("토큰만료")) {
                        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 응답
                        response.setContentType("application/json");
                        response.setCharacterEncoding("UTF-8");
                        response.getWriter().write("{\"error\": \"Access Token expired\"}");
                        return;
                    }


                    if (errorCause.equals("로그인실패")) { //jwtLoginFilter 로그인시도부분.
                        response.setStatus(
                            HttpServletResponse.SC_UNAUTHORIZED); //로그인실패도 401이 보통
                        response.setContentType("application/json;charset=UTF-8");
                        response.getWriter().write("{\"error\": \"아이디 비번 틀림.\"}");
                        return;
                    }
                })
            );
        return http.build();
    }
}

 

 

 

 

 

JWT와 상관없는 회원가입 및 CustomUserAccount 세팅

회원가입,  로그인 판단(CustomUserDetailsService) , 

로그인  성공했을 때 security가 사용할 사용자 정보 자체는 기존 security 방식과 똑같다. 

다른점은 로그인 시도 후 성공이면  access token을 클라이언트에 전달, 

실패하면  로그인실패사실을 클라이언트에 전달하기.

또 클라이언트가 access token을 이용해 인증이 필요한 곳에 접근하고 이를 처리하는 부분이다.

 

 

JoinDTO

@Setter
@Getter
public class JoinDTO {
    private String username;
    private String password;
}

 

 

UserEntity

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String username;

    private String password;


    private List<String> roles=new ArrayList<>();
}

 

 

 

CustomUserAccount  (securiy에서 사용자정보로 사용할 객체)

@Getter
public class CustomUserAccount  implements UserDetails {
    private UserEntity userEntity;

    public CustomUserAccount(UserEntity userEntity) {
        this.userEntity = userEntity;
    }

    @Override
    public String getUsername() {
        return userEntity.getUsername();
    }

    @Override
    public String getPassword() {
        return userEntity.getPassword();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return userEntity.getRoles().stream()
            .map(SimpleGrantedAuthority::new)
            .toList();
    }
}

 

 

 

JoinController

@Controller
@RequestMapping("/api/join")
@RequiredArgsConstructor
public class JoinController {

    private  final JoinService joinService;

    @ResponseBody
    @PostMapping
    public  String joinPost(@RequestBody JoinDTO joinDTO){ //클라이언트가 body로 보낸다고 가정.
        joinService.joinProcess(joinDTO);
        return "회원가입 완료!";
    }
}

 

 

UserRepository

public interface UserRepository extends JpaRepository<UserEntity, Long> {
    UserEntity findByUsername(String username);
}

 

 

JoinService

@Service
public class JoinService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public void joinProcess(JoinDTO joinDTO) {
        //db에 이미 동일한 username을 가진 회원이 존재하는지?
        UserEntity find = userRepository.findByUsername(joinDTO.getUsername());
        if(find!=null) {
            System.out.println("이미 있는 ID입니다.");
        }
        UserEntity user = new UserEntity();
        user.setUsername(joinDTO.getUsername());
        user.setPassword(passwordEncoder.encode(joinDTO.getPassword()));  //DB에 저장될 때는 반드시 encoding되서 저장되어야한다.
        user.getRoles().add("USER");     //hasAuthority("USER") 에 맞게 USER로 세팅

        userRepository.save(user);
    }
}

 

 

 

CustomUserDetailsService

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserEntity userData = userRepository.findByUsername(username);
        if (userData != null) {
            return new CustomUserAccount(userData);
        }
        throw new UsernameNotFoundException(username+"에 대한 회원정보가 없습니다.");
    }
}

 

 

 

JoinController 

(JWT를 사용하는 API서버에서는 보통 파라미터보다는 body에 담겨온다 -> @RequestBody)

@Controller
@RequestMapping("/api/join")
@RequiredArgsConstructor
public class JoinController {

    private  final JoinService joinService;

    @ResponseBody
    @PostMapping
    public  String joinPost(@RequestBody JoinDTO joinDTO){ //클라이언트가 body로 보낸다고 가정.
        joinService.joinProcess(joinDTO);
        return "회원가입 완료!";
    }
}

 

 

MainController

@Controller
@RequestMapping("/api")
public class MainController {

    //로그인 후
    @RequestMapping("/my/info")
    @ResponseBody
    public String  myInfo(@AuthenticationPrincipal CustomUserAccount customUserAccount
        ){
        StringBuilder sb=new StringBuilder();
        sb.append(  "권한 : "+    customUserAccount.getAuthorities().iterator().next().getAuthority() +"<br>"   );  //첫번째권한.
        sb.append(  "password : "+  customUserAccount.getPassword()  +"<br> ");
        sb.append(  "username : "+  customUserAccount.getUsername() + "<br>" );
        return sb.toString();
        //api서버에서는 보통 DTO로 보내지만..
    }
}

 

 

 

 

 

JwtUtil

로그인 성공,  인증필요한 곳 접근등에 사용할 token을 JWT로 만들건데

직접 만들면 힘드니까 JwtUtil클래스를 만든다.

이 jwtUtil에서는  access token 생성,  검증, 추출(username 등) 의 기능을 제공한다.

JwtUtil

@Component
public class JwtUtil {

    @Value("${jwt.secret}")
    private String secretKey;

    @Value("${jwt.expiration_access}")
    private long expirationAccess;


    private SecretKey getSigningKey() {
        return Keys.hmacShaKeyFor(secretKey.getBytes());   //HMAC 알고리즘일 때는 SecretKey로 return하기.
    }

    // Access Token 생성
    public String createAccessToken(String username) {
        return Jwts.builder()
            .subject(username) // ✅ setSubject() -> subject()
            .claim("token_type", "access")
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + expirationAccess))
            .signWith(getSigningKey()) // ✅ SignatureAlgorithm.HS256 대신 Jwts.SIG.HS256 사용
            .compact();
    }


    //토큰에서 username 추출.  뭐 subject에 uuid를 넣기도하지만.. 여기서는 subject에 username세팅했었음.
    public String extractUsername(String token) {
        return Jwts.parser()
            .verifyWith(getSigningKey())  // 0.12.3버전에서는 verifyWith에 Key말고 SecretKey가 와야한다.
            .build()
            .parseSignedClaims(token)
            .getPayload()
            .getSubject();
    }

    //토큰에서 인증여부 확인.  코드상 문제가없다면 보통 만료됐을 때 false
    public boolean validateToken(String token) {
        try {
            Claims claims = Jwts.parser()
                .verifyWith(getSigningKey())  // ✅ 서명 검증
                .build()
                .parseSignedClaims(token)    // ✅ JWT 파싱
                .getPayload();               // ✅ claims(토큰 정보) 추출
            //  토큰 만료 확인
            Date expiration = claims.getExpiration();
            return expiration.after(new Date()); // 현재 시간보다 만료 시간이 뒤에 있어야 유효

        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }

}

//jjwt 버전에 따라 구현방식이 다르다. 현재는 0.12.3 버전.

 

 

 

 

Access token으로 로그인 구현하기 

로그인시도

이런식으로 동작하면 되는데 이 때 로그인url은 security기본설정에 의해 /login 이다

/login으로 클라이언트가 요청을 했다면 security는 기본적으로 UsernamePasswordAuthenticationFilter가

로그인 판단 + 로그인 성공/실패 처리를 하게된다.  

로그인판단은  우리가 만든 CustomUserDetailsService에서 하는거고,

이건 JWT에서도 똑같이 사용할것이라 상관없다.

UsernamePasswordAuthenticationFilter의 로그인성공은 session에 사용자정보를 담는다.

UsernamePasswordAuthenticationFilter의 로그인실패는 다시 로그인하도록 redirect 된다.

이 기본 동작대신에 우리는  성공했을 때 access token만들어서 클라이언트한테 전달.

실패했을 때 실패 사실전달을 하도록 하는 필터를 만들어 UsernamePasswordAuthenticationFilter를 대체한다. 

 

그래서 SecurityConfig를 보면  UsernamePasswodAuthentcationFilter 를 대체하는코드가 있다.

http          //필터
    .userDetailsService(customUserDetailsService)
    .addFilterAt(
        new JwtLoginFilter(authenticationConfiguration.getAuthenticationManager(), jwtUtil),
        UsernamePasswordAuthenticationFilter.class)  //기존 세션방식의 로그인 검증필터 대체.
        // 이부분!!!
        
    .addFilterBefore(
        new JwtAccessTokenCheckAndSaveUserInfoFilter(jwtUtil, customUserDetailsService),
        UsernamePasswordAuthenticationFilter.class);

 

 

 

JwtLoginFilter

//username, password를 이용해 로그인판단을 하는 필터
//  /login URL일 때 동작
@RequiredArgsConstructor
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {

    private  final AuthenticationManager authenticationManager;  //new 로 생성하면 부모의 authenticationManager필드는 null이기 때문에 생성자로 주입.
    private final JwtUtil jwtUtil;

    // 로그인 시도
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            // 요청에서 username, password 추출
            // jwt는 API서버 분리된방식.  username,password는  body에 포함되서 옴.
            // 파라미터에 포함되서 오지않음 보통.  이것때문에 재정의. UsernamePasswordAuthetnctionFilter는 parameter 를 처리함.
            Map<String, String> credentials = new ObjectMapper().readValue(request.getInputStream(), HashMap.class);
            String username = credentials.get("username");
            String password = credentials.get("password");


            //이 부분은 UsernamePasswordAuthetnctionFilter 코드 그대로.
            // AuthenticationManger를 통해 확인하는건
            // 결국 username,password를 가지고 CustomUserDetailsService의 return값(CustomUserAccount)이랑 비교.
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
            this.setDetails(request, authRequest);
            return authenticationManager.authenticate(authRequest);  //여기서 AuthenticationException 발생.
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("Failed to parse authentication request", e);  //readValue하는과정에서 발생.
        }

    }

    // 로그인 성공 → JWT 토큰 발급,  성공했을 때는 아무 문제없이 필터체인을 통과하게 된다.
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        CustomUserAccount customUserAccount = (CustomUserAccount) authResult.getPrincipal();

        String accessToken = jwtUtil.createAccessToken(customUserAccount.getUsername());
        // 토큰을 응답에 포함
        response.setContentType("application/json");
        response.getWriter().write("{\"access_token\": \"" + accessToken + "\"}");

    }

    //실패하면 config의 authenticationEntryPoint로    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request,
        HttpServletResponse response, AuthenticationException failed)
        throws IOException, ServletException {
        request.setAttribute("ERROR_CAUSE" , "로그인실패"); //실패 후 config의 entryPoint로
        super.unsuccessfulAuthentication(request,response,failed);
    }
}

 

 

 

 

로그인 성공 후 토큰을 가지고 /api/my/info 접근

클라이언트는 받은 토큰을 가지고  /api/my/info에 접근합니다.

기존 security방식에서는 세션에 사용자정보(를 포함하는 securityContext)가 저장되어있지만, 

API서버에서는 access token을 이용해 매 요청마다 

securityContext에 CustomUserAccount를  저장해야합니다. 

참고로 아까 만든 JwtLoginFilter는 /login일 때만 통과하는 필터입니다.  여기서는 사용X 

 

매 요청마다 이런과정을 거쳐야하기 때문에 OncePerRequestFilter를 상속받은 필터를 만들어

SecurityConfig에서 필터체인에 등록해야합니다. 

http          //필터
    .userDetailsService(customUserDetailsService)
    .addFilterAt(
        new JwtLoginFilter(authenticationConfiguration.getAuthenticationManager(), jwtUtil),
        UsernamePasswordAuthenticationFilter.class)  //기존 세션방식의 로그인 검증필터 대체.
    
    //이부분!!
    .addFilterBefore(
        new JwtAccessTokenCheckAndSaveUserInfoFilter(jwtUtil, customUserDetailsService),
        UsernamePasswordAuthenticationFilter.class);

 

 

JwtAccessTokenCheckAndSaverUserInfoFilter

//보통 이름은 JwtAuthorizationFilter를 한다.  
//이름을 잘 못 지어도  jwt관련 필터들이 정확히 무슨처리하는지 알면 덜 헷갈린다. 
// access token을 검증하고  CustomUserAccount(UserDetails)를 SecurityContext에 저장하는 필터
@RequiredArgsConstructor
public class JwtAccessTokenCheckAndSaveUserInfoFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;  //단순히  jwt기능제공
    private final UserDetailsService userDetailsService;  //내가만들고 빈 등록한 CustomUserDetailsService

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

        String token = getTokenFromRequest(request);

        if (token == null) {
            //토큰 없을 떄는 그냥 다음필터로
            //인증이 필요없는곳 => 무사통과
            //인증이  필요한곳  =>  securityConfig의 authentication에 걸림 => config의  authenticationEntryPoint 에서 처리
            chain.doFilter(request, response);
            return;
        }

        if (!jwtUtil.validateToken(token)) { //토큰이 문제 있다면.. 보통 만료돼었을 때
            request.setAttribute("ERROR_CAUSE", "토큰만료");
            chain.doFilter(request,response);   //로그인을 안한거기 때문에 역시 seucirtyConfig의 authetncation에 걸림.
            return;
        }
        String username = jwtUtil.extractUsername(token);

        //토큰도 있고, 토큰도 문제없음.   securityContext에 담아 로그인한걸로 판단!!.
        UserDetails userDetails = userDetailsService.loadUserByUsername(
            username); //내가 만든 CustomUserAccount
        UsernamePasswordAuthenticationToken authenticationToken =
            new UsernamePasswordAuthenticationToken(userDetails, null,
                userDetails.getAuthorities());
        authenticationToken.setDetails(
            new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        chain.doFilter(request, response);
    }

    private String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {  //띄어쓰기 주의
            return bearerToken.substring(7);
        }
        return null;
    }
}

 

 

 

 

 

 

실행결과

 

로그인전 /api/my/info.

(JwtAccessTokenCheckAndSaveUserInfoFilter의   이 부분 실행

=> 이후 config의 authenticationEntryPoint)

if (token == null) {
    //토큰 없을 떄는 그냥 다음필터로
    //인증이 필요없는곳 => 무사통과
    //인증이  필요한곳  =>  securityConfig의 authentication에 걸림 => config의  authenticationEntryPoint 에서 처리
    chain.doFilter(request, response);
    return;
}

 

 

 

 

 

 

회원가입전 로그인 시도 /login

/login 이기때문에  JwtLoginFilter 동작. 

DB에  아직 회원정보 없기때문에 

attemptAuthentication()에서 AuthenticationException발생 => unsuccessfulAuthencation() 실행

=> 이후  config의 authenticationEntryPoint

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    try {
        Map<String, String> credentials = new ObjectMapper().readValue(request.getInputStream(), HashMap.class);
        String username = credentials.get("username");
        String password = credentials.get("password");
        UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
        this.setDetails(request, authRequest);
        return authenticationManager.authenticate(authRequest);  //여기서 AuthenticationException 발생.
    } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException("Failed to parse authentication request", e);  //readValue하는과정에서 발생.
    }


 //AuthenticationException이 발생해서 여기 코드 실행
	@Override
    protected void unsuccessfulAuthentication(HttpServletRequest request,
        HttpServletResponse response, AuthenticationException failed)
        throws IOException, ServletException {
        request.setAttribute("ERROR_CAUSE" , "로그인실패"); //실패 후 config의 entryPoint로
        super.unsuccessfulAuthentication(request,response,failed);
    }

}

 

 

 

 

 

 

 

회원가입

 

 

 

 

회원가입 후 로그인시도 => 로그인성공 

JwtLoginFilter의  attemptAuthentcation() => 문제없음 => successfulAuthentication()에서 토큰발급 

// 로그인 시도
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    try {
        Map<String, String> credentials = new ObjectMapper().readValue(request.getInputStream(), HashMap.class);
        String username = credentials.get("username");
        String password = credentials.get("password");

    
        UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
        this.setDetails(request, authRequest);
        return authenticationManager.authenticate(authRequest);  
    } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException("Failed to parse authentication request", e);  //readValue하는과정에서 발생.
    }

}

// 로그인 성공 → JWT 토큰 발급,  성공했을 때는 아무 문제없이 필터체인을 통과하게 된다.
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    CustomUserAccount customUserAccount = (CustomUserAccount) authResult.getPrincipal();
    String accessToken = jwtUtil.createAccessToken(customUserAccount.getUsername());
    // 토큰을 응답에 포함
    response.setContentType("application/json");
    response.getWriter().write("{\"access_token\": \"" + accessToken + "\"}");
}

 

 

 

 

올바른 access token을 가지고 /api/my/info 요청

JwtAccessTokenCheckAndSaveUserInfoFilter에서    securityContext에 사용자 정보 저장

=> security가 인증된걸로 판단 => 컨트롤러까지 

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

    String token = getTokenFromRequest(request);
    if (token == null) {
        chain.doFilter(request, response);
        return;
    }

    if (!jwtUtil.validateToken(token)) { //토큰이 문제 있다면.. 보통 만료돼었을 때
        request.setAttribute("ERROR_CAUSE", "토큰만료");
        chain.doFilter(request,response);   //로그인을 안한거기 때문에 역시 seucirtyConfig의 authetncation에 걸림.
        return;
    }
    String username = jwtUtil.extractUsername(token);

    //토큰도 있고, 토큰도 문제없음.   securityContext에 담아 로그인한걸로 판단!!.
    UserDetails userDetails = userDetailsService.loadUserByUsername(
        username); //내가 만든 CustomUserAccount
    UsernamePasswordAuthenticationToken authenticationToken =
        new UsernamePasswordAuthenticationToken(userDetails, null,
            userDetails.getAuthorities());
    authenticationToken.setDetails(
        new WebAuthenticationDetailsSource().buildDetails(request));
    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    chain.doFilter(request, response);
}

 

 

 

 

 

만료된 access token가지고 /api/my/info 요청 

JwtAccessTokenCheckAndSaveUserInfoFilter에서    if문에 걸림. securityContext에 저장안함

=> security가 인증안된걸로 판단=> 이후 config의 authenticationEntryPoint로

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

    String token = getTokenFromRequest(request);
    if (token == null) {
        //토큰 없을 떄는 그냥 다음필터로
        //인증이 필요없는곳 => 무사통과
        //인증이  필요한곳  =>  securityConfig의 authentication에 걸림 => config의  authenticationEntryPoint 에서 처리
        chain.doFilter(request, response);
        return;
    }

    if (!jwtUtil.validateToken(token)) { //토큰이 문제 있다면.. 보통 만료돼었을 때
        request.setAttribute("ERROR_CAUSE", "토큰만료");
        chain.doFilter(request,response);   //로그인을 안한거기 때문에 역시 seucirtyConfig의 authetncation에 걸림.
        return;
    }
    String username = jwtUtil.extractUsername(token);

    //토큰도 있고, 토큰도 문제없음.   securityContext에 담아 로그인한걸로 판단!!.
    UserDetails userDetails = userDetailsService.loadUserByUsername(
        username); //내가 만든 CustomUserAccount
    UsernamePasswordAuthenticationToken authenticationToken =
        new UsernamePasswordAuthenticationToken(userDetails, null,
            userDetails.getAuthorities());
    authenticationToken.setDetails(
        new WebAuthenticationDetailsSource().buildDetails(request));
    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    chain.doFilter(request, response);
}