[Security] Jwt Authentication ๊ตฌํ by Overriding Spring Security ์ธ์ฆ ์ํคํ ์ณ
by rlaehddnd0422์ด๋ฒ ํฌ์คํ ์์๋ Spring Security์ Jwt ์ ์ ์ฉํ ์ธ์ฆ(Authentication)๋ฐฉ์์ ๋ํด ์์๋ณด๊ณ ,
๋ค์ ํฌ์คํ ์์๋ ์ธ์ฆ์ ๊ธฐ๋ฐํ ์ธ๊ฐ(๊ถํ๊ฒ์ฌ,Authorization)๋ฐฉ์์ ๋ํด ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
๐ฉSecurity Filter
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // ์ธ์
์ฌ์ฉ X, Stateless
.and()
.formLogin().disable() // ํผ ๋ก๊ทธ์ธ ์ฌ์ฉ X
.httpBasic().disable() // http ๊ธฐ๋ฐ ์ธ์ฆ๋ฐฉ์ (ID, PW๋ก ๊ฒ์ฆ) ์ฌ์ฉ X
- csrf().disable() : Cross Site Request Forgery ๋ฐฉ์ง
- sessionCreationPolicy : JWT ์ธ์ฆ๋ฐฉ์์ ์ฌ์ฉํ ์์ ์ด๊ณ , ์ธ์ ์ ์ฌ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์, ์ธ์ ์ ์ ์ฉํ์ง ์๊ธฐ ์ํด SessionCreationPolicy๋ฅผ Stateless๋ก ๋ณ๊ฒฝ.
- formLogin().disable() : HTTP Form Based Authentication์ ์ฌ์ฉํ์ง ์๊ณ ๋ก๊ทธ์ธ ์ Json์ผ๋ก request ์์ ์ด๋ฏ๋ก ํผ ๋ก๊ทธ์ธ ์ต์ ์ ๊บผ์ค๋๋ค.
- httpBasic().disable() : HTTP Basic Authentication ๋ฐฉ์์ ์ฌ์ฉํ์ง ์์ ์์ ์ด๋ฏ๋ก ์ต์ ์ ๊บผ์ค๋๋ค.
HTTP Basic Authentication ์ด๋?
ํน์ ๋ฆฌ์์ค์ ์ ๊ทผ ์์ฒญํ ๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์ฌ์ฉ์์ username, password ๋ง ํ์ธํ์ฌ ์ ํํ๋ ์ธ์ฆ ๋ฐฉ๋ฒ์ ๋๋ค.
HTTP Basic Authentication์ ์ด๋ป๊ฒ ๋์ํ ๊น ?
1. ์ธ์ฆ๋์ง ์์ ์ ์ ๊ฐ ์ ํ๋ ์์ฒญ์ ์๋ฒ์ ์์ฒญ
2. ์๋ฒ๋ ํด๋ผ์ด์ธํธ์๊ฒ username, password ์์ฒญ
3. ํด๋ผ์ด์ธํธ๋ ๋ค์ username, password๋ฅผ ํค๋์ ๋ด์ ์์ฒญ
4. ์๋ฒ์์ ์ผ์นํ๋ฉด 200 ์คํจํ๋ฉด 401 ์๋ฌ ๋ฆฌํด
์ฟ ํค์ ์ธ์ ์ฌ์ฉํ์ง ์๊ณ ๋ณด์์ ์ทจ์ฝํ๋จ ์ ์ด ํน์ง์ด๊ณ ,
์ธ๋ถ์์๋ ์์ฒญ ํค๋๋ฅผ ๋ณผ ์ ์์ผ๋ฏ๋ก HTTPs์ ์ฌ์ฉํ๋ ๊ฒ์ด ํ์์ ์ ๋๋ค.
์ด์ "/login" ์์ฒญ์ ๋ํด Spring Security๊ฐ ๊ฐ๋ก์ฑ์ ์ฒ๋ฆฌํ ์ ์๋ ํํฐ๋ฅผ ๋ง๋ค์ด๋ด ์๋ค.
๐ฉJwt-Authentication-Filter ์์ฑ
์ ์ฒด์ ์ธ ๋์๋ฐฉ์์ ๋น์ฐํ Spring Security์ ์ธ์ฆ ์ํคํ ์ณ์ ๋์ผํ Flow๋ฅผ ๊ฐ์ต๋๋ค.
๋ค๋ง ํํฐ๋ฅผ Jwt ์ธ์ฆ๋ฐฉ์์ผ๋ก ์ปค์คํ ํด์ฃผ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
์ฐ์ ์์์ผ ํ ์ ์, Spring Security์๋ UsernamepasswordAuthenticationFilter ๋ผ๋ ํํฐ๊ฐ /login ์์ฒญ์ ๋ํด์ ๊ฐ๋ก์ฑ์ ์ฒ๋ฆฌํด์ค๋ค๋ ๊ฒ์ ๋๋ค.
์ฐ๋ฆฌ๋ ์ด UsernamePasswordAuthenticationFilter ํํฐ๋ฅผ ์์๋ฐ์ ์ค๋ฒ๋ผ์ด๋ฉ ํ Jwt ๋ฐฉ์์ผ๋ก ๋ฆฌํํ ๋งํ์ฌ SecurityConfig์ ๋ฑ๋กํด์ฃผ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
// /login ์์ฒญ์ ํ๋ฉด ๋ก๊ทธ์ธ ์๋๋ฅผ ์ํด์ ์คํ๋๋ ํจ์
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
// Attempt attemptAuthentication ํจ์์์ ๋ก๊ทธ์ธ Authentication ๊ฐ์ฒด๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ๋ฆฌํด๋ฐ์์ ๋ ์คํ๋๋ ํจ์
// ์ฆ, ์ธ์ฆ ์ฑ๊ณต์ ์คํ
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
- UsernamePasswordAuthenticationFilter์๋ ์คํ๋๋ ์์ ์ ๋ฐ๋ฅธ ๋ฉ์๋๊ฐ ๋ง์ด ์์ง๋ง, ๋ก๊ทธ์ธ ๊ณผ์ ์์๋ ๋ ๊ฐ์ ๋ฉ์๋๋ง ์ฌ์ฉํฉ๋๋ค.
- 1. attemptAuthentication ๋ฉ์๋ : /login ์์ฒญ์ด ์ค๋ฉด ๋ก๊ทธ์ธ ์๋๋ฅผ ์ํด์ ์คํ๋๋ ๋ฉ์๋์
๋๋ค.
- 1. UsernamePasswordAuthenticationToken์ ์์ฑํฉ๋๋ค.
- 2. AuthenticationManager์ ์ ๊ทผํ์ฌ ์์ฑํ ํ ํฐ์ผ๋ก ์ธ์ฆ๊ณผ์ ์ ๊ฑฐ์ณ Authenticate ๊ฐ์ฒด ๋ด๋ถ์ PrincipalDetails๋ฅผ ๋ด์ ๋ฆฌํดํฉ๋๋ค.
- 2. successfulAuthentication ๋ฉ์๋ : attemptAuthentication ๋ฉ์๋์์ Authentication ๊ฐ์ฒด๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ๋ฆฌํด๋ฐ์์๋ ์คํ๋๋ ํจ์๋ก ์ฝ๊ฒ ๋งํด ์ธ์ฆ๊ณผ์ ์ ์ฑ๊ณต์ ์ผ๋ก ํต๊ณผํ์ ๋ ์คํ๋๋ ๋ฉ์๋์
๋๋ค.
- ์ด ๋ฉ์๋๋ฅผ ํตํด์ ์ ๊ทธ๋ฆผ์ ๊ณผ์ ๊ณผ ๊ฐ์ด ๊ตฌํํฉ์๋ค.
- ์ด ๋ฉ์๋๋ฅผ ํตํด PrincipalDetails๋ฅผ ํ ๋๋ก Jwt๋ฅผ ์์ฑํ์ฌ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฆฌํดํด์ฃผ๋๋ก ๋ณ๊ฒฝํฉ์๋ค.
์ฐ์ login ์์ฒญ์ด ์ค๋ฉด ๋ก๊ทธ์ธ์ ์๋ํ๊ธฐ ์ํด ์คํ๋๋ ํจ์์ธ attemptAuthentication ๋ฉ์๋๋ฅผ ๋ฆฌํํ ๋ง ํด๋ด ์๋ค.
๐ฉUsernamePasswordAuthenticationFilter ๊ตฌํ - 1. attemptAuthentication ๋ฉ์๋
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 1. Json(Username, Password) -> loginRequestDto ๋ณํ
ObjectMapper om = new ObjectMapper();
LoginRequestDto loginRequestDto = null;
try {
loginRequestDto = om.readValue(request.getInputStream(), LoginRequestDto.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
1. Username / Password Parsing ( Json To LoginRequestDto )
- ์ฐ์ , /login ์์ฒญ์ด username๊ณผ password์ ํจ๊ป ๋ค์ด์์ ๋ ํํฐ์์๋ ์์ฒญ๋ฐ์ username๊ณผ password์ ๋ํด ํ์ฑ์์ ์ ํด์ฃผ์ด์ผํฉ๋๋ค.
- RequestBody ํ์์ผ๋ก ์์ฒญํ๊ธฐ ๋๋ฌธ์ ObjectMapper๋ฅผ ์ฌ์ฉํด request์ json InputStream์ ์ฝ์ด LoginRequestDto ํด๋์ค๋ก ํ์ฑ์์ ์ ํด์ฃผ์์ต๋๋ค.
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 1. Json(Username, Password) -> loginRequestDto
...
// 2. loginRequestDto -> UsernamePasswordAuthentication Token ์์ฑ
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginRequestDto.getUsername(),loginRequestDto.getPassword());
2. UsernamePasswordAuthenticationToken ์์ฑ
- LoginRequestDto๋ก ํ์ฑ๋ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก UsernamePasswordAuthenticationToken์ ์์ฑํฉ๋๋ค
- ์ธ์ฝ๋ฉ ๋ฐฉ์์ ๋ฐ๋ก ์ง์ ํ์ง ์์ผ๋ฉด Bcrypt ๋ฐฉ์์ผ๋ก ์ธ์ฝ๋ฉํ์ฌ ํ ํฐ์ ๋ง๋ญ๋๋ค.
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 1. Json(Username, Password) -> loginRequestDto
...
// 2. loginRequestDto -> UsernamePasswordAuthentication Token ์์ฑ
...
// 3. 2์์ username, password๋ฅผ ๊ธฐ๋ฐ์ผ๋ก BcryptEncoding์ผ๋ก ์์ฑํ ํ ํฐ์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด ๊ฒ์ฆ
Authentication authentication = authenticationManager.authenticate(authenticationToken);
3. AuthenticationManager์์ ์์์ ์์ฑํ UsernamePasswordAuthentication Token์ผ๋ก AuthenticationProvider์๊ฒ ๊ฒ์ฆ์ ์์ฒญํฉ๋๋ค.
AuthenticationManager์์ authenticate() ๋ฉ์๋ ํธ์ถ ์ ์ฌ์ค ๋ด๋ถ์ ์ผ๋ก AuthenticationProvicder๊ฐ ์๋ ์์ ๋ค์ ์ํํฉ๋๋ค.
authenticate() ๋ฉ์๋
0. ๋ด๋ถ์ ์ผ๋ก AuthenticateProvider ํธ์ถ
1. AuthenticateProvider๊ฐ UserDetailsService์ loadUserByUsername(username) ๋ฅผ ํธ์ถ
2. loadUserByUsername(username) - username๊ณผ ์ผ์นํ๋ ์ฌ์ฉ์๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด๋ถ์ ์กด์ฌํ๋์ง ํ์ธ
3. loadUserByUsername(username) - Bcrpyt๋ก ์ธ์ฝ๋ฉ๋ password Token์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์กด์ฌํ๋์ง ํ์ธ
4. ์ฒด์ธ์ ๊ฑธ์ณ Authentication ๊ฐ์ฒด๋ด๋ถ์ UserDetails๋ฅผ ๋ด์์ ๋ฆฌํดํฉ๋๋ค.
5. ์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด Authentication ๊ฐ์ฒด๊ฐ Session ์์ญ์ ์ ์ฅ๋ฉ๋๋ค!
๐ฉ UsernamePasswordAuthenticationFilter ๊ตฌํ - 2. successfulAuthentication ๋ฉ์๋
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException
UsernamePasswordAuthenticationFilter์ attemptAuthentication ํจ์์์ ์ฑ๊ณต์ ์ผ๋ก Authentication ๊ฐ์ฒด๋ฅผ ๋ฆฌํด๋ฐ์ผ๋ฉด ArgumentResolver๋ฅผ ํตํด successfulAuthentication ๋ฉ์๋์ Argument์ธ authResult์ ์๋์ผ๋ก ๋์ ๋ฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ์ฐ๋ฆฌ๊ฐ ์ฌ๊ธฐ์ ๊ตฌํํด์ผ ํ ๋ถ๋ถ์ ๋ฐ๋ก Authentication ๋ด๋ถ์ UserDetails์ ์ ๋ณด๋ฅผ ํ ๋๋ก JWT ํ ํฐ์ ๋ง๋ค์ด ํด๋ผ์ด์ธํธ์๊ฒ ๋ฆฌํดํ๋ ๊ฒ์ ๋๋ค.
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();
String jwtToken = JWT.create()
.withSubject(principalDetails.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
.withClaim("id", principalDetails.getUser().getId())
.withClaim("username", principalDetails.getUser().getUsername())
.sign(Algorithm.HMAC512(JwtProperties.SECRET));
response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX+jwtToken);
}
- Jwt ๋ฅผ gradle์ dependencies๋ก ์ฃผ์
ํด์ฃผ์๊ธฐ ๋๋ฌธ์, Builder๋ก Jwt ํ ํฐ์ ์์ฑํ ์ ์์ต๋๋ค.
- withSubject() : ํ ํฐ์ ์ด๋ฆ์ ์ง์ ํฉ๋๋ค.
- withExpiresAt() : ํ ํฐ์ ์ ํจ๊ธฐ๊ฐ์ ์ง์ ํฉ๋๋ค.
- withClaim() : ํ ํฐ์ ๋ด๊ธธ ์ ๋ณด๋ฅผ ์ง์ ํฉ๋๋ค.
- sign() : ํ ํฐ ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ ์ง์ ํฉ๋๋ค. / ์ฌ๊ธฐ์ ์ง์ ํ๋ SECRET์ ์๋ฒ๋ง ์๊ณ ์์ด์ผ ํ๋ ์ ๋ณด์ด๋ฏ๋ก ๋ณด์์ฑ์๊ฒ ์ค์ ํฉ์๋ค.
- response์ ํค๋์ ์์ฑํ Jwt๋ฅผ ๋ด์ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฆฌํดํฉ๋๋ค!
๋ชจ๋ ์ค๋น๊ฐ ๋๋ฌ์ต๋๋ค. ์ด์ ๋ง์ง๋ง์ผ๋ก ์ปค์คํ ํ ํํฐ๋ฅผ ๋ฎ์ด์์ ์ค์๋ค.
๐ฉ SecurityConfig์ UsernamePasswordAuthentication Override
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // ์ธ์
์ฌ์ฉ X, Stateless
.and()
.formLogin().disable() // ํผ ๋ก๊ทธ์ธ ์ฌ์ฉ X
.httpBasic().disable() // ๊ธฐ๋ณธ์ธ์ฆ๋ฐฉ์ ID, PW ์ฌ์ฉ X
.apply(new MyCustomDsl())
.and()
.authorizeRequests()
.antMatchers("/api/v1/user/**")
.access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/manager/**")
.access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.antMatchers("/api/v1/admin/**")
.access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll();
return http.build();
}
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
@Override
public void configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
http
.addFilter(corsFilter)
.addFilter(new JwtAuthenticationFilter(authenticationManager));// ์ธ์ฆ
}
}
๐ฉ Postman Test
ํ์ ๊ฐ์
- json์ผ๋ก ๋ค์ด์จ password๋ฅผ Bcrypt๋ฐฉ์์ผ๋ก ์ธ์ฝ๋ฉํ์ฌ DB์ ์ ์ฅํด์ค์๋ค.
๋ก๊ทธ์ธ
- ์๋ต ํค๋์ Jwt๊ฐ ๋ด๊ฒจ์๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
- ์ ํ๋ ๋ฆฌ์์ค์ ์ ๊ทผํ ๋ ์ด Authorization ํค๋์ jwtํ ํฐ ์ ๋ณด๋ฅผ ์๋ฒ์ ํจ๊ป ๋ณด๋ด์, ์๋ฒ์์ ํ ํฐ ๊ฒ์ฆ์ ํ๋๋ก ํ์ฌ์ผ ํ๋๋ฐ, ์ด ์ธ๊ฐ(Authorization)๊ณผ์ ์ ๋ค์ ํฌ์คํ ์์ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
<์ ๋ฆฌ>
- ์ด๋ ๊ฒ Spring Security ์ธ์ฆ ์ํคํ ์ณ๋ฅผ ๋ฆฌํํ ๋งํ์ฌ /login ์์ฒญ์ ์ฑ๊ณต์ ์ผ๋ก ์ธ์ฆํ ํด๋ผ์ด์ธํธ์ ์ธ์ ์์ญ์ authentication ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๊ณ , Jwt ํ ํฐ์ Response Header์ ๋ด์ ๋ฆฌํดํด๋ณด์์ต๋๋ค.
- ์ด์ ํด๋ผ์ด์ธํธ๋ ๋ฆฌํด๋ฐ์ Jwtํ ํฐ์ ์์ฒญํค๋์ ํจ๊ป ๋ณด๋ด ์ ํ๋ ๋ฆฌ์์ค์ ์ ๊ทผํ ์ ์์ต๋๋ค.
- ๋ค์ ํฌ์คํ ์์๋ ์๋ฒ์์ ์ ํ๋ ๋ฆฌ์์ค ์์ฒญ์ ๋ํด Jwt ํ ํฐ์ ๊ฒ์ฆํ๋ ์์ ์ธ Jwt Authorization์ ๊ตฌํํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
<์ฐธ๊ณ ์๋ฃ>
https://datamoney.tistory.com/334
https://datamoney.tistory.com/332
https://nordvpn.com/ko/blog/csrf/
https://devscb.tistory.com/123
https://www.youtube.com/watch?v=mW-8MQ-4arU
'๐ Backend > Spring Security' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Security] JWT ๊ตฌํ (1) - ํ๋ก์ ํธ ์์ฑ (0) | 2023.06.06 |
---|---|
[Security] Jwt Authorization ๊ตฌํ by Overriding Spring Security ์ธ์ฆ ์ํคํ ์ณ (0) | 2023.06.05 |
[Security] JWT(Json Web Token) ์ธ์ฆ ๋ฐฉ์ (0) | 2023.06.02 |
[Security] ์ฟ ํค vs ์ธ์ vs ํ ํฐ (0) | 2023.06.02 |
[Security] OAuth2.0 ๋ค์ด๋ฒ, ์นด์นด์ค ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ์ถ๊ฐ (0) | 2023.06.01 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
rlaehddnd0422