본문 바로가기

Dot Programming/Spring Clone

[스프링 웹앱 프로젝트 #20] 로그인 기억하기

20. 로그인 기억하기

세션이 만료 되더라도 로그인을 유지하고 싶을 때 사용하는 방법

 > 쿠키에 인증 정보를 남겨두고 세션이 만료 됐을 때에는 쿠키에 남아있는 정보로 인증한다.

해시 기반의 쿠키
 > Username
 > Password
 > 만료 기간
 > Key (애플리케이션 마다 다른 값을 줘야 한다.)
 > 치명적인 단점, 쿠키를 다른 사람이 가져가면... 그 계정은 탈취당한 것과 같다

조금 더 안전한 방법은?

 > 쿠키안에 랜덤한 문자열(토큰)을 만들어 같이 저장하고 매번 인증할 때마다 바꾼다.
 > Username, 토큰
 > 문제는, 이 방법도 취약하다. 쿠키를 탈취 당하면, 해커가 쿠키로 인증을 할 수 있고, 희생자는 쿠키로 인증하지 못한다.

조금 더 개선한 방법 
 > www.programering.com/a/MDO0MzMwATA.html
 > Username, 토큰(랜덤, 매번 바뀜), 시리즈(랜덤, 고정)
 > 쿠키를 탈취 당한 경우, 희생자는 유효하지 않은 토큰유효한 시리즈와 Username으로 접속하게 된다.
 > 이 경우, 모든 토큰을 삭제하여 해커가 더이상 탈취한 쿠키를 사용하지 못하도록 방지할 수 있다.
 > 이렇게 되면 form기반의 로그인 창으로만 로그인을 하여 인증이 가능하게 된다.

스프링 시큐리티 설정 : 해시 기반 설정
 > http.rememberMe().key("랜덤한 키 값")
          >> 안전하지 않음


스프링 시큐리티 설정 : 보다 안전한 영속화 기반 설정 
  http.rememberMe()
        .userDetailsService(accountService)
        .tokenRepository(tokenRepository());

@Bean
public PersistentTokenRepository tokenRepository() {
  // JDBC 기반의 tokenRepository 구현체
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
     jdbcTokenRepository.setDataSource(dataSource); // dataSource 주입
     return jdbcTokenRepository;
}


persistent_logins 테이블 만들기
    create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "
     + "token varchar(64) not null, last_used timestamp not null)
또는 @Entity 매핑으로 생성

 

로그인 기억하기 해시 기반의 쿠키 ?

계정을 하고 로그인을 하면 JsessionID가 생김

 

 

 해쉬된 쿠키로 remeberme 구현시 로그아웃해도 쿠키가 해당 웹사이트에 남아있게 되어 보안에 취약하다

 

 웹사이트에서 로그아웃할 때 쿠키, 세션ID날리는게 정상적인 처리

 > 혹여 쿠키를 저장하는게 싫다면 크롬인 경우 시크릿 모드( shift + cmd + n)을 사용

 

쿠키안에 랜덤한 문자열(토큰)을 만들어 같이 저장하고 매번 인증할 때마다 바꾸면?

로그인 할 때 마다 새로운 토큰을 주어지게 되는데 , 만약 탈취 당했을 시에 희생자가 갖고 있는 토큰은 무효화 되고 해커가 지닌 쿠키만 유효하게 된다.

 

가장 안전한 방법 : 랜덤한 토큰 값(매번 바뀌는 랜덤 값) + 시리즈 (고정된 랜덤 값)

  1. 해커가 쿠키 탈취를 시도한 경우 인증이 되고 토큰 값 새로 바뀜 
  2. 그런데 희생자도 쿠키로 로그인을 시도하는데 토큰 값이 이미 바뀌어서 매칭시 유효하지 않음 
  3. 이런 경우, 시리즈 값으로 쿠키가 탈취당했다고 판단되어 DB에 저장된 쿠키를 삭제함 
  4. 그렇게 되면 둘다 인증을 시도 못하고 희생자(주인)만 form로그인 창으로 로그인을 시도하여 들어갈 수 있음

 

 

백엔드 로직 작성

SecurityConfig.java

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final AccountService accountService;
    private final DataSource dataSource; // jpa이라 자동으로 등록되어 있음

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ...

        http.rememberMe()
                .userDetailsService(accountService)
                .tokenRepository(tokenRepository());  //DB에서 토큰 값 가져오기  (Username, 토큰, 시리즈)
    }

    @Bean
    public PersistentTokenRepository tokenRepository() {
        // JDBC 기반의 tokenRepository 구현체
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource); // dataSource 주입
        return jdbcTokenRepository;
    }

   ...
}

 

 

JPA에서 inmemory DB를 쓸 때는 엔티티의 정보를 보고 테이블을 알아서 만들어줌

JdbcTokenRepositoryImpl의 CREATE_TABLE_SQL에 매핑이 되는 테이블을 생성해주면 됨

public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements
		PersistentTokenRepository {
	
   /** Default SQL for creating the database table to store the tokens */
   public static final String CREATE_TABLE_SQL = "create table persistent_logins "
    + "(username varchar(64) not null, series varchar(64) primary key, "
    + "token varchar(64) not null, last_used timestamp not null)";
            
}

 

CREATE_TABLE_SQL에 매핑이 되는 PersistentLogins 테이블 생성

/**
 * 로그인 기억하기 테이블
 */

@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;

}

 

뷰 작성

마지막으로 프론트단에 remeber me 설정한다.

login.html

 <!-- rememeber me-->
<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>

 

 

결과화면

1. 로그인 유지를 체크하고 로그인시 remeber-me 토큰 생성

 

 

2. 세션이 만료(JsessionID를 삭제)되면 원래 로그아웃이 되지만 remeber-me토큰이 있어서 로그인 상태가 유지된다

세션 만료

 

3. 새로 고침하면 세션 다시 생성되고, remember-me 토큰 값도 변경

 


참고

인프런 강의 - 스프링과 JPA 기반 웹 애플리케이션 개발