2021๋…„ 11์›” 14์ผ๐Ÿ˜ญ

๊น€๊ด‘ํ›ˆยท2021๋…„ 11์›” 14์ผ
0

TIL(Today I Learned)

๋ชฉ๋ก ๋ณด๊ธฐ
40/49

๐Ÿ’ปํ• ๊ฒŒ ๋„ˆ๋ฌด ๋งŽ์•„

์˜ค๋Š˜ ํ•˜๋ฃจ๋Š” Spring Security์— ๋Œ€ํ•œ ์˜๋ฌธ์ ์œผ๋กœ ์‹œ์ž‘ํ•ด์„œ Spring Security์— ๋Œ€ํ•œ ์˜๋ฌธ์ ์œผ๋กœ ๋๋‚ฌ๋‹ค. ๋ฌผ๋ก  ํ•ด๊ฒฐํ•ด์„œ ๊ธฐ๋ถ„์€ ์ข‹๋‹ค๐Ÿค—


โœ” ์˜ค๋Š˜ ํ•œ ์ผ

  • Spring Security์™€ JWT ํ† ํฐ ๋ฐฉ์‹์— ๋Œ€ํ•œ ๊ณ ์ฐฐ
  • Spring Security Flow ์—ฐ๊ตฌ

๐Ÿ’ป Spring Security & JWT ๋กœ๊ทธ์ธ ๋ฐฉ์‹ ์„ค์ •

