728x90
OAuth2AuthorizationRequestResolver


- Authorization Code Grant 방식에서 클라이언트가 인가서버로 권한부여 요청할 때 실행되는 클래스
- OAuth2AuthorizationRequestResolver 는 OAuth 2.0 인가 프레임워크에 정의된 표준 파라미터 외에 다른 파라미터를 추가하는 식으로 인가 요청을 할 때 사용한다
- DefaultOAuth2AuthorizationRequestResolver 가 디폴트 구현체로 제공 되며Consumer<OAuth2AuthorizationRequest.Builder> 속성에 커스텀할 내용을 구현한다
application.yml 설정

TEST
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
application.yml
server:
port: 8081
spring:
security:
oauth2:
client:
registration:
keycloak1:
client-id: oauth2-client-app
client-name: oauth2-client-app
client-secret: xSlqD456gfAeLZO93BLbTwQys0NEc8KL
authorization-grant-type: authorization_code #authorization_code, authorization_code with pkce, implicit
scope:
- profile
- openid
client-authentication-method: client_secret_basic
redirect-uri: http://localhost:8081/login/oauth2/code/keycloak
provider: keycloak
keycloakWithPKCE:
client-id: oauth2-client-app2
client-name: oauth2-client-app2
client-secret: g6jIAAN2lmfg3hgovuydzH3BO61ssAAU
authorization-grant-type: authorization_code
scope:
- profile
- openid
client-authentication-method: none
redirect-uri: http://localhost:8081/login/oauth2/code/keycloak
provider: keycloak
keycloak2:
client-id: oauth2-client-app3
client-name: oauth2-client-app3
authorization-grant-type: implicit
scope:
- profile
- openid
client-authentication-method: none
redirect-uri: http://localhost:8081/home
provider: keycloak
provider:
keycloak:
authorization-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/auth
issuer-uri: http://localhost:8080/realms/oauth2
jwk-set-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/certs
token-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/token
user-info-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/userinfo
user-name-attribute: preferred_username
home.html
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script>
function authorizationCode(){
window.location = new URL('http://localhost:8081/oauth2/authorization/keycloak1');
}
function authorizationCodeWithPKCE(){
window.location = new URL('http://localhost:8081/oauth2/authorization/keycloakWithPKCE');
}
function implicit(){
window.location = new URL('http://localhost:8081/oauth2/authorization/keycloak2');
}
</script>
</head>
<body>
<div>Welcome</div>
<div sec:authorize="isAuthenticated()"><a th:href="@{/logout}">Logout</a></div>
<form sec:authorize="isAnonymous()" action="#">
<p><input type="button" onclick="authorizationCode()" value="AuthorizationCode Grant" />
<p><input type="button" onclick="authorizationCodeWithPKCE()" value="AuthorizationCode Grant with PKCE" />
<p><input type="button" onclick="implicit()" value="Implicit Grant" />
</form>
</body>
</html>
keycloak 생성

oauth2-client-app2

oauth2-client-app3

keycloak PKCE 수정

