๐ ์ด๋ฒ ๊ธ์์๋ ์ง๋ ์๊ฐ์ ์ด์ด ๋ก๊ทธ์ธ ์ jwt๋ฅผ ๋ฐ๊ธํ์ฌ ์๋ต ํค๋์ ์ฃ๊ณ , ํ ํฐ์ redis์ ์ ์ฅํ๋ ๊ณผ์ ์ ๊ตฌํํด ๋ณด๊ฒ ์ต๋๋ค.
๐ค ์ ์ ์คํ๋ง ์ํ๋ฆฌํฐ ๊ตฌํ์ ์๋์ ๊ฐ์ ์๋๋ฆฌ์ค๋ฅผ ๊ธฐ์ค์ผ๋ก ํฉ๋๋ค.
- ํ๋ก ํธ ์๋์ ๋ฐฑ์๋๊ฐ ๋๋์ด ์งํ๋๋ ํ๋ก์ ํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ฌ ์คํ๋ง ์ํ๋ฆฌํฐ ์ค์ ์์ ๋ก๊ทธ์ธ ํ์ด์ง์ ๋ํ ์ค์ ์ ๋ฐ๋ก ํ์ง ์์
- JWT ํ ํฐ ์ธ์ฆ ๋ฐฉ์์ ์ฌ์ฉํจ
- ํ ํฐ ๊ด๋ฆฌ์ redis๋ฅผ ์ด์ฉํจ
๐ ์ด์ ๊ธ ๋ณด๊ธฐ
์ฒซ ๋ฒ์งธ ๊ธ๋ถํฐ ์ ๋ ํ์๋ฉด ๋ณด๋ค ์ฝ๊ฒ ์ดํดํ์ค ์ ์์ต๋๋ค!
https://suzuworld.tistory.com/438 - ๋น์ ์ ์ฒซ ํ๋ก์ ํธ๋ฅผ ์ํ ์คํ๋ง ์ํ๋ฆฌํฐ ํบ์๋ณด๊ธฐ
์ด์ ๊ธ
https://suzuworld.tistory.com/446 - JwtTokenizer์ Redis ํด๋์ค ์์ฑํ๊ธฐ
๐ ๋ชฉ์ฐจ
์คํ๋ง ์ํ๋ฆฌํฐ ํบ์๋ณด๊ธฐ
SecurityConfig ๊ตฌ์ฑํ๊ธฐ
์ธ์ฆ ๋ฐฉ์ ๊ฐ๋ ๊ณผ AuthenticationFilter
AuthenticationManager, AuthenticationProvider, UserDetailsService, UserDetails
๋ก๊ทธ์ธ ํ ์คํธ ๋ฐ JWT, redis ๊ฐ๋ ์ ๋ฆฌ
JwtTokenizer์ Redis ํด๋์ค ์์ฑํ๊ธฐ
๋ก๊ทธ์ธ ์ JWT ๋ฐ๊ธ๊ณผ redis ์ ์ฅ ๊ตฌํํ๊ธฐ (ํ์ฌ ๊ธ)
๐ฃ ์ง๋ ์๊ฐ๊น์ง์ ๋ด์ฉ ์ ๋ฆฌ
- ์ฐ๋ฆฌ๋ ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ด์ฉํ์ฌ ๋ก๊ทธ์ธ์ ๊ตฌํํ์ต๋๋ค.
- ๊ตฌํํ ๋ก๊ทธ์ธ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํด๋ผ์ด์ธํธ(web, app)์์ ์ ๋ ฅ๋ฐ์ id, password๊ฐ http ์์ฒญ ๋ฐ๋์ ์ค๋ ค ์๋ฒ๋ก ๋์ด์ฌ ๋ ์๋ธ๋ฆฟ ํํฐ(AuthenticationFilter)์ ์ํด ํํฐ๋ง๋์ด ๋ก๊ทธ์ธ ์ฒ๋ฆฌ๊ฐ ์์๋ฉ๋๋ค.
- ์ดํ AuthenticationManager, AuthenticationProvider, UserDetailsService๋ฅผ ๊ฑฐ์ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด์ ์๋ id, password์ ๋น๊ต๋ฅผ ํ์ฌ ๋ก๊ทธ์ธ ์ฑ๊ณต ์ฌ๋ถ๊ฐ ๊ฒฐ์ ๋ฉ๋๋ค.
- ๊ทธ๋ฐ๋ฐ ๋ก๊ทธ์ธ ์ ์ ๋ ฅ๋ฐ์ id, password์, ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด id, password๋ฅผ ๋น๊ตํ๋ ๊ฒ์ผ๋ก ๋ก๊ทธ์ธ์ด ์ฑ๊ณตํ๋ค!, ์คํจํ๋ค! ์ด ์ธ์ ์ด๋ค ์์ ๋ ํ์ง ์์์ต๋๋ค.
- ๊ทธ๋์ ์ด๋ฒ ์๊ฐ์๋ ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ํด๋ผ์ด์ธํธ์๊ฒ ๋ณด๋ผ ์๋ต ํค๋์ access token๊ณผ refresh token์ ์ค์ด ๋ณด๋ด๊ณ , redis์ ํ ํฐ์ ๊ด๋ฆฌํ๋ ๋ถ๋ถ์ ๊ตฌํํ๋ ค๊ณ ํฉ๋๋ค.
์ง๋ ์๊ฐ๊น์ง์ ํฌ์คํธ๋งจ ํ ์คํธ ์ฑ๊ณต ์์