์˜ค๋Š˜ Spring Security์— JWTํ† ํฐ ํ•„ํ„ฐ๋ฅผ ์„ค์ •ํ•ด์„œ ๋กœ๊ทธ์ธ ๋ฐฉ์‹์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ์˜๋ฌธ์ ์„ ๊ฐ–๊ณ  ์žˆ์–ด ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํŒ€์›๋“ค๊ณผ ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ ๋ฐ ๊ฐ€์ •์„ ํ•œ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜๊ณ ์ž ํ•œ๋‹ค.(ํ‹€๋ฆด ์ˆ˜๋„ ์žˆ์Œ)

  • Spring Security์˜ ์„ค์ •์— ์„œ๋ฒ„์— ์ ‘๊ทผ ์‹œ JWTํ† ํฐ ์œ ๋ฌด๋ฅผ ๊ฒ€์‚ฌํ•˜๋Š” ํ•„ํ„ฐ๋ฅผ ๊ฑฐ์น˜๋„๋ก ์„ค์ •ํ•œ๋‹ค.

    http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    // UsernamePasswordAuthenticationFilter ์•ž์— jwtRequestFilter๋ฅผ ๋จผ์ € ๊ฑฐ์น˜๋„๋ก ์„ค์ •.
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ์ ‘๊ทผํ•˜๋ฉด ํ•ด๋‹น ๋กœ์ง์— ๋”ฐ๋ผ JWTํ† ํฐ ์œ ๋ฌด๋ฅผ ํŒ๋‹จํ•œ๋‹ค.

    @RequiredArgsConstructor
    @Component
    public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
      private final UserDetailsService userDetailsService;
      private final JwtTokenUtil jwtTokenUtil;
    
      String HEADER_STRING = "Authorization";
      String TOKEN_PREFIX = "Bearer ";
    
      @Override
      protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
          String header = req.getHeader(HEADER_STRING);
          String username = null;
          String authToken = null;
          if (header != null && header.startsWith(TOKEN_PREFIX)) {
              authToken = header.replace(TOKEN_PREFIX,"");
              try {
                  username = jwtTokenUtil.getUsernameFromToken(authToken);
              } catch (IllegalArgumentException e) {
                  logger.error("an error occured during getting username from token", e);
              } catch (ExpiredJwtException e) {
                  logger.warn("the token is expired and not valid anymore", e);
              } catch(SignatureException e){
                  logger.error("Authentication Failed. Username or Password not valid.");
              }
          } else {
              logger.warn("couldn't find bearer string, will ignore the header");
          }
          if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
    
              UserDetails userDetails = userDetailsService.loadUserByUsername(username);
    
              if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                  UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
                  authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
                  logger.info("authenticated user " + username + ", setting security context");
                  SecurityContextHolder.getContext().setAuthentication(authentication);
              }
          }
    
          chain.doFilter(req, res);
      }
    }
    
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ responseํ•ด์ฃผ๋Š” status๋Š” ๋‹ค์Œ์˜ ์ฝ”๋“œ์—์„œ ์ •์˜ํ•ด์ค€๋‹ค.

    @Component
    public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { // ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์— ๋Œ€ํ•œ exception์„ ์ •์˜ํ•  ๋•Œ ์ด๋Ÿฐ์‹์œผ๋กœ ์ž‘์„ฑํ•จ
    
      @Override
      public void commence(HttpServletRequest request, HttpServletResponse response,
                           AuthenticationException authException) throws IOException {
    
          response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); //status : 401 ๋Œ€๊ฒŒ ์ธ์ฆ ์‹คํŒจ๋Š” 401 ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ด
      }
    }
  • ๋กœ๊ทธ์ธ&๊ฐ€์ž…ํ•˜๊ธฐ์™€ ๊ฐ™์ด ํŠน์ˆ˜ํ•œ ํŽ˜์ด์ง€๋Š” Spring Security์˜ ์„ค์ •์„ ํ†ตํ•ด ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ—ˆ์šฉํ•ด์ค€๋‹ค.

    http.authorizeRequests()
                  // image ํด๋”๋ฅผ login ์—†์ด ํ—ˆ์šฉ
                  .antMatchers("/images/**").permitAll()
                  // css ํด๋”๋ฅผ login ์—†์ด ํ—ˆ์šฉ
                  .antMatchers("/css/**").permitAll()
                  // ํšŒ์› ๊ด€๋ฆฌ URL ์ „๋ถ€๋ฅผ login ์—†์ด ํ—ˆ์šฉ
                  .antMatchers("/user/**").permitAll()
                  // h2-console URL ์„ login ์—†์ด ํ—ˆ์šฉ
                  .antMatchers("/h2-console/**").permitAll()
                  .antMatchers("/basic.js").permitAll() 
                  .antMatchers("/login").permitAll()
                  .antMatchers("/login/kakao").permitAll()
                  .antMatchers("/signup").permitAll()
                  .antMatchers("/").permitAll()
                  .antMatchers("/docs/**").permitAll()
                  // ๊ทธ ์™ธ ๋ชจ๋“  ์š”์ฒญ์€ ์ธ์ฆ๊ณผ์ • ํ•„์š”
                  .anyRequest().authenticated()
  • ๋กœ๊ทธ์ธ์‹œ jwtํ† ํฐ ์ƒ์„ฑ์€ ๋‹ค์Œ์˜ ์ฝ”๋“œ์—์„œ ์‹คํ–‰ํ•ด์ค€๋‹ค.

    @Component
    public class JwtTokenUtil implements Serializable { // ํ† ํฐ์„ ๋งŒ๋“ค๊ณ  ํ† ํฐ์œผ๋กœ๋ถ€ํ„ฐ ์–ด๋–ค ์œ ์ €์ธ์ง€ ์•Œ์•„๋ณด๋Š” ํด๋ž˜์Šค ๊ทธ๋ฆฌ๊ณ  ํ† ํฐ์˜ ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์„ ์ •ํ•˜๋Š” ํด๋ž˜์Šค
    
      private static final long serialVersionUID = -2550185165626007488L;
    
      public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
    
      @Value("${jwt.secret}") // application properties์—์„œ secretkey๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค. ์™œ? ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์‹œ ํ…Œ์ŠคํŠธ์šฉ, ๋ฐฐํฌ์šฉ ๋‘๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ application properties๋ฅผ ๋‚˜๋ˆ ์„œ ์„ค์ •์„ ํ•  ์˜ˆ์ •์ด๊ธฐ ๋•Œ๋ฌธ์—
      private String secret;
    
      //retrieve username from jwt token
      public String getUsernameFromToken(String token) {
          return getClaimFromToken(token, Claims::getSubject);
      }
    
      //retrieve expiration date from jwt token
      public Date getExpirationDateFromToken(String token) {
          return getClaimFromToken(token, Claims::getExpiration);
      }
    
      public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
          final Claims claims = getAllClaimsFromToken(token);
          return claimsResolver.apply(claims);
      }
      //for retrieveing any information from token we will need the secret key
      private Claims getAllClaimsFromToken(String token) {
          return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
      }
    
      //check if the token has expired
      private Boolean isTokenExpired(String token) {
          final Date expiration = getExpirationDateFromToken(token);
          return expiration.before(new Date());
      }
    
      //generate token for user
      public String generateToken(UserDetails userDetails) {
          Map<String, Object> claims = new HashMap<>();
          return doGenerateToken(claims, userDetails.getUsername());
      }
    
      //while creating the token -
      //1. Define  claims of the token, like Issuer, Expiration, Subject, and the ID
      //2. Sign the JWT using the HS512 algorithm and secret key.
      //3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
      //   compaction of the JWT to a URL-safe string
      private String doGenerateToken(Map<String, Object> claims, String subject) {
    
          return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                  .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
                  .signWith(SignatureAlgorithm.HS512, secret).compact();
      }
    
      //validate token
      public Boolean validateToken(String token, UserDetails userDetails) {
          final String username = getUsernameFromToken(token);
          return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
      }
    }

