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)을 사용
쿠키안에 랜덤한 문자열(토큰)을 만들어 같이 저장하고 매번 인증할 때마다 바꾸면?
로그인 할 때 마다 새로운 토큰을 주어지게 되는데 , 만약 탈취 당했을 시에 희생자가 갖고 있는 토큰은 무효화 되고 해커가 지닌 쿠키만 유효하게 된다.
가장 안전한 방법 : 랜덤한 토큰 값(매번 바뀌는 랜덤 값) + 시리즈 (고정된 랜덤 값)
- 해커가 쿠키 탈취를 시도한 경우 인증이 되고 토큰 값 새로 바뀜
- 그런데 희생자도 쿠키로 로그인을 시도하는데 토큰 값이 이미 바뀌어서 매칭시 유효하지 않음
- 이런 경우, 시리즈 값으로 쿠키가 탈취당했다고 판단되어 DB에 저장된 쿠키를 삭제함
- 그렇게 되면 둘다 인증을 시도 못하고 희생자(주인)만 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 토큰 값도 변경됨
참고
'Dot Programming > Spring Clone' 카테고리의 다른 글
[스프링 웹앱 프로젝트 #22] Open Entitymanager (또는 Session) In View 필터 (0) | 2020.12.15 |
---|---|
[스프링 웹앱 프로젝트 #21] 프로필 뷰 (0) | 2020.12.11 |
[스프링 웹앱 프로젝트 #19] 로그인 / 로그아웃 테스트 (0) | 2020.12.09 |
[스프링 웹앱 프로젝트 #18] 로그인 로그아웃 (0) | 2020.12.08 |
[스프링 웹앱 프로젝트 #17] 가입 확인 이메일 재전송 (0) | 2020.12.08 |