헤르메스 LIFE

[Spring Boot] Spring Boot + JWT를 이용한 Token 처리 본문

Spring Boot Framework

[Spring Boot] Spring Boot + JWT를 이용한 Token 처리

헤르메스의날개 2022. 3. 26. 01:22
728x90

jwt.zip
0.12MB
jwt.zip
0.14MB

개발환경

Spring Boot : 2.6.5 + JPA

Spring Web : 2.6.5

Spring Security : 5.6.2

Spring Tomcat 

java-JWT : 3.18.3

Lombok : 1.18.22

DB : Postgresql


개발목표

1. Postgresql 에 접속해서 사용자의 정보를 조회한다.

2. JWT를 이욯해서 Token을 생성한다.

3. 생성된 Token을 이용해서 인증에 성공한다.


CorsConfig.java

package com.study.jwt.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

   @Bean
   public CorsFilter corsFilter() {
      CorsConfiguration config = new CorsConfiguration();
      config.setAllowCredentials(true);
      //config.addAllowedOrigin("*"); // java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
      config.addAllowedOriginPattern("*"); // e.g. http://domain1.com
      config.addAllowedHeader("*");
      config.addAllowedMethod("*");

      UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
      source.registerCorsConfiguration("/**", config);
      return new CorsFilter(source);
   }

}

SecurityConfig.java

Spring Security 5.70 이후부터 WebSecurityConfigurerAdapter를 상속 받는 방식은 deprecated

참조 : https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter/

 

Spring Security without the WebSecurityConfigurerAdapter

In Spring Security 5.7.0-M2 we deprecated the WebSecurityConfigurerAdapter, as we encourage users to move towards a component-based security configuration. To assist with the transition to this new style of configuration, we have compiled a list of common

spring.io

package com.study.jwt.config;

import org.springframework.beans.factory.annotation.Autowired;
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.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.study.jwt.config.jwt.JwtAuthenticationFilter;
import com.study.jwt.config.jwt.JwtAuthorizationFilter;
import com.study.jwt.repository.UserRepository;

/*
 * Spring Security 5.70 이후부터 WebSecurityConfigurerAdapter를 상속 받는 방식은 deprecated
 */
@Configuration
@EnableWebSecurity // 시큐리티 활성화 -> 기본 스프링 필터체인에 등록
public class SecurityConfig extends WebSecurityConfigurerAdapter{   
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private CorsConfig corsConfig;
    
    // BCryptPasswordEncoder는 Spring Security에서 제공하는 비밀번호 암호화 객체입니다.
    // Service에서 비밀번호를 암호화할 수 있도록 Bean으로 등록합니다.
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilter(corsConfig.corsFilter())
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .formLogin().disable()
                .httpBasic().disable()
 
                .addFilter(new JwtAuthenticationFilter(authenticationManager()))
                .addFilter(new JwtAuthorizationFilter(authenticationManager(), userRepository))
                .authorizeRequests()
                .antMatchers("/**")
                .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll();
    }
}

PrincipalDetails.java

package com.study.jwt.config.auth;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.study.jwt.model.User;

/**
 * Spring Security가 /login.do 요청이 들어오면
 * 로그인이 완료되면 Security Session 을 생성한다. ( SecurityHolder )
 * Object Type => Authentication 타입 객체
 * Authentication 안에 User 정보가 있어야 함.
 * User Object Type => UserDetails Type 객체
 * 
 * Security Session -> Authentication -> UserDetails(PrincipalDetails)
 */
@SuppressWarnings("serial")
public class PrincipalDetails implements UserDetails{

    private User user;

    public PrincipalDetails(User user){
        this.user = user;
    }

    public User getUser() {
        return user;
    }

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

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

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authList = new ArrayList<>();
        authList.add(new SimpleGrantedAuthority(user.getRole()));
        return authList;
    }
}

PrincipalDetailsService.java

package com.study.jwt.config.auth;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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 com.study.jwt.model.User;
import com.study.jwt.repository.UserRepository;

import lombok.RequiredArgsConstructor;

//UserDetailsService는 IoC로 찾음
///loginProcess.do 가 찾아오는 클래스임.
@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService{
    
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 어썬티케이션 매니저가 낚아챔
        // JPA는 기본적인 CRUD만 있어서 다른걸 쓰려면 만들어줘야함
        logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        logger.info("username :: {}", username);
        User user = userRepository.findByUserId(username);
        logger.info("user :: {}", user);
        
        if ( user != null ) {
            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
            String                 roles[]     = user.getRole().split(",");
            for ( int i = 0; i < roles.length; i++ ) {
                authorities.add(new SimpleGrantedAuthority(roles[i]));
            }

            logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
            
            return new PrincipalDetails(user);  // SecurityContext의 Authertication에 등록되어 인증정보를 가진다.
        }
        
        
        logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        return null;
    }
}

