๐ค ์ค๋ฅ ๋ฐ์
์๋ก์ด ํ ์ด ํ๋ก์ ํธ, NEO๋ฅผ ๊ฐ๋ฐํ๋ฉด์ ์์
๋ก๊ทธ์ธ ๊ธฐ๋ฅ ๊ตฌํ์ ์ํด OAuth2 Client
๊ฐ๋ฐ์ ์งํํ์ต๋๋ค.
๊ฐ๋ฐ์ด ๋๋ ํ, API ์์ฒญ์ ํตํด ํด๋น ๊ธฐ๋ฅ์ ๊ฒํ ํ๋ ๋์ค ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ OAuth2 ์ธ์ฆ ๊ณผ์ ์ ์งํํ ์ดํ ์๋ก์ด ํ์ ๊ฐ์ ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋์ง ์๋ ์ด์ํ ํ์์ ๋ฐ๊ฒฌํ์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฌธ์ ๋ผ๋ฉด, ๋ถ๋ช ๋ก๊ทธ๊ฐ ์์ฑ๋์์ํ ๋ฐ, ํด๋น ๋ก๊ทธ๊ฐ ์๋ ๊ฒ๋ ์ด์ํด ๋ฌธ์ ์ ์์ธ์ ํธ๋ํนํ์ต๋๋ค.
OAuth2 ์ธ์ฆ ๊ณผ์ ์ค, ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์ป์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๋ ์ฝ๋๋, OAuth2UserService<OAuth2UserRequest, OAuth2User>
๋ฅผ implementํ NEOOAuth2UserService
์ ์์ต๋๋ค.
@Slf4j
@Service
@RequiredArgsConstructor
public class NEOOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final NEOUserRepository userRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("CustomOAuth2UserService.loadUser() ์คํ - OAuth2 ๋ก๊ทธ์ธ ์์ฒญ ์ง์
");
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
NEOOAuth2ProviderType providerType = NEOOAuth2ProviderType.ofRegistrationId(registrationId);
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); // OAuth2 ๋ก๊ทธ์ธ ์ ํค(PK)๊ฐ ๋๋ ๊ฐ
Map<String, Object> attributes = oAuth2User.getAttributes(); // ์์
๋ก๊ทธ์ธ์์ API๊ฐ ์ ๊ณตํ๋ userInfo์ Json ๊ฐ(์ ์ ์ ๋ณด๋ค)
NEOOAuth2AttributesDTO extractAttributes = NEOOAuth2AttributesDTO.of(providerType, userNameAttributeName, attributes);
NEOUserEntity createdUser = getUser(extractAttributes, providerType); // getUser() ๋ฉ์๋๋ก User ๊ฐ์ฒด ์์ฑ ํ ๋ฐํ
return new NEOCustomOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(createdUser.getUserType().getKey())),
attributes,
extractAttributes.getNameAttributeKey(),
createdUser.getEmail(),
createdUser.getUserType(),
createdUser.getUserName(),
createdUser.getPhoneNumber(),
createdUser.getGender()
);
}
private NEOUserEntity getUser(NEOOAuth2AttributesDTO attributes, NEOOAuth2ProviderType providerType) {
NEOUserEntity foundUser;
try {
foundUser = userRepository.findByProviderTypeAndSocialID(providerType,
attributes.getOauth2UserInfo().getId())
.orElseGet(() -> saveUser(attributes, providerType));
} catch (Exception e){
throw new NEOUnexpectedException("OAuth ์งํ ๊ณผ์ ์ค, DB ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์๋ฒ ๊ด๋ฆฐ์์๊ฒ ๋ฌธ์ํ์ธ์.");
}
return foundUser;
}
private NEOUserEntity saveUser(NEOOAuth2AttributesDTO attributes, NEOOAuth2ProviderType socialType) {
NEOUserEntity createdUser = attributes.toEntity(socialType, attributes.getOauth2UserInfo());
return userRepository.save(createdUser);
}
}
ํด๋น ํด๋์ค์ loadUser()
์์, getUser()
์ saveUser()
๋ฅผ ํธ์ถํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
DB์ ์ ์ฅ๋์ง ์์๋ค๋ ๊ฒ์, getUser()
๋ฉ์๋๊ฐ ํธ์ถ๋์ง ์์๋ค๋ ๊ฒ์ด๊ณ , ๋ง์ฐฌ๊ฐ์ง๋ก loadUser()
๋ฉ์๋๊ฐ ํธ์ถ๋์ง ์์์์ ๋ปํฉ๋๋ค. ๋๋ฒ๊ฑฐ ํฌ์ธํธ๋ฅผ ํตํด์๋ ํ์ธํ ์ฌ์ค์
๋๋ค.
โ๏ธ Security Config Code
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class NEOSecurityConfig {
private final NEOOAuth2SuccessHandler oAuth2LoginSuccessHandler;
private final NEOOAuth2FailureHandler oAuth2LoginFailureHandler;
private final NEOOAuth2UserService customOAuth2UserService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// ํผ ๋ก๊ทธ์ธ ์ฌ์ฉ X
http.formLogin(AbstractHttpConfigurer::disable)
// httpBasic ์ธ์ฆ ๋ฐฉ์ ์ฌ์ฉ X
.httpBasic(AbstractHttpConfigurer::disable)
// ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค๋ ์ ์ ํ์ csrf X (NEO๋ ์ฑ)
.csrf(AbstractHttpConfigurer::disable)
// ์ธ์
๋ฏธ์ฌ์ฉ, JWT Token ๋ฐฉ์ ์ฌ์ฉ
.sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// OAuth2 ์์
๋ก๊ทธ์ธ
.oauth2Login((oauth2Login) -> oauth2Login
.successHandler(oAuth2LoginSuccessHandler)
.failureHandler(oAuth2LoginFailureHandler)
.userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig.userService(customOAuth2UserService)))
// URL๋ณ ๊ถํ ๊ด๋ฆฌ
.authorizeHttpRequests(requests -> requests
// ๋ก๊ทธ์ธ ๊ด๋ จ URL ๋ชจ๋ ํ๊ฐ
.requestMatchers("/login", "/oauth2/authorization/**", "/api/v1/oauth2/**").permitAll()
// API ๊ฐ๋ฐ ๋ฌธ์ URL ๋ชจ๋ ํ๊ฐ
.requestMatchers("/docs/**").permitAll()
// ๋๋จธ์ง URL
.anyRequest().authenticated());
return http.build();
}
}
โ๏ธ application-oauth.yml
spring:
security:
oauth2:
client:
registration:
google:
client-id: {google-client-id}
client-secret: {google-client-secret}
redirect-uri: http://localhost:8080/api/v1/oauth2/google
scope: profile, email
naver:
client-id: {naver-client-id}
client-secret: {naver-client-secret}
redirect-uri: http://localhost:8080/api/v1/oauth2/naver
authorization-grant-type: authorization_code
scope: name, email, gender, mobile
client-name: Naver
kakao:
client-id: {kakao-client-id}
client-secret: {kakao-client-secret}
redirect-uri: http://localhost:8080/api/v1/oauth2/kakao
client-authentication-method: POST
authorization-grant-type: authorization_code
scope: account_email, gender
client-name: Kakao
provider:
naver:
authorization_uri: https://nid.naver.com/oauth2.0/authorize
token_uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
user_name_attribute: response
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
์์ ์ฝ๋์ ๊ฐ์ด NEOSecurityConfig
์ oauth2Login.userInfoEndpoint()
์ ์ฐ๋ฆฌ๊ฐ ๊ฐ๋ฐํ OAuth2UserService
๋ฅผ ๋ฑ๋กํ๋๋ฐ ์ loadUser()
๋ฅผ ํธ์ถํ์ง ์์๋ ๊ฒ์ผ๊น์?
ํด๋น ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ณผ์ ์์ OAuth2 Client
์ ๋ํด ๊น๊ฒ ํ๊ณ ๋ค์ด ๋ ์ ์ดํดํ๋ ๊ณผ์ ์ด ๋์๊ธฐ์ ํด๋น ๋ด์ฉ์ ์ ๋ฆฌํ๊ธฐ๋ก ๋ง์๋จน์์ต๋๋ค.
๐ค 1. OAuth2UserService๋ ์ ๋ฑ๋ก๋์๋๊ฐ?
oauth2Login.userInfoEndpoint
๋ฉ์๋๋ OAuth2
์ Authorization Server
์ ์ฌ์ฉ์ ์ ๋ณด ์๋ํฌ์ธํธ๋ฅผ ๊ท์ ํ๋ ๋ฉ์๋์
๋๋ค.
๋ฐ๋ผ์ UserInfoEndpointConfig.userSerivce()
๋ฅผ ํตํด์ Resource Owner
์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ป์ด์ฌ ์ ์๋ ์ฌ์ฉ์ ์ ๋ณด ์๋ํฌ์ธํธ๋ฅผ ์ค์ ํ๊ฒ ๋ฉ๋๋ค.
ํ๋ผ๋ฏธํฐ๋ก OAuth2UserService
๋ฅผ ์๊ตฌํ๊ณ ์๋๋ฐ, ํด๋น ํ์
์ ์ฐ๋ฆฌ๊ฐ ์ฝ๋์์ ๊ตฌํํ NEOOAuth2UserService
์ ํด๋นํฉ๋๋ค.
OAuth2UserService
๋ฅผ ์ ์ ์ดํด๋ณด๋ฉด, Resource Owner
์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ํ๋ํ ์ ์๋ ์ฑ
์์ด ์๋ ์ธํฐํ์ด์ค๋ก์จ,
Authorization Server
๋ก๋ถํฐ ์ป์ด์จ Access Token
์ ํตํด OAuth2User
์ ํ์์ ๊ฐ์ง AuthenticatedPrincipal
์ ์ป์ด์ฌ ์ ์์ต๋๋ค. ์ฆ Resource Server
๋ก๋ถํฐ Access Token
์ ํตํด Resource Owner
์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค๋ ์๋ฏธ์
๋๋ค.
์ฐ์ , OAuth2UserService
๊ฐ ์ ๋ฑ๋ก๋์๋์ง ํ์ธํ๊ธฐ ์ํด์๋ OAuth2LoginConfigurer
์ init
๋ฉ์๋๋ฅผ ์ดํด๋ณผ ์ ์์์ต๋๋ค.
OAuth2LoginConfigurer
์ init()
๋ฉ์๋๋ ๋ค์๊ณผ ๊ฐ์ ์ผ์ ํฉ๋๋ค.
@Override
public void init(B http) throws Exception {
OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder()), this.loginProcessingUrl);
authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
this.setAuthenticationFilter(authenticationFilter);
super.loginProcessingUrl(this.loginProcessingUrl);
if (this.loginPage != null) {
// Set custom login page
super.loginPage(this.loginPage);
super.init(http);
}
else {
Map<String, String> loginUrlToClientName = this.getLoginLinks();
if (loginUrlToClientName.size() == 1) {
// Setup auto-redirect to provider login page
// when only 1 client is configured
this.updateAuthenticationDefaults();
this.updateAccessDefaults(http);
String providerLoginPage = loginUrlToClientName.keySet().iterator().next();
this.registerAuthenticationEntryPoint(http, this.getLoginEntryPoint(http, providerLoginPage));
}
else {
super.init(http);
}
}
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = this.tokenEndpointConfig.accessTokenResponseClient;
if (accessTokenResponseClient == null) {
accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
}
OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = getOAuth2UserService();
OAuth2LoginAuthenticationProvider oauth2LoginAuthenticationProvider = new OAuth2LoginAuthenticationProvider(
accessTokenResponseClient, oauth2UserService);
GrantedAuthoritiesMapper userAuthoritiesMapper = this.getGrantedAuthoritiesMapper();
if (userAuthoritiesMapper != null) {
oauth2LoginAuthenticationProvider.setAuthoritiesMapper(userAuthoritiesMapper);
}
http.authenticationProvider(this.postProcess(oauth2LoginAuthenticationProvider));
boolean oidcAuthenticationProviderEnabled = ClassUtils
.isPresent("org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader());
if (oidcAuthenticationProviderEnabled) {
OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService = getOidcUserService();
OidcAuthorizationCodeAuthenticationProvider oidcAuthorizationCodeAuthenticationProvider = new OidcAuthorizationCodeAuthenticationProvider(
accessTokenResponseClient, oidcUserService);
JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = this.getJwtDecoderFactoryBean();
if (jwtDecoderFactory != null) {
oidcAuthorizationCodeAuthenticationProvider.setJwtDecoderFactory(jwtDecoderFactory);
}
if (userAuthoritiesMapper != null) {
oidcAuthorizationCodeAuthenticationProvider.setAuthoritiesMapper(userAuthoritiesMapper);
}
http.authenticationProvider(this.postProcess(oidcAuthorizationCodeAuthenticationProvider));
}
else {
http.authenticationProvider(new OidcAuthenticationRequestChecker());
}
this.initDefaultLoginFilter(http);
}
- OAuth2LoginAuthenticationFilter๋ฅผ ์์ฑํ๊ณ ์ค์ ํฉ๋๋ค.
- loginPage๋ฅผ ์ค์ ํ๋ฉฐ, ์ค์ ๋ ๊ฐ์ด ์๋ค๋ฉด ๊ธฐ๋ณธ ์ค์ ์ผ๋ก loginPage๋ฅผ ์์ฑํฉ๋๋ค.
- provider์ ๋ง๋ ๋งํฌ๋ฅผ ๊ตฌ์ฑํฉ๋๋ค. ์๋ฅผ ๋ค์ด, [kakao, naver, google]์ ์ฌ์ฉํ๋ค๋ฉด ๊ฐ๊ฐ์ authorization์ ๊ฐ๋ฅ์ผ ํ๋ ๋งํฌ๋ฅผ ์์ฑํฉ๋๋ค.
- Authorization grant code๋ฅผ Access Token์ผ๋ก ๊ตํํ๋ ์๋ํฌ์ธํธ๋ฅผ ์ค์ ํฉ๋๋ค.(OAuth2AccessTokenResponseClient๋ฅผ OAuth2LoginAuthenticationProvider์ ๋ฑ๋ก)
- AccessToken์ ํตํด Resource ์๋ฒ์ Resource Owner์ ์ ๋ณด๋ฅผ ํ๋ํ ์ ์๋ OAuth2UserService๋ฅผ ์ค์ ํฉ๋๋ค.
- ๊ธฐ๋ณธ ๋ก๊ทธ์ธ ํ์ด์ง ์์ฑ ๋ฐ ๊ฐ Provider์ ๋ก๊ทธ์ธ ํผ์ผ๋ก ์ ์ํ๋ URL ์์ฑ ๋ฑ.
init
๋ฉ์๋ ๋ด์ getOAuth2UserService()
๋ฉ์๋๋ฅผ ํตํด, ์ฐ๋ฆฌ๊ฐ ๋ง๋ NEOOAuth2UserService
๊ฐ์ฒด๊ฐ ์ ๋ค์ด๊ฐ ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.
๊ฐ์ฒด๊ฐ ์ ๋ฑ๋ก๋์์์๋, loadUser()
๊ฐ ํธ์ถ๋์ง ์์ ๋์ฑ ๊ถ๊ธํด์ก์ต๋๋ค.
๐ค 2. ๋ฌธ์ ์ถ์ ํ๊ธฐ
๊ทธ๋์ ๊ณง๋ฐ๋ก loadUser()
๋ ๋๊ฐ ํธ์ถํ๋์ง, ํธ๋ํน ํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
loadUser()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๊ณณ์ ์ด 6๊ฐ์ด์ง๋ง OpenID๋ฅผ ๋ค๋ฃจ๋ Oidc๋, OAuth2UserService
๋ฅผ ๊ฐ๋จํ๊ฒ ์์ํ ์ ์๋ DelegatingOAuth2UserService
, ์ฐ๋ฆฌ๊ฐ ๊ตฌํํ NEOOAuth2UserService
๋ฅผ ์ ์ธํ๋ฉด
OAuth2LoginAuthenticationProvider
๊ฐ ์ค์ง์ ์ผ๋ก loadUser()
๋ฅผ ํธ์ถํ๊ณ ์์ต๋๋ค.
ํด๋น ๋ถ๋ถ์ ๋ฐ๋ผ๊ฐ๋ฉด, OAuth2LoginAuthenticationProvider
์ authenticate
๋ฉ์๋์ ๋์ฐฉํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ํด๋น authenticate
๋ฉ์๋๋ ProviderManager
์ authenticate
์์ ํธ์ถํ๊ณ ์์ต๋๋ค.
ํด๋น ๋ฉ์๋๋ ์ ๋ฌ๋ฐ์ Authentication
๊ฐ์ฒด๋ก ํ์ฌ๊ธ ์ธ์ฆ์ ์๋ํ๋ ๋ฉ์๋๋ก, AuthenticationProvider
์ ์ํด ์๋๋ฉ๋๋ค.
์ฆ, ์ ์์ ์ผ๋ก OAuth2LoginAuthenticationProvider
๊ฐ ์ ๋ฑ๋ก๋์๋ค๋ฉด, ProviderManager
์ authenticate
๋ฉ์๋์์, getProviders()
๋ฅผ ํตํด for๋ฌธ์ ๋๋ฉฐ ํด๋น ํ๋ก๋ฐ์ด๋์ ๋ํด authenticate
๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด์ ์ธ์ฆ ๊ณผ์ ์ ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ฌ๋, ์ ์์ ์ผ๋ก ๋ฑ๋ก์ด ๋์ง ์์๊ธฐ ๋๋ฌธ์ ํด๋น ๋ฉ์๋๋ ํธ์ถ๋์ง ์์๋ ๊ฒ์ ๋๋ค.
์ ์์ ์ผ๋ก ๋ฑ๋ก๋์ง ์์ ์์ธ์ ์ข ๋ ํ์
ํ๊ธฐ ์ํด์ ํด๋น authenticate
๋ฉ์๋๋ฅผ ๋๊ฐ ํธ์ถํ๋์ง ์ดํด๋ณด๋ ค๊ณ ํฉ๋๋ค.
ํ ๋ฒ ๋ ํ๊ณ ์ฌ๋ผ๊ฐ๋ฉด, OAuth2LoginAuthenticationFilter
์ ๋์ฐฉํ๊ฒ ๋ฉ๋๋ค.
โ๏ธ OAuth2LoginAuthenticationFilter
An implementation of an AbstractAuthenticationProcessingFilter for OAuth 2.0 Login. This authentication Filter handles the processing of an OAuth 2.0 Authorization Response for the authorization code grant flow and delegates an OAuth2LoginAuthenticationToken to the AuthenticationManager to log in the End-User. The OAuth 2.0 Authorization Response is processed as follows: Assuming the End-User (Resource Owner) has granted access to the Client, the Authorization Server will append the code and state parameters to the redirect_uri (provided in the Authorization Request) and redirect the End-User's user-agent back to this Filter (the Client). This Filter will then create an OAuth2LoginAuthenticationToken with the code received and delegate it to the AuthenticationManager to authenticate. Upon a successful authentication, an OAuth2AuthenticationToken is created (representing the End-User Principal) and associated to the Authorized Client using the OAuth2AuthorizedClientRepository. Finally, the OAuth2AuthenticationToken is returned and ultimately stored in the SecurityContextRepository to complete the authentication processing.
[์ธ์ฉ : Javadoc]โ๏ธ OAuth2LoginAuthenticationFilter ํ๊ธ ์์ฝ
- AbstractAuthenticationProcessingFilter์ OAuth2.0 ๋ก๊ทธ์ธ์ ์ํ ๊ตฌํ์ฒด
- OAuth2์ "Authorization code grant" ๋ฐฉ์์์ OAuth 2.0 Authorization Response์ ๋ค๋ฃจ๊ณ , "OAuth2LoginAuthenticationToken"์ AuthenticationManager์๊ฒ ์์ํด์ ์ต์ข ์ฌ์ฉ์๋ฅผ ๋ก๊ทธ์ธ ํ ์ ์๋๋ก ๋์์ฃผ๋ ํํฐ.- OAuth 2.0 Authorization Response์ ํ๋ก์ธ์ค
1. Resource Owner๊ฐ Client๋ก ์ ๊ทผํ ์ ์๋ค๋ ๊ฐ์ ํ์, ์ธ์ฆ ์๋ฒ๋ redirect_uri์ code์ state๋ฅผ ์ถ๊ฐ
2. ์ต์ข ์ฌ์ฉ์์ ์์ด์ ํธ๋ฅผ ๋ค์ ์ด ํํฐ๋ก ๋ฆฌ๋ค์ด๋ ์
3. OAuth2LoginAuthenticationToken์ ์์ฑ
4. AuthenticationManager์๊ฒ ์ธ์ฆ ์์
5. ์ฑ๊ณต์ ์ผ๋ก ์ธ์ฆ์ด ์๋ฃ ๋๋ฉด, OAuth2LoginAuthenticationToken์ ํ๋๋ฅผ ๋ฐํ์ผ๋ก OAuth2AuthenticationToken์ด ์์ฑ.
OAuth2LoginAuthenticationFilter
์์ ์ค์ ์ ์ผ๋ก ๋ณด์์ผ ํ ๊ฒ์ ์ด ๋ ๊ฐ์ง ์
๋๋ค.
1. OAuth2LoginAuthenticationFilter๋ OAuth2LoginConfigurer์ init ๋ฉ์๋์์ ์์ฑ์๋ฅผ ํตํด ์์ฑ๋ ์ ์ด ์๋ค.
2. attemptAuthentication ๋ฉ์๋
์ฒซ ๋ฒ์งธ ์ค์ ์ผ๋ก OAuth2LoginAuthenticationFilter
๋ OAuth2LoginConfigurer
์ init
๋ฉ์๋์์ ์์ฑ์๋ฅผ ํตํด ์์ฑ๋ ์ ์ด ์์ต๋๋ค.
๊ฐ์ฅ ์ฒ์ ๋์ค๊ฒ ๋๋ ์ฝ๋์ฃ .
OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder()), this.loginProcessingUrl);
์์ฑ์๋ฅผ ์ดํด๋ณด๋ฉด, ์๋์ ๊ฐ์ ์์ฑ์๋ฅผ ์ฌ์ฉํ๊ณ ์์์ต๋๋ค.
OAuth2LoginConfigurer.loginProcessUrl
์ด OAuth2LoginAuthenticationFilter
์ ์์ฑํ ๋ ์ ๋ฌ๋๊ณ , ๊ทธ ๊ฐ์ ๊ทธ๋๋ก ๋ถ๋ชจ์๊ฒ filterProcessesUrl
์ผ๋ก ์ ๋ฌ๋๊ฒ ๋ฉ๋๋ค.
OAuth2LoginAuthenticationFilter
์ ๋ถ๋ชจ๋, AbstractAuthenticationProcessingFilter
์
๋๋ค.
์์ ๊ฐ์ ์ฝ๋๋ก ์ธํด AbstractAuthenticationProcessingFilter
์ requiresAuthenticationRequestMatcher
๋ OAuth2LoginConfigurer.loginProcessUrl
์ ์ํฅ์ ๊ณ ์ค๋ํ ๋ฐ๊ณ ์์์ต๋๋ค.
์ด ์ ์ ๊ธฐ์ตํ ์ํ์์, ์๋์ ๊ธ์ ๋ณด๋ฉด ์ค๋ง๋ฆฌ๊ฐ ํ๋ฆฌ๊ฒ ๋ฉ๋๋ค.
๋ ๋ฒ์งธ ์ค์ ์ผ๋ก OAuth2LoginAuthenticationFilter
์ attemptAuthentication
๋ฉ์๋๋, Resource Owner
๊ฐ ์์
๋ก๊ทธ์ธ ์งํํ๊ณ ๋ ์ดํ์ ๋์ํ๋ฉฐ Authorization Server
๋ก๋ถํฐ ์ ๋ฌ๋ code๋ฅผ HttpServletRequest
๋ก๋ถํฐ ๊ฐ์ ธ์ค๋๋ก ํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ด ๋ฉ์๋๋, AbstractAuthenticationProcessingFilter
์ doFilter
๋ฉ์๋์์ ์คํ๋๊ณ ์์์ต๋๋ค.
์ฌ๊ธฐ์ ์ ๋ณด์์ผ ํ ๋ถ๋ถ์, try๋ฌธ์ ๋ค์ด๊ฐ๊ธฐ ์ ์ ์กฐ๊ฑด์ ๋๋ค.
requiresAuthentication
์ boolean
๋ฆฌํด๊ฐ์ ์ํด ์๋์ attemptAuthentication
์ ์คํํ ๊ฒ์ธ์ง, ์๋์ง๊ฐ ๊ฒฐ์ ๋ฉ๋๋ค.
ํด๋น ๋ฉ์๋๋ก ๊ฐ์ ์ด๋ค ๋ก์ง์ธ์ง ํ์ธํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
ํด๋น ๋ฉ์๋๋ ์ด ํํฐ๊ฐ ๋ก๊ทธ์ธ ์์ฒญ ์คํ์ ์๋ํด์ผ ํ๋์ง๋ฅผ ๊ฒฐ์ ํ๋ ์ญํ ์ ํ๋ ๋ฉ์๋์ ๋๋ค.
์ฆ, this.requiresAuthenticationRequestMatcher.matches()
์ ๊ฐ์ด false
๋ผ๋ฉด, ํด๋น ํํฐ๊ฐ ๋ก๊ทธ์ธ ํ๋ก์ธ์ค ์คํ์ ์๋ํ์ง ์๋๋ค๋ ๊ฒ์
๋๋ค.
this.requiresAuthenticationRequestMatcher
๋ ์์์ ๋ณด์๋ฏ์ด, OAuth2LoginConfigurer.loginProcessUrl
์ ์ํฅ์ ๋ฐ์ต๋๋ค.
loginProcessUrl
์ OAuth2LoginAuthenticationFilter
์ DEFAULT\_FILTER\_PROCESSES\_URI
์ด๋ฉฐ, ํด๋น ๊ฐ์ "/login/oauth2/code/*"๋ก ๊ณ ์ ๋์ด ์์ต๋๋ค.
๋ค์ ์์ application-oauth.yml์ ์ดํด๋ณด๋ฉด,
์ ๊ฐ์ด ์ค์ ๋์ด ์๊ธฐ ๋๋ฌธ์, ํด๋น url๋ก๋ ๋ก๊ทธ์ธ ํ๋ก์ธ์ค๋ฅผ ์คํํ์ง ์์๋ ๊ฒ์ ๋๋ค.
"์ด? ๋จ์ํ application.yml์ redirect-url์ ๋ด๊ฐ ์ํ๋ ๋ฐฉํฅ์ผ๋ก ๋ณ๊ฒฝํ๋ค๊ณ ํด์ ๋ฐ๋ก ๋์ํ๋๊ฒ ์๋๊ตฌ๋?"
๊ทธ๋ ๋ค๋ฉด ์์ ๋ด์ฉ์ ํ ๋๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ์, OAuth2LoginConfigurer
์ loginProcessUrl
์ ๋ณ๊ฒฝํ๋ ๊ฒ์
๋๋ค.
ํด๋น ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋ชจ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.
๋ฐ๋ผ์ ์์ธ๋ก ๊ฐ๋จํ Security Config ์ฝ๋์์ ๋จ ํ ์ค๋ง ์ถ๊ฐ ํ๋ฉด ํด๊ฒฐํ ์ ์๋ ๋ฌธ์ ์๋ค์.
โ๏ธ ๋ณ๊ฒฝ๋ Security Config Code
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class NEOSecurityConfig {
private final NEOOAuth2SuccessHandler oAuth2LoginSuccessHandler;
private final NEOOAuth2FailureHandler oAuth2LoginFailureHandler;
private final NEOOAuth2UserService customOAuth2UserService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// ํผ ๋ก๊ทธ์ธ ์ฌ์ฉ X
http.formLogin(AbstractHttpConfigurer::disable)
// httpBasic ์ธ์ฆ ๋ฐฉ์ ์ฌ์ฉ X
.httpBasic(AbstractHttpConfigurer::disable)
// ๋ธ๋ผ์ฐ์ ๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค๋ ์ ์ ํ์ csrf X (NEO๋ ์ฑ)
.csrf(AbstractHttpConfigurer::disable)
// ์ธ์
๋ฏธ์ฌ์ฉ, JWT Token ๋ฐฉ์ ์ฌ์ฉ
.sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// OAuth2 ์์
๋ก๊ทธ์ธ
.oauth2Login((oauth2Login) -> oauth2Login
.loginProcessingUrl("/api/v1/oauth2/*") // ์ถ๊ฐ๋จ!
.successHandler(oAuth2LoginSuccessHandler)
.failureHandler(oAuth2LoginFailureHandler)
.userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig.userService(customOAuth2UserService)))
// URL๋ณ ๊ถํ ๊ด๋ฆฌ
.authorizeHttpRequests(requests -> requests
// ๋ก๊ทธ์ธ ๊ด๋ จ URL ๋ชจ๋ ํ๊ฐ
.requestMatchers("/login", "/oauth2/authorization/**", "/api/v1/oauth2/**").permitAll()
// API ๊ฐ๋ฐ ๋ฌธ์ URL ๋ชจ๋ ํ๊ฐ
.requestMatchers("/docs/**").permitAll()
// ๋๋จธ์ง URL
.anyRequest().authenticated());
return http.build();
}
}
๐ค 3. ์ด์ ๋ฆฌ ๋ฐ ํ๊ธฐ
๋ฌธ์ ๋ loginProcessUrl์ ์ค์ ํ์ง ์์๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ์ต๋๋ค.
๋จ์ํ application.yml์ redirect-url์ ์ค์ ํ๋ฉด ๋ด๋ถ์ ์ผ๋ก oauth2 client
๊ฐ ๋ชจ๋ ๊ฒ์ ์ฒ๋ฆฌํด ์ค ๊ฒ์ด๋ผ๋ ์์ผํ ์๊ฐ์ด ํด๋น ๋ฌธ์ ๋ฅผ ๋ฐ์์์ผฐ๋ค์.
๊ทธ๋๋ ์ด๋ฌํ ์ค์ ๋๋ถ์ oauth2 client
๊ฐ ์ด๋ป๊ฒ authroization code grant
๋ฐฉ์์ ์ฒ๋ฆฌํ๋์ง, ๋ด๋ถ๋ฅผ ๋ค์ฌ๋ค ๋ณผ ์ ์๋ ์ข์ ๊ธฐํ๊ฐ ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์์ผ๋ก๋ ๋จ์ํ ์ง๊ด๋ณด๋ค๋ ์ข ๋ ๊น์ ์ดํด๋ฅผ ํ ์ํ๋ก ๊ฐ๋ฐํ๋ ์ต๊ด์ ๋ค์ฌ์ผ๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค.