Profile picture

10cheon00์˜ Archive

24-2-14 Spring Security JWTAuthenticationFilter

February 14, 2024

JWTAuthenticationFilter๋Š” ๐Ÿ”—์œ ํŠœ๋ธŒ ์˜์ƒ์„ ๋ณด๊ณ  ๋”ฐ๋ผํ—€๊ณ , ์ด ๊ฒŒ์‹œ๊ธ€์€ ๊ทธ ๋‚ด์šฉ์„ ์ถ”๊ฐ€๋กœ ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.

JWT

ํ† ํฐ์˜ ํƒ€์ž…(๋Œ€์ฒด๋กœ JWT)๊ณผ ์„ ํƒํ•œ ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜์— ๋Œ€ํ•ด ๋ช…์‹œํ•˜๊ณ  ์žˆ๋‹ค.

Copy
{
  "alg": "HS256",
  "typ": "JWT"
}

์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ๋Š” HS256๊ณผ RS256์ด ์žˆ๋‹ค.

Payload

Claim์„ ๋‹ด๊ณ  ์žˆ๋‹ค. Claim์ด๋ž€ ๋‹จ์ˆœํ•˜๊ฒŒ ํ† ํฐ์— ๋‹ด๊ธด ์ •๋ณด๋ฅผ ์˜๋ฏธํ•˜๋Š”๋ฐ ๋ณดํ†ต ์‚ฌ์šฉ์ž์— ๊ด€ํ•œ ์ •๋ณด๋ฅผ ๋‹ด๋Š”๋‹ค. Claim์—๋Š” 3๊ฐ€์ง€ ์ข…๋ฅ˜๊ฐ€ ์žˆ๋‹ค.

  • Registered

    ๋ฏธ๋ฆฌ ์ •ํ•ด์ง„ ํ•ญ๋ชฉ๋“ค์ด๋‹ค. ํ•„์ˆ˜๋Š” ์•„๋‹ˆ์ง€๋งŒ ๊ถŒ์žฅ๋˜๋Š” ํ•ญ๋ชฉ๋“ค์ด๋‹ค. iss, exp, sub, aud ๋“ฑ์˜ ํ•ญ๋ชฉ์ด๋‹ค.

  • Public

    ์ž์œ ๋กญ๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ์„œ๋น„์Šค์™€ ์ถฉ๋Œ์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์›ฌ๋งŒํ•˜๋ฉด ํ‘œ์ค€์— ํ•ด๋‹นํ•˜๋Š” ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ์„ค์ •ํ•˜๋„๋ก ๊ถŒ์žฅํ•œ๋‹ค.

  • Private

    ํ† ํฐ์„ ์“ฐ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐ„์— ์ •ํ•œ ํ•ญ๋ชฉ์„ ์˜๋ฏธํ•˜๊ณ , Registered๋‚˜ Public claim์— ํ•ด๋‹นํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.

Copy
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

์ด๊ณณ์— ์œ ์ €๋ช…์ด๋‚˜ ๊ด€๋ จ๋œ ์ •๋ณด๋“ค์„ ๋‹ด์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์ฆํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๊ณต๊ฐœ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ๋‹ด์œผ๋ฉด ์•ˆ๋œ๋‹ค.

Signature

Header์™€ Payload๋ฅผ ์•”ํ˜ธํ™”ํ•˜์—ฌ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์ฆํ•œ๋‹ค. ์ด ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ Header์— ์ •์˜๋˜์–ด ์žˆ๋‹ค.

HS256๋Š” ๋Œ€์นญํ‚ค ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ, ๋‘ ์†ก์ˆ˜์‹ ์ž ๊ฐ„์— ๊ณต์œ ๋˜๋Š” ๋Œ€์นญํ‚ค๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

RS256๋Š” ๋น„๋Œ€์นญํ‚ค ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ, ๋น„๋ฐ€ํ‚ค์™€ ๊ณต๊ฐœํ‚ค๊ฐ€ ์กด์žฌํ•œ๋‹ค. ๋น„๋ฐ€ํ‚ค๋Š” ๋…ธ์ถœ๋˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.

JWT๋ฅผ ์“ฐ๋Š” ์ด์œ 

์žฅ์ 

  • ์„œ๋ช…๋œ ํ† ํฐ์ด๋ฏ€๋กœ, ๋‹ค๋ฅธ ํ† ํฐ๋ณด๋‹ค ๋ณด์•ˆ์„ฑ์ด ๋†’๋‹ค.
  • ํ† ํฐ์˜ payload์— ์ •๋ณด๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋„๋ฉ”์ธ๊ณผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๊ด€๊ณ„์—†์ด ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ํ‘œ์ค€์œผ๋กœ ์ง€์ •๋˜์–ด ์žˆ๋‹ค.

