로그인 시, jsessionid(tomcat 컨테이너에서 세션을 유지하기 위해 발급하는 키)가 생긴다. 이것을 통해 웹브라우저와 서버가 연결된다.
이 id에 해당하는 서버 쪽에 세션이라는 메모리가 있다.
만약 이것에 해당하는 객체가 서버 쪽에 없거나, 클라이언트가 키값을 보내지 않으면 세션을 찾지 못한다. 즉 세션에 넣어논 정보는 무의미하다.
스프링부트의 기본 세션 타임은 30분이다. 만약 이 값이 많이 커지면 불필요한 메모리 낭비가 심하다.
메모리 낭비를 막으면서 로그인을 유지하고 싶으면 Remember-me라는 것을 이용해야한다.
Remember-me는 쿠키를 하나 더 사용하는 것이다. 인증을 했을 때, 쿠키에 암호화한 인증정보를 담아서 넣어놓는다. 이 값은 세션이 만료되었거나, 세션이 없을 때 사용한다. 즉, 해당 요청에 해당하는 세션을 찾지 못할 때, 같이 보내온 Remember-me 쿠키가 있으면 그 쿠키에 들어있는 인증정보로 인증을 시도한다. 인증이 성공하면, 새로운 세션 id와 쿠키가 발급이된다.
이러한 경우 문제점이 하나 있다. 로그인 시 쿠키를 탈취 당할 수 있다. 로그아웃시 세션과 쿠키를 모두 날려줘야한다.
좀 더 안전한 방법이 있다. 랜덤한 토큰값(매번 바뀜)을 사용하고, 랜덤한 시리즈(고정)라는 것을 사용한다. 토큰은 매번 인증할 때마다 새로 바뀌고, 시리즈는 인증을 해도 바뀌지 않는다.
이 경우 해커가 쿠키를 탈취하면, 사용자가 인증하려고 할 때, 유효하지 않은 토큰과 유효한 시리즈의 username으로 접속한다. 이 경우 토큰이 유효하지 않기 때문에 모든 토큰을 삭제하여 해커가 더 이상 탈취한 쿠키를 사용하지 못하도록 방지해준다. 그 후, form 기반으로 다시 로그인한다.
http.rememberMe()에 userDetailsService(DB에서 유저 정보를 가져오는 역할)의 구현체와 JdbcTokenRepositoryImpl(dataSource)타입을 값으로 준다.
@Configuration
@EnableWebSecurity // spring security 설정을 개발자가 하겠다는 뜻
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AccountService accountService; // UserDetailsService 구현체
private final DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.rememberMe()
.userDetailsService(accountService)
.tokenRepository(tokenRepository()); // username, 토큰, 시리즈를 조합한 토큰 정보를 DB에 저장(rememberMe 쿠키랑 일치하는 지 확인하기 위함)
}
// tokenRepository의 구현체
@Bean
public PersistentTokenRepository tokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
}
설정을 해주는 게 끝이 아니라, 쿠키값을 사용하기 위한 엔티티도 만들어줘야한다.
@Table(name = "persistent_logins")
@Entity
@Getter @Setter
public class PersistentLogins {
@Id
@Column(length = 64)
private String series;
@Column(nullable = false, length = 64)
private String username;
@Column(nullable = false, length = 64)
private String token;
@Column(name = "last_used", nullable = false, length = 64)
private LocalDateTime lastUsed;
}
마지막으로, 로그인 뷰에 로그인 유지 체크박스를 만들어준다.
<!-- 이 값이 true여야지 rememberme 쿠키를 만들어준다. -->
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="rememberMe" name="remember-me" checked>
<label class="form-check-label" for="rememberMe" aria-describedby="rememberMeHelp">로그인 유지</label>
</div>