๋‹ค์‹œํ•œ๋ฒˆ ์ •๋ฆฌํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด Spring Security์˜ JWTํ•„ํ„ฐ๊ฐ€ JWTํ† ํฐ์˜ ์œ ๋ฌด๋ฅผ ํŒ๋ณ„ํ•œ๋‹ค.(JWTํ† ํฐ์˜ ์œ ๋ฌด๋งŒ ํŒ๋‹จํ•˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€๋Š” ํŒ๋‹จํ•˜์ง€ ์•Š์Œ)
  1. ์ดํ›„ ํ† ํฐ๊ฐ’์„ ๊ฐ–๊ณ  ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ UserDetails์„ ์ƒ์„ฑํ•œ๋‹ค. ์ด ๊ฐ’์€ @AuthenticationPrincipal ์–ด๋…ธํ…Œ์ด์…˜์„ ์ด์šฉํ•ด ๊ฐ€์ ธ๋‹ค ์“ธ ์ˆ˜ ์žˆ๋‹ค.
  1. ๋กœ๊ทธ์ธ๊ณผ ํšŒ์›๊ฐ€์ž…๊ณผ ๊ฐ™์ด ํ† ํฐ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ํŽ˜์ด์ง€ ๋˜๋Š” api๋Š” securityConfigure์—์„œ ์ ‘๊ทผํ—ˆ์šฉ์„ ํ•ด์ค€๋‹ค.

๐Ÿ‘€Todo List