JwtAuthenticationFilter.java

package com.study.jwt.config.jwt;

import java.io.IOException;
import java.util.Date;

import javax.servlet.FilterChain;
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.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.study.jwt.config.auth.PrincipalDetails;
import com.study.jwt.dto.LoginRequestDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final Logger                logger = LoggerFactory.getLogger(this.getClass());
    private final AuthenticationManager authenticationManager;

    // Authentication 객체 만들어서 리턴 => 의존 : AuthenticationManager
    // 인증 요청시에 실행되는 함수 => /login
    @Override
    public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response )
            throws AuthenticationException {
        
        logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        logger.info("JwtAuthenticationFilter : 진입");

        // request에 있는 username과 password를 파싱해서 자바 Object로 받기
        ObjectMapper    om              = new ObjectMapper();
        LoginRequestDto loginRequestDto = null;
        try {
            loginRequestDto = om.readValue(request.getInputStream(), LoginRequestDto.class);
        } catch (Exception e) {
            e.printStackTrace();
        }

        logger.debug("JwtAuthenticationFilter :: {}", loginRequestDto);

        // 유저네임패스워드 토큰 생성
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                loginRequestDto.getUserId(), loginRequestDto.getPassword());

        logger.debug("JwtAuthenticationFilter : 토큰생성완료");

        // authenticate() 함수가 호출 되면 인증 프로바이더가 유저 디테일 서비스의
        // loadUserByUsername(토큰의 첫번째 파라메터) 를 호출하고
        // UserDetails를 리턴받아서 토큰의 두번째 파라메터(credential)과
        // UserDetails(DB값)의 getPassword()함수로 비교해서 동일하면
        // Authentication 객체를 만들어서 필터체인으로 리턴해준다.

        // Tip: 인증 프로바이더의 디폴트 서비스는 UserDetailsService 타입
        // Tip: 인증 프로바이더의 디폴트 암호화 방식은 BCryptPasswordEncoder
        // 결론은 인증 프로바이더에게 알려줄 필요가 없음.
        Authentication authentication = authenticationManager.authenticate(authenticationToken);

        PrincipalDetails principalDetailis = (PrincipalDetails) authentication.getPrincipal();
        logger.debug("Authentication :: {}", principalDetailis.getUser().getUserName());

        logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        
        return authentication;
    }

    // JWT Token 생성해서 response에 담아주기
    @Override
    protected void successfulAuthentication( HttpServletRequest request, HttpServletResponse response,
            FilterChain chain, Authentication authResult ) throws IOException, ServletException {
        logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        
        PrincipalDetails principalDetailis = (PrincipalDetails) authResult.getPrincipal();

        logger.debug("UserId :: {}", principalDetailis.getUser().getUserId());
        logger.debug("UserName :: {}", principalDetailis.getUser().getUserName());
        
        String jwtToken = JWT.create().withSubject(principalDetailis.getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
                .withClaim("userId", principalDetailis.getUser().getUserId())
                .withClaim("username", principalDetailis.getUser().getUserName())
                .sign(Algorithm.HMAC512(JwtProperties.SECRET));
        logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        
        response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + jwtToken);
    }

}

JwtAuthorizationFilter.java

package com.study.jwt.config.jwt;

import java.io.IOException;

import javax.servlet.FilterChain;
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.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.study.jwt.config.auth.PrincipalDetails;
import com.study.jwt.model.User;
import com.study.jwt.repository.UserRepository;

// 인가
public class JwtAuthorizationFilter extends BasicAuthenticationFilter{
    
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private UserRepository userRepository;
    
    public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
        super(authenticationManager);
        this.userRepository = userRepository;
    }
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        
        String header = request.getHeader(JwtProperties.HEADER_STRING);
        if(header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
                        return;
        }
        
        logger.debug("header :: {}", header);
        
        String token = request.getHeader(JwtProperties.HEADER_STRING)
                .replace(JwtProperties.TOKEN_PREFIX, "");
        
        logger.debug("token :: {}", token);
        
        // 토큰 검증 (이게 인증이기 때문에 AuthenticationManager도 필요 없음)
        // 내가 SecurityContext에 집적접근해서 세션을 만들때 자동으로 UserDetailsService에 있는 loadByUsername이 호출됨.
        String username = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET)).build().verify(token)
                .getClaim("userId").asString();
        
        logger.debug("username :: {}", username);
        
        if(username != null) {  
            User user = userRepository.findByUserId(username);
            
            // 인증은 토큰 검증시 끝. 인증을 하기 위해서가 아닌 스프링 시큐리티가 수행해주는 권한 처리를 위해 
            // 아래와 같이 토큰을 만들어서 Authentication 객체를 강제로 만들고 그걸 세션에 저장!
            PrincipalDetails principalDetails = new PrincipalDetails(user);
            Authentication authentication =
                    new UsernamePasswordAuthenticationToken(
                            principalDetails, //나중에 컨트롤러에서 DI해서 쓸 때 사용하기 편함.
                            null, // 패스워드는 모르니까 null 처리, 어차피 지금 인증하는게 아니니까!!
                            principalDetails.getAuthorities());
            
            // 강제로 시큐리티의 세션에 접근하여 값 저장
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    
        logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        
        chain.doFilter(request, response);
    }
    
}