์๋ฒ ๋ก๊ทธ

- ์์์ ์ค๋ช ํ ๋ฐ์ ๊ฐ์ด ์ง๋ ์๊ฐ๊น์ง์ ๊ตฌํ์ ๊ทธ์ ๋ก๊ทธ๋ก ์ฑ๊ณต๊ณผ ์คํจ๋ฅผ ํ์ํ๋ ๊ฒ ์ธ์๋ ์๋ฌด๊ฒ๋ ์์์ต๋๋ค.
- ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ๋ ๋ฐ๋ก ์ด์ ๊ธ์ JwtTokeninzer ํด๋์ค์, redis ๊ด๋ จ ํด๋์ค๋ฅผ ์์ฑํ์ต๋๋ค.
๐ ์ง๋ ์๊ฐ๊น์ง์ ๋ก๊ทธ์ธ ๊ตฌํ ๋ด์ฉ ๊ฐ๋จ ๋ณต์ต
JwtAuthenticationFilter

- ์์ฒญ์ ๊ฐ๋ก์ฑ ํํฐ์์ ๋ก๊ทธ์ธ DTO๋ฅผ ์์ฑํ์ฌ AuthenticationManager์๊ฒ ๋ก๊ทธ์ธ ์์์ ์ฒ๋ฆฌํฉ๋๋ค.
SecurityConfig
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
...
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// ๋ก๊ทธ์ธ ํํฐ
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/login");
jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());
jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());
...
}
}
- ์์ JwtAuthenticationFilter๊ฐ ํด๋ผ์ด์ธํธ๋ก๋ถํฐ์ ๋ก๊ทธ์ธ ์์ฒญ์ ๊ฐ๋ก์ฑ ์ ์๋ ์ด์ ๋ SecurityConfig ํด๋์ค์์ "/login"์ด๋ผ๋ ์๋ํฌ์ธํธ๋ฅผ ๋ก๊ทธ์ธ์ฉ ์๋ํฌ์ธํธ๋ก ์ค์ ํด ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
- ์ดํ ๋ก๊ทธ์ธ ์ฑ๊ณต ์ฌ๋ถ์ ๋ฐ๋ผ ๋ด๋ถ์ ์ผ๋ก ์ฑ๊ณต ์์๋ SuccessHandler๋ก, ์คํจ ์์๋ FailureHandler๋ก ๊ฐ๋๋ก ์ค์ ํด ๋์์ต๋๋ค.
MemberAuthenticationSuccessHandler
@Slf4j
public class MemberAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
log.info("๋ก๊ทธ์ธ ์ฑ๊ณต!");
}
}
MemberAuthenticationFailureHandler
@Slf4j
public class MemberAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
log.info("๋ก๊ทธ์ธ ์คํจ!");
}
}
์๋ฒ ๋ก๊ทธ

