JWT

JWT ํ† ํฐ์œผ๋กœ ๋กœ๊ทธ์ธ

  • ๋กœ๊ทธ์ธ์‹œ ์›๋ž˜๋Š” localhost:8080/login ์„ ํ˜ธ์ถœํ•˜๋ฉด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ์•Œ์•„์„œ UserDetailsService ๋นˆ์„ ์ฐพ์•„ loadUserByUsername์„ ํ˜ธ์ถœํ•˜๋Š”๋ฐ filterChain ์—์„œ formLogin์„ disableํ•˜๊ณ  ์ปค์Šคํ…€ ํ•„ํ„ฐ๋ฅผ ๋“ฑ๋กํ•ด์„œ ๋กœ๊ทธ์ธ์„ ํ• ๊ฒƒ์ด๋‹ค.

SecurityConfig.java

security ๊ธฐ๋ณธ์— ์ ์–ด๋†“์€ config์™€ ๊ฐ™๋‹ค

@Configuration
@EnableWebSecurity // ์‹œํ๋ฆฌํ‹ฐ ํ™œ์„ฑํ™” -> ๊ธฐ๋ณธ ์Šคํ”„๋ง ํ•„ํ„ฐ์ฒด์ธ์— ๋“ฑ๋ก
public class SecurityConfig {

   @Autowired
   private UserRepository userRepository;

   @Autowired
   private CorsConfig corsConfig;

   @Bean
   SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      return http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .formLogin().disable()
            .httpBasic().disable()
            .apply(new MyCustomDsl()) // ์ปค์Šคํ…€ ํ•„ํ„ฐ ๋“ฑ๋ก
            .and()
            .authorizeRequests(authroize -> authroize.antMatchers("/api/v1/user/**")
                  .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
                  .antMatchers("/api/v1/manager/**")
                  .access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
                  .antMatchers("/api/v1/admin/**")
                  .access("hasRole('ROLE_ADMIN')")
                  .anyRequest().permitAll())
            .build();
   }

   public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
      @Override
      public void configure(HttpSecurity http) throws Exception {
         AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
         http
               .addFilter(corsConfig.corsFilter())
               .addFilter(new JwtAuthenticationFilter(authenticationManager))
               .addFilter(new JwtAuthorizationFilter(authenticationManager, userRepository));
      }
   }

}

JwtAutenticationFilter.java

  • ๋กœ๊ทธ์ธ์‹œ ์‹คํ–‰๋˜๋Š” ํ•„ํ„ฐ๋ฅผ ์ƒ์†๋ฐ›์€ ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด์ค˜์„œ SecurityConfig ์— ๋“ฑ๋กํ•ด์ค˜์•ผํ•จ

  • UsernamePasswordAuthenticationFilter๋Š” AuthenticationManager๊ฐ€ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ํ•„์š”ํ•จ. ์œ„์˜ SecurityConfig.java์˜ ํ•„ํ„ฐ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ๋ถ€๋ถ„์„ ๋ณด๋ฉด AuthenticationManager ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์„œ ๋„˜๊ฒจ์ฃผ๋Š”๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์Œ

  • PrincipalDetailsService ์™€ PrincipalDetails๋Š” security ๊ธฐ๋ณธ์— ์ž‘์„ฑ๋˜์–ด ์žˆ์Œ, ์ƒ์„ฑํ•ด์ค˜์•ผํ•จ