๋‹จ์ 

  • payload๊ฐ€ ์ปค์งˆ ๊ฒฝ์šฐ ํ†ต์‹ ์— ๋ถ€ํ•˜๊ฐ€ ๊ฑธ๋ฆฌ๊ฒŒ ๋œ๋‹ค.
  • ํ† ํฐ์˜ ์ˆ˜๋ช…์„ ๊ด€๋ฆฌํ•ด์•ผํ•œ๋‹ค.

DefaultSecurityFilterChain

Spring Security๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ์šฉ๋˜๋Š” FilterChain์€ DefaultSecurityFilterChain์ด๋‹ค. ์ค‘๋‹จ์ ์„ ์ฐ๊ณ  ๋””๋ฒ„๊น…์„ ํ•ด๋ณด๋ฉด ์•„๋ž˜ ์‚ฌ์ง„์ฒ˜๋Ÿผ FilterChain์— ๋‹ด๊ฒจ์žˆ๋Š” ์—ฌ๋Ÿฌ ํ•„ํ„ฐ๋“ค์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

alt text

๊ฐ„๋‹จํ•˜๊ฒŒ ์–ด๋–ค ํ•„ํ„ฐ๋“ค์ธ์ง€ ์ •๋ฆฌํ•ด๋ณด์•˜๋‹ค.

ํ•„ํ„ฐ ์„ค๋ช…
DisableEncodeUrlFilter Url๋กœ ์—ฌ๊ฒจ์ง€์ง€ ์•Š๋Š” Url์— ์„ธ์…˜ id๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
WebAsyncManagerIntegrationFilter SecurityContext์™€ Spring์˜ WebAsyncManager๋ฅผ ํ†ตํ•ฉํ•œ๋‹ค.
SecurityContextHolderFilter SecurityContextRepository๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ SecurityContext๋ฅผ ํš๋“ํ•œ ํ›„ SecurityContextHolder์— ์ €์žฅํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋‹ค์–‘ํ•œ ์ธ์ฆ๊ณผ์ •์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
HeaderWriterFilter X-Frame-Options, X-Xss-Protection, X-Content-Type-Option๊ณผ ๊ฐ™์€ ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.
CorsFilter pre-flight ์š”์ฒญ๊ณผ CORS ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‘๋‹ต ํ—ค๋”๋ฅผ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
LogoutFilter ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค. ์‹ค์ œ๋กœ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ๊ฐ€ ๋˜์—ˆ๋‹ค๋ฉด ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ํ•œ๋‹ค.
RequestCacheAwareFilter ํ˜„์žฌ ์š”์ฒญ์ด ์บ์‹œ๋œ ์š”์ฒญ๊ณผ ์ผ์น˜ํ•˜๋ฉด ์บ์‹œ๋œ ์š”์ฒญ์„ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•œ๋‹ค.
SecurityContextHolderAwareRequestFilter ...
AnonymousAuthenticationFilter ...
ExceptionTranslationFilter ...
AuthorizationFilter ...

Jwt Authentication Flow

์ดˆ๊ธฐํ™” ๊ณผ์ •์—์„œ 3๊ฐœ์˜ ํ•„ํ„ฐ๊ฐ€ ๋” ์ถ”๊ฐ€๋˜๋Š”๋ฐ, ์•„๋ž˜ ์‚ฌ์ง„์„ ๋ณด๋ฉด UsernamePasswordAuthenticationFilter์™€ DefaultLoginPageGeneratingFilter, DefaultLogoutPageGeneratingFilter๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ๋‹ค.

alt text

Copy
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        String username = obtainUsername(request);
        username = (username != null) ? username.trim() : "";
        String password = obtainPassword(request);
        password = (password != null) ? password : "";
        UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
                password);
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

UsernamePasswordAuthenticationFilter์„ ํ†ต๊ณผํ•  ๋•Œ attemptAuthentication ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ์š”์ฒญ์œผ๋กœ๋ถ€ํ„ฐ Username, password๋ฅผ ํš๋“ํ•œ ํ›„ AuthenticationToken์„ ์ƒ์„ฑํ•˜๊ณ  AuthenticationManager์—๊ฒŒ ์ธ์ฆ์„ ๋งก๊ธด๋‹ค.