- ์ ๋ ํธ๋ค๋ฌ์ ์ํด ๋ก๊ทธ๊ฐ ๋จ์๋ ๊ฒ์ด์ฃ .
- ์ด ๋ ๋์ ์ธ์ ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ์ด๋ ํ ๋ณํ๋ ์๋ ๊ฒ์ด ํ์ฌ ์ํ์ ๋๋ค.
โ๐ป ํด์ผ ํ ์ผ
์ด์ ๋ถํฐ๋ ๋ก๊ทธ์ธ ์ฑ๊ณต ์์๋ jwt๋ฅผ ๋ฐ๊ธํ์ฌ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌํ๊ณ , redis์ ํ ํฐ์ ์ ์ฅํ๋ ๊ฒ์ ๊ตฌํํด ๋ณด๊ฒ ์ต๋๋ค.
JwtAuthenticationFilter ์์

- ๊ธฐ์กด JwtAuthenticationFilter์ successfulAuthentication ๋ฉ์๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.

- ์ธํ
๋ฆฌ์ ์ด์์ Mac ๊ธฐ์ค cmd + n ๋ช
๋ น์ด๋ก ์ฝ๊ฒ ์ค๋ฒ๋ผ์ด๋ํ ๋ฉ์๋๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
AbstractAuthenticationProcessingFilter

- successfulAuthentication ๋ฉ์๋๋ AbstractAuthenticationProcessingFilter์์ ๊ตฌํ๋ ๋ฉ์๋๋ก ์ธ์ฆ ์ฑ๊ณต ์ ์๋์ผ๋ก ํธ์ถ๋๋๋ก ํฉ๋๋ค.
- ์ด๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํ์ฌ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค.
SecurityConfig ์์
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final JwtTokenizer jwtTokenizer; // ์ถ๊ฐ
private final RedisService redisService; // ์ถ๊ฐ
...
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// ๋ก๊ทธ์ธ ํํฐ
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager(), jwtTokenizer, redisService); // ์์
jwtAuthenticationFilter.setFilterProcessesUrl("/login");
jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());
jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());
...
}
}
- SpringConfig์ JwtTokenizer์ RedisService๋ฅผ ์์กด์ฑ ์ถ๊ฐํ๊ณ ,
- new ํค์๋๋ก ์์ฑํ JwtAuthenticationFilter ํ๋ผ๋ฏธํฐ์ jwtTokenizer์ redisService๋ฅผ ์ถ๊ฐํฉ๋๋ค.
JwtAuthenticationFilter ์์

- JwtAuthenticationFilter ํด๋์ค์ ๋ง์ฐฌ๊ฐ์ง๋ก JwtTokenizer์ RedisService ์์กด์ฑ์ ์ถ๊ฐํฉ๋๋ค.
- ๋ํ, successfulAuthentication์์ ์ธ์ฆ ์ฑ๊ณต ํ Jwt๋ฅผ ๋ฐ๊ธํ์ฌ ์๋ต ํค๋์ ์ฃ๊ณ , redis์ refresh token์ ์ ์ฅํ๋ ๋ก์ง์ ๊ตฌํํฉ๋๋ค.
- ์ฐธ๊ณ ๋ก ์ ์ฝ๋๋ log.debug๋ก ์ค์ ํ๋๋ฐ log.info๋ก ๋ณ๊ฒฝํ์๋ฉด ๋ณ๋์ yaml ํ์ผ ์ค์ ์์ด ๋ก๊ทธ๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
๐จ๐ปโ๐ฌ successfulAuthentication ๋ฉ์๋ ์์ธ ์ค๋ช
MemberDetailsService

