예전에 Azure Active Directory(이하 Azure AD)와 OAuth2.0을 통해서 SSO를 구현했던 일이 있었는데 까먹을까봐 기록해놓는다.
회사에서 Azure Active Directory를 통해서 임직원PC 계정을 관리하고 있었다. 그래서 대부분 사내시스템이 Azure AD와 연동해서 SSO 기능을 구현하고 있었다.
현재 우리가 개발하고 있는 프로젝트도 SSO기능 구현 요건이 들어왔고 우리는 Spring boot에서 개발하고 있기에 Spring Security에서 외부인증(OAuth2.0)을 받는 방법을 알아보았다.
아래 마이크로소프트 가이드를 참고하였으나 해당 가이드는 SSO보다는 REST-API 호출시 외부인증을 받는 방법에 초점이 맞춰져 있어서 그대로 적용할 수는 없었다.
우리는 REST-API는 내부인증을 사용하고 SSO시에만 외부인증을 사용할 것이기 때문이다.
참고 :
Spring Security 5 및 Azure Active Directory를 사용하여 REST API 보호
Azure Portal에 접속해서 인증받고자 하는 AD로 들어가서 앱 등록을 한다.
앱등록을 하면 클라이언트ID와 테넌트ID가 발급되는데 나중에 Spring에서 설정할 때도 필요하니 복사해 놓자
인증서 및 암호탭으로 들어가서 새 클라이언트 암호를 생성한다.
그러면 클라이언트 시크릿키 가 발급되는데 이것도 나중에 Spring에서 설정할 때 필요하니 복사해놓자
인증탭으로 들어가서 플랫폼 추가 > 웹을 구성한다.
리디렉션URL을 입력한다. 이 주소는 AD로 인증 후 리다이렉트될 서버URL이다. 나는 Spring OAuth2.0 클라이언트 기본 URL설정했다.
default URL : domain:port/login/oauth2/code/
리다이렉션URL은 여러개 등록 가능하다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'com.azure.spring:azure-spring-boot-starter-active-directory:3.12.0'
}
Azure Portal에서 발급받았던 키들을 properties나 yaml에 등록한다.
# Azure AD Properties
azure.activedirectory.tenant-id=xxxxxxx-xxx-xxxx-xxxx-xxxxxxxxxxx // 테넌트ID
azure.activedirectory.client-id=xxxxxxx-xxx-xxxx-xxxx-xxxxxxxxxxx // 클라이언트ID
azure.activedirectory.client-secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx // 클라이언트 시크릿
WebSecurityConfig에서 Oauth를 위한 설정을 구현한다. 기존 Azure SDK에서는 모든 인증을 OAuth를 통해 인증받도록 구현되어 있다. 우리는 SSO 외에는 내부인증을 받음으로 Azure SDK를 분석해서 필요한 부분만 발췌해서 구현해놓았다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()//oauth2Login에 대한 설정 시작
//인가에 대한 요청을 서비스 인가에 대한 요청을 서비스할 때 사용한다. 기본 URL은 /oauth/authorize
//클라이언트가 사용하며, user-agent 리다이렉트를 통해 리소스 소유자에게 인가를 요청한다.
.authorizationEndpoint()
//인가에 대한 요청을 사용자 정의한다.
//기존 oauth2.0 Request 양식을 Azure 기준에 맞게 가공한 것 같다.
//기존 BaseUri등과 같이 개발자가 지정해야줘야 하는 설정들을 Azure SDK에서 알아서 설정해주는 듯 하다.
.authorizationRequestResolver(aadRequestResolver())
.and()
//클라이언트가 사용하며, 전형적인 클라이언트 인증과 함께, 인가 코드를 액세스 토큰으로 바꾼다.
//액세스토큰을 분석하여 부여된 권한을 확인하는 듯 하다.
.tokenEndpoint()
.accessTokenResponseClient(aadAccessTokenResponseClient())
.and()
//클라이언트에 부여한 액세스 토큰으로 최종사용자의 속성을 가져온다.
//oidcUserService OIDC 지원하는 구현체로 Azure SDK 구현한 것을 사용한다.
//Authentication에서 해당 결과를 가져와서 사용자 속성을 확인할 수 있다.
.userInfoEndpoint()
.oidcUserService(aadOAuth2UserService())
.and()
.successHandler(ssoAuthenticationSuccessHandler) // oauth2.0 성공시 처리해야할 프로세스
.failureHandler(ssoAuthenticationFailureHandler) // oauth2.0 실패시 처리해야할 프로세스
;
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
/**
* Gets the access token response client.
* Returns:
* the access token response client
*/
protected OAuth2AuthorizationRequestResolver aadRequestResolver() {
return new AADOAuth2AuthorizationRequestResolver(this.repo, properties);
}
/**
* Gets the request resolver.
* Returns:
* the request resolver
*/
protected OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> aadAccessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient result = new DefaultAuthorizationCodeTokenResponseClient();
if (repo instanceof AADClientRegistrationRepository) {
result.setRequestEntityConverter(
new AADOAuth2AuthorizationCodeGrantRequestEntityConverter(
((AADClientRegistrationRepository) repo).getAzureClientAccessTokenScopes()));
}
return result;
}
/**
* Creates a new instance of AADOAuth2UserService.
* Params:
* properties – the AAD authentication properties
*/
public AADOAuth2UserService aadOAuth2UserService() {
return new AADOAuth2UserService(properties);
}
SuccessHandler는 외부인증이 정상적으로 완료되었을 경우 실행되는 메소드이다. 외부인증이 완료 후 각 도메인의 비즈니스 로직을 구현할 때 사용한다.
@Slf4j
@Component
public class AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{
@Value("${oauth.azure.successUrl}")
private String successReUrl;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//외부인증 성공 후 최종 리다이렉트해야 URL 주소 설정
setDefaultTargetUrl(successReUrl);
//Azure Ad Oidc 기준으로 설정된 인증정보를 초기화해준다.
SecurityContextHolder.getContext().setAuthentication(null);
// 외부인증 받은 사용자의 정보를 받음
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
//원하는 비지니스 로직 구현
...
...
...
super.onAuthenticationSuccess(request,response,authentication);
}
}
@Component
@Slf4j
public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Value("${oauth.azure.failureUrl}")
private String failureReUrl;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
setDefaultFailureUrl(failureReUrl);
super.onAuthenticationFailure(request,response,exception);
}
}
아래 주소를 입력하면 마이크로소프트 로그인 창이 뜨고 ID와 패스워드를 입력하면 successhandler에서 설정한 URL로 라다이렉트 된다.