Redis로 세션관리 및 세션으로 인증 및 인가 (Spring Boot 3.1.5)

Do_It·2023년 11월 17일

오늘은 어디~?

목록 보기
3/9

환경
spring boot 3.1.5
JDK 17

이번 프로젝트에서 인증 방식은 session으로 해보기!

이전 프로젝트에서는 Jwt를 활용해 인증과 인가를 구축했다.
즉, 토큰 인증만을 진행해봤다. 그래서 이번에는 세션 인증을 해보려고 한다. 그래도 둘의 차이는 알고 이용해야겠다.

  • 토큰 인증 : 사용자가 로그인 하면 서버는 사용자의 인증정보를 담은 토큰을 발급해 클라이언트에게 반환, 클라이언트가 해당 토큰 소유하고 매 요청마다 헤더에 토큰을 첨부해 보내고, 서버는 해당 토큰의 유효성을 검증하여 사용자 판별
  • 세션 인증 : 사용자가 로그인하면 서버는 인증 정보를 서버에 저장, 사용자에게는 고유한 세션 ID부여. 세션 ID를 쿠키를 이용해 브라우저에 저장 후 요청 때마다 쿠키를 헤더에 첨부해 보내고, 서버는 쿠키를 받아 저장된 세션 ID의 유효성을 검증하고 서버에 기록된 유저 판별.
  • 두 방식의 차이점 : 인증정보와 유저정보 저장 공간이 다름
    ->토큰 인증 : 토큰에 인증정보 + 유저정보 / 인가 작업 시 서버는 토큰의 유효성만 검사
    ->세션 인증 : 서버에 인증정보 + 유저정보 / 인가 작업 시 서버는 사용자가 보낸 쿠키에 저장 된 세션ID와 서버에 저장된 세션 ID 비교해 유효성 검사

Redis?

Redis는 다양한 방식으로 쓰일 수 있는 미들웨어라고 생각한다. 물론 데이터베이스, 캐시 , 메세지 브로커 등으로 쓰일 수 있다. 쓰기 나름이다!

레디스의 특징은

  • in -memory 구조 : 데이터를 메모리에 저장하여 빠르게 액세스 가능
  • 다양한 데이터 구조 지원 : 다양한 데이터 구조를 지원하여 유연한 데이터 모델을 제공함
  • 클라이언트-서버 모델 : 클라이언트와 서버 간에 간단한 프로토콜을 사용하여 통신
  • 분산 시스템 지원 : 마스터- 슬레이브 복제 및 클러스터링을 지원

이런 대표적인 특징을 가지고 있다.

Redis를 사용해보려는 이유는
1. 처음 써보는 기술이기 때문에 해보고 싶음
2. 레디스의 특성 상 빠른 읽기와 쓰기 제공, 간편한 확장성,높은 가용성 때문
3. Spring security는 Authentication 인증 객체를 세션에 보관하여 사용하는 것이 기본값인데, 이는 분산 환경이 될시에 로그인 상태를 확인하는 기능에 장애를 유발할 수 있기 때문, 따라서 in memory 방식인 레디스를 이용하여 세션을 대체

단 Redis를 사용시 단점도 있다.
1. 레디스는 메모르 기반이기 때문에 서버가 재시작되면 데이터 손실될 수 있음
2. 레디스에 데이터 저장 시 암호화를 적용하지 않으면 세션ID 노출 될 수 있음

이러한 단점들을 생각하면서 개발을 진행해야한다.

Redis 세션 절차

  1. 사용자가 로그인 요청을 서버에 보냄
  2. 서버는 로그인 요청을 받아 인증을 수행
  3. 인증되면 세션 정보를 생성 후 레디스에 저장 후 세션 ID 클라이언트에게 전송
  4. 레디스 서버는 세션 데이터를 저장, 세션 ID를 키로 사용하여 세션 데이터를 저장하고 조회
  5. 클라이언트는 세션 ID를 쿠키를 통해 저장
  6. 사용자의 후속 요청이 올 때마마다 세션 식별자를 확인
  7. 세션의 만료 시간을 설정하고 일정 기간 활동이 없으면 세션은 자동으로 만료되지만, 사용자의 활동이 있을 때마다 세션의 만료 시간을 갱신

절차

  1. 라이브러리 추가

// redis 연결을 위한 라이브러리
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// 세션 데이터를 redis로 연결하는 라이브러리
implementation 'org.springframework.session:spring-session-data-redis'

  1. application.yml에 Redis 설정 추가
    redis:
    port: ${REDIS_PORT}
    host: ${REDIS_HOST}

Github에 올라갈 것이므로 환경 변수로 셋팅

  1. LettuceConnectionFactory로 Spring과 Redis 연결
@Configuration
@EnableRedisHttpSession // 레디스를 세션 저장소로 사용할 수 있게 해줌
public class RedisConfig {

    @Value("${REDIS_HOST}")
    private String host;

    @Value("${REDIS_PORT}")
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

}

@EnableRedisHttpSession 을 통해 세션 데이터를 Redis를 사용하여 관리하도록 하는 기능을 활성화

  1. CustomFilter 생성 및 Security 구성
public class UserAuthenticationFilter extends OncePerRequestFilter{

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        User user = (User) request.getSession().getAttribute("user");
        if(!isNull(user)) {
            GrantedAuthority authority = new SimpleGrantedAuthority("USER"); // 사용자 권한
            Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, Collections.singleton(authority)); // 현재 사용자의 인증 정보
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request,response);
    }

}
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private static final String[] PERMIT_URL_ARRAY = {
           "/swagger-resources/**","/v3/api-docs/**","/swagger-ui/**",
            "/api/login","/api/signup"
    };
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(authorize -> authorize
                                .requestMatchers(PERMIT_URL_ARRAY).permitAll()
                                .anyRequest().authenticated())
                .csrf(AbstractHttpConfigurer::disable)
                .exceptionHandling(exceptionHandling -> exceptionHandling
                        .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
                .addFilterBefore(new UserAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .build();
    }

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

로그인 로직

 @Transactional(readOnly = true)
    public String login(Login login, HttpSession session) {
        User user = userRepository.findByEmail(login.getEmail());
        if(user == null || !user.isPasswordMatch(passwordEncoder,login.getPassword()))
            throw new UserException(UserErrorCode.FAIL_TO_LOGIN);
        session.setAttribute("user",user);
        return session.getId();
    }

session.setAttribute("user", user) 를 통해서

Redis에는
-> key 로 세션Id spring:session:sessions:{세션ID} 이런형식
-> value 로 "user"(user정보)
으로 저장 됨

테스트를 해보면

로그인한 세션Id가 Redis에도 저장 된 것을 볼 수 있다.

그리고 인가에 대한 부분도
로그인을 하고 로그아웃을 한 경우(인가됨)

로그인을 하지 않고 로그아웃을 한다면(인가 되지 않음)

이것만 셋팅해도 시간이 꽤 걸렸기에 다음에는 Redis 비밀번호 설정 및 세션 시간도 늘리면 될 것 같당

profile
오늘의 노력이 내일의 성장으로 이어지고 있음을

0개의 댓글