- ์ง๋ ์๊ฐ ๊ตฌํํ MemberDetailsService ํด๋์ค์ loadUserByUsername ๋ฉ์๋๋ฅผ ๋ณด์๋ฉด ์์๊ฒ ์ง๋ง, UserDetails์ ๊ตฌํ์ฒด์ธ MemberDetails๋ฅผ ๋ฆฌํดํ๊ธฐ ๋๋ฌธ์ successfulAuthentication์ ํ๋ผ๋ฏธํฐ์ธ authResult์ MemberDetails๊ฐ ํฌํจ๋๊ฒ ๋ฉ๋๋ค.
Member ๊ฐ์ฒด๋ก ์บ์คํ
Member member = (Member) authResult.getPrincipal();
- MemberDetails๋ Member์ ์์ ํด๋์ค์์ผ๋ก ์์ ๊ฐ์ด Member๋ก ์บ์คํ
๊ฐ๋ฅํฉ๋๋ค.
- ๋ฆฌ์ค์ฝํ ์นํ ์์น(LSP)์ด ์ฌ๊ธฐ์ ๋ฑ์ฅํฉ๋๋ค.
createAccessToken & RefreshToken
String accessToken = jwtTokenizer.createAccessToken(member);
String refreshToken = jwtTokenizer.createRefreshToken(member);

- ์ดํ Member ๊ฐ์ฒด๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ํ์ฌ AccessToken๊ณผ RefreshToken์ ๋ฐ๊ธํฉ๋๋ค.
- ์์ธํ ๋ก์ง์ ์ง๋ ์๊ฐ ๋ฉ์๋๋ฅผ ์ฐธ๊ณ ํ์ฌ ๋ฐ๋ผ๊ฐ ๋ณด์ธ์.
setRefreshToken
redisService.setRefreshToken(member.getMemberId(), refreshToken, jwtTokenizer.getRefreshTokenExpirationMinutes());

- ๋ฐ๊ธํ refresh token์ redis์ ์ ์ฅํฉ๋๋ค.
ValueOperations<K, V>
default void set(K key, V value, Duration timeout)
- redis์ key๋ก๋ refresh token, value๋ก๋ member์ memberId(id)๋ฅผ ์ ์ฅํ๋ฉฐ ์์ฒด์ ์ผ๋ก timeout ์๊ฐ์ ์ค์ ํ ์ ์์ต๋๋ค.
- ์ด๋ก์จ refreshToken์ ๊ฒ์ฆ์ ์ฌ์ฉํ ๋ ์ ์ ์ refresh token(key)์ ํตํด id(value)์ ๊บผ๋ด์ด ๊ฒ์ฆํ ์ ์๋ ๊ฒ์ ๋๋ค.
- timeout์ด ์ค์ ๋์ด ์๊ธฐ ๋๋ฌธ์ ์๊ฐ์ด ์ง๋๋ฉด redis์์ ์์ฒด์ ์ผ๋ก ์ญ์ ํ์ฌ ์๊ฐ์ด ๋ง๋ฃ๋ ํ ํฐ์ ์ฌ์ฉ์ด ๋ถ๊ฐ๋ฅํ๋๋ก ํฉ๋๋ค.
redis์ refresh token๋ง ์ ์ฅํ๋ ์ด์ ?
- Access Token์ ์์ฒด์ ์ผ๋ก ๊ฒ์ฆ ๊ฐ๋ฅ โ ์๋ฒ์์ JWT์ ์๋ช ์ ํ์ธํ๋ฉด ๊ฒ์ฆ ๋ (DB ์กฐํ ๋ถํ์).
- Refresh Token์ ์๋ฒ์์ ๊ด๋ฆฌ ํ์ โ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํด ์ ์ฅํ๊ณ ๋ง๋ฃ/๋ก๊ทธ์์ ์ ์ญ์ ๊ฐ๋ฅ.
- ๋ณด์ ๊ฐํ โ Refresh Token์ ํ์ทจ๋นํ๋ฉด ์ํํ๋ฏ๋ก Redis์์ ๊ด๋ฆฌํ์ฌ ์ฆ์ ๋ฌดํจํ ๊ฐ๋ฅ.
- ํจ์จ์ฑ โ Access Token๊น์ง Redis์ ์ ์ฅํ๋ฉด ์์ฒญ๋ง๋ค ์กฐํํด์ผ ํ๋ฏ๋ก ๋ถํ์ํ ๋ถํ ๋ฐ์.
์ฆ, Access Token์ ๊ฒ์ฆ๋ง ํ๋ฉด ๋๊ณ , Refresh Token์ ๊ด๋ฆฌ๊ฐ ํ์ํ๊ธฐ ๋๋ฌธ์ Redis์ ์ ์ฅํ๋ ๊ฒ์ ๋๋ค.
์๋ต ํค๋์ ์ฃ๊ณ ์ฑ๊ณต ํธ๋ค๋ฌ ํธ์ถ
response.addHeader("Authorization", "Bearer " + accessToken);
response.addHeader("Refresh", refreshToken);
this.getSuccessHandler().onAuthenticationSuccess(request, response, authResult);
- redis๊น์ง ์ ์ฅํ๊ณ ๋๋ฉด, ์ด์ ์๋ต ํค๋์ ํ ํฐ์ ์ฃ๊ณ successHandler๋ฅผ ํธ์ถํ๋๋ก ํฉ๋๋ค.
- ์ด๋, AccessToken์ Bearer๋ฅผ ๋ถ์ด๋ ์ด์ ๋ OAuth 2.0 ๊ธฐ๋ฐ์ JWT ๋ฑ์ ์ธ์ฆ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๋ค๋ ๊ด๋ก๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
๐จ๐ปโ๐ฌ ๋ก๊ทธ์ธ ํ ์คํธ

