스프링 시큐리티를 활용한 JWT 기반 인증과 권한 부여

스프링 시큐리티를 활용한 JWT 기반 인증과 권한 부여

Authentication Concept

스프링 시큐리티와 JWT 기반 인증의 개요

웹 애플리케이션에서 인증과 권한 부여는 보안적인 측면에서 매우 중요합니다. 스프링 시큐리티는 스프링 생태계에서 제공되는 보안 프레임워크로, 보안 설정을 간편하게 처리할 수 있습니다. JWT는 JSON Web Token의 약자로, 인증 정보를 안전하게 전송하기 위한 방법 중 하나입니다. 이번 글에서는 스프링 시큐리티와 JWT를 결합하여 인증과 권한 부여를 구현하는 방법에 대해 알아보겠습니다.

스프링 시큐리티는 스프링부트에서 기본적으로 제공되는 보안 프레임워크입니다. 스프링 시큐리티를 사용하면 간단한 설정만으로 인증과 권한 부여를 처리할 수 있습니다. 인증은 사용자가 누구인지 확인하는 과정이고, 권한 부여는 인증된 사용자에 대해 허용된 작업을 할 수 있는 권한을 부여하는 과정입니다.

JWT는 인증 정보를 JSON 형태로 표현한 토큰입니다. JWT는 토큰 자체에 인증 정보가 포함되어 있으므로, 서버에서 인증 정보를 저장하거나 조회할 필요가 없습니다. 클라이언트는 JWT를 HTTP 헤더나 쿼리 파라미터로 전송하여 인증을 수행합니다.

JWT를 사용한 인증 및 토큰 발급과 검증

JWT는 세 가지 부분으로 구성되어 있습니다. 첫 번째는 헤더(header)로, 토큰의 유형과 사용된 암호화 알고리즘을 지정합니다. 두 번째는 페이로드(payload)로, 인증 정보를 JSON 형태로 포함합니다. 세 번째는 서명(signature)으로, 토큰을 인증하는데 사용됩니다.

토큰을 발급할 때는, 서버는 사용자 정보를 페이로드에 담아 JWT를 생성합니다. JWT는 암호화되지 않으므로, 사용자 정보는 원본 그대로 토큰에 담겨 전송됩니다. 서버는 JWT를 클라이언트에게 전송하고, 클라이언트는 이후 모든 요청에 JWT를 함께 전송합니다.

토큰을 검증할 때는, 클라이언트는 JWT의 서명을 검증하여 토큰이 유효한지 확인합니다. 서명은 토큰의 헤더와 페이로드를 조합한 후, 지정된 암호화 알고리즘을 사용하여 생성된 값입니다. 서버는 이를 검증하여 토큰이 변경되지 않았는지 확인합니다.

public String createToken(User user) {
    String token = JWT.create()
        .withSubject(user.getUsername())
        .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
        .sign(Algorithm.HMAC512(SECRET.getBytes()));
    return token;
}

public Authentication getAuthentication(HttpServletRequest request) {
    String token = resolveToken(request);
    if (token != null && validateToken(token)) {
        String username = getUsernameFromToken(token);
        User user = userRepository.findByUsername(username);
        return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    }
    return null;
}

public boolean validateToken(String token) {
    try {
        JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
            .build()
            .verify(token);
        return true;
    } catch (JWTVerificationException e) {
        return false;
    }
}

위 코드는 JWT를 사용하여 토큰을 생성하고 검증하는 방법입니다. createToken 메소드는 사용자 정보를 페이로드에 담아 JWT를 생성합니다. getAuthentication 메소드는 요청 헤더에서 JWT를 가져와 검증한 후, 사용자 정보를 반환합니다. validateToken 메소드는 JWT의 서명을 검증하여 토큰이 변경되지 않았는지 확인합니다.

스프링 시큐리티를 활용한 권한 부여 방법

스프링 시큐리티를 사용하면, 간단한 설정만으로 권한 부여를 처리할 수 있습니다. 스프링 시큐리티는 인증 정보를 사용자 세션에 저장하고, 필터 체인을 통해 요청을 검사하여 권한을 확인합니다. 스프링 시큐리티에서 사용되는 권한 정보는 GrantedAuthority 인터페이스를 구현한 객체로 표현됩니다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
            .anyRequest().authenticated()
            .and().httpBasic()
            .and().csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }

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

위 코드는 스프링 시큐리티를 사용하여 권한 부여를 처리하는 방법입니다. configure 메소드에서는, /admin/** 경로에 대해 ADMIN 권한을 가진 사용자만 접근을 허용하고, /user/** 경로에 대해 ADMIN 또는 USER 권한을 가진 사용자만 접근을 허용합니다. configure 메소드에서는 httpBasic 방식으로 인증을 처리하며, csrf를 비활성화합니다. configure 메소드에서는 AuthenticationManagerBuilder를 사용하여 사용자 정보를 조회하고, PasswordEncoder를 사용하여 비밀번호를 암호화합니다.

JWT와 스프링 시큐리티를 통한 보안 강화 방안

스프링 시큐리티와 JWT를 결합하여 보안을 강화하는 방법은 다양합니다. 일반적으로는, HTTPS 프로토콜을 사용하여 통신을 암호화하고, JWT의 유효 기간을 짧게 설정하여 보안을 강화합니다.

또한, JWT를 사용하면 서버에서 세션을 관리하지 않으므로, 서버 부하를 줄일 수 있습니다. JWT는 클라이언트에서 인증 정보를 관리하므로, 서버에서 세션을 관리할 필요가 없습니다. 이를 통해 서버의 부하를 줄이고, 확장성을 높일 수 있습니다.

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
    private final JwtTokenProvider jwtTokenProvider;
    private final UserDetailsService userDetailsService;

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager, JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
        super(authenticationManager);
        this.jwtTokenProvider = jwtTokenProvider;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String token = jwtTokenProvider.resolveToken(request);
        if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
            Authentication authentication = jwtTokenProvider.getAuthentication(token, userDetailsService);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }
}

위 코드는 JWT를 사용하여 인증 정보를 관리하는 방법 중 하나입니다. JWTAuthorizationFilterBasicAuthenticationFilter를 상속하여, HTTP 요청에 JWT가 포함되어 있는지 검증합니다. JWT가 유효하면, 인증 정보를 SecurityContextHolder에 저장하여 다음 요청에서 사용할 수 있도록 합니다.

결론

이번 글에서는 스프링 시큐리티와 JWT를 사용하여 인증과 권한 부여를 구현하는 방법에 대해 알아보았습니다. JWT를 사용하면 클라이언트에서 인증 정보를 관리하므로, 서버에서 세션을 관리할 필요가 없습니다. 스프링 시큐리티를 사용하면 간단한 설정만으로 권한 부여를 처리할 수 있습니다. 이를 결합하여 보안을 강화하는 방법에 대해 알아보았습니다. 보안은 애플리케이션을 구성하는 중요한 요소이므로, 보안을 고려한 개발을 실천하는 것이 좋습니다.