๐ Spring Security in Action
์ฑ ์ ๋ชจ๋ ๋ด์ฉ์ด ์๋ ๊ธฐ์ตํ ๋ด์ฉ + ์ถ๊ฐ ์กฐ์ฌํ ๋ด์ฉ์ ์์ฃผ๋ก ์ ๋ฆฌํ ํฌ์คํ ์ ๋๋ค.
Contents
Spring Security 5์์ ์ถ๊ฐ๋ ์๋ก์ด ๊ธฐ๋ฅ
OAuth 2.0 ์ง์ ๊ฐ์
- OAuth 2.0 ํด๋ผ์ด์ธํธ ์ง์ ๊ฐ์
- OAuth 2.0 ๋ฆฌ์์ค ์๋ฒ ์ง์ ๊ฐ์
- JWT(JWT Token) ์ง์ ๊ฐ์
- JDK 9 ๋ชจ๋ ์์คํ ์ง์
WebFlux ์ง์
- Spring WebFlux๋ฅผ ์ฌ์ฉํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ Spring Security๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
PasswordEncoder ์ธํฐํ์ด์ค ๋ณ๊ฒฝ
- PasswordEncoder ์ธํฐํ์ด์ค๊ฐ ๋ณ๊ฒฝ๋์ด ๋์ฑ ์ ์ฐํ ๋น๋ฐ๋ฒํธ ์ํธํ ๊ตฌํ์ด ๊ฐ๋ฅํด์ก์ต๋๋ค.
Deprecated ํด๋์ค ๋ฐ ๋ฉ์๋ ์ ๊ฑฐ
- AuthenticationProvider ์ธํฐํ์ด์ค์ Deprecated ๋ฐ ์ ๊ฑฐ
- RememberMeAuthenticationProvider ํด๋์ค์ Deprecated ๋ฐ ์ ๊ฑฐ ๋ฑ
1์ฅ. ์ค๋๋ ์ ๋ณด์
์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ํ์น 2.0 ๋ผ์ด์ ์ค์ ๋ฐ๋ผ ๋ฆด๋ฆฌ์ค๋๋ ์คํ ์์ค ์ํํธ์จ์ด์ ๋๋ค. ์คํ๋ง ํ๋ ์์ํฌ์ ํจ๊ป ์ ํ๋ฆฌ์ผ์ด์ ๋จ์์ ๋ณด์๊ฐ๋ฐ์ ๋์์ฃผ๋ฉฐ ์คํ๋ง์ ๋ฐฉ์์ธ ์ด๋ ธํ ์ด์ , ๋น, SpEL(Spring Expression Language) ๋ฑ์ ์ด์ฉํฉ๋๋ค.
- ์ํ์น ์๋ก
- GDRR(Genernal Data Protection Regulations, ์ผ๋ฐ ๋ฐ์ดํฐ ๋ณดํธ ๊ท์ )
๋ณด์์ ๋ํ ๋ถ๋ถ์ ์์คํ
์ ์ํคํ
์ณ์ ์ธ ์ฑ๊ฒฉ, ๋ชจ๋๋ก์์ธ์ง ์๋๋ฉด ์ฌ๋ฌ ์ ํ๋ฆฌ์ผ์ด์
์ผ๋ก ๊ตฌ์ ๋ ์์คํ
์ธ ๋ง์ดํฌ๋ก์๋น์ค ํจํด์ธ์ง์ ๋ํด์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ธ์ฆ๊ณผ ๊ถํ ๋ถ์ฌ ๋ถ๋ถ์์ ํ์ ์ด์์ ๊ถํ์ด ๋ถ์ฌ๋ ์ ์๋๋ก ์ค์ ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ด์ธ์๋ ๋ฐ์ดํฐ ๋ณด์์์ ์ ์ก์ค์ธ ๋ฐ์ดํฐ์ ์ ์ฅ๋ ๋ฐ์ดํฐ์ ๋ณด์, ๋ด๋ถ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ, ํ ๋คํ์ ์ด์ฉ๊ด๋ฆฌ ๋ฑ ๋ค๋ฐฉ๋ฉด์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์
์ ์ ๋ณด ๋ณด์์ ์ํด ๊ณ ๋ฏผํ ํ์๊ฐ ์์ต๋๋ค.
์ฌ๊ธฐ์ ์ดํด๋ณด๊ธฐ ์ข์ ์คํ์์ค ์น ๋ณด์ ํ๋ก๊ทธ๋จ์ผ๋ก OWASP(Open Web Application Security Project)๊ฐ ์๋๋ฐ, 10๋ ๋ณด์ ์ทจ์ฝ์ฑ์ ๋ํ ๋ถ๋ถ๋ค์ ๋ค๋ฃจ๊ณ ์๊ณ ์ธ๋ถ ํญ๋ชฉ์ผ๋ก๋ ์ธ์ฆ ์ทจ์ฝ์ฑ, ์ธ์
๊ณ ์ , XSS, CSRF, ์ฃผ์
, ๊ธฐ๋ฐ ๋ฐ์ดํฐ ๋
ธ์ถ, ๋ฉ์๋ ์ ๊ทผ ์ ๊ฑฐ ๋ถ์กฑ, ์๋ ค์ง ์ทจ์ฝ์ฑ์ด ์๋ ์ข
์์ฑ ์ฌ์ฉ ๋ฑ์ด ์์ต๋๋ค.
์ธ์ฆ ์ทจ์ฝ์ฑ
์ธ์ฆ, ๊ถํ๋ถ์ฌ(์ธ๊ฐ)์ ๋ํ ์ทจ์ฝ์ ์ผ๋ก ์ธ์ฆ๊ณผ ์ธ์
๊ด๋ฆฌ๊ฐ ๋ช
ํํ ๊ตฌ๋ถ๋์ด ์์ง ์๋ ๋ฑ, ์ธ์ฆ, ์ธ๊ฐ์ ํ์ํ ํ ํฐ ํ์ทจ ๊ฐ๋ฅ์ฑ์ ๋ํ ์ทจ์ฝ์ ์ ๋งํฉ๋๋ค. ์ฌ๊ธฐ์ ์ธ์ฆ์ด๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์ฌ์ฉ์๋ฅผ ์๋ณํ๋ ํ๋ก์ธ์ค๋ฅผ ๋งํ๊ณ , ๊ถํ ๋ถ์ฌ๋ ์ธ์ฆ ํธ์ถ์๊ฐ ๊ธฐ๋ฅ๊ณผ ๋ฐ์ดํฐ ์ด์ฉ ๊ถ๋ฆฌ๊ฐ ์๋์ง๋ฅผ ํ์ธํ๋ ๊ณผ์ ์ ๋งํฉ๋๋ค.
์ธ์
๊ณ ์
๊ณต๊ฒฉ์๊ฐ ์ ํ ์ธ์
ID๋ฅผ ์ด์ฉํด์ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ์ธ์ฆ์ ํ๊ฒ ๋๋ ๊ฒฝ์ฐ, ๊ณต๊ฒฉ์๋ ๊ฐ์ ์ธ์
ID๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ ์ธ์
ํ์ด์ฌํน์ผ๋ก ์ฌ์ฉ์์ ๋์ผํ๊ฒ ์ ๋ณด๋ฅผ ๊ณต์ ๋ฐ๊ฒ ๋ฉ๋๋ค.
XSS
๊ต์ฐจ ์ฌ์ดํธ ์คํฌ๋ฆฝํธ๋ก ๊ถํ์ด ์๋ ์ฌ์ฉ์๊ฐ ๊ณต๊ฒฉํ๋ ค๋ ์ฌ์ดํธ์ ์คํฌ๋ฆฝํธ๋ฅผ ๋ฃ๋ ๋ฐฉ์์ผ๋ก ๊ทธ ์คํฌ๋ฆฝํธ๊ฐ ์คํ๋๊ฒ ํ๋ ๋ฐฉ์์ ๋งํฉ๋๋ค. ์ฑ
์ ์ฌ๋ก์์๋ ๋๊ธ์ ์ด์ฉํ XSS๋ก ๋ค๋ฅธ ์ฌ์ฉ์๋ค์ด ๊ทธ ์ฌ์ดํธ๋ฅผ ์คํํ ๋, ์
๋ ฅ๋ ๋๊ธ ์คํฌ๋ฆฝํธ๊ฐ ์คํ๋๋ฉฐ ๋ค์์ ์ด์ฉ์๊ฐ ์คํฌ๋ฆฝํธ์ ๊ด๋ จ๋ ์์ฒญ์ ๋ณด๋ด๋ ๊ฒฝ์ฐ๋ฅผ ์์๋ก ๋ค๊ณ ์์ต๋๋ค.
CSRF
์ฌ์ดํธ ๊ฐ ์์กฐ๋ ํน์ ์ ํ๋ฆฌ์ผ์ด์
์์ ์๋ํ๋ url์ด ์ธ๋ถ์์ ์ฌ์ฌ์ฉํ๋ฏ๋ก์จ, url์ ์ผ๋ถ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ณ๊ฒฝํ๋ ๋ฑ์ ๋ฐฉ์์ผ๋ก ์ฝ๊ฒ ๋ณ๊ฒฝํด์ ํ๋ก๊ทธ๋จ์ด ์
์ฉ๋ ์ ์๋๋ก ํ๋ ๋ฐฉ์์ ๋งํฉ๋๋ค.
GET http: //banking.com/transfer.do?acct=John&amount=1000 HTTP/1.1
GET http: //banking.com/transfer.do?acct=Mike&amount=5000
์ฃผ์
SQL ์ฃผ์
, XPath ์ฃผ์
, OS ๋ช
๋ น ์ฃผ์
, LDAP ์ฃผ์
๋ฑ์ด ์์ต๋๋ค. ์ค์๋ฐ์ดํฐ๋ ๋ณผํธ์ ๋ฃ์ด์ค์ผ ํฉ๋๋ค. ๋ณผํธ๋ ์ผ๋ฐ์ ์ผ๋ก ํด๋ผ์ฐ๋ ๊ธฐ๋ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐ ๋ฐ์ดํฐ ์ ์ฅ์ ์๋น์ค์ธ๋ฐ, ์ค์ํ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
LDAP(Lightweight Directory Access Protocol)์ด ๋ฌด์์ผ๊น?
LDAP๋ ์ฌ์ฉ์๊ฐ ์กฐ์ง, ๊ตฌ์ฑ์ ๋ฑ์ ๋ํ ๋ฐ์ดํฐ๋ฅผ ์ฐพ๋ ๋ฐ ๋์์ด ๋๋ ํ๋กํ ์ฝ์
๋๋ค. ๋๋ถ๋ถ ๊ฒ์์ ๋ํ ์์ฒญ์ผ๋ก DAP๋ณด๋ค ํต์ ๋คํธ์ํฌ ๋์ญํญ ์์ ๊ฐ๋ฒผ์ด ํน์ฑ์ด ์์ต๋๋ค. LDAP ์๋ฒ์์ ์ฃผ๋ก ํน์ ๋ฐ์ดํฐ๋ฅผ ์ค์์์ ๊ด๋ฆฌํ๋ ๊ฒฝ์ฐ ํธ๋ฆฌ๊ตฌ์กฐ๋ก ์ ์ฅํ๊ฑฐ๋ ์กฐํํ๋ ๊ฒฝ์ฐ์ ์ฌ์ฉ๋ฉ๋๋ค.
๊ธฐ๋ฐ ๋ฐ์ดํฐ ๋
ธ์ถ
๊ณต๊ฐ ์ ๋ณด๋ ๋ก๊ทธํํ์ง ๋ง์์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด 500 ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋, ๊ทธ๋๋ก ์๋ฌ ๋ด์ญ์ด ํด๋ผ์ด์ธํธ ๋จ์ ๋
ธ์ถ๋๋ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์
๊ตฌ์กฐ์ ๊ทธ ์ข
์์ฑ๊น์ง ๋ค ์ ์ ์๊ธฐ ๋๋ฌธ์ ์ํํฉ๋๋ค.
2์ฅ. ์๋ ! ์คํ๋ง ์ํ๋ฆฌํฐ
์คํ๋ง ์ํ๋ฆฌํฐ์์ ์ง์๋๋ UserDetailService
์ PasswordEncoder
๋ฅผ ์ฌ์ ์ํด์ ์ ๊ทธ๋ฆผ์ฒ๋ผ ์ธ์ฆ ๋
ผ๋ฆฌ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค. ์ง๊ธ์ @Decrypted
๋ WebSecurityConfigurerAdapter
๋ก ์ฑ
๋ด์ฉ์ด ์ค๋ช
๋ ๋ถ๋ถ๋ค์ด ๋ง์ ์ฝ๊ฐ์ ์ฐจ์ด์ ์ ์์ง๋ง ์ธ์ฆ ๋
ผ๋ฆฌ๋ฅผ ์ํด config๋ฅผ ๊ตฌ์ฑํ๋ ํฐ ํ๋ฆ์ ๋ณํ๊ฐ ์๋ค๋ ๋ถ๋ถ์ ์ค์ฌ์ ๋๊ณ ๋ณด๋ ๊ฒ์ด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค.
UserDetailService
๊ณ์ฝ์ ๊ตฌํํ๋ ๊ฐ์ฒด๋ก ์ฌ์ฉ์์ ๋ํ ์ธ๋ถ์ ๋ณด๋ฅผ ๊ด๋ฆฌํฉ๋๋ค. ๋ด๋ถ ๋ฉ๋ชจ๋ฆฌ์ ๊ธฐ๋ณธ ์๊ฒฉ ์ฆ๋ช ์ ๋ฑ๋กํ๋ฏ๋ก ์คํ๋ง ์ปจํ ์คํธ๊ฐ ๋ก๋๋ ๋ ์๋์ผ๋ก ์์ฑ๋ฉ๋๋ค.PasswordEncoder
๋ ๋ง ๊ทธ๋๋ก ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ผ๋ก ์ํธํ ๋ฟ๋ง ์๋๋ผ ์ํธํ๋ ๋น๋ฐ๋ฒํธ๊ฐ ๊ธฐ์กด ์ธ์ฝ๋ฉ๋ ๋น๋ฐ๋ฒํธ์์ ์ผ์น๋ฅผ ํ์ธํ ์ ์๋๋กmatch
๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ํฉ๋๋ค.
์ง๊ธ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ฌ๋ผ์ง ๋ถ๋ถ์ด ๋ง์์ ์ฑ ์์ ์ ์ํ๋ ๊ตฌํ๋จ ์์๋ง ๋ช ๊ฐ์ง ์์ฑํ์ต๋๋ค.
๊ตฌํ ์ฌ๋ก๋ก ๋ ์์ ์ฝ๋ 1
UserDetailService
์ PasswordEncoder
๋ฅผ ๊ตฌํํ๋ ๋ค์ํ ๋ฐฉ๋ฒ์ด ์์ง๋ง, ๋ ๋ถ๋ถ์ ๋ฉ์๋๋ฅผ @Bean
์ผ๋ก ์ค์ ํ์ง ์๊ณ ํ ๋ฒ์ ๊ฐ์ config๋ก ์ค์ ํ๋ ๊ฒฝ์ฐ์๋ ๊ตฌ์ฑ์ด ํผํฉ๋์ด ์ฑ
์์ด ๋ถ๋ฆฌ๋์ง ์์๊ธฐ ๋๋ฌธ์ ๊ถ์ฅํ์ง ์์ต๋๋ค.
์๋ ์ฝ๋์์๋ @Deprecated
๋ NoOpPasswordEncoder
๋ฅผ ์ฌ์ฉํด์ ์ํธํ๋ฅผ ๊ตฌํํ์ง๋ง, ์ค์ ๋ก๋ return PasswordEncoderFactories.createDelegatingPasswordEncoder();
์ ๊ฐ์ ๋ฐฉ์ ๋๋ ๊ฐ๋ฐ์ ๋๋ฆ์ ์ฌ์ ์๋ฅผ ํตํด ์์ฑํ๋ ๊ฒ์ด ๋ฐ๋์งํฉ๋๋ค.
@Configuration
public class UserManagementConfig {
@Bean
public UserDetailsService userDetailsService() {
var userDetailsService = new InMemoryUserDetailsManager();
var user = User.withUsername("john")
.password("12345")
.authorities("read")
.build();
userDetailsService.createUser(user);
return userDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
๊ตฌํ ์ฌ๋ก๋ก ๋ ์์ ์ฝ๋ 2
UserDetailService
์ PasswordEncoder
๋ฅผ ๊ตฌ ์ด ๋ ๊ตฌ์ฑ์์์ ์์
์ ์์ํ๋ AuthenticationProvider(์ธ์ฆ๊ณต๊ธ์)
๋ฅผ ๊ตฌํํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ AuthenticationManagerBuilder
๋ฅผ ์ด์ฉํ ๊ตฌํ์ config์์ AuthenticationProvider(์ธ์ฆ๊ณต๊ธ์)
์ ์ฃผ์
๋ฐ์์ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ์ ์ฌ๋ก๋ ์์ต๋๋ค.
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = String.valueOf(authentication.getCredentials());
if ("john".equals(username) && "12345".equals(password)) {
return new UsernamePasswordAuthenticationToken(username, password, Arrays.asList());
} else {
throw new AuthenticationCredentialsNotFoundException("Error!");
}
}
@Override
public boolean supports(Class<?> authenticationType) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authenticationType);
}
}
3์ฅ. ์ฌ์ฉ์ ๊ด๋ฆฌ
3์ฅ์ User
, UserDetailService
, UserDetailManager
์ ๋ํ ๋ด์ฉ์ ๋ค๋ฃจ๊ณ ์์ต๋๋ค. ์ฌ์ฉ์๊ฐ ์ธ์ฆ์ ํ๋ ๊ณผ์ ์์ ์ธ์ฆ ๋
ผ๋ฆฌ์ ๋ฐ๋ผ ์ธ์ฆ ์ ๊ณต์๋ ์ฌ์ฉ์๋ฅผ ์ธ์ฆํ๋ ๊ณผ์ ์ ๊ฑฐ์น๊ฒ ๋๋๋ฐ ์ด ๋, ๋ฉ๋ชจ๋ฆฌ User๋ฅผ ๊ด๋ฆฌํ๋ ์ฒด๊ณ์ ๋ํ ๋ด์ฉ์
๋๋ค.
UserDetils์ ๊ตฌํ
์ฌ์ฉ์ ๊ธฐ์ ์ ์ํด์ ์คํ๋ง ์ํ๋ฆฌํฐ๋ UserDetils
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ ์ค์ํฉ๋๋ค. UserDetails
๋ฅผ ์ง์ ํด๋์ค๋ก ๊ตฌํํด๋ ๋๊ณ , ์ฌ์ฉ์ ๋ฐ๋ผ UserDetail
๋ฅผ ๋น๋ํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
UserDetails
๋ ํ๋ ์ด์์ ๊ถํ, password, username์ ์กฐํํ๊ฑฐ๋ ๊ณ์ ์ ํ์ฑํ ๋ฐ ๋นํ์ฑํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฉ์๋๋ค์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ๊ณ์ ์ ๊ด๋ฆฌ๋ ๊ธฐ๋ณธ์ ์ผ๋ก true
์ฒ๋ฆฌ๋ฅผ ํ์ง๋ง ๋ณ๋ ๋ด๋ถ ์๋น์ค ๋ฐฉ์นจ์ ๋ฐ๋ผ ์ปค์ฆํฐ๋ง์ด์งํด ๊ตฌํํ ์ ์์ต๋๋ค.
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
์ด๋ ๊ฒ UserDetails
๋ฅผ ํ๋์ ํด๋์ค๋ก implements ํด์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ๋ ์์ง๋ง, UserDetailService
๋ฅผ ์ํด ์ ๊ณต๋๋ User
๋ชจ๋ธ์ ๋น๋ํ ์ ์์ต๋๋ค. ์ด ๋ชจ๋ธ๋ UserDetails
๊ตฌํ์ฒด์
๋๋ค.
์ด ๊ฒฝ์ฐ withUsername
๋ฉ์๋๋ฅผ ์ด์ฉํด UserBuilder
๋ฅผ ๋ง๋ค์ด UserDetiails
๊ฐ์ฒด๋ฅผ ๋ง๋ค ์๋ ์์ต๋๋ค.
UserDetails userDetails = User.withUsername(member.getEmail())
.password(member.getPassword())
.roles(member.getRole())
.build();
UserDetailService์ ์ธ ๊ฐ์ง UserDetailManager์ ๋ํ์ฌ
UserDetailService
๋ ์ธ์ฆ์ ํต์ฌ์ด ๋๋ ๋ถ๋ถ์ผ๋ก ์ธ์ฆ ๋
ผ๋ฆฌ์์ ๋ฉ๋ชจ๋ฆฌ ๋ด์์ User๋ฅผ ๊ด๋ฆฌํฉ๋๋ค. UserDetailService
์ธํฐํ์ด์ค๋ ๋จ ํ ๊ฐ์ง ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์๋๋ฐ loadUserByUsername(String username)
๋ฅผ ๊ฐ์ง๊ณ Username์ ํด๋นํ๋ ์ฌ์ฉ์๊ฐ ์๋์ง ๋ฉ๋ชจ๋ฆฌ์์ ํ์ธํฉ๋๋ค. ํด๋นํ๋ ์ฌ์ฉ์๊ฐ ์๋ค๋ฉด, UsernameNotFoundException
๋ก RuntimeException
๋ฅผ ๋์ง๋๋ค.
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
์ฑ
์์ ์ ์ํ loadUserByUsername
๊ตฌํ์ ์์
๋๋ค. User๋ค ์ค์์ username์ด ๋์ผํ User๋ง์ ์ถ์ถํด UserDetails๋ก ๋ฆฌํดํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
return Users.stream()
.filter(u -> u.getUsername().equals(username))
.findFirst()
.orElseThrows(() -> new UsernameNotFoundException("User not found"));
}
UserDetailManager
๋ UserDetailService
์ ๊ธฐ๋ฅ์ ํ์ฅํ๊ณ ๋ฉ์๋๋ฅผ ์ถ๊ฐํฉ๋๋ค. UserDetailManager
์ ๊ตฌํํด๋์ค๋ก InMemoryUserDetailsManager
์ JdbcUserDetailsManager
๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค. ๋ด๋ถ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋์ง ๋๋ SQL ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ ์ฌ์ฉ์๋ฅผ ๊ด๋ฆฌํ๋ฉฐ JDBC๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ์ฐ๊ฒฐํ๋์ง์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ฌ์ฉ๋ฉ๋๋ค.
LdapUserDetailsManager
๋ ์ ๊ณตํ๊ณ ์์ง๋ง LDAP๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ๋ณ๋ dependency ์ค์ ์ด ํ์ํฉ๋๋ค.
public interface UserDetailsManager extends UserDetailsService {
void createUser(UserDetails user);
void updateUser(UserDetails user);
void deleteUser(String username);
void changePassword(String oldPassword, String newPassword);
boolean userExists(String username);
}
์ถ๊ฐ๋ก setUsersByUsernameQuery
๋ฑ๊ณผ ๊ฐ์ด JdbcUserDetailsManager
์ ์ฟผ๋ฆฌ๋ฅผ ๋ณ๊ฒฝํ ์ ์์ผ๋ฉฐ @Bean์ผ๋ก UserDetailsService
config๋ก ๋ฑ๋กํ ์ ์์ต๋๋ค.
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
String usersByUsernameQuery = "select username, password, enabled from spring.users where username = ?";
String authsByUserQuery = "select username, authority from spring.authorities where username = ?";
var userDetailsManager = new JdbcUserDetailsManager(dataSource);
userDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
userDetailsManager.setAuthoritiesByUsernameQuery(authsByUserQuery);
return userDetailsManager;
}
4์ฅ. ์ํธ์ฒ๋ฆฌ
์ธ์ฆ๊ณต๊ธ์๊ฐ ์ ๊ณตํ password๋ฅผ ์ด์ฉํด์ PasswordEncorder
๋ฅผ ์ด์ฉํด์ ์ํธ๋ฅผ ๊ฒ์ฆํฉ๋๋ค.
์ด ๋ถ๋ถ์ ์์ฑ์์ ์ธ ํ์ฌ(2023-09)์ ์ฐจ์ด๊ฐ ์ข ์์ง๋ง ํ๋ก์ธ์ค๋ฅผ ์ดํดํ๊ธฐ ์ํ ๊ฒ์ผ๋ก ์ฑ
์ ๊ธฐ๋ฐ์ผ๋ก ์์ ํ๊ณ ์ ํฉ๋๋ค.
PasswordEncorder ์ธํฐํ์ด์ค
encode()
๋ฅผ ํตํด ์ํธํ๋ฅผ matches()
๋ฅผ ํตํด ์ธ์ฝ๋ฉ๋ ๋ฌธ์์ด์ด ์ํธ์ ์ผ์น์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. upgradeEncoding(CharSequence encodedPassword)
๋ ๊ธฐ๋ณธ๊ฐ์ด false
์ด์ง๋ง true ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ ์ธ์ฝ๋ฉ๋ ์ํธ๋ฅผ ๋ค์ ์ธ์ฝ๋ฉํ๊ฒ ๋ฉ๋๋ค.
Spring security์์ ์ ๊ณตํ๋ PasswordEncoder
๊ตฌํ ์ต์
๋ค์ ๋ค์ ์ต์
๋ค์ด ์์ต๋๋ค. ๊ฐ ํด์ฑ ์๊ณ ๋ฆฌ์ฆ์ ๋ํ ์ค๋ช
- NoOpPasswordEncoder
- BCryptPasswordEncoder
- Pbkdf2PasswordEncoder
- SCryptPasswordEncoder
- Argon2PasswordEncoder
- DelegatingPasswordEncoder
NoOpPasswordEncoder
ํ
์คํธ๋ ์ด์ ์์คํ
๊ณผ์ ํธํ์ฑ์ ์ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉ๋์ด์ผ ํฉ๋๋ค. ํ์ฌ๋ Deprecated ๋์์ต๋๋ค.
BCryptPasswordEncoder
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(16);
}
์ด๋ ๊ฒ ์ธ์ฝ๋ฉ ํ๋ก์ธ์ค์ ์ด์ฉ๋๋ ๋ก๊ทธ ๋ผ์ด๋๋ฅผ ๋ํ๋ด๋ ๊ฐ๋ ๊ณ์๋ฅผ ์ง์ ํ ์๋ ์์ต๋๋ค. ์๋ ์์ ์์ 4๋ ๊ฐ๋ ๊ณ์์ ๋๋ค. ์ด ๊ฐ์ 2์ 4์ ๊ณฑ ์ฆ, 16๋ฒ์ ํด์ฑ ๋ผ์ด๋๋ฅผ ์๋ฏธํฉ๋๋ค. b๋ BCrypt์์ ์ํธ(salt)๋ฅผ ์์ฑํ ๋ ์ฌ์ฉ๋ฉ๋๋ค.
SecureRandom s = SecureRandom.getInstanceStrong();
PasswordEncoder p = new BCryptPasswordEncoder(4, s);
DelegatingPasswordEncoder
์ฌ๋ฌ ํด์ฑ ์ ๋ต์ ์ ์ฐํ๊ฒ ๊ด๋ฆฌํ ์ ์๊ฒ ๋์์ค๋๋ค. ์ ๋์ฌ {noop}
์ ๋ํด NoOpasswordEncoder๊ฐ, {bcrypt}
์ธ ๊ฒฝ์ฐ์๋ BCryptPasswordEncoder, {scrypt}
์ด๋ฉด, SCryptPasswordEncoder๋ฅผ ๋ฑ๋กํฉ๋๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ์๋ DelegatingPasswordEncoder์ ๊ตฌํ์ ๋ฐํํ๋ ์ ์ ๋ฉ์๋๋ฅผ ์ ๊ณตํฉ๋๋ค.
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
์คํ๋ง ์ํ๋ฆฌํฐ์์ DelegatingPasswordEncoder๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ๊ฒ ๋๋ฉด์, NoOpPasswordEncoder์ ๊ฐ์ deprecated๋ PasswordEncoder๋ฅผ ํฌํจํ์ฌ ์ฌ๋ฌ ์ ๋ต์ ์ง์ํ๊ฒ ๋์์ต๋๋ค. ์ด๋, NoOpPasswordEncoder
์ ๋ํ deprecated ๊ฒฝ๊ณ ๋ฅผ ํผํ๊ธฐ ์ํด createDelegatingPasswordEncoder
๋ฉ์๋์ @SuppressWarnings("deprecation")
์ ์ถ๊ฐ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๊ทธ๋ผ ๋น๋ฐ๋ฒํธ ์ํธํ ์ธ์๋ ์ด๋ป๊ฒ ์ํธํ๋ฅผ ๊ตฌํํ ๊น์?
์คํ๋ง ์ํ๋ฆฌํฐ ์ํธํ ๋ชจ๋(SSCM)์๋ ํค ์์ฑ๊ธฐ์ ์ํธ๊ธฐ๋ฅผ ๊ตฌํํ๋ ๋์์ด ์์ต๋๋ค.
StringKeyGenerator keyGenerator = KeyGenerators.string();
String salt = keyGenertor.generateKey();
BytesKeyGenertor keyGenerator = KeyGenerators.secureRandom(16);
BytesKeyGenertor keyGenerator = KeyGenerators.shared(16);
์ํธ๊ธฐ๋ ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๋ ๊ฐ์ฒด์ ๋๋ค. BytesEncryptor์ TextEncryptor๋ผ๋ ์ํธ๊ธฐ๊ฐ ์๊ณ ๊ฐ๊ฐ์ ๋ค๋ฅธ ๋ฐ์ดํฐ ํ์์ ์ฒ๋ฆฌํฉ๋๋ค. BytesEncryptor๊ฐ ๋ฌธ์์ด๋ก ์ถ๋ ฅ์ ๋ฐํํ๋ ๋ฐ๋ฉด, TextEncryptor๋ ๋ ๋ฒ์ฉ์ ์ด๊ณ ๋ฐ์ดํธ ๋ฐฐ์ด๋ก ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ต๋๋ค.
BytesEncryptor e = Encryptors.stronger(password, salt);
TextEncryptor๋ Encryptors.text()
, Encryptors.delux()
, Encryptors.queryableText()
์ ์ธ ๊ฐ์ง ์ฃผ์ ํ์์ ๊ฐ์ง๊ณ ์์ต๋๋ค. Encryptors.text()
, Encryptors.delux()
๋ encrypt()
๋ฉ์๋๋ฅผ ๋ฐ๋ณต ํธ์ถํด๋ ๋ค๋ฅธ ์ถ๋ ฅ์ด ๋ฐํ๋๋๋ฐ, ์ด๊ธฐํ ๋ฒกํฐ๊ฐ ์์ฑ๋๊ธฐ ๋๋ฌธ์
๋๋ค.
Encryptors.queryableText()
๋ ์ฟผ๋ฆฌ ๊ฐ๋ฅ ํ
์คํธ๋ก ์
๋ ฅ์ด ๊ฐ์ผ๋ฉด ๊ฐ์ ์ถ๋ ฅ์ ๋ฐํํ๋ ๊ฒ์ ๋ณด์ฅํฉ๋๋ค.
TextEncryptor e = Encryptors.queryableText(password, salt);
String encrypted = e.encrypt(valueToEncrypt);
5์ฅ. ์ธ์ฆ ๊ตฌํ
- ๋ง์ถคํ AuthenticationProvider๋ก ์ธ์ฆ ๋ ผ๋ฆฌ ๊ตฌํ
- HTTP Basic ๋ฐ ์์ ๊ธฐ๋ฐ ๋ก๊ทธ์ธ ์ธ์ฆ ๋ฉ์๋ ์ด์ฉ
- SecurityContext ๊ตฌ์ฑ ์์์ ์ดํด ๋ฐ ๊ด๋ฆฌ
์ธ์ฆ ํํฐ๊ฐ ๊ฐ๋ก์ฑ ์์ฒญ์ ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ธ์ฆ ์ฑ ์์ ์ธ์ฆ ๊ด๋ฆฌ์์ ์์ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ธ์ฆ ๊ณต๊ธ์์์ ํ์ธํ ์ธ์ฆ ๊ฒฐ๊ณผ๋ ๋ณด์ ์ปจํ ์คํธ(SecurityContext)์ ์ ์ฅ๋ฉ๋๋ค.
AuthenticationProvider์ ์ดํด
Authentication ๊ณ์ฝ์ธ Principal ๊ณ์ฝ์ ์์ํ๋๋ฐ Authentication์์๋ ์ํธ๊ฐ์ ์๊ตฌ ์ฌํญ์ด๋ ์ธ์ฆ ์์ฒญ์ ๋ํ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ์ด๋ ์๋ฐ ์ํ๋ฆฌํฐ API์ Principal ๊ณ์ฝ์ ํ์ฅํ๋๋ก ์ค๊ณ๋์ด ํธํ์ฑ ์ธก๋ฉด์ ์ด์ ์ด ๋ฉ๋๋ค. (๋ง์ด๊ทธ๋ ์ด์
์ด์ )
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
AuthenticationProvider ์ธํฐํ์ด์ค๋ ์ฌ์ฉ์๋ฅผ ์ฐพ๋ ๊ฒ์ UserDetailsService์ ์์ํ๊ณ PasswordEncoder๋ก ์ธ์ฆ ํ๋ก์ธ์ค์์ ์ํธ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
ํ์ง๋ง, AuthenticationProvider
๋ Spring Security 5๋ถํฐ Deprecated ๋์์ผ๋ฉฐ, ๋์ ์ AuthenticationManager๋ฅผ ์ฌ์ฉํ๋๋ก ๊ถ์ฅ๋ฉ๋๋ค. AuthenticationManager๋ AuthenticationProvider์ ๋ค์ํ ์ธ์ฆ ๋ฐฉ์์ ์ง์ํ๋ Provider๋ค์ ํตํฉ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ , ์ค์ ์ธ์ฆ ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํ๋ ์ญํ ์ ์ํํ๊ธฐ ๋๋ฌธ์
๋๋ค.
๊ตฌํ๋ถ์ AuthenticationProvider
์ WebSecurityConfigurerAdapter
๋ ๋ค ํ์ฌ๋ ์ฌ์ฉ๋์ง ์๋ ๋ถ๋ถ์ผ๋ก ์์ฑํ์ง ์์์ต๋๋ค.
๊ธ ๋ง์ง๋ง์ ์ด๋ฐ ๋ด์ฉ์ด ์กด์ฌํ๋๋ฐ ์ด ๋ถ๋ถ์ด ์คํ๋ ค ์ฑ ์ ํต์ฌ ๋ด์ฉ์ด์, ์์กด์ฑ์ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์๋ค์ด ํํ ๊ณ ๋ คํด์ผ ํ ๋ถ๋ถ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
ํ๋ ์์ํฌ, ํนํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ฆฌ ์ด์ฉ๋๋ ํ๋ ์์ํฌ๋ ์๋ง์ ๋๋ํ ๊ฐ๋ฐ์์ ์ฐธ์ฌ๋ก ๊ฐ๋ฐ๋๋ค. ๊ทธ๋ ๋ค๊ณ ํด๋ ํ๋ ์์ํฌ๊ฐ ์๋ชป ๊ตฌํ๋๋ ๊ฒฝ์ฐ๋ ๋ง์ง ์๋ค. ์ด๋ค ๋ฌธ์ ๊ฐ ํ๋ ์์ํฌ์ ์๋ชป์ด๋ผ๊ณ ๊ฒฐ๋ก ๋ด๋ฆฌ๊ธฐ ์ ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ถ์ํ์.
ํ๋ ์์ํฌ๋ฅผ ์ด์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค๋ฉด ์ต๋ํ ํ๋ ์์ํฌ์ ์๋๋ ์ฉ๋์ ๋ง๊ฒ ์ด์ฉํ๋ค. ์๋ฅผ ๋ค์ด ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ด์ฉํ๋ฉด์ ๋ณด์ ๊ตฌํ์ ํ๋ ์์ํฌ๊ฐ ์ ๊ณตํ๋ ๊ฒ๋ณด๋ค ๋ง์ถคํ ์ฝ๋๋ฅผ ์์ฑํ๋ ์ผ์ด ๋ง๋ค๊ณ ๋๋๋ค๋ฉด ์ด๋ฐ ์ผ์ด ์ ์๊ธฐ๋์ง ์๋ฌธ์ ๊ฐ์ ธ์ผ ํ๋ค.
SecurityContext ์ด์ฉ
๋ณด์ ์ปจํ
์คํธ๋ Authentication ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๋ ์ธ์คํด์ค์
๋๋ค.
public interface SecurityContext extends Serializable{
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
SecurityContext๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด ์คํ๋ง ์ํ๋ฆฌํฐ๋ SecurityContextHolder๋ผ๋ ๊ฐ์ฒด๋ฅผ ์ ๊ณตํฉ๋๋ค. MODE_THREADLOCAL, MODE_INHRITABLETHREADLOCAL, MODE_GLOBAL ์ต์ ์ ์ ๊ณตํ๊ณ ์ค์ ์ Config์์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ ์ ์์ต๋๋ค.
return() -> SecurityContextHolder.setStrategyName( SecurityContextHodler.MODE_INHERITABLETHREADLOCAL);
MODE_THREADLOCAL
(๋ณด์ ์ปจํ
์คํธ๋ฅผ ์ํ ๋ณด์ ์ ๋ต ์ด์ฉ)
๊ฐ ์ค๋ ๋๊ฐ ๋ณด์ ์ปจํ
์คํธ ๊ฐ์ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ๊ฒ์ผ๋ก ๊ธฐ๋ณธ๊ฐ์
๋๋ค. ์ ์ค๋ ๋๋ ์์ฒด ๋ณด์ ์ปจํ
์คํธ๋ฅผ ๊ฐ์ง๋ฉฐ ์์ ์ค๋ ๋์ ์ธ๋ถ ์ ๋ณด๊ฐ ์ ์ค๋ ๋์ ๋ณด์ ์ปจํ
์คํธ๋ก ๋ณต์ฌ๋์ง ์์ต๋๋ค.
์ด SecurityContext์์ Authentication์ ์ป๊ธฐ ์ํด์๋ ์ค๋ํฌ์ธํธ ๋งค๊ฐ๋ณ์์ ๋ฐ๋ก ์ฃผ์
ํด์ ์ป์ ์ ์์ต๋๋ค.
@GetMapping
public String hello(Authentication a){
return a.getName();
}
MODE_INHRITABLETHREADLOCAL
(๋น๋๊ธฐ ํธ์ถ์ ์ํ ๋ณด์ ์ ๋ต ์ด์ฉ)
MODE_THREADLOCAL์ ๋น์ทํ์ง๋ง ๋น๋๊ธฐ ๋ฉ์๋์ ๊ฒฝ์ฐ ๋ณด์ ์ปจํ
์คํธ๋ฅผ ๋ค์ ์ค๋ ๋๋ก ๋ณต์ฌํ๋๋ก ์คํ๋ง ์ํ๋ฆฌํฐ์ ์ง์ํฉ๋๋ค.
๋ถ๋ชจ ์ค๋ ๋์์ ์์ฑ๋ SecurityContext๋ฅผ ์์ ์ค๋ ๋์์๋ ๊ณต์ ํ ์ ์์ต๋๋ค. ์ฆ, ์๋ ์ค๋ ๋์ ์๋ ์ธ๋ถ ์ ๋ณด๋ฅผ ๋น๋๊ธฐ ๋ฉ์๋์ ์๋ก ์์ฑ๋ ์ค๋ ๋๋ก ๋ณต์ฌํฉ๋๋ค.
MODE_GLOBAL
(๋
๋ฆฝํ ์ ํ๋ฆฌ์ผ์ด์
์ ์ํ ๋ณด์ ์ ๋ต ์ด์ฉ)
์ ํ๋ฆฌ์ผ์ด์
์ ๋ชจ๋ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ณด์ ์ปจํ
์คํธ ์ธ์คํด์ค๋ฅผ ๋ณด๊ฒ ํฉ๋๋ค. ์ค๋ ๋ ์์ ์ ์ง์ํ์ง ์์์ ์ ์ํด์ผ ํฉ๋๋ค. ๊ณต์ ์ค๋ ๋ ์ ๋ต์์๋ ์คํ๋ง์ด ๊ด๋ฆฌํ๋ ์ค๋ ๋์๋ง ์ ๋ต์ด ์ ์ฉ๋๋ค๋ ๊ฒ์ ๊ธฐ์ตํด์ผ ํฉ๋๋ค.
๋ณด์ ์ปจํ ์คํธ๋ฅผ ์๋ก ์์ฑํ ์ค๋ ๋๋ก ์ ํํ๊ฒ ๋์์ฃผ๋ ๋ช ๊ฐ์ง ์คํ๋ง ์ํ๋ฆฌํฐ์ ์ ํธ๋ฆฌํฐ ํด์ด ์์ต๋๋ค.
DelegatingSecurityContextRunnable
Runnable ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค๋ก, ์์ฑ์๋ก Runnable ๊ฐ์ฒด์ ํ์ฌ SecurityContext๋ฅผ ๋ฐ์์, ์๋ก ์์ฑํ ์ค๋ ๋์์ Runnable ๊ฐ์ฒด๋ฅผ ์คํํ ๋ ๋ณด์ ์ปจํ
์คํธ๋ฅผ ์ ํํฉ๋๋ค. ๋ฐํ๊ฐ์ด ์๋ ์์
์คํ ํ ์ด์ฉํ ์ ์์ต๋๋ค.
SecurityContext securityContext = SecurityContextHolder.getContext();
Runnable runnable = new MyRunnable();
Runnable wrappedRunnable = new DelegatingSecurityContextRunnable(runnable, securityContext);
new Thread(wrappedRunnable).start();
DelegatingSecurityContextCallable
๋ฐํ๊ฐ์ด ์๋ ์์
์๋ DelegatingSecurityContextCallable ๋์์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํ์ฌ ๋ณด์ ์ปจํ
์คํธ๋ฅผ ๋ณต์ฌํด ๋น๋๊ธฐ์ ์ผ๋ก ์คํํ Callable ์์
์ ์ง์ ํฉ๋๋ค.
DelegatingSecurityContextExecutorService
ExecutorService ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค๋ก, ๋ณด์ ์ปจํ
์คํธ๋ฅผ ์๋ก ์์ฑํ ์ค๋ ๋์์ ์คํํ๋ ์์
์ ์๋์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์ด์ ์ ์ฌํ๊ฒ DelegatingSecurityContextScheduledExecutorService๋ ์์ฝ๋ ์์
์ ์ํด ๋ณด์ ์ปจํ
์คํธ ์ ํ๋ฅผ ๊ตฌํํด์ผ ํ๋ ๊ฒฝ์ฐ์ ๋ฐ์ฝ๋ ์ดํฐ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
SecurityContext securityContext = SecurityContextHolder.getContext();
ExecutorService executorService = Executors.newFixedThreadPool(10);
ExecutorService wrappedExecutorService = new DelegatingSecurityContextExecutorService(executorService, securityContext);
wrappedExecutorService.submit(new MyRunnable());
HTTP Basic ์ธ์ฆ ์์
http.httpBasic(c->{
// ์์ญ ์ด๋ฆ์ ๋ณ๊ฒฝํ๋ ๋ฐฉ๋ฒ
c.realName("OTHER");
// custom AuthenticationEntryPoint ์ ์ฉ
c.authenticationEntiryPoint(new CustomEntryPoint());
})
http.authorizeRequests().anyRequest().authenticated();
AuthenticationEntryPoint ์ธํฐํ์ด์ค
Spring Security์์๋ ์ฌ๋ฌ ๊ฐ์ง AuthenticationEntryPoint ๊ตฌํ์ฒด๋ฅผ ์ ๊ณตํ๋ฉฐ, ์ด๋ฅผ ํตํด ๋ค์ํ ์ธ์ฆ ์คํจ ์๋๋ฆฌ์ค์ ๋์ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์น ์ ํ๋ฆฌ์ผ์ด์
์์๋ LoginUrlAuthenticationEntryPoint
๊ฐ ์์ฃผ ์ฌ์ฉ๋๋ฉฐ, REST API์์๋ Http403ForbiddenEntryPoint
๊ฐ ์์ฃผ ์ฌ์ฉ๋ฉ๋๋ค.
์์ ๊ธฐ๋ฐ ๋ก๊ทธ์ธ ์ธ์ฆ
์ํ์ ํ์ฅ์ฑ์ด ํ์ํ ๋ํ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ณด์ ์ปจํ ์คํธ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ ์๋ฒ ์ชฝ ์ธ์ ์ ์ด์ฉํ๋ ๊ฒ์ ์ข์ง ์์ต๋๋ค.
์์ ๊ธฐ๋ฐ ๋ก๊ทธ์ธ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ formLogin() ๋ฉ์๋๋ฅผ ์ด์ฉํฉ๋๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ์์ ๋ก๊ทธ์ธ์ ํ์ง ์๋ ๊ฒฝ์ฐ๋ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์ ๋๊ฒ ์ฒ๋ฆฌ๋ฉ๋๋ค.
http.formLogin();
@RestController
๊ฐ ์๋ @Controller
๋ฅผ ์ฌ์ฉํ๋ฏ๋ก์จ ๋ฉ์๋ ๋ฐํ ๊ฐ์ HTTP ์๋ต์ผ๋ก ๋ณด๋ด๋ ๊ฒ์ด ์๋ home.html
๋ก ๋๋๋งํด์ค๋๋ค.
์ด ๋ ๋ก๊ทธ์ธ์ ํ์ง ์์๋ค๋ฉด, /login ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
๋๊ณ /logout ๊ฒฝ๋ก๋ก ์ ๊ทผํ๋ฉด ๋ก๊ทธ์์ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
๋ฉ๋๋ค. (HTTP 302)
@Controller
public class HelloController{
@Getmapping("/home")
public String home(){ return "home.html"; }
}
formLogin()
์ ๋ง์ถค ๊ตฌ์ฑ์ ์ํด AuthenticationSuccessHandler
์ AuthenticationFailureHandler
๊ฐ์ฒด๋ฅผ ์ด์ฉํ ์ ์์ต๋๋ค.
6์ฅ ์ค์ : ์๊ณ ์์ ํ ์น ์ ํ๋ฆฌ์ผ์ด์
์ฃผ๋ก ๊ตฌํ๋จ์ ๋ํ ์ด์ผ๊ธฐ๊ฐ ์๋ ์ฑํฐ์ ๋๋ค. ์์ฑ์ผ์ ๊ธฐ์ค Deprecated๋ ๋ด์ฉ์ ์ ์ธํ์ต๋๋ค.
- ์์กด์ฑ ์ถ๊ฐ
- SQL ๋ฒ์ ์ง์ ์ ์ํ ํ๋ผ์ด์จ์ด, ๋ฆฌํด๋ฒ ์ด์ค ์ข ์์ฑ ์ด์ฉํ ์ ์์ต๋๋ค.
- ๋น๋ฐ๋ฒํธ ์ํธํ๋ฅผ ์ํ PasswordEncoder
@Bean
์ผ๋ก ๋ฑ๋ก - User, Authority ์ํฐํฐ ์ค์
- UserDetails ์ธํฐํ์ด์ค์ ๊ตฌํ
@Override
public CustomUserDetails loadUserByUsername(String username){
Supplier<UsernameNotFoundException> s = () -> new UsernameNotFoundException("user not found");
User user = userRepository.findByUsername(username)
.orElseThrow(s);
return new CustomUserDetails(user);
}
์ธ์ฆ๋
ผ๋ฆฌ์์ ์ํธ๊ฐ ์ผ์นํ๋ฉด encoder.matches(rawPassword, user.getPassword())
์ธ์ฆ์ด ๋์์ผ๋ฏ๋ก, Authentication์ ๋ฐํํฉ๋๋ค.
๋๋ถ๋ถ์ ๊ฐ์ ๊ธฐ๋ฅ์ ์ฌ๋ฌ ๊ฐ์ง ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํ ์ ์์ผ๋ฉฐ, ๊ฐ์ฅ ๋จ์ํ ํด๊ฒฐ์ฑ ์ ์ ํํด ์ฝ๋๋ฅผ ์ดํดํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค์ด ์ค๋ฅ์ ๋ณด์ ์นจํด ์ฌ์ง๋ฅผ ์ค์ผ ํ์๊ฐ ์์ต๋๋ค.
7์ฅ. ๊ถํ ๋ถ์ฌ ๊ตฌ์ฑ: ์ก์ธ์ค ์ ํ
์คํ๋ง ์ํ๋ฆฌํฐ์์๋ ์ธ์ฆ ํํฐ์์ ๋ณด์ ์ปจํ ์คํธ์ ์ธ์ฆ๋ ์ฌ์ฉ์ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ณ ์ดํ์ ๊ถํ ๋ถ์ฌ ํํฐ๋ก ์์ฒญ์ ํ์ฉํ ์ง ๊ฒฐ์ ํฉ๋๋ค.
ํ ์ฌ์ฉ์๋ ํ๋ ์ด์์ ๊ถํ์ ๊ฐ์ต๋๋ค. ์ฆ GrantedAuthority๋ก UserDetils๋ ํ๋ ์ด์์ ๊ถํ์ ๊ฐ์ต๋๋ค. UserDetils์ getAuthorities()
๋ฉ์๋๋ก ์ฌ์ฉ์ ์ธ๋ถ ์ ๋ณด์ ๋ชจ๋ ๊ถํ์ ๋ฐํํฉ๋๋ค.
์๋ํฌ์ธํธ ๊ถํ ์ ์ด
permitAll()
์ด ์๋ ํน์ ๊ถํ ์ ์ด๋ก โWRITEโ ๊ถํ์ ๊ฐ์ง ๊ฒฝ์ฐ์๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํ๋๋ก ํ ์ ์์ต๋๋ค. ์ด์ธ์๋ ๊ถํ ์ ์ด๋ฅผ ์ํ ๋ค์ ์ธ ๊ฐ์ง ๋ฉ์๋๊ฐ ์์ต๋๋ค.
- hasAuthority()
- hasAnyAuthority()
- access()
// .hasAuthority()์ ์ด์ฉ
http.authoriseRequests()
.anyRequest()
.hasAuthority("WRITE");
// .access()์ ์ด์ฉ
// ๋ณต์กํ ์์ ์์ฑํด์ผ ํ๋ ๊ฒฝ์ฐ์ ์ค์ ์๋๋ฆฌ์ค๊ฐ ์์ ์ ์์ต๋๋ค.
http.authoriseRequests()
.anyRequest()
.access("hasAuthority('WRITE') and !hasAuthority('DELETE')");
์คํ๋ง ์(SpEL)์ด๋?
Spring Framework์์ ์ ๊ณตํ๋ ํํ ์ธ์ด์
๋๋ค.
์คํ๋ง ์์ XML์ด๋ ์ ๋
ธํ
์ด์
๊ธฐ๋ฐ์ ๊ตฌ์ฑ์์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ๊ฐ์ฒด ๊ทธ๋ํ๋ฅผ ํ์ํ๊ณ ์กฐ์ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์คํ๋ง ์์ Java์ ๋ค์ํ ์ฐ์ฐ์์ ํจ์๋ฅผ ์ง์ํ๋ฉฐ, ๊ฐ์ฒด์ ํ๋กํผํฐ๋ ๋ฉ์๋ ํธ์ถ, ๋ฆฌ์คํธ๋ ๋งต์ ์ธ๋ฑ์ค ์ ๊ทผ, ์ฐ์ ์ฐ์ฐ ๋ฑ ๋ค์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
์ฑ
์ ์์ ์์๋ ์ ์ค ์ดํ์๋ง ์๋ํฌ์ธํธ ์ ๊ทผ์ ํ์ฉํ๋ ๊ฒฝ์ฐ ๋ฑ SpEL ์์ ํ์ฉํด์ access()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๋ ๋ฒ์ฉ์ ์ธ ์ํฉ์์ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
๊ถํ์ด ์๋ ๊ฒฝ์ฐ์ ์์ฒญ์ HTTP ์ํ๋ HTTP 403 Forbidden
์ ๋ฐํํฉ๋๋ค.
์ฌ๊ธฐ์ ๊ถํ๊ณผ ๋ฌ๋ฆฌ ์ญํ ์ ๊ถํ๋ณด๋ค ๊ฒฐ์ด ๊ตต์๋ฐ ๊ฐ์ GrantedAuthority๊ฐ ์ฌ์ฉ๋๋ฉฐ, ์ญํ ์ด๋ฆ์ ROLE_
๋ก ์์ํด์ผ ํฉ๋๋ค.
User.withUsername("john")
.password("qwer1234")
.authorities("ROLE_MEMBER")
.build();
permitAll()
๋ก ๋ชจ๋ ์ ๊ทผ ๊ถํ์ ํ์ฉํ ์ ์์ผ๋ฉฐ, denyAll()
๋ก ๋ชจ๋ ์์ฒญ์ ๊ฑฐ๋ถํ ์๋ ์์ต๋๋ค.
8์ฅ. ๊ถํ ๋ถ์ฌ ๊ตฌ์ฑ: ์ ํ ์ ์ฉ
์ ํ๊ธฐ ๋ฉ์๋๋ก ์๋ํฌ์ธํธ ์ ํ
์คํ๋ง ์ํ๋ฆฌํฐ์์ ์ ๊ณตํ๋ ์ ํ๊ธฐ ๋ฉ์๋๋ MVC ์ ํ๊ธฐ, ์คํธ ์ ํ๊ธฐ, ์ ๊ท์ ์ ํ๊ธฐ๊ฐ ์์ต๋๋ค.
์ด์ฉํ๋ ์ ํ๊ธฐ๊ฐ ์ด๋ค ๊ฒ์ด์ง๋ ๋ชจ๋ฅด๊ณ ํ๋ ๋ณต์ฌ-๋ถ์ฌ๋ฃ๊ธฐ ํ๋ก๊ทธ๋๋ฐ์ ์ํํ ์ ๊ทผ๋ฒ์ ์ด๋ณด ๊ฐ๋ฐ์๊ฐ ๋๋ฌด ์์ฃผ ์ด์ฉํฉ๋๋ค.
์ด๋ป๊ฒ ์๋ํ๋์ง ์ดํดํ๊ธฐ ์ ์๋ ์ด์ฉํ์ง ๋ง์์ผ ํฉ๋๋ค!
MVC ์ ํ๊ธฐ
์ฑ
์์๋ ์คํธ ์ ํ๊ธฐ๋ณด๋ค MVC ์ ํ์ ๊ถ์ฅํ๋๋ฐ, ๋งคํ์ผ๋ก ์ธํ ์ํ์ ๋ฐฉ์งํ๊ธฐ ์ํจ์
๋๋ค.
http.authorizeRequests()
.mvcMatchers(HttpMethod.Post, "/hello").authenticated()
// ๊ธธ์ด์ ๊ด๊ณ์์ด ์ซ์๋ฅผ ํฌํจํ๋ ๋ฌธ์์ด์ ๋ํ๋ด๋ ์ ๊ท์
.mvcMatchers("/reg/{code:^[0-9]*$*}").permitAll()
.anyRequest().authenticated();
์คํธ ์ ํ๊ธฐ
MVC ์ ํ๊ธฐ์ ๋ฌ๋ฆฌ ์คํ๋ง MVC์ ์๋์ ๊ณ ๋ คํ๋ ๊ฒ์ด ์๋ ๋ฐฉ์์ผ๋ก /hello ๊ฒฝ๋ก์ ์๋ํฌ์ธํธ๋ฅผ ์ค์ ํ๋ค๊ณ ํ์ ๋, /hello/ ๊ฒฝ๋ก๋ ๋ณดํธ๋์ง ์์ต๋๋ค. ์ฆ ํ์คํ ๊ฒฝ๋ก ์ค์ ์ด ํ์ํฉ๋๋ค.
http.authorizeRequests()
.antMatchers("/hello/**").hasRole("ADMIN")
.anyRequest().authenticated();
์ ๊ท์ ์ ํ๊ธฐ
์จ๋ผ์ธ ์ ๊ท์ ์ ํ๊ธฐ
MVC์ ์ํธ ์์ผ๋ก ํด๊ฒฐํ ์ ์๋ ๊ฒฝ์ฐ์ ์ด์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
http.authorizeRequests()
.regexMatchers(".*/(kr|ca)+/(en).**").authenticated()
.anyRequest().authenticated();
์๋ชป๋ ์๊ฒฉ ์ฆ๋ช
์ผ๋ก ์๋ํฌ์ธํธ๋ฅผ ํธ์ถํ๋ฉด ๊ถํ์ด ์๋๋ผ๋ ์ธ์ฆ ํํฐ์์ ๋จผ์ ์ธ์ฆ์ด ์คํจํ๋ ๊ฒฝ์ฐ ๊ถํ๋ถ์ฌ ํํฐ๊น์ง ๊ฐ์ง ์๊ณ ์๋ต ์ํ HTTP 401 Unauthorized
๋ฅผ ๋ฐํํฉ๋๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก CSRF(์ฌ์ดํธ ๊ฐ ์์ฒญ ์์กฐ)์ ๋ํ ๋ณดํธ๋ฅผ ์ ์ฉํ๋๋ฐ ๋ชจ๋ ์๋ํฌ์ธํธ ํธ์ถ์ ์ํด ๋นํ์ฑํํ ์ ์์ต๋๋ค.
http.csrf().disable();
9์ฅ. ํํฐ ๊ตฌํ
์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ ๊ณตํ๋ ํํฐ BasicAuthenticationFilter ์ธ์ ๋ง์ถคํ ํํฐ ์ฒด์ธ์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๊ถํํํฐ ์ ์ด๋ฒคํธ๋ ๋ก๊น
ํํฐ ๊ตฌ์ถ์ด ๊ฐ๋ฅํด์ง๋๋ค.
์คํ๋ง์์ ์ ๊ณตํ๋ ํํฐ ๊ฐ์๋ ์์๊ฐ ์ด๋ฏธ ์ ํด์ ธ ์์ต๋๋ค. CorsFilter
โ CsrfFilter
โ BasicAuthenticationFilter
์์
๋๋ค.
๋ง์ถคํ ํํฐ ๊ตฌํ
public class RequestVaildationFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
var httpRequest = (HttpServletRequest) request;
var httpResponse = (HttpServletResponse) response;
String requestId = httpRequest.getHeader("Request_id");
if(requestId.isBlank){
httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
filterChain.doFilter(request, response);
}
}
๋ง์ถคํ ํํฐ ์์ ์ง์
์ด๋ ๊ฒ addFilterBefore
๋ฅผ ์ฌ์ฉํ๋ฉด, BasicAuthenticationFilter ์ ์ RequestVaildationFilter๋ฅผ ์ฌ์ฉํ ์ ์ ๋ฉ๋๋ค.
์ธ์ฆ ํต๊ณผ๋ฅผ ๊ธฐ๋กํ๋ ํํฐ๋ฅผ ๋ง๋ ๋ค๊ณ ํ๋ค๋ฉด BasicAuthenticationFilter ๋ค์ ๋ฐฐ์นํ ์ ์๋๋ก addFilterAfter
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
http.addFilterBefore(
new RequestVaildationFilter(),
BasicAuthenticationFilter.class)
.authorizeRequests().anyRequest().permitAll();
)
๊ทธ๋ ๋ค๋ฉด ๋ค๋ฅธ ํํฐ ์์น์ ํํฐ๋ฅผ ์ถ๊ฐํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น?
BasicAuthenticationFilter์ ๋ฐฐ์นํ๋ ค๋ฉด addFilterAt
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ ํํฐ ๋์ฒด๊ฐ ์๋ ์ถ๊ฐ์ ์ธ ๊ฐ๋
์ผ๋ก ๋ด์ผํฉ๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ์ ์๋๋ฆฌ์ค์ ๋ํด์ ์ฑ ์์๋ ๋ค์ ์ธ ๊ฐ์ง ์ฌ๋ก๋ฅผ ๋ค๊ณ ์์ต๋๋ค.
- ์ธ์ฆ์ ์ํ ์ ์ ํค๋ ๊ฐ์ ๊ธฐ๋ฐ์ ๋ ์๋ณ
- ์ํธํ ์๋ช ์ด ์๋ ๋จ์ํ ๋ฌธ์์ด ์ ๊ณต์ผ๋ก ์ธ์ฆํ๋ ๊ฒฝ์ฐ
- ๋์นญ ํค๋ฅผ ์ด์ฉํด ์ธ์ฆ ์์ฒญ ์๋ช
- ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ํค๋ฅผ ๊ณต์ ํ๋ ๊ฒฝ์ฐ
- ์ธ์ฆ ํ๋ก์ธ์ค์ OTP ์ด์ฉ
- ํ๊ฐ ์ธ์ฆ ์๋ฒ์์ OTP๋ฅผ ์ป์ด์ ์ ํ๋ฆฌ์ผ์ด์ ๋ค๋จ๊ณ ์ธ์ฆ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
์ค์ ์๋๋ฆฌ์ค์์ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ ์ฅํ ๋๋ ๋น๋ฐ ๋ณผํธ๋ฅผ ์ด์ฉํด์ผ ํฉ๋๋ค.
UserDetailsService ๊ตฌ์ฑ์ ๋นํ์ฑํ ํ๊ธฐ ์ํด์ exclude
ํน์ฑ์ ์ด์ฉํ ์ ์์ต๋๋ค.
@SpringBootApplication(exclude={UserDetailsServiceAutoConfiguration.class})
OncePerRequestFilter
์คํ๋ง ์ํ๋ฆฌํธ์์ ์ ๊ณตํ๋ ํํฐ๋ฅผ ์ด์ฉํ๋ ๊ฒ๋ ์ข์ง๋ง ์ต๋ํ ๊ฐ๋จํ๊ฒ ๊ตฌํํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋ค๋ฉด ๊ทธ๋ฅ ํ์ฅํ๋ ๊ฒ์ ํผํด์ผ ํฉ๋๋ค. ๊ฐ์ ์์ฒญ์ ํ ๋ฒ์ ํํฐ ํธ์ถ์ ์ํด OncePerRequestFilter ํด๋์ค๋ฅผ ์ด์ฉํด ํํฐ๋ฅผ ๊ตฌํํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค.
OncePerRequestFilter๋ ํ์์ ํ๋ณํํ์ฌ HttpServletReqeust ๋ฐ HttpServletResponse๋ก ์ง์ ์์ฒญ์ ์์ ํฉ๋๋ค. ํํฐ์์ ์์ฒญ ๋ฐ ์๋ต์ ์ง์ ์กฐ์ํ ์ ์์ผ๋ฏ๋ก ํ์ํ ์์
์ ์ํํ ์ ์์ต๋๋ค. ์ด๋ฌํ ๊ฐ์ฒด๋ฅผ ์ง์ ์ฌ์ฉํ๋ฉด ํํฐ์์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์๋น์ค์ ์์กดํ์ง ์์๋ ๋๋ฏ๋ก ๋
๋ฆฝ์ฑ๊ณผ ์ ์ฐ์ฑ์ ๋์ฌ์ค๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ๋น๋๊ธฐ ์์ฒญ์ด๋ ์ค๋ฅ ๋ฐ์ก ์์ฒญ์๋ ์ ์ฉ๋์ง ์์ต๋๋ค. ํ์ง๋ง ๋ฉ์๋ ์ฌ์ ์๋ก ๊ฐ๋ฅํ๊ฒ ํ ์ ์์ต๋๋ค. ํํฐ๊ฐ ์ ์ฉ๋ ์ง ์ฌ๋ถ๋ ๋ฉ์๋ ์ฌ์ ์๋ก ์ ํ ์ ์์ต๋๋ค.
10์ฅ. CSRF ๋ณดํธ์ CORS ์ ์ฉ
CSRF(์ฌ์ดํธ ๊ฐ ์์ฒญ ์์กฐ) ๋ณดํธ ์ ์ฉ
POST, PUT, DELETE๋ฅผ ๋น๋กฏํ ๋ณ๊ฒฝ ํธ์ถ ํ์ด์ง๋ ์๋ต์ผ๋ก CSRF ํ ํฐ์ ๋ฐ๊ณ ๋ค์ ํธ์ถ์ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ์ฌ๊ธฐ์ CSRF ๊ณต๊ฒฉ์ ์ฌ์ฉ์๊ฐ ์
์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์๋ ํ์ด์ง๋ฅผ ์ด์์ ๋, ์ฌ์ฉ์ ๋์ ์์
์ ์ํํ๊ฒ ๋ฉ๋๋ค.
CsrfFilter
๋ฅผ ํตํด ๋ฐ๊ธ๋๋๋ฐ, ์ด ํํฐ๋ GET, HEAD, TRACE, OPTIONS๋ฅผ ์ฌ์ฉํ HTTP ๋ฐฉ์์ ์์ฒญ์ ๋ชจ๋ ํ์ฉํ๊ณ CSRF ํ ํฐ์ ๋ด์์ ์๋ตํฉ๋๋ค.
POST, PUT, DELETE ์ ์๋ํฌ์ธํธ์์ ๊ฐ๋ฐ์ ํ๊ธฐ ์ํด์๋ CSRT ๋ณดํธ ํ์ฑํ๋ ๊ฒฝ์ฐ ํ ํฐ์ด ํ์ํ๋ฐ, _csrf
ํน์ฑ์ ์ถ๊ฐ๋์ด ์๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์๋ต ๋๋ thymeleaf๋ฅผ ์ด์ฉํด ์ถ๋ ฅํฉ๋๋ค.
CSRF ๋ณดํธ๋ ๋ธ๋ผ์ฐ์ ์์ ์คํ๋๋ ์น ์ฑ์ ์ฌ์ฉ๋๋ฉฐ, ๋ก๊ทธ์ธ๊ณผ ๊ฐ์ด ์ฑ ๋ด์์ ์ํํ๋ ๋ณ๊ฒฝ๋๋ ์์ ์ด ์๋ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ฐ์ ์๋ฒ๊ฐ ํ๋ฐํธ์ ๋ฐฑ์ ๋ด๋นํ๋ ์ํคํ ์ฒ์์๋ ์ ์๋ํ์ง๋ง ์๋ฃจ์ ์ด ๋ ๋ฆฝ์ ์ผ ๋๋ ๊ทธ์ ๋ฐ๋ฅธ ๋ณด์ ์ ๊ทผ๋ฒ(11~15์ฅ)์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
Spring security๋ ๊ธฐ๋ณธ์ ์ผ๋ก CSRF ๋ณดํธ๋ฅผ ์ง์ํ๋๋ฐ, ์๋ฒ์์ ์์ฑ๋ ๋ฆฌ์์ค๋ฅผ ์ด์ฉํ๋ ํ์ด์ง๊ฐ ๊ฐ์ ์๋ฒ์์ ์์ฑ๋ ๊ฒฝ์ฐ์๋ง ์ด์ฉํฉ๋๋ค.
๊ทธ๋ผ ๋ชจ๋ ๊ฒฝ๋ก๊ฐ ์๋๋ผ ์ผ๋ถ ๊ฒฝ๋ก์๋ง ๋ณดํธ๋ฅผ ์ค์ ํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น?
CSRF ๋ณดํธ๋ฅผ ์๋ ์ฝ๋ ์ฒ๋ผ disable
์ฒ๋ฆฌํ๋ ๊ฒ์ด ์๋๋ผ ์ ๊ณต๋๋ handler์ matcher๋ค ์ด์ฉํด์ ๋ณดํธ ์ ์ธ ๊ฒฝ๋ก๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค.
http.httpBasic(HttpBasicConfigurer::disable)
.csrf(CsrfConfigurer::disable)
์๋ฒ ์ชฝ ์ธ์
์ csrf ํ ํฐ์ ์ ์ฅํ๋ ๊ฒ์ ์ํ์ ํ์ฅ์์ ์ ํฉํ์ง ์์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํ ํฐ์ ๊ด๋ฆฌํ๋ ๋ฐฉ์์ผ๋ก ์ ํํ๊ธฐ ์ํด์๋ CsrfTokenRepository
๋ฅผ ์๋ก ๊ตฌํํด์ ์ธ์
ID๋ฅผ ์ด์ฉํด ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
CsrtTokenRepository
๊ตฌํ์ฒด๋ฅผ ๋ง๋ค๋ฉด override๋ generateToken()
๊ณผ saveToken()
, loadToken()
๋ฉ์๋๋ฅผ ๊ตฌํํด์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
๋ค๋ฅธ ๋์์ผ๋ก๋ ํ ํฐ์ ๋ง๋ฃ์๊ฐ์ ์ ํด์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ๋ ์์ต๋๋ค.
CORS(๊ต์ฐจ ์ถ์ฒ ๋ฆฌ์์ค ๊ณต์ ) ์ด์ฉ
CORS๋ ์ผ๋ถ ์กฐ๊ฑด์์ ์๋ก ๋ค๋ฅธ ์ถ์ฒ ๊ฐ ์์ฒญ์ ํ์ฉํฉ๋๋ค. ์๋ฅผ ๋ค์ด domain.com
์์ domain2.com
์์ domain.com
์ REST ์๋ํฌ์ธํธ๋ฅผ ํธ์ถํ๋ค๊ณ ํ ๋, ํธ์ถ์ ๊ฑฐ๋ถ๋ฉ๋๋ค. CORS๋ฅผ ์ด์ฉํ๋ฉด ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
Access-Control-Allow-Origin
์ ๊ทผ ๊ฐ๋ฅํ ์ธ๋ถ ๋๋ฉ์ธ ์ง์ Access-Control-Allow-Methods
๋ค๋ฅธ ๋๋ฉ์ธ์ ๋ํ ์ ๊ทผ์ ํ์ฉํ์ง๋ง ํน์ ์๋ํฌ์ธํธ๋ง ํ์ฉAccess-Control-Allow-Headers
ํน์ ์์ฒญ์ ์ด์ฉํ ์ ์๋ ํค๋์ ์ ํ์ ์ถ๊ฐ
CSRF์ ๊ฐ๋ ์ ํท๊ฐ๋ฆฌ์ง ๋ง์์ผํ๋๋ฐ CORS๋ ๊ต์ฐจ ๋๋ฉ์ธ ํธ์ถ์ ์ํํด์ฃผ๋ ๊ฐ๋ ์ด๊ณ ๋ธ๋ผ์ฐ์ ์ ๊ดํ ๊ฒ์ด๋ฉฐ ์๋ํฌ์ธํธ๋ฅผ ๋ณดํธํ๋ ๋ฐฉ๋ฒ์ ์๋๋๋ค. CSRF ๋ณดํ๋ ๊ณต๊ฒฉ์ ๋ง๋ ๊ฐ๋ ์ ๋๋ค.
HTTP OPTIONS ๋ฐฉ์์ผ๋ก ์ฌ์ ํ ์คํธ ์์ฒญ์ ํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ์ด ์์ฒญ์ด ์คํจํ๋ฉด ๋ธ๋ผ์ฐ์ ๋ ์๋ ์์ฒญ์ ์๋ฝํ์ง ์์ต๋๋ค.
CSRF์ ์ ์ฑ
์ @CrossOrigin
์ผ๋ก ์ ์ฉํ ์ ์์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ๋ฉ์๋ ์์ ์ ์ฉํ๋ฉด ํด๋น ๋ฉ์๋๋ localhost ์ถ์ฒ์ ๋ํ ๊ต์ฐจ ์ถ์ฒ ์์ฒญ์ ํ์ฉํ๊ฒ ๋ฉ๋๋ค. ์ด ๋ *
๋ก ๋์ ๋ฒ์์ ์ถ์ฒ๋ฅผ ํ์ฉํ๋ ๊ฒฝ์ฐ๋ XXS(๊ต์ฐจ ์ฌ์ดํธ ์คํฌ๋ฆฝํ
) ์์ฒญ์ ๋
ธ์ถ๋์ด DDoS ๊ณต๊ฒฉ์ ์ทจ์ฝํด์ง ์ ์์ต๋๋ค.
@CrossOrigin("http://localhost:8080")
ํ์ง๋ง ๊ฐ๋ณ ๊ด๋ฆฌ๋ฅผ ์ด๋ฐ ์์ผ๋ก ํ๊ฒ ๋๋ฉด, ๊ด๋ฆฌ๊ฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์ Security Config์ CorsConfigurationSource
๋ฅผ ์ ์ํด์ ํ์ฉํ ์ถ์ฒ์ ๋ฉ์๋๋ฅผ ์ง์ ํด์ cors()
์ ์ ์ฉํ ์ ์์ต๋๋ค.
11์ฅ. ์ค์ : ์ฑ ์์ ๋ถ๋ฆฌ
์ธ์ฆ๋
ผ๋ฆฌ
OTP ํ ํฐ ์ธ์ฆ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊ฒ์ธ๊ฐ์ ๋ํด ๊ทธ ์์๋ฅผ ์ฑ
์์ ์ ์ํ ์์๋๋ก ํ๊ธฐํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํด๋ผ์ด์ธํธ๊ฐ ์๋ํฌ์ธํธ์ ํจ๊ป ์๊ฒฉ์ฆ๋ช ์ ๋ณด๋ ๋๋ค.
- ๊ทธ๋ผ ๋น์ฆ๋์ค ๋ ผ๋ฆฌ ์๋ฒ๋ ์ธ์ฆ์๋ฒ์์ ์ฌ์ฉ์๋ฅผ ์ธ์ฆํ๊ณ OTP๋ฅผ ๋ณด๋ ๋๋ค.
- ์ฌ๊ธฐ์ ์ธ์ฆ์๋ฒ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฌ์ฉ์๋ฅผ ์ธ์ฆํฉ๋๋ค.
- ๊ทธ๋ฆฌ๊ณ OTP๋ฅผ ํด๋ผ์ด์ธํธ์ ๋ณด๋ ๋๋ค.
๋น์ง๋์ค ๋ ผ๋ฆฌ์๋ฒ์ ์ธ์ฆ์๋ฒ๊ฐ ์๋ก OTP๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๊ฑด ์๋ชป ๋๋ค๊ณ ํ ์ ์์ง๋ง ์ฑ ์์๋ ์ค๋ช ์ ์ํด ์ฌ์ด ๊ตฌํ ์์๋ฅผ ๋ค์ด ์ค๋ช ํ๊ธฐ ์ํด ์ด๋ ๊ฒ ์ค์ ํ๋ค๊ณ ํฉ๋๋ค.
ํ ํฐ
์๋ํฌ์ธํธ ์ ๊ทผ์ ์ํด ํค๋์ ํ ํฐ(UUID ํน์ JWT ๋ฑ)์ด๋ผ๋ ๋ฌธ์์ด์ ๋ฃ์ด์ ์ธ์ฆํ๊ฒ ๋ฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์
๋ฆฌ์์ค์ ์ ๊ทผํ ์ ์์ต๋๋ค.
- ์์ฒญ์๋ง๋ค ์๊ฒฉ ์ฆ๋ช ๊ณต์ ํ ํ์๊ฐ ์์ต๋๋ค.
- ์๋ช ์ ์ง์ ํด ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
- ํ ํฐ์ ์ธ๋ถ์ ๋ณด๋ฅผ ์ ์ฅํ ์ ์์ต๋๋ค.
JWT(JSON Web Token)
JSON ๋ฐ์ดํฐ ํ์์ ํฌํจํ๋ ํ ํฐ์ ๋งํฉ๋๋ค. ๋ง์นจํ๋ก ๋ถ๋ฆฌ๋ ํค๋, ๋ณธ๋ฌธ, ์๋ช
์ ์ธ ๊ฐ Base64 ์ธ์ฝ๋ฉ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ์๋ฐ์์๋ JJWT ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ง์ํด JWT ํ ํฐ ์์ฑ์ ์ฝ๊ฒ ํ ์ ์๋๋ก ์ง์ํฉ๋๋ค.
JJWT github overview์์ ์ฌ์ฉ๋ฒ, builder()
์ฒ๋ฆฌ, claims
, parser()
, key ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ๋ํด ์ ์ ์์ต๋๋ค.
12์ฅ. OAuth 2๊ฐ ์๋ํ๋ ๋ฐฉ๋ฒ
OAuth 2๋ ๊ถํ ๋ถ์ฌ ํ๋ ์์ํฌ๋ผ๊ณ ๋ ํ๋ฉฐ, ์์ ํ๋กํ ์ฝ์ด๋ผ๊ณ ๋ ํฉ๋๋ค.
HTTP Basic์์๋ ์ธ์ฆ๋ฐฉ์์์ ๋ชจ๋ ์์ฒญ์ ์๊ฒฉ์ฆ๋ช
์ ๋ณด๋ด์ผ ํ๊ณ ๋ณ๋ ์์คํ
์ด ๊ด๋ฆฌํด์ผํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ด ๋ป์ ์ฆ ๋คํธ์ํฌ์ ์๊ฒฉ์ฆ๋ช
์ด ์์ฃผ ๊ณต์ ๋๊ณ ๋ณด์์ ์ทจ์ฝํด์ง๋๋ค.
OAuth 2๋ ๋ฆฌ์์ค ์๋ฒ, ์ฌ์ฉ์, ํด๋ผ์ด์ธํธ, ๊ถํ ๋ถ์ฌ ์๋ฒ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ๋ฆฌ์์ค ์์ ์๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์์ฒญ์ ๋ณด๋ด๊ณ ๊ถํ ๋ถ์ฌ ์๋ฒ์์ ๋ฐ์ดํฐ ์์
์น์ธ์ ๋ฐ์๋ค๋ ์ฆ๋ช
์ ์ ๊ณต ๋ฐ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ์ฆ๋ช
์ ๋ฆฌ์์ค ์๋ฒ์ ๋๊ฒจ ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ด์ฉํ ์ ์๋๋ก ํฉ๋๋ค.
๊ถํ ๋ถ์ฌ ์๋ฒ๋ ํด๋ผ์ด์ธํธ์ ์ก์ธ์ค ํ ํฐ์ ์ ๊ณตํ ๋ ๋ฆฌ๋๋ ์
URI๋ก ํด๋ผ์ด์ธํธ๋ฅผ ํธ์ถํฉ๋๋ค.
์ฌ์ฉ์ - ํด๋ผ์ด์ธํธ - ๊ถํ ๋ถ์ฌ ์๋ฒ - ๋ฆฌ์์ค ์๋ฒ
OAuth 2 ๊ทธ๋ํธ ์ ํ
OAuth 2์์ ๊ทธ๋ํธ ์ ํ์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ์ ํ์ผ๋ก ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์น์ธ ์ฝ๋ ๊ทธ๋ํธ ์ ํ
- ์น์ธ ์ฝ๋ ๊ทธ๋ํธ ์ ํ์ผ๋ก ์ธ์ฆ ์์ฒญ ์ํ
- ์ฌ์ฉ์๋ ์๋ํฌ์ธํธ๋ฅผ
response_type
,client_id
,redirect_uri
,scope
,state
๊ฐ ๋ด๊ธด ์ฟผ๋ฆฌ๋ก ํธ์ถํฉ๋๋ค. - ์ธ์ฆ ์ฑ๊ณต์ ๋ฆฌ๋ค์ด๋ ํธ URI๋ก ํด๋ผ์ด์ธํธ๋ฅผ ํธ์ถํ๊ณ ์ฝ๋์ ์ํ๊ฐ์ ์ ๊ณตํฉ๋๋ค.
- ์ฌ์ฉ์๋ ์๋ํฌ์ธํธ๋ฅผ
- ์น์ธ ์ฝ๋ ๊ทธ๋ํธ ์ ํ์ผ๋ก ์ก์ธ์ค ํ ํฐ ์ป๊ธฐ
- ์น์ธ ์ฝ๋๋ฅผ ๊ฐ์ง๊ณ ์ธ์ฆ ์๋ฒ์์ ํ ํฌ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ด ๋ ์์ฒญ์๋
code
,client_id
,client_secret
,redict_uri
,grant_type
๊ณผ ๊ฐ์ ์ธ๋ถ์ ๋ณด๊ฐ ๋ค์ด์์ต๋๋ค.
- ์น์ธ ์ฝ๋ ๊ทธ๋ํธ ์ ํ์ผ๋ก ๋ณดํธ๋ ๋ฆฌ์์ค ํธ์ถ
์ํธ ๊ทธ๋ํธ ์ ํ
์น์ธ ์ฝ๋ ๊ทธ๋ํธ ์ ํ๊ณผ ๋ฌ๋ฆฌ ํด๋ผ์ด์ธํธ์ ๊ถํ ๋ถ์ฌ ์๋ฒ๋ฅผ ๊ฐ์ ์กฐ์ง์์ ๊ตฌ์ถํ ๊ฒฝ์ฐ์ ์ด์ฉํฉ๋๋ค. ๊ฑฐ์ ์ ์ฌํ์ง๋ง ์น์ธ ์ฝ๋๋ฅผ ๋ฐ๋ ๊ณผ์ ์์ด ํด๋ผ์ด์ธํธ๋ grant_type
, client_id
, client_secret
, scope
, username
, password
๊ฐ ๋ด๊ธด ์ธ๋ถ์ ๋ณด๋ฅผ ๋ณด๋ด๊ณ ์ก์ธ์ค ํ ํฐ์ ๋ฐ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ํ ํฐ์ผ๋ก ๋ฆฌ์์ค๋ฅผ ํธ์ถํฉ๋๋ค.
์ฆ, ๋ฆฌ์์ค ์์ ์๊ฐ ํด๋ผ์ด์ธํธ๋ฅผ ์ ๋ขฐํ ์ ์๋ ํ๊ฒฝ์์ ๊ฐ๋ฅํฉ๋๋ค.
ํด๋ผ์ด์ธํธ ์๊ฒฉ ์ฆ๋ช
๊ทธ๋ํธ ์ ํ
์ฌ๊ธฐ์๋ ์ฌ์ฉ์๊ฐ ๊ด์ฌํ์ง ์๊ณ , ๋ ์ ํ๋ฆฌ์ผ์ด์
๊ฐ ์ธ์ฆ์ ๊ตฌํํ๋ ๋ฐฉ์์ ๋งํฉ๋๋ค. grant_type
, client_id
, client_secret
, scope
๋ฅผ ๊ฐ์ง๊ณ ๊ถํ ๋ถ์ฌ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ธ ๋ค, ์ก์ธ์ค ํ ํฐ์ ๋ฐ์ ๋ฆฌ์์ค ์๋ฒ์ ํธ์ถํ๋ ๋ฐฉ์์
๋๋ค.
๊ฐฑ์ ํ ํฐ์ ์ด์ฉํ๊ธฐ
์ํธ ๊ทธ๋ํธ ์ ํ์ ์ฌ์ฉ์์๊ฒ ์ฌ์ธ์ฆ ์์ฒญ์ ํ๊ฑฐ๋ ์๊ฒฉ์ฆ๋ช
์ ์ ์ฅํด์ผ ํฉ๋๋ค. ๋ฐ๋ฉด ๊ฐฑ์ ํ ํฐ์ด ์๋ค๋ฉด, ๋ณด์ ์ํ์ด ์ค์ด๋ค๊ณ ๊ถํ ๋ถ์ฌ ์๋ฒ์์ ์์ธ์ค ํ ํฐ๊ณผ ํจ๊ป ๊ฐ์ด ๋ฐํ๋ฉ๋๋ค.
ํด๋ผ์ด์ธํธ ์๊ฒฉ ์ฆ๋ช
๊ทธ๋ํธ ์ ํ์์๋ ๋ถํ์ํ๋ฐ, ์ฌ์ฉ์ ์๊ฒฉ์ฆ๋ช
์ด ํ์ํ์ง ์๊ธฐ ๋๋ฌธ์
๋๋ค.
OAuth 2๋ ํ๋ ์์ํฌ์ด๋ฏ๋ก ๊ธฐ๋ฅ์ ์๊ณ ์ ๋๋ก ๊ตฌํํด์ผ ์ ํ๋ฆฌ์ผ์ด์ ์ทจ์ฝ์ฑ์ ์ํํ ์ ์์ต๋๋ค.
๊ฐ๋จํ SSO(Single Sign-On) ์ ํ๋ฆฌ์ผ์ด์
๊ตฌํ
๊ถํ๋ถ์ฌ ์๋ฒ๋ฅผ ๊นํ๋ธ๋ฅผ ์ด์ฉํด ์น์ธ ์ฝ๋ ๊ทธ๋ํธ ์ ํ์ ๊ตฌํํ ์ ์์ต๋๋ค.
๋จผ์ ๊นํ OAuth application์ ๋ฑ๋กํด์ผ ํฉ๋๋ค.
์ข ์์ฑ ์ถ๊ฐ
- spring-boot-starter-oauth2-client
- spring-boot-starter-security
- spring-boot-starter-web
Security Config ์ค์ ์๋ http.oauth2Login()
์ ์ฌ์ฉํด ๊ตฌํํ ์ ์์ต๋๋ค. formLogin()
์ฒ๋ผ ํํฐ์ฒด์ธ์ OAuth2LoginAuthenticationFilter ์ ์ธ์ฆ ํํฐ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
ClientRegistration ์ธ์คํด์ค
ClientRegistration cr =
ClientRegistration.withRegistrationId("github")
.clientId("ae4a6f5a46fe6af5vs")
.clientSecret("1f6sadf86a5wef364f51dcs8ae4615")
.scope(new String[]("read:user"))
.authorizationUri("https://github.com/login/oauth/authorize")
.tokenUri("https://github.com/login/oauth/access_token")
.userInfoUri("https://api.github.com/user")
.userNameAttributeName("id")
.clientName("Github")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
.build();
์ด๋ฏธ ์คํ๋ง ์ํ๋ฆฌํฐ์์๋ withRegistrationId()
๋ฅผ ์ด์ฉํ ์ค์ ์ ํ์ง ์์๋ ์ค์ ํ ์ ์๋๋ก ์ผ๋ฐ์ ์ธ ๊ณต๊ธ์์ ๋ํ ์ธ์ฆ์ ๋ค์๊ณผ ๊ฐ์ด ClientOAuth2Provider๋ก ์ง์ํ๊ณ ์์ต๋๋ค.
ClientRegistration cr =
ClientOAuth2Provider.GITHUB
.getBuilder("github")
.clientId("a4f6a5e3ws48d615a")
.clientSecret("a1465eafw184651waf1aw4615waw")
.build();
์์ฑ์์ ์ ์์ง spring security doc์์ ์์ง spring boot 2.x๋ก ์ค๋ช ํ๊ณ ์๋์ง ๋ชจ๋ฅด๊ฒ ์ง๋งโฆ.?! ์ด ์ฑ ์ ์๋ ํ๊ณ spring boot 3.x ๊ธฐ์ค oauth 2 ์ค์ ์ํ ์ฝ๋์ ๋งํฌ๋ฅผ ์ ๋ฐ์ดํธํด์ผ ๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์๊ธฐ์ ์ฐ์ ์ฑ ๊ธฐ์ค์ผ๋ก ์ค๋ช ํ๊ณ ๋์ด๊ฐ๊ฒ ์ต๋๋ค;;
ClientRegistrationRepositroy ๊ตฌํ
UserDetailsService์ ์ ์ฌํ๋ฉฐ, spring security์์๋ ClientRegistrationRepositroy์ ๊ตฌํ์ธ InMemoryClientRegistrationRepository๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋น์ผ๋ก ๋ฑ๋กํ๊ฑฐ๋ oauth2login()
๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ก Customizer ๊ฐ์ฒด๋ฅผ ์ด์ฉํด์ ๋ฑ๋กํ ์ ์์ต๋๋ค.
์ ์ฒด SSO ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌํ์ ์ํ ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- OAuth application ๋ฑ๋กํ๊ณ ,
client_id
,client_secret
๋ฐ์์ค๊ธฐ - ์ข ์์ฑ ์ถ๊ฐ
- Security Configuration์ผ๋ก OAuth 2๋ฅผ ์ํ ํํฐ ์ ์ฉ
- ClientRegistration ์ธ์คํด์ค ๊ตฌํ
- ClientRegistrationRepositroy ๊ตฌํ
- OAuth2AuthenticationToken์์ ์ธ์ฆ ์ฌ์ฉ์์ ์ธ๋ถ ์ ๋ณด ์ป์ด์ค๊ธฐ
13์ฅ. OAuth 2: ๊ถํ ๋ถ์ฌ ์๋ฒ ๊ตฌํ
์์ ๋ฐฐ์ด OAuth 2 ๊ทธ๋ํธ ์ ํ์ ๋ฐ๋ฅธ ๊ถํ ๋ถ์ฌ ์๋ฒ๋ฅผ ๊ตฌํํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ๊นํ, ๊ตฌ๊ธ ๋ฑ ๊ถํ๋ถ์ฌ ์๋ฒ๋ฅผ ์ด์ฉํ๋ ๊ฒฝ์ฐ๋ ํด๋นํ์ง ์์ง๋ง ์ง์ ๊ถํ ๋ถ์ฌ ์๋ฒ๋ฅผ ๊ตฌํํ๋ ๊ฒฝ์ฐ์๋ ๋ง์ถคํ์ผ๋ก ๊ถํ ๋ถ์ฌ ์๋ฒ ๊ตฌํ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ์น์ธ์ฝ๋ ๊ทธ๋ํธ ์ ํ
- ์ํธ ๊ทธ๋ํธ ์ ํ
- ํด๋ผ์ด์ธํธ ์๊ฒฉ ์ฆ๋ช ๊ทธ๋ํธ ์ ํ
ํ์ฌ Spring security๋ OAuth 2์ ๊ด๋ จํด์ ๋ก๊ทธ์ธ, ํด๋ผ์ด์ธํธ, ๋ฆฌ์์ค ์๋ฒ์ ๊ด๋ จ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ๊ด๋ จ Link
์ฑ
์์๋ spring security oauth 2์ ์ข
์์ฑ ์ง์์ด ์ค๋จ๋ ์์ ์ผ๋ก๋ถํฐ ๋ง์ถคํ ๊ถํ ๋ถ์ฌ ์๋ฒ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ค๋ฃจ๊ณ ์์ต๋๋ค.
(Keycloak, Okta์ ๊ฐ์ ํด์ ๋์์ผ๋ก ์ ํํ ์๋ ์์ต๋๋ค.)
๋ง์ถคํ ๊ถํ ๋ถ์ฌ ์๋ฒ ๊ตฌํ
@EnableAuthorizationServer๋ฅผ ์ฌ์ฉํด configuration์ ๊ถํ ๋ถ์ฌ ์๋ฒ ๊ตฌ์ฑ์ ์ํ ์ค๋น๋ฅผ ํฉ๋๋ค.
๊ถํ ๋ถ์ฌ ์๋ฒ๋ ์์ฒด ์๊ฒฉ์ฆ๋ช
์ด ํ์ํ๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์์ ํด๋ผ์ด์ธํธ์ ๋ํ ์๊ฒฉ์ฆ๋ช
์ ์ ์ฅํ๊ณ ํด๋ผ์ด์ธํธ ์๊ฒฉ์ฆ๋ช
์ด ๋ฑ๋ก๋ ๊ฒฝ์ฐ์ ํํด์ ๊ถํ์ ๋ถ์ฌํฉ๋๋ค.
๋ฉ๋ชจ๋ฆฌ์์ ClientDetails๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์ด๋ UserDetailsService๋ฅผ ์ด์ฉํ ๋์ ์ ์ฌํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋๋ฐ ์ค์ ๋ก๋ ๋ฉ๋ชจ๋ฆฌ๊ฐ ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ด์ฉํด ๊ตฌํํ๋ ๊ฒ์ด ๋ฐ๋์งํฉ๋๋ค.
clients.inMemory()
.withClient("client")
.secret("secret")
.authorizedGrantTypes("password")
.scope("read");
์น์ธ ์ฝ๋ ๊ทธ๋ํธ ์ ํ์ด ๊ฐ๋ฅํ๊ฒ ํ๊ธฐ ์ํด์๋ ํด๋ผ์ด์ธํธ์ ์ ๊ณต๋ ์น์ธ ์ฝ๋๋ฅผ ์ด์ฉํด์ ์ก์ธ์ค ํ ํฐ์ ์ป์ ์ ์๋๋ก ํด์ผ ํฉ๋๋ค. ์ฆ ๊ถํ ๋ถ์ฌ ์๋ฒ๋ ํด๋ผ์ด์ธํธ์ ๋ฆฌ๋๋ ์ URI์ ์น์ธ์ฝ๋๋ฅผ ๋ฐํํ ์ ์์ด์ผ ํฉ๋๋ค.
ํด๋ผ์ด์ธํธ๋ ๋ชจ๋ ๊ฐ์ ์๊ฒฉ์ฆ๋ช ์ผ๋ก ๊ถํ ๋ถ์ฌ ์๋ฒ์์ ์์ธ์ค ํ ํฐ์ ์ป์ ์ ์๋๋ก ๊ตฌํํ๋ ๊ฒ์ด ๋ฐ๋์งํฉ๋๋ค. ๊ทธ ์ด์ ๋ ํ๋์ ํ ํฐ์ ์ป๋ ๊ฒฝ์ฐ ๋ค๋ฅธ ํด๋ผ์ด์ธํธ๋ ๋ชจ๋ ํดํนํ ์ ์ ์ํ์ด ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
๊ฐฑ์ ํ ํฐ ์ ํ์ ์ด์ฉํด์ผ ํ๋ ๊ฒฝ์ฐ์๋ authorizedGrantTypes()
์ refresh_token
์ ํ์ ๋ฃ์ด์ฃผ์ด์ผ ํฉ๋๋ค.
14์ฅ. OAuth 2: ๋ฆฌ์์ค ์๋ฒ ๊ตฌํ
์ฌ์ฉ์๊ฐ ๋ฆฌ์์ค ์๋ฒ์ ์ ๊ทผํ๋ ค๊ณ ํ ๋, ๋ฆฌ์์ค ์๋ฒ๋ ์ฌ์ฉ์๊ฐ ์ ๋ฌํ ์ก์ธ์ค ํ ํฐ์ด ์ฌ๋ฐ๋ฅธ์ง ํ์ธํ๋ ๊ณผ์ ์ ๊ฑฐ์นฉ๋๋ค. ์ด ๋ ์ฌ์ฉ๋๋ ํ ํฐ ๊ฒ์ฆ ๋ฐฉ์์ ๋ค์ํ๋ฐ, ๊ถํ ๋ถ์ฌ ์๋ฒ์์ ๊ฒํ ํ๊ฒ ํ๋ ๋ฐฉ์, ๋ฆฌ์์ค ์๋ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐธ์กฐ ๋ฐฉ์, ํ ํฐ ์๋ช ์ ์ด์ฉํ๋ ๋ฐฉ์์ด ์์ต๋๋ค.
์ด ๋ถ๋ถ๋ ํ์ฌ spring security์ ์ฐจ์ด๊ฐ ์์ด์(@EnableResourceServer ์ง์ ์ค๋จ ๋ฑ) ํฐ ํ๋ฆ๋ง ์ง๊ณ ๋์ด๊ฐ๊ธฐ๋ก ํ์ต๋๋ค.
์๊ฒฉ์ผ๋ก ๊ฒ์ฆ
๋ฆฌ์์ค ์๋ฒ๊ฐ ์ง์ ๊ถํ ๋ถ์ฌ ์๋ฒ๋ฅผ ํธ์ถํ๋ ํ ํฐ ๊ฒ์ฆ์ ๊ตฌํํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ๋ฆฌ์์ค ์๋ฒ๋ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐ์ ํ ํฐ์ ๊ถํ ๋ถ์ฌ ์๋ฒ์ ์ ๋ฌํ๊ณ ์ฌ์ฉ์ ์ธ๋ถ ์ ๋ณด๋ฅผ ์ป์ต๋๋ค. ๋์ ์๋ก์ด ํ ํฐ์ด ์์ ๋๋ง๋ค ๊ถํ ๋ถ์ฌ ์๋ฒ๋ฅผ ํญ์ ํธ์ถํด์ผ ํ๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐธ์กฐ๋ฐฉ์
์ ๋ฐฉ์์ฒ๋ผ ๋งค๋ฒ ๊ถํ ๋ถ์ฌ ์๋ฒ์ ์ ๊ทผํ ํ์๋ ์ฌ๋ผ์ก์ง๋ง, ๊ณต์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ถ๊ฐํด์ผ ํ๊ณ ๋ณ๋ชฉ์ด ๋ฐ์ํ ์ ์๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
์ธ์ฆ ํํฐ์์ HTTP ์์ฒญ์ ๊ฐ๋ก์ฑ๊ณ ์ด๋ฅผ ํ ํฐ ์ ์ฅ์(TokenStore)์์ ํ ํฐ ๊ฒ์ฆ ํ ์ฌ์ฉ์ ์ธ๋ถ ์ ๋ณด๋ฅผ ๊ฒ์ํฉ๋๋ค. ์ด ์ธ๋ถ ์ ๋ณด๋ ๋ณด์ ์ปจํ
์คํธ์ ์ ์ฅํฉ๋๋ค.