- ํฌ์คํธ๋งจ์ผ๋ก ํ์๊ฐ์ ์ ์งํํฉ๋๋ค.

- ๊ทธ๋ค์ ๋ก๊ทธ์ธ API๋ฅผ ์ด์ฉํ์ฌ ๋ก๊ทธ์ธํด ๋ด ๋๋ค.
- ์ ์์ ์ผ๋ก ๋ก๊ทธ์ธ์ด ์งํ๋๋ฉฐ ์๋ฒ๋ก๋ถํฐ ๋ฐ์ ์๋ต ํค๋์ AccessToken(Authorization), RefreshToken(Refresh)๊ฐ ์ค๋ฆฐ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๐ redis ๋ด ์ ์ฅ๋ refreshToken ํ์ธํ๊ธฐ
// ์๋ฒ ๋ก๊ทธ(log.debug๋ก ์ฐํ ๋ก๊ทธ)
// debug ๋ก๊ทธ๋ฅผ ๋ณด๋ ค๋ฉด yaml ํ์ผ์์ ๋ณ๋์ ์์ ํ์!
[ JwtAuthenticationFilter - successfulAuthentication ] refreshToken :
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLtmY3quLjrj5kiLCJpYXQiOjE3NDE1MjY5OTMsImV4cCI6MTc0MTUzMDU5M30.wcyAmL-ZL0_wN_VQ62iXJEBJ9gleDnOp0R7sA9U3INM
// docker ๋ด redis ์ ์ํ๊ธฐ
$ docker ps
// docker ps๋ก redis ์ปจํ
์ด๋ id or name ํ์ธํ์ฌ ์ ์
$ docker exec -it <container id or name> redis-cli
// key(memberId)๋ก value(refresh token) ๊ฐ์ ธ์ค๊ธฐ
127.0.0.1:6379> get "aaa"
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLtmY3quLjrj5kiLCJpYXQiOjE3NDE1MjY5OTMsImV4cCI6MTc0MTUzMDU5M30.wcyAmL-ZL0_wN_VQ62iXJEBJ9gleDnOp0R7sA9U3INM"
- redis์ ์ ์ํ์ฌ refreshToken์ด ์ ์์ ์ผ๋ก ์ ์ฅ๋์๋์ง ํ์ธํฉ๋๋ค.
๐ฅธ ๋ค์ ์๊ฐ
- ์ด์ ๊ฑฐ์ ๋์ด ๋ณด์ ๋๋ค.
- ํด๋น AccessToken์ผ๋ก ๋ณด์ ์์ฒญ์ ์คํํ๋ ๋ก์ง, ํ ํฐ์ด ๋ง๋ฃ๋์์ ๋ ์ฌ๋ฐํํ๋ ๋ก์ง, ๋ง์ง๋ง์ผ๋ก ๋ก๊ทธ์์ ํ๋ ๋ก์ง์ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.