JwtProperties.java

package com.study.jwt.config.jwt;

public interface JwtProperties {
    String SECRET = "1234"; // 우리 서버만 알고 있는 비밀값
    int EXPIRATION_TIME = 864000000; // 10일 (1/1000초)
    String TOKEN_PREFIX = "Bearer ";
    String HEADER_STRING = "Authorization";
}

LogingRequestDto.java

package com.study.jwt.dto;

import lombok.Data;

@Data
public class LoginRequestDto {
    private String userId;
    private String password;
}

User.java

Postgresql 은 User 테이블이 예약테이블입니다. 그래서, User 객체를 사용할 수 없습니다. 또한 Sequence 를 사용하기 보다 UserId 를 이용해서 사용자를 조회하도록 변경했습니다.

package com.study.jwt.model;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

import org.hibernate.annotations.CreationTimestamp;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity(name = "member")
@NoArgsConstructor
@Getter
public class User {

    @Id
    @Column(name = "user_id")
    private String userId;

    @Column(name = "user_name")
    private String    userName;
    private String    password;
    private String    email;
    private String    role;          // ROLE_USER, ROLE_ADMIN
    @CreationTimestamp
    private Timestamp createDate;

    /**
     * @param username
     * @param password
     */
    public User(String userId, String userName, String password) {
        this.userId   = userId;
        this.userName = userName;
        this.password = password;
    }

    /**
     * @param username
     * @param password
     */
    public User(String userId, String userName, String password, String email) {
        this.userId   = userId;
        this.userName = userName;
        this.password = password;
        this.email    = email;
    }

    /**
     * @param username
     * @param password
     */
    public User(String userId, String userName, String password, String email, String role) {
        this.userId   = userId;
        this.userName = userName;
        this.password = password;
        this.email    = email;
        this.role     = role;
    }

    public void setRole( String role ) {
        this.role = role;
    }

    public List<String> getRoles() {
        if ( this.role.length() > 0 ) {
            return Arrays.asList(this.role.split(","));
        }

        return new ArrayList<>();
    }

    @Override
    public String toString() {
        return "User [userId=" + userId + ", userName=" + userName + ", password=" + password + ", email=" + email
                + ", role=" + role + ", createDate=" + createDate + "]";
    }

}

UserRepository.java

Sequence 를 사용하기 보다 UserId 를 이용해서 사용자를 조회하도록 변경했습니다.

package com.study.jwt.repository;

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

import com.study.jwt.model.User;

public interface UserRepository extends JpaRepository<User, Long>{
    // Jpa Naming 전략
    // SELECT * FROM member WHERE id = ?
    // Insert into member ( user_id, password, user_name, createdate, email, role) values ( 'hermeswing', '$2a$10$C4BZPH4raAlKGrvy9dtyyufBp56af2W6fge0hD1wLctWvNEjrK.AG', '홍길동', now(), 'hermeswing@test.com', 'ROLE_ADMIN')
    // id:1, pw:1234
    User findByUserId( String userId ); // JPA Query Method
}

http://localhost:9090/login 

- 사실 어떤 URL을 호출해도 Body에 정확한 사용자 ID와 비밀번호만 있으면 Authorization 을 받을 수 있습니다.

CorsFilter 와 SecurityConfig 에 특별한 경로를 제한하지 않았기 때문입니다.

Authorization 의 생성은 JwtAuthenticationFilter의 successfulAuthentication() 에서 처리합니다.

필자는 userId를 Key로 사용할 예정이기 때문에 아래와 같이 처리했습니다.