์ด๋ฒˆ์— ์ฃผ๋ง์— ์ด๋ฃจ๊ธฐ๋กœ ์•ฝ์†ํ•ด๋…ผ ๊ฒƒ์ค‘ ๋Œ€๋ถ€๋ถ„ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ–ˆ๋‹ค. ๋‚ด๊ฐ€ ์ƒ๊ฐํ–ˆ๋˜๊ฒƒ ๋ณด๋‹ค ์ผ์ •์ด ๋งŽ์ด ํƒ€์ดํŠธํ–ˆ๋‚˜๋ณด๋‹ค ์‚ฌ์ด์— ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ด์„œ ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ์‹œ๊ฐ„์ด ๊ฝค๋‚˜ ๊ฑธ๋ฆฐ ๊ฒƒ๋„ ๋ฌธ์ œ๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

  • โ›ํ•ด๋ณด๊ณ  ์‹ถ์€ ํ”„๋กœ์ ํŠธ ํ•œ๋ฒˆ ์ƒ๊ฐํ•ด๋ณด์ž
    ํ•ด๋ณด๊ณ  ์‹ถ์€ ํ”„๋กœ์ ํŠธ ํ•œ๋ฒˆ ์ƒ๊ฐํ•ด๋ณด๊ณ  ๊ฐ„๋‹จํ•œ ๊ฒƒ ๋ถ€ํ„ฐ ํ•˜๋‚˜์”ฉ ํ•ด๋ณด์ž. ๊ทธ๋ฆฌ๊ณ  ๋ฐฐ์šด ๊ฑธ ์ด ๋™์›ํ•ด์„œ ๋‹ค์Œ์˜ ํ•ญ๋ชฉ์„ ๊ตฌ์ถ•ํ•ด ๋†“๊ณ  ์ด๊ฒƒ ์ €๊ฒƒ ๋งŒ๋“ค์–ด ๋ณด์ž. ์ž๋™๋ฐฐํฌ(GitAction) ์ธํ”„๋ผ ๊ตฌ์ถ•ํ•ด๋†“๊ธฐ, ํ”„๋ก ํŠธ ์—”๋“œ ์ •์ ํ˜ธ์ŠคํŒ…์œผ๋กœ ๋‚˜๋ˆ„๊ธฐ, ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ EB์— ํ•˜๋‚˜ ๋งŒ๋“ค์–ด ๋†“๊ธฐ
    ์ง€๊ธˆ ๋งŒ๋“œ๋Š” ํ”„๋กœ์ ํŠธ๋Š” ์ผ๋‹จ AWS, GitAction ์—ฐ์Šตํ•˜๋Š” ๋Š๋‚Œ์œผ๋กœ ๊ฐ€์ž.
    'ํ•ด๋ณด๊ณ  ์‹ถ์€ ํ”„๋กœ์ ํŠธ ์ฃผ์ œ ๋ฆฌ์ŠคํŠธ' = [์†Œ์„ค ๋ณผ ์ˆ˜ ์žˆ๋Š” ์›น์‚ฌ์ดํŠธ, ์ถ”๊ฐ€์˜ˆ์ •...]
    (์ƒ๊ฐ๋ณด๋‹ค ํ•ด์•ผ๋ ๊ฒŒ ๋งŽ๋‹ค. ์ผ๋‹จ ์บ ํ”„ ์ผ์ •์— ์ถฉ์‹คํ•˜๊ฒŒ ๊ตด์ž...)

  • โ›์‹ค์‹œ๊ฐ„ ๊ฐ•์˜๋กœ ๋ฐฐ์šด ์Šคํ”„๋ง ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์งœ๋ณด๊ธฐ!!
    ์ง€๊ธˆ์˜ ๋‚ด๊ฒŒ๋Š” ๋ฐ˜๋ณต๋งŒ์ด ์‚ด๊ธธ... ์ด๋ฒˆ์— ์ฒ˜์Œ๋ถ€ํ„ฐ ์ง์ ‘ ์ƒ๊ฐํ•ด๋ณด๋ฉด์„œ ์Šคํ”„๋ง ๊ฐ•์˜ ๋‚ด์šฉ ์ง์ ‘ ์งœ๋ณด์ž, ~~(๊นƒํ—™์— ์ œ๋ฐœ์ข€ ์˜ฌ๋ ค...)~~
    ์ตœ์†Œํ•œ ์Šคํ”„๋ง 4๊ฐ•๊นŒ์ง€ ์™„๋ฃŒํ•˜์ž.
    (JWTํ† ํฐ ๊ตฌํ˜„๊นŒ์ง€ ์™„๋ฃŒํ–ˆ๋‹ค. ํ‰์ผ๋™์•ˆ ๊พธ์ค€ํžˆ ๋” ์ž‘์„ฑํ•ด์„œ ๋งˆ๋ฌด๋ฆฌ ์ง“์ž.)

  • โ›์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋‹จ๊ณ„๋ณ„ ๋ฌธ์ œ ํ’€๊ธฐ!
profile
์ž˜ ๋ถ€ํƒ๋“œ๋ ค์š”

0๊ฐœ์˜ ๋Œ“๊ธ€