Config
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authRequest -> authRequest.antMatchers("/home").permitAll()
.anyRequest().authenticated());
http.oauth2Login(Customizer.withDefaults());
http.logout().logoutSuccessUrl("/home");
return http.build();
}
HomeController
@Controller
public class HomeController {
@GetMapping("/home")
public String home() {
return "home";
}
}
- build.gradle에 thymeleaf의 시큐리티 설정을 해야 HTML 클래스를 적용시켜 logout 버튼이 보이지 않는다.
- 3개의 클라이언트를 만들어, 1번은 이전 상태 그대로, 2번은 PKCE 방식, 3번은 Implicit 방식으로 한다.
- oauth2-client-app2 같은 경우, PKCE 방식이기 때문에 인증서버에서 PKCE설정을 추가적으로 해주어야 한다.
- client-authentication-method가 none이 아니면, PKCE가 동작하지 않는다(DefaultOAuth2AuthorizationRequestResolver 에서 None일 경우 PKCE로 전달하기 때문)
- 단, None으로 설정을 하여도 동작하지 않게 되는데 왜 그러냐면 2번째 filter에서 PKCE가 적용되었지만, secret 키가 포함이 되지 않기 때문에 오류가 발생한다.
- 따라서 PKCE를 설정해 주는 메서드를 수정을 해줘야 한다 -> DefaultOAuth2AuthorizationRequestResolver.getBuilder() 메서드를 커스텀하게 바꿔주면 된다.
TEST(PKCE 이슈 해결)
CustomOAuth2AuthorizationRequestResolver
public class CustomOAuth2AuthorizationRequestResolver implements
OAuth2AuthorizationRequestResolver {
private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
private ClientRegistrationRepository clientRegistrationRepository;
private String baseUri;
private DefaultOAuth2AuthorizationRequestResolver defaultOAuth2AuthorizationRequestResolver;
private final AntPathRequestMatcher authorizationRequestMatcher;
private static final Consumer<Builder> DEFAULT_PKCE_APPLIER = OAuth2AuthorizationRequestCustomizers
.withPkce();
public CustomOAuth2AuthorizationRequestResolver(
ClientRegistrationRepository clientRegistrationRepository, String BaseUri) {
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizationRequestMatcher = new AntPathRequestMatcher(
BaseUri + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}");
this.baseUri = BaseUri;
defaultOAuth2AuthorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(
clientRegistrationRepository, BaseUri);
}
//PKCE를 제외한 값은 기존방식을 이용할 것이다.
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
String registrationId = resolveRegistrationId(request);
if (registrationId == null) {
return null;
}
if( registrationId.equals("keycloakWithPKCE")){
OAuth2AuthorizationRequest oAuth2AuthorizationRequest = defaultOAuth2AuthorizationRequestResolver.resolve(
request);
return customResolver(oAuth2AuthorizationRequest, registrationId);
}
return defaultOAuth2AuthorizationRequestResolver.resolve(request);
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request,
String clientRegistrationId) {
String registrationId = resolveRegistrationId(request);
if (registrationId == null) {
return null;
}
if( registrationId.equals("keycloakWithPKCE")){
OAuth2AuthorizationRequest oAuth2AuthorizationRequest = defaultOAuth2AuthorizationRequestResolver.resolve(
request);
return customResolver(oAuth2AuthorizationRequest, clientRegistrationId);
}
return defaultOAuth2AuthorizationRequestResolver.resolve(request);
}
private OAuth2AuthorizationRequest customResolver(OAuth2AuthorizationRequest oAuth2AuthorizationRequest, String clientRegistrationId) {
Map<String, Object> extraParam = new HashMap<>();
extraParam.put("customName1", "customValue1");
extraParam.put("customName2", "customValue2");
extraParam.put("customName3", "customValue3");
OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest
.from(oAuth2AuthorizationRequest)
.additionalParameters(extraParam);
DEFAULT_PKCE_APPLIER.accept(builder);
return builder.build();
}
private String resolveRegistrationId(HttpServletRequest request) {
if (this.authorizationRequestMatcher.matches(request)) {
return this.authorizationRequestMatcher.matcher(request).getVariables()
.get(REGISTRATION_ID_URI_VARIABLE_NAME);
}
return null;
}
}

- 해당 코드는 DefaultOAuth2AuthorizationRequestResolver를 참고해서 작성했으며, DefaultOAuth2AuthorizationRequestResolver을 builder() 한 이후의 값을 한번 더 builder 하여 파라미터를 추가할 수 있다.
- client authentication off 하여 해결할 수도 있지만, 이는 보안에 취약하기 때문에 위와 같이 Resolver를 만들어 PKCE에서 파라미터를 추가로 전달하여 인증처리를 해야 한다.
- 지금의 SpringSecurity는 none일 때, PKCE로 파악하여, 인증서버로 요청하게 되는데 이때, 인증서버의 secret 값을 가지고 가지 않기 때문에 오류가 발생하는 것으로, 이 부분을 파라미터를 추가하는 방식으로 해결할 수 있을 것이다.
- 현재는 파라미터를 추가할 수 있다는 것만 설명하였다.
728x90
'스프링 시큐리티 OAuth2 > OAuth2LoginConfigurer 초기화 이해' 카테고리의 다른 글
| API 커스텀 구현 -Authorization BaseUrl & Redirection BaseUrl (0) | 2023.01.24 |
|---|---|
| OAuth2 로그인 구현 - Spring MVC 인증 객체 참조하기 (0) | 2023.01.24 |
| OAuth2 로그인 구현 - OpenID Connect 로그아웃 (0) | 2023.01.23 |
| OAuth2 로그인 구현 - UserInfo 엔드포인트 요청하기 (0) | 2023.01.22 |
| OAuth2 로그인 구현 - Oauth 2.0 User 모델 소개 (0) | 2023.01.17 |