Jwt๋กœ ์ธ์ฆ์„ ์ง„ํ–‰ํ•œ๋‹ค๋ฉด, ์ด ๊ณผ์ •์„ ๊ฑฐ์น˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ดํ›„์— ์‹คํ–‰๋˜๋Š” AuthorizationFilter์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚  ์ˆ˜๋ฐ–์— ์—†๋‹ค(Authentication ๊ฐ์ฒด๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— Role์ด๋‚˜ Authority๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์—†๋‹ค). ๋”ฐ๋ผ์„œ ํ•„ํ„ฐ ๋‚ด๋ถ€์—์„œ Jwt๋ฅผ ๊ฒ€์ฆํ•˜๊ณ , ์ ์ ˆํ•œ Authentication ๊ฐ์ฒด๋ฅผ ์ฐพ์•„ SecurityContext์— ๋“ฑ๋กํ•ด์•ผ ํ•œ๋‹ค.

alt text

  1. http ํŒจํ‚ท ํ—ค๋”์— ๋‹ด๊ธด ํ† ํฐ์„ ๊ฒ€์ฆํ•˜์—ฌ ์œ ํšจํ•œ์ง€ ํŒ๋‹จํ•œ๋‹ค. ๋งŒ์•ฝ ์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ด๋ผ๋ฉด ํ•„ํ„ฐ๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.
  2. ํ† ํฐ์— ๋‹ด๊ธด Payload๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ ํ† ํฐ์— ๋‹ด๊ธด ์‚ฌ์šฉ์ž๋ช…์„ ์ฝ์–ด์˜จ ํ›„ ๋ฏธ๋ฆฌ ์„ค์ •ํ•œ UserDetailsService Bean ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ํ•ด๋‹นํ•˜๋Š” ์‚ฌ์šฉ์ž์˜ UserDetails ๊ฐ์ฒด๋ฅผ ์–ป์–ด์˜จ๋‹ค.
  3. UserDetails๋ฅผ AutheticationToken์œผ๋กœ ๊ฐ€๊ณตํ•˜์—ฌ SecurityContext์— ๋“ฑ๋กํ•œ๋‹ค.
  4. AuthorizationFilter์—์„œ SecurityContext๋ฅผ ์กฐํšŒํ•  ๋•Œ, 3๋ฒˆ์—์„œ ๋“ฑ๋กํ•œ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•จ์œผ๋กœ ์ธ์ฆ๊ณผ ํ—ˆ๊ฐ€๊ฐ€ ํ†ต๊ณผ๋œ๋‹ค.

๊ตฌํ˜„

Copy
// JwtAuthenticationFilter.java

package com.learn.security;

import com.learn.security.service.UserService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtGenerator jwtGenerator;

    @Autowired
    private UserService userService;

    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain) throws ServletException, IOException {
        String token = getJwtFromRequest(request);
        if (StringUtils.hasText(token) && jwtGenerator.validateToken(token)) {
            String username = jwtGenerator.getUsernameFromJWT(token);
            UserDetails userDetails = userService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    userDetails,
                    null,
                    userDetails.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }
}

JwtAuthenticationFilter๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ์—๋Š” OncePerRequestFilter๋ฅผ ์ƒ์†ํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค. ์ด ํด๋ž˜์Šค๋Š” ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ํ•œ ๋ฒˆ์”ฉ ๊ผญ ์‹คํ–‰๋˜๋Š” ํ•„ํ„ฐ์ด๋ฏ€๋กœ, ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ๊ฑฐ์น˜๊ฒŒ ๋œ๋‹ค.

Copy
// JwtGenerator.java
package com.learn.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;

import static com.learn.security.SecurityConstants.*;

@Component
public class JwtGenerator {
    public boolean validateToken(String token) {
        SecretKey key = getSecretKey();
        try {
            Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
            return true;
        } catch (Exception e) {
            throw new AuthenticationCredentialsNotFoundException("Jwt was expired or incorrect.");
        }
    }

    public String getUsernameFromJWT(String token) {
        SecretKey key = getSecretKey();
        Claims claims = Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload();
        return claims.getSubject();
    }

    public String generateToken(Authentication authentication) {
        String username = authentication.getName();
        Date currentDate = new Date();
        Date expireDate = new Date(currentDate.getTime() + JWT_EXPIRATION);

        SecretKey key = getSecretKey();
        String token = Jwts.builder()
                .subject(username)
                .issuedAt(expireDate)
                .signWith(key, Jwts.SIG.HS512)
                .compact();
        return token;
    }

    public SecretKey getSecretKey() {
        // JWT_SECRET์€ ๋‹ค๋ฅธ ํด๋ž˜์Šค์— ์ •์˜ํ•ด๋‘” ์ž„์˜์˜ ๋‚œ์ˆ˜๋กœ ์•”ํ˜ธํ™”์— ์‚ฌ์šฉ๋œ๋‹ค.
        return Keys.hmacShaKeyFor(JWT_SECRET.getBytes());
    }
}
Copy
// SecurityConfig.java