String jwtToken = JWT.create().withSubject(principalDetailis.getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
                .withClaim("userId", principalDetailis.getUser().getUserId())
                .withClaim("username", principalDetailis.getUser().getUserName())
                .sign(Algorithm.HMAC512(JwtProperties.SECRET));

정상 처리된 로그가 보여집니다.

01:36:48.657 INFO  c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(39) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:48.657 INFO  c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(40) - JwtAuthenticationFilter : 진입
01:36:48.658 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(51) - JwtAuthenticationFilter :: LoginRequestDto(userId=hermeswing, password=1234)
01:36:48.658 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(57) - JwtAuthenticationFilter : 토큰생성완료
01:36:48.659 INFO  c.s.j.c.auth.PrincipalDetailsService.loadUserByUsername(33) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:48.659 INFO  c.s.j.c.auth.PrincipalDetailsService.loadUserByUsername(34) - username :: hermeswing
01:36:48.660 DEBUG org.hibernate.SQL.logStatement(144) - 
    select
        user0_.user_id as user_id1_0_,
        user0_.createDate as createda2_0_,
        user0_.email as email3_0_,
        user0_.password as password4_0_,
        user0_.role as role5_0_,
        user0_.user_name as user_nam6_0_ 
    from
        member user0_ 
    where
        user0_.user_id=?
Hibernate: 
    select
        user0_.user_id as user_id1_0_,
        user0_.createDate as createda2_0_,
        user0_.email as email3_0_,
        user0_.password as password4_0_,
        user0_.role as role5_0_,
        user0_.user_name as user_nam6_0_ 
    from
        member user0_ 
    where
        user0_.user_id=?
01:36:48.661 TRACE o.h.type.descriptor.sql.BasicBinder.bind(64) - binding parameter [1] as [VARCHAR] - [hermeswing]
01:36:48.662 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([user_id1_0_] : [VARCHAR]) - [hermeswing]
01:36:48.663 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([createda2_0_] : [TIMESTAMP]) - [2022-03-08 13:58:33.884322]
01:36:48.663 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([email3_0_] : [VARCHAR]) - [hermeswing@test.com]
01:36:48.663 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([password4_0_] : [VARCHAR]) - [$2a$10$C4BZPH4raAlKGrvy9dtyyufBp56af2W6fge0hD1wLctWvNEjrK.AG]
01:36:48.663 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([role5_0_] : [VARCHAR]) - [ROLE_ADMIN]
01:36:48.664 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([user_nam6_0_] : [VARCHAR]) - [홍길동]
01:36:48.664 INFO  c.s.j.c.auth.PrincipalDetailsService.loadUserByUsername(36) - user :: User [userId=hermeswing, userName=홍길동, password=$2a$10$C4BZPH4raAlKGrvy9dtyyufBp56af2W6fge0hD1wLctWvNEjrK.AG, email=hermeswing@test.com, role=ROLE_ADMIN, createDate=2022-03-08 13:58:33.884322]
01:36:48.664 INFO  c.s.j.c.auth.PrincipalDetailsService.loadUserByUsername(45) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:49.014 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(71) - Authentication :: 홍길동
01:36:49.014 INFO  c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(73) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:49.014 INFO  c.s.j.c.jwt.JwtAuthenticationFilter.successfulAuthentication(82) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:49.014 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.successfulAuthentication(86) - UserId :: hermeswing
01:36:49.014 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.successfulAuthentication(87) - UserName :: 홍길동
01:36:49.015 INFO  c.s.j.c.jwt.JwtAuthenticationFilter.successfulAuthentication(94) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

역시 암호나 아이디가 틀린경우에는 403 Forbidden 오류가 발생합니다.


역시 특별한 URL의 제약을 두지 않았기 때문에 http://localhost:9090/login2 를 호출해 봤습니다.

Header 에 Authorization 정보를 http://localhost:9090/login 에서 받았던 Authorization 로 채워넣습니다.

성공 후 이동 페이지 및 내용이 없기 때문에 오류(?)가 발생하지 않은 정도의 결과가 보여집니다. ( 404 Not Found 가 발생 합니다. - 권한 없음이 뜨지는 않았죠. )

잘못된 Authorization 정보를 넣으면 403 Forbidden 오류 결과가 보여집니다.

서버에서는 SignatureVerificationException 이 발생합니다.

01:32:30.877 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet].log(175) - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
com.auth0.jwt.exceptions.SignatureVerificationException: The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA512

참고 : 인프런 - 최주호 님의 스프링 부트 시큐리티 & JWT 강의

https://github.com/codingspecialist/Springboot-Security-JWT-Easy

 

GitHub - codingspecialist/Springboot-Security-JWT-Easy

Contribute to codingspecialist/Springboot-Security-JWT-Easy development by creating an account on GitHub.

github.com

https://atin.tistory.com/590

 

[Spring Security] 필터 Filter, SecurityFilterChain 이해하기

Spring Security를 커스터마이징하기 위해서는 그리고 이해하기 위해서는 아래 필터 체인을 이해하는 것이 좋다. 아래 그림은 인터넷에 돌아다니는 Spring Security 호출 그림을 내가 다시 깔끔하게 그

atin.tistory.com

 

728x90