[Spring boot] Redis๋ก JWT ๋ง๋ฃ Logout (Redis BlackList)
์ฑ ๋ฐ์นญ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์, ๋ก๊ทธ์์ api๋ฅผ ์์ฑํ๋ค.
JWT๋ก User์ ์ ๋ณด๋ฅผ ์ ๊ทผํ๋ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์, ๋ก๊ทธ์์์ ํ๊ธฐ ์ํด์๋ AccessToken์ ์๊ฐ์ ๋ง๋ฃํด์ผํ๋ค.
์์ฑํ Token์ ๋ง๋ฃํ๊ธฐ ์ํด์๋ Redis๋ฅผ ์ฌ์ฉํด์ผํ๋ค๋ ๊ฒ์ ์๊ฒ ๋์๊ณ , ๋ธ๋ก๊ทธ์ ์์ฑํ๋ฉฐ ๋ณต์ตํด๋ณด๊ณ ์ ํ๋ค.
1. build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
build.gradle์ ์์ ๊ฐ์ dependencies ์ถ๊ฐ
2. RedisRepositoryConfig
@RequiredArgsConstructor
@Configuration
@EnableRedisRepositories
public class RedisRepositoryConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
// lettuce
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
@Value Annotation์ ์๋ spring.redis.host, port๋ application properties์์ ๋ฐ๋ก ์์ฑํด์ค๋ค.
spring.redis.host=localhost
spring.redis.port=6379
redis๋ฅผ localhost์์ ์ฌ์ฉํ๋ฉฐ, port๋ 6379๋ฅผ ์ฌ์ฉํ ์์ ์ด๋ค.
3. JWTFilter
private final TokenProvider tokenProvider;
private final RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
// 1. Request Header ์์ ํ ํฐ์ ๊บผ๋
String jwt = resolveToken(request);
// 2. validateToken ์ผ๋ก ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฌ
// ์ ์ ํ ํฐ์ด๋ฉด ํด๋น ํ ํฐ์ผ๋ก Authentication ์ ๊ฐ์ ธ์์ SecurityContext ์ ์ ์ฅ
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt, request)) {
// String ~ if ๋ฌธ๊น์ง ์๋กญ๊ฒ ์ถ๊ฐ
String isLogout = (String)redisTemplate.opsForValue().get(jwt);
if (ObjectUtils.isEmpty(isLogout)) {
Authentication authentication = tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
String isLogout ๋ถํฐ if๋ฌธ ๊น์ง ์๋กญ๊ฒ ์ถ๊ฐํ๊ณ RedisTemplate ๋ฅผ DI๋ก ์ถ๊ฐํด์ค๋ค.
4. JwtSecurityConfig
JwtFilter์ DI๋ฅผ ์ถ๊ฐํ๊ธฐ ๋๋ฌธ์ JwtSecurityConfig์๋ ์์กด์ฑ ์ถ๊ฐํ๊ณ , Constructor๋ก ๋๊ฒจ์ค๋ค.
// ์ง์ ๋ง๋ TokenProvider ์ JwtFilter ๋ฅผ SecurityConfig ์ ์ ์ฉํ ๋ ์ฌ์ฉ
@RequiredArgsConstructor
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final RedisTemplate redisTemplate;
// TokenProvider ๋ฅผ ์ฃผ์
๋ฐ์์ JwtFilter ๋ฅผ ํตํด Security ๋ก์ง์ ํํฐ๋ฅผ ๋ฑ๋ก
@Override
public void configure(HttpSecurity http) {
JwtFilter customFilter = new JwtFilter(tokenProvider, redisTemplate);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
5. SecurityConfig
private final RedisTemplate redisTemplate;
....
...
..
.
.and()
.apply(new JwtSecurityConfig(tokenProvider, redisTemplate))
6. TokenProvider
public Long getExpiration(String accessToken) {
// accessToken ๋จ์ ์ ํจ์๊ฐ
Date expiration = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody().getExpiration();
// ํ์ฌ ์๊ฐ
Long now = new Date().getTime();
return (expiration.getTime() - now);
}
getExpiration์ ์ถ๊ฐํด์ค๋ค. ํ์ฌ accessToken์ ์ ๊ทผ ๊ฐ๋ฅ ์๊ฐ์ ์์ ๊ณ blacklist๋ก ์ฌ๋ฆฐ๋ค.
7. Service
String accessToken = request.getHeader("Authorization").substring(7);
Long expiration = tokenProvider.getExpiration(accessToken);
redisTemplate.opsForValue()
.set(accessToken, "logout", expiration, TimeUnit.MILLISECONDS);
getExpiration์ ํตํด accessToken์ ์ ๊ทผ ์๊ฐ์ ๋ง๋ฃํ๊ธฐ ์ํด token์ ๊ฐ์ ๋ถ๋ฌ์ค๊ณ , redisTemplate๋ก ์ ๊ทผ์๊ฐ์ ๋ง๋ฃํ๋ค.
์ด ๋, service ๋ถ๋ถ์๋ redisTemplate DI๋ฅผ ์์ฑํด์ค์ผ ํ๋ค.
8. Redis Window ๋ค์ด๋ก๋
๋๋ ์๋ง์ ๋ธ๋ก๊ทธ ๊ธ์ ์ฐพ์๋ณด๋ฉด์ build.gradle์ dependencies์์๋ง ์ ์ฉ์ ํ๋ฉด redis๊ฐ ๋๋ ์ค ์์์ง๋ง ๋ฐ๋ก ๋ค์ด๋ก๋ ํ port ์คํ์ ํด์ผ์ง ๊ฐ๋ฅํ ๊ฒ์ด์๋ค.
https://github.com/microsoftarchive/redis/releases
์ ๊นํ๋ธ ์ฃผ์์์
Redis-x64-3.0.504.msi๋ฅผ ๋ค์ด๋ฐ์ผ๋ฉด Redis๋ฅผ ํ์ฉํ ๋ก๊ทธ์์ ๊ธฐ๋ฅ ๊ตฌํ ์์ฑ์ด๋ค.!!!
์ฑ ๋ฐ์นญ์ ์ํด redis๋ฅผ ๋ฐฐ์ ์ง๋ง, ์๋ฒฝํ๊ฒ ๋ฐฐ์ฐ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ์๊ฐ์ ๋ด์ด ๋ค์ ๊ณต๋ถ๋ฅผ ์งํํด์ผ ํ ๊ฒ ๊ฐ๋ค.
๋ํ redis๋ฅผ ์๋ฒ ๋ฐฐํฌ๋ฅผ ์งํํ์ ๋์๋ ์ฌ์ฉํ๊ธฐ ์ํด์ ๋ฏธ๋ฆฌ ๊ณต๋ถ๋ฅผ ์งํํ ์์ ์ด๋ค.
Spring boot Security๋ฅผ ์ฒ์ ์ ํด๋ดค์ง๋ง, jwt, redis ๋ฑ์ ์ด๋ฒ ๊ธฐํ๋ฅผ ํตํด ๋ง์ด ์๊ฒ๋ ๊ฒ ๊ฐ๋ค.
[์ถ์ฒ]
https://wildeveloperetrain.tistory.com/61
https://github.com/microsoftarchive/redis/releases