//์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ UsernamePasswordAuthenticationFilter ๊ฐ€ ์žˆ์Œ
// /login ์š”์ฒญํ•ด์„œ username,password ์ „์†กํ•˜๋ฉด ์ด ํ•„ํ„ฐ๊ฐ€ ๋™์ž‘์„ ํ•จ, ๊ทผ๋ฐ formlogin์„ disableํ•ด๋†”์„œ ์ง€๊ธˆ ์ž๋™์œผ๋กœ ์ž‘๋™์•ˆํ•จ
//๊ทธ๋ž˜์„œ ํ•„ํ„ฐ๋ฅผ ์ƒ์†๋ฐ›์•„ ๋งŒ๋“ค์–ด์ฃผ๊ณ  securityconfig์— ๋“ฑ๋ก์‹œ์ผœ ์ฃผ๋Š”๊ฒƒ์ž„
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter{

   private final AuthenticationManager authenticationManager;
   
   // Authentication ๊ฐ์ฒด ๋งŒ๋“ค์–ด์„œ ๋ฆฌํ„ด => ์˜์กด : AuthenticationManager
   // ์ธ์ฆ ์š”์ฒญ์‹œ์— ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜ => /login (๋กœ๊ทธ์ธ์‹œ๋„์‹œ ์‹คํ–‰๋จ)
   @Override
   public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
         throws AuthenticationException {
      
      System.out.println("JwtAuthenticationFilter : ์ง„์ž…");
      /*
      * 1. username,password ๋ฐ›์•„์„œ
      * 2. ์ •์ƒ์ธ์ง€ ๋กœ๊ทธ์ธ ์‹œ๋„๋ฅผ ํ•จ. authenticationManager๋กœ ๋กœ๊ทธ์ธ ์‹œ๋„๋ฅผ ํ•˜๋ฉด PrincipalDetailsService๊ฐ€ ํ˜ธ์ถœ๋จ
      * 3. loadUserByUsername ํ•จ์ˆ˜ ์‹คํ–‰๋จ
      * 4. PrincipalDetail๋ฅผ ์„ธ์…˜์— ๋‹ด๊ณ  -> ์„ธ์…˜์— ์•ˆ๋‹ด์•„์ฃผ๋ฉด ๊ถŒํ•œ ๊ด€๋ฆฌ๊ฐ€ ์•ˆ๋จ
      * 5. JWTํ† ํฐ์„ ๋งŒ๋“ค์–ด์„œ ์‘๋‹ต
      */
      // request์— ์žˆ๋Š” username๊ณผ password๋ฅผ ํŒŒ์‹ฑํ•ด์„œ ์ž๋ฐ” Object๋กœ ๋ฐ›๊ธฐ
      ObjectMapper om = new ObjectMapper();
      LoginRequestDto loginRequestDto = null;
      try {
         loginRequestDto = om.readValue(request.getInputStream(), LoginRequestDto.class);
      } catch (Exception e) {
         e.printStackTrace();
      }
      
      System.out.println("JwtAuthenticationFilter : "+loginRequestDto);
      
      // ์œ ์ €๋„ค์ž„ํŒจ์Šค์›Œ๋“œ ํ† ํฐ ์ƒ์„ฑ
      UsernamePasswordAuthenticationToken authenticationToken = 
            new UsernamePasswordAuthenticationToken(
                  loginRequestDto.getUsername(), 
                  loginRequestDto.getPassword());
      
      System.out.println("JwtAuthenticationFilter : ํ† ํฐ์ƒ์„ฑ์™„๋ฃŒ");
      
      // authenticate() ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ ๋˜๋ฉด ์ธ์ฆ ํ”„๋กœ๋ฐ”์ด๋”๊ฐ€ ์œ ์ € ๋””ํ…Œ์ผ ์„œ๋น„์Šค์˜
      // loadUserByUsername(ํ† ํฐ์˜ ์ฒซ๋ฒˆ์งธ ํŒŒ๋ผ๋ฉ”ํ„ฐ) ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ 
      // UserDetails๋ฅผ ๋ฆฌํ„ด๋ฐ›์•„์„œ ํ† ํฐ์˜ ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฉ”ํ„ฐ(credential)๊ณผ
      // UserDetails(DB๊ฐ’)์˜ getPassword()ํ•จ์ˆ˜๋กœ ๋น„๊ตํ•ด์„œ ๋™์ผํ•˜๋ฉด
      // Authentication ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ํ•„ํ„ฐ์ฒด์ธ์œผ๋กœ ๋ฆฌํ„ดํ•ด์ค€๋‹ค.
      
      // Tip: ์ธ์ฆ ํ”„๋กœ๋ฐ”์ด๋”์˜ ๋””ํดํŠธ ์„œ๋น„์Šค๋Š” UserDetailsService ํƒ€์ž…
      // Tip: ์ธ์ฆ ํ”„๋กœ๋ฐ”์ด๋”์˜ ๋””ํดํŠธ ์•”ํ˜ธํ™” ๋ฐฉ์‹์€ BCryptPasswordEncoder
      // ๊ฒฐ๋ก ์€ ์ธ์ฆ ํ”„๋กœ๋ฐ”์ด๋”์—๊ฒŒ ์•Œ๋ ค์ค„ ํ•„์š”๊ฐ€ ์—†์Œ.
      Authentication authentication = 
            authenticationManager.authenticate(authenticationToken);
      
      PrincipalDetails principalDetailis = (PrincipalDetails) authentication.getPrincipal();
      System.out.println("Authentication : "+principalDetailis.getUser().getUsername());
      //authentication๊ฐ์ฒด๊ฐ€ session์˜์—ญ์— ์ €์žฅ์„ ํ•ด์•ผํ•˜๊ณ  ๊ทธ ๋ฐฉ๋ฒ•์ด return ํ•ด์ฃผ๋ฉด๋จ
      //๊ทธ ์ด์œ ๋Š” ๊ถŒํ•œ ๊ด€๋ฆฌ๋ฅผ ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ๋Œ€์‹  ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ํŽธํ•˜๋ ค๊ณ  ํ•˜๋Š”๊ฑฐ์ž„
      //๊ตณ์ด JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์„ธ์…˜์„ ๋งŒ๋“ค ์ด์œ ๊ฐ€ ์—†์ง€๋งŒ ๋‹จ์ง€ ๊ถŒํ•œ์ฒ˜๋ฆฌ ๋•Œ๋ฌธ์— ์„ธ์…˜์— ๋„ฃ์–ด์ฃผ๋Š”๊ฒƒ
      return authentication;
   }

   //์œ„์˜ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰์ด ์ข…๋ฃŒ๋˜๋ฉด(์ธ์ฆ์ด ์ •์ƒ์œผ๋กœ ๋˜๋ฉด) ์ด์–ด์„œ successfulAuthentication๊ฐ€ ์‹คํ–‰๋จ
   //JWTํ† ํฐ์„ ๋งŒ๋“ค์–ด์„œ requset์š”์ฒญํ•œ ์‚ฌ์šฉ์ž์—๊ฒŒ JWTํ† ํฐ์„ reponseํ•ด์คŒ
   // JWT Token ์ƒ์„ฑํ•ด์„œ response์— ๋‹ด์•„์ฃผ๊ธฐ
   @Override
   protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
         Authentication authResult) throws IOException, ServletException {
      
      PrincipalDetails principalDetailis = (PrincipalDetails) authResult.getPrincipal();
      
      //RSA๋ฐฉ์‹์€ ์•„๋‹ˆ๊ณ  Hash์•”ํ˜ธ ๋ฐฉ์‹
      String jwtToken = JWT.create()
            .withSubject(principalDetailis.getUsername()) //ํฌ๊ฒŒ ์ค‘์š”ํ•˜์ง€ ์•Š์Œ
            .withExpiresAt(new Date(System.currentTimeMillis()+JwtProperties.EXPIRATION_TIME)) //์–ธ์ œ๊นŒ์ง€ ์œ ํšจํ• ์ง€ ๋งŒ๋ฃŒ์‹œ๊ฐ„
            .withClaim("id", principalDetailis.getUser().getId())
            .withClaim("username", principalDetailis.getUser().getUsername())
            .sign(Algorithm.HMAC512(JwtProperties.SECRET));
      
      response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX+jwtToken);
   }
   
}

JwtProperties.java

public interface JwtProperties {
   String SECRET = "cos"; // ์šฐ๋ฆฌ ์„œ๋ฒ„๋งŒ ์•Œ๊ณ  ์žˆ๋Š” ๋น„๋ฐ€๊ฐ’
   int EXPIRATION_TIME = 864000000; // 10์ผ (1/1000์ดˆ)
   String TOKEN_PREFIX = "Bearer ";
   String HEADER_STRING = "Authorization";
}

Last updated