๊ธฐ์กด ์ธ์ฆ์ JWT๋ฅผ ์ด์ฉํ ํํฐ๋ก, ์ธ๊ฐ๋ ์ธํฐ์ ํฐ๋ก ์ ๋ ธํ ์ด์ ์ ์ ์ํด ์์ ๊ตฌํํ์๋ค.
์ด๋ฒ์ ๋ฆฌํฉํฐ๋ง์ ํ๋ฉด์, ์คํ๋ง์ด ์ ๊ณตํ๋ ๋ณด์ ๊ด๋ จ ํ๋ ์์ํฌ์ธ ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํด๋ณด์๋ ๊ฒฐ์ ์ ๋ด๋ ค ๋์ ํ๊ฒ ๋์๋ค.
์ด๋ ต๋ค๊ณ ๋ ์ผํ ๋ค์์ง๋ง, ์ด๋ ๊ฒ ์ค๋ ๊ฑธ๋ฆด ์ค ๋ชฐ๋๋ค. ์ฌ๋ฌ ๋ธ๋ก๊ทธ๋ค์ ์ฐธ์กฐํ๊ณ , ์ ์ฝํ์ง๋ ์๋ ๊ณต์๋ฌธ์๋ค์ ๋ณด์๋ ๋๋ฌด์ง ์ดํด๊ฐ ๊ฐ์ง ์์๋ค.
์ผ๋จ ๋จธ๋ฆฟ์์ ๊ทธ๋ ค์ ธ์ผ ๊ฐ์ด ์กํ๋๋ฐ, ์ด๊ฑด ๋ญ ํํฐ๋ ์ฌ๋ฌ ๊ฐ์ด๋ฉฐ ๊ตฌํ์ฒด๋ ์ ์ด๋ ๊ฒ ๋ง์์ง ๊ทธ์ ๋ง๋ ์ฑ ์๊ณผ ์ญํ ์ด ๋์ ํ ๊ฐ์ด ์ค์ง ์๋๋ค.
์ ๋ถ ์ถ์ํ ๋์ด ์์ด ์ปค์คํ ํด์ ์ฌ์ฉํ๊ธฐ๋ ํธํ๊ฒ ๋์๋ค๋๋ฐ, ์ ๋ฐ์ ์ผ๋ก ๋ชจ๋ ๋ด์ฉ์ ์ดํดํ๊ธฐ์ ์ฝ์ง ์๊ธฐ๋ ํ๊ณ ๋ฐ๋ก ๊ณต๋ถ๊ฐ ํ์ํ ํ๋ ์์ํฌ๋ผ๊ณ ์๊ฐํ๋ค.
๊ฑฐ์ง 1์ฃผ ๋ฐ์ด ๋์ด์์ผ ๋ด๊ฐ ๊ตฌํํ๊ณ ์ ํ๋ ์ธ์ฆ ๋ฐฉ์์ ์ ์ฉํ ๋งํ ๊ฐ๋ ์ ์ดํด๊ฐ ๊ฐ ๋ก๊ทธ์ธ, ๋ก๊ทธ์์, ๊ถํ ์ฒ๋ฆฌ๊น์ง๋ ๊ตฌํํ๊ฒ ๋์๋ค. ๋ง๊ฒ ๊ตฌํํ ๊ฒ์ธ์ง, ํด๋ฆฐ ํ ์ฝ๋๊ฐ ์๋ ํ ์ง๋ง ๊ทธ ํ๋ํ ๊ณผ์ ์ ์ ๋ฆฌํด๋ณด๊ฒ ๋ค.
์ฐธ๊ณ ๋ก ์ ๋ Access Token ํ๋๋ง ๋ฐ๊ธํด ์งํํ๋ต๋๋ค~
์คํ๋ง ์ํ๋ฆฌํฐ๋
- ์คํ๋ง ๊ธฐ๋ฐ์ Authentication(์ธ์ฆ), Authorization(๊ถํ)์ ๋ด๋นํ๋ ํ์ ํ๋ ์์ํฌ
- ํํฐ ๊ธฐ๋ฐ์ ๋์
- ์คํ๋ง ์ํ๋ฆฌํฐ์ ํํฐ ์ฒด์ธ ์์์ด๋ค. ์ฒ์์ ๋ณ๋ก ๊ณต๋ค์ฌ ๋ณด์ง ์์์ง๋ง, ์ด๋์ ๋ ๋ณธ์ธ์ด ๊ตฌํํ๊ณ ์ถ์ ๋ฐฉ์์ ํํฐ๋ฅผ ์์๋ณด๊ณ ์์๋ฅผ ๋ด ๋๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค..
- ๋ฑ๋กํ ์์ฒญ์ด ์ค๋ฉด ์ด๋ฐ์์ ํํฐ ์ฒด์ด๋์ ๊ฑฐ์ณ ๋์ค ํจ์ณ ์๋ธ๋ฆฟ์ ๋์ฐฉํ๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ ์ค์
์ด ๊ธ์ ์์ ํ Jwt Token์ ์ด์ฉํ ์ธ์ฆ, ์ธ๊ฐ์ Rest Api๋ฅผ ์์ฃผ๋ก ๊ตฌํํ๊ธฐ ๋๋ฌธ์ ์ด ๋ฐฉ์์ ์์ฃผ๋ก ์ค์ ํด๋ณธ๋ค.
1. cofigure(WebSecurity web)
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/db/**",
"/mapper/**",
"/static/**",
"/templates/**"
); // ํ
์คํธ ์ path ์์ ํ ๊ฒ.
}
=> ์ํ๋ฆฌํฐ ํํฐ ์ฒด์ธ์ ๋ฌด์ํ๋ url์ ์ค์ ํ ์ ์๋ค. ์ฌ๋งํ๋ฉด /resources ํจ์ค๋ฅผ ์ค์
2. configure(HttpSecurity http)
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(corsConfig.corsFilter())
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic().disable()
.authorizeRequests()
.antMatchers("/login, /logout").permitAll()
.antMatchers("/api/v1/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/api/v2/**").hasRole("ADMIN")// ํ
์คํธ ์ path ๊ด๋ฆฌํ ๊ฒ
.and()
.formLogin()
.loginProcessingUrl("/login")
.and()
.logout()
.logoutUrl("/logout")
.addLogoutHandler((request, response, authentication) -> {
String token =
jwtProvider.getResolvedToken(request, JwtProvider.TOKEN_HEADER_KEY);
redisService.deleteData(token);
})
.logoutSuccessHandler((request, response, authentication) -> response.getWriter().write("Logout succeed"))
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtProvider, redisService))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtProvider, jwtValidator, jwtAuthenticator, redisService))
/* .exceptionHandling()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.accessDeniedHandler(new CustomAccessDeniedHandler())*/;
}
=> ์... ์ด๊ฒ ๋๋์ฒด ๋ฌด์จ ๋ฉ์๋ ์ฒด์ธ์ด๋ค๋..
์ฒ์ ๋ดค์ ๋ ๋ญ ์ด๋ฐ๊ฒ ๋ค ์์ด๋ผ๋ ์๊ฐ์ ํ๋ค. ๊ฑฑ์ ๋ง์,, ๊ณ์ ์ฐพ์๋ณด๋ค ๋ณด๋ฉด ์ด ๋ฉ์๋๊ฐ ์ด๊ฑฐ๊ตฌ๋ ํ๊ณ ์ค์ ํ๊ฒ ๋๋ค.. ใ ใ
๊ธฐ๋ณธ์ ์ผ๋ก rest api ์ด๋ฏ๋ก, crsf(), httpBasic() ์ disable() ์ค์ ์ ํ๋ค.
๋ jwt ์ธ์ฆ ๊ธฐ๋ฐ์ด๋ฏ๋ก ์ธ์ ์ธ์ฆ์ ๊บผ๋์๋ค.
๊ทธ๋ฆฌ๊ณ , authorizeRequests() ๋ฉ์๋๋ก ๊ถํ์ ํ์ฉํ๋ ํจ์ค๋ค์ ๊ด๋ฆฌํ ์ ์๋ค.
์ hasRole() ์ด๋ฐ ๋ฉ์๋๋ค์ access()๋ฅผ ๋์ ์ด์ฉํด ํํ์์ ํ๋ผ๋ฏธํฐ๋ก๋ ๋ฐ๋๋ฐ, ๊ทธ๋ด ๊ฒฝ์ฐ์ ๊ฐ๋ ์ฑ์ด ์ ์ข์ ๊ทธ๋ฅ ์ด ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ค.
๋ก๊ทธ์ธ๊ณผ, ๋ก๊ทธ์์์ ํด๋น๋๋ ์ค์ ์ ํ ์๋ ์๋ค. ๋๋ ์ธ์ฆ ๊ด๋ จ ์์ ์ ์ ๋ถ ํํฐ์์ ์ฒ๋ฆฌํ ๊ฒ์ด๋ฏ๋ก, ์์ฒญ url์ ์ค์ ํ๊ณ ํํฐ์์ ์ธ์ฆ ์ฒ๋ฆฌ๋ฅผ ํ๊ธฐ ์ํด ์ ์ ํ login()์ ์ ์ฒด์ด๋ ํ๋ค.
๋ก๊ทธ์์ ๊ด๋ จ๋ ๋ง์ฐฌ๊ฐ์ง๋ค. logout()์ ์ด์ฉํด ํ์ํ ๋ฉ์๋๋ค์ ์ ์ฒด์ด๋ ํด ๋๋ค์์ผ๋ก ๊ฐ๋จํ ์ก์ ์ ์ปค์คํ ํ ์ ์๋ค.
๋ค์ ํ๋ฒ ๋งํ์ง๋ง, ์ํ๋ฆฌํฐ์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ๋ํดํธ ๋์์ด ์๋๋ฐ, ๋ณธ์ธ์ด ์ํ๋ ๋ฐฉ์์ ์ธ์ฆ์ด ์๋๋ผ๋ฉด ๋ฐ๋์ ์์๋ฐ์ ์ปค์คํ ํด์ฃผ๊ณ , Bean์ผ๋ก ๋ฑ๋กํ๊ฑฐ๋ ํด์ผ ํ๋ค.
@Bean
public AuthenticationProvider authenticationProvider() {
return new CustomAuthenticationProvider(userRepository, principalDetailService);
}
=> ์ ๋์ฒ๋ผ~
๋๋ง์ ๋น๋ฐ๋ฒํธ ์ํธํ ๋ฐฉ์์ด ์์ด ๊ทธ๊ฑธ ๊ตฌํํ๊ธฐ ์ํด ์ปค์คํ ํ๋ค.
๋๋ต์ ์ธ ํ๋ก์ฐ
=> ์ฒ์ ๋ณด๋ฉด ๊ฐ์ด ์ ์ ์ฌ ์๋ ์๋ค. ๊ณ์ ๋ณด๊ณ ์ฝ๋์ ์ ์ฉ์ํค๊ณ ์คํํด๋ณด๋ฉด ๋จธ๋ฆฌ์ ์๋ฆฌ ์กํ๋ค.
ํ๋ก์ฐ๋ฅผ ๊ทธ๋ ค๋ณด๋ฉด์ ๋ณต๊ธฐํ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ ๊ฐ๋ค.
- ์์ฝํ์๋ฉด, ์์ฒญ์ด ๋ค์ด์ค๋ฉด ์ธ์ฆ ํํฐ์์ ์ฌ์ฉ์ ์ธ์ฆ์ฉ ํ ํฐ์ ๋ฐ๊ธํ๋ค. (UsernamePasswordAuthenticationToken)
- AuthenticatioManager๋ผ๋ ์ธํฐํ์ด์ค์์ ๊ตฌํ์ฒด์ธ ProvideManager๊ฐ ๋ํดํธ ์ธ์ฆ ๋ฐฉ์์ ํตํด ํ ํฐ์ ์ด์ฉํ์ฌ ์ธ์ฆํ๋ค. ๊ทธ ๊ณผ์ ์์ ํ์ํ ๊ฒ์ด UserDetails ๊ฐ์ฒด์ธ๋ฐ ์ด ๊ฒ๋ ์ธํฐํ์ด์ค๋ค! (๋ฌด์จ ๋ง์ด๋๋ฉด ๊ตฌํํ๋ผ๋ ๋ป์ด๊ฒ ์ฃ ? ใ ใ )
- UserDetailService ์ธํฐํ์ด์ค์๋ loadByUsername(String username)์ด๋ผ๋ ์ถ์ ๋ฉ์๋๊ฐ ์๋๋ฐ ๋น์ฐํ ์ค๋ฒ๋ผ์ด๋ ํด์ DB์์ ์ ์ ๋ฅผ ๊ฐ์ ธ์ UserDetials๋ฅผ ๊ณ์นํ ๊ฐ์ฒด ๋์๋ค๊ฐ ๋งคํํด ๋ฆฌํด ์ํด ๋๋ค~ (๋๋ MapStruct ์ด์ฉ)
- ๊ทธ ํ ๊ฐ์์ ๋ฐฉ์์ ๋ง๋ ์ธ์ฆ๊ณผ ์ธ๊ฐ ํํฐ๋ฅผ ๊ณจ๋ผ ๊ตฌํํ๊ณ ์ธ์ฆ์ด ์๋ฃ๋ ๊ฐ์ฒด๋ฅผ SecurityContextHolder๋ผ๋ ๊ณณ์๋ค๊ฐ ์ ์ฅ์ํค๋ฉด๋๋ค. ๊ทธ๋ผ ํ์ํ ๊ณณ์์ ์ ์ญ์ ์ผ๋ก ์ ์ฅํ authentication ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ ์ ์๋ค. ์์ธํ ๋ด์ฉ์ ์ด๊ณณ์ ์ฐธ์กฐํ๊ธธ ๋ฐ๋๋ค. (์์ฃผ ์ค๋ช ์ด ์ข์) https://catsbi.oopy.io/f9 b0 d83 c-4775-47da-9c81-2261851 fe0d0
์คํ๋ง ์ํ๋ฆฌํฐ ์ฃผ์ ์ํคํ ์ฒ ์ดํด
๋ชฉ์ฐจ
catsbi.oopy.io
์ฝ๋๋ฅผ ๋ด ์๋ค
JwtAuthenticationFilter.java - ๋ก๊ทธ์ธ
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JwtProvider jwtProvider;
private final RedisService redisService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtProvider jwtProvider, RedisService redisService) {
super(authenticationManager);
this.jwtProvider = jwtProvider;
this.redisService = redisService;
setFilterProcessesUrl("/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
LoginRequestDto loginRequestDto = new ObjectMapper()
.readValue(request.getInputStream(), LoginRequestDto.class);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginRequestDto.getLoginId(), loginRequestDto.getPassword(), new ArrayList<>()
);
return getAuthenticationManager().authenticate(authenticationToken);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();
String token = jwtProvider.createToken(String.valueOf(principalDetails.getId()), principalDetails.getLoginId(), principalDetails.getRole());
redisService.setDataExpire(principalDetails.getLoginId(), token, JwtProvider.getEXPIRED_TIME());
response.addHeader(JwtProperties.TOKEN_HEADER_KEY.getKey(), JwtProperties.AUTH_TYPE.getKey() + token);
}
}
- /login ์์ฒญ์ ํํด ํํฐ๋ฅผ ๊ฑฐ์น๋ค. setFilterProcessesUrl() ์ด์ฉ
- attempAuthentication()์ Override ํด์ ์ธ์ฆ์ ์๋ํ ํ ํฐ์ ์์ฑ ํ authenticationManager๋ฅผ ํตํด ์ธ์ฆ์ ํ๋ค.
- ์ฑ๊ณตํ ๊ฒฝ์ฐ Override๋ successfulAuthentication()์ ํตํด ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ๋ฐ์์ ๊ทธ๊ฒ์ ๊ธฐ๋ฐ์ผ๋ก ํ ํฐ์ ์์ฑํด ํค๋๋ก ๋ฃ์ด์ค๋ค.
- ๋ ๊ฐ์ ๊ฒฝ์ฐ๋ ๋ก๊ทธ์์๊น์ง ๊ตฌํํ๊ธฐ ๋๋ฌธ์ redis์๋ ํ ํฐ์ ๋ฃ์ด์ฃผ์๋ค.
JwtAuthorizationFilter.java - ์ธ์ฆ&์ธ๊ฐ
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private final JwtProvider jwtProvider;
private final JwtValidator jwtValidator;
private final JwtAuthenticator jwtAuthenticator;
private final RedisService redisService;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, JwtProvider jwtProvider, JwtValidator jwtValidator, JwtAuthenticator jwtAuthenticator, RedisService redisService) {
super(authenticationManager);
this.jwtProvider = jwtProvider;
this.jwtValidator = jwtValidator;
this.jwtAuthenticator = jwtAuthenticator;
this.redisService = redisService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader(JwtProperties.TOKEN_HEADER_KEY.getKey());
if (header == null || !header.startsWith(JwtProperties.AUTH_TYPE.getKey())) {
chain.doFilter(request, response);
return;
}
String token = jwtProvider.getResolvedToken(request, JwtProperties.AUTH_TYPE.getKey());
Authentication authentication = getAuthentication(token);
if (token != null && jwtValidator.validateAccessToken(token)) {
if(!redisService.getData((String) authentication.getPrincipal()).equals(token)) {
throw new RuntimeException(ErrorMessages.TOKEN_MISMATCH.getMessage());
}
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
private Authentication getAuthentication(String token) {
PrincipalDetails principalDetails = (PrincipalDetails) jwtAuthenticator.getAuthentication(token).getPrincipal();
return new UsernamePasswordAuthenticationToken(
principalDetails.getLoginId(), principalDetails.getPassword(), principalDetails.getAuthorities()
);
}
}
- ํ ํฐ์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฌ์ ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ์์ฑํด SecurityContext์ ์ ์ฅํ๋ค.
- Redis์๋ ํ ํฐ์ ๋ฃ์๊ธฐ ๋๋ฌธ์, ์ถ๊ฐ์ ์ผ๋ก ์ผ์น ์ฌ๋ถ๋ฅผ ๊ฒ์ฌํ๋ค.
=> ์ด๋ ๊ฒ ๊ฑฐ์น๊ฒ ๋๋ฉด ์ธ์ฆ ๊ฐ์ฒด์ธ Authentication (์ค์ง์ ์ผ๋ก UsernamePasswordAuthentication์ด์ง๋ง ์๊ฐ ํ์ํด๋์ค์ด๋ฏ๋ก ๋์ ์ด์ฉํ๊ฒ์ง)๋ฅผ SecurityConfig์ ์ ์ํ๋๋ก role์ ์ฒดํฌํด ๊ถํ ๋ถ์ฌ๊ฐ ์ด๋ฃจ์ด์ ธ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
Logout - ์ต๋ช ๋ด๋ถ ํด๋์ค ์ด์ฉ
.and()
.logout()
.logoutUrl("/logout")
.addLogoutHandler((request, response, authentication) -> {
String token =
jwtProvider.getResolvedToken(request, JwtProperties.TOKEN_HEADER_KEY.getKey());
redisService.deleteData(token);
})
.logoutSuccessHandler((request, response, authentication) -> response.getWriter().write("Logout succeed"))
=> ์์์ ๋ณด์ จ๋ค์ํผ ๋๋ค์์ผ๋ก ๊ฐํธํ๋ ๋ด๋ถ ํด๋์ค์ ๋ฉ์๋๋ฅผ ์ ์ ์ ํด Redis์์ ํ ํฐ์ ์ญ์ ํ๋ ๋ฐฉ์์ผ๋ก ๋ก๊ทธ์์์ ๊ฐ๋จํ ๊ตฌํํ๋ค.
์ฐธ์กฐ : https://catsbi.oopy.io/f9b0d83c-4775-47da-9c81-2261851fe0d0
์คํ๋ง ์ํ๋ฆฌํฐ ์ฃผ์ ์ํคํ ์ฒ ์ดํด
๋ชฉ์ฐจ
catsbi.oopy.io
๋ค์์ ์์ธ์ฒ๋ฆฌ ๊ณผ์ ์ ์งํํ๊ฒ ์ต๋๋ค~
'๐ Spring Framework > Spring ๊ฐ๋ ์ ๋ฆฌ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
WebFlux๋ ๋ฌด์์ด๊ณ , ์ ๋์๊ณ , ์ธ์ ์ฐ์ด๋๊ฐ? (0) | 2022.08.31 |
---|---|
Spring Security [2] - ์์ธ ์ฒ๋ฆฌ AuthenticationEntryPoint & AccessDeniedHandler (0) | 2022.08.21 |
2022.05.17 ใ@Transactional ์ต์ ๋ฐ ์ฑ๋ฅใ (0) | 2022.05.17 |
2022.05.13 ใSession๊ณผ Token(Jwt) & ์ธ์ฆ๊ณผ ์ธ๊ฐใ (0) | 2022.05.13 |
2022.05.08 ใRestFulํ api? & ResponseEntity์ ์ฌ์ฉใ (0) | 2022.05.08 |