package com.learn.security;

import com.learn.security.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.Objects;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .exceptionHandling((exception) -> exception.authenticationEntryPoint(jwtAuthEntryPoint()))
                .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests((authorize) ->
                        authorize
                                .requestMatchers("/api/auth/**").permitAll()
                                .anyRequest().authenticated()
                );
        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }


    @Bean
    public UserService userDetailsService() {
        return new UserService();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }

    @Bean
    public JwtAuthEntryPoint jwtAuthEntryPoint() {
        return new JwtAuthEntryPoint();
    }
}

Jwt ์ธ์ฆ๋ฐฉ์‹์€ ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์„ธ์…˜์˜ ์ƒํƒœ๋ฅผ STATELESS๋กœ ์„ค์ •ํ•ด์•ผํ•œ๋‹ค.

๊ตฌํ˜„ํ•œ JwtAuthenticationFilter๋ฅผ ๋“ฑ๋กํ•  ๋•Œ์—๋Š” addFilterBefore๋ฅผ ์ด์šฉํ•˜์—ฌ UsernamePasswordAuthenticationFilter์˜ ์ง์ „์— ๋“ฑ๋กํ–ˆ๋‹ค.

/api/auth/** ํŒจํ„ด์— ๋ถ€ํ•ฉํ•˜๋Š” URL๋กœ ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ, ๋กœ๊ทธ์ธ ๋ฐ ๊ณ„์ •๊ณผ ๊ด€๋ จ๋œ ์š”์ฒญ์ด๊ธฐ ๋•Œ๋ฌธ์— permitAll()์ฒ˜๋ฆฌ๋ฅผ ํ•˜์—ฌ ์ธ์ฆ์„ ์š”๊ตฌํ•˜์ง€ ์•Š๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ธ์ฆ์— ์‹คํŒจํ–ˆ์„ ๋•Œ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด JwtAuthEntryPoint๋ฅผ ๋“ฑ๋กํ•˜์˜€๋‹ค.

Copy
// LoginController.java

package com.learn.security.controller;

import com.learn.security.dto.AuthResponseDto;
import com.learn.security.JwtGenerator;
import com.learn.security.dto.LoginDto;
import com.learn.security.entity.User;
import com.learn.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.web.bind.annotation.*;

import java.util.Optional;

@RequestMapping("/api/auth")
@RestController
public class LoginController {
    private final UserService userService;
    private final AuthenticationManager authenticationManager;
    private final JwtGenerator jwtGenerator;

    @Autowired
    public LoginController(UserService userService, AuthenticationManager authenticationManager, JwtGenerator jwtGenerator) {
        this.userService = userService;
        this.authenticationManager = authenticationManager;
        this.jwtGenerator = jwtGenerator;
    }

    // 201 ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด ResponseEntity๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.
    @PostMapping("/signup")
    public ResponseEntity<User> signup(@RequestBody LoginDto loginDto) {
        Optional<User> joinedUser = userService.join(loginDto);
        return new ResponseEntity<>(joinedUser.get(), HttpStatus.CREATED);
    }

    @PostMapping("/signin")
    @ResponseBody
    public AuthResponseDto signIn(@RequestBody User user) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        user.getUsername(),
                        user.getPassword()
                ));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String token = jwtGenerator.generateToken(authentication);
        return new AuthResponseDto(token);
    }
}

๋กœ๊ทธ์ธ์„ ํ•  ๋•Œ์—๋Š” username๊ณผ password๊ฐ€ ์ „๋‹ฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์ธ์ฆํ•œ ํ›„ jwt๋ฅผ ์ƒ์„ฑํ•ด์„œ ๊ณง๋ฐ”๋กœ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

LoginController๋งŒ permitAll()์ด ๋˜์–ด์žˆ๊ณ  ๊ทธ ์™ธ์—๋Š” ์ „๋ถ€ authenticated()์ด๋ฏ€๋กœ, JwtAuthenticationFilter๊ฐ€ ๋™์ž‘ํ•œ๋‹ค.

LoginDto, User, UserService ๊ฐ™์€ ํด๋ž˜์Šค๋“ค์€ ๊นƒํ—ˆ๋ธŒ ๋งํฌ๋กœ ๋Œ€์ฒดํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค... github.com/10cheon00/learn-spring-security


Loading script...
ยฉ 2024, Built with Gatsby