지난시간에는 OIDC 가 무엇인지에 대해서 설명을 했습니다 Oauth2 는 제한된 권한으로 서드파티 어플리케이션에 접근해서 정보를 가져오는 반면에 OIDC는 인증을 위한 프레임워크인 반면에 OAuth2 는 인가의 목적이 큰 프레임워크입니다
OIDC 설정
1
2
3
spring.security.oauth2.client.registration.keycloak.scope=openid
maven 추가
1
2
3
4
5
6
7
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-oauth2-jose -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
application.properties 에서 scope 에 openid 라고 적어주게 되면 이제 이 시큐리는 OIDC 계층과 통신을 시도하게 됩니다 단 애초에 OIDC 자체가 OAUth2 위에 있기 때문에 사용하는 클래스는 동일합니다 그리고 이 maven 이 왜 필요한지는 이따 소스를 따라가면 나오게 됩니다
OAuth2LoginConfigurer 이 클래스에서 init , configure 를 세팅을 하게 되고
OAuth2AuthorizationRequestRedirectFilter 이곳에서 로그인 페이지 url 을 만들게 됩니다
다만 여기서 좀 다른것은
OIDC 주소
1
2
3
4
http://localhost:8080/realms/Srping-Oauth2-Authorizaion-Project/protocol/openid-connect/auth?response_type=code&client_id=Spring-Oauth2-Authorizaion-client&scope=openid&state=3zWLz6U01v5b7tGnf_bWPxcDoqIUIyCoVtwRJbCR0bc%3D&redirect_uri=http://localhost:8081/login/oauth2/code/keycloak&nonce=38XBll3TR_ABtdeK3tfSx17CzgOBNOAdDWJHpws8nXo
scope 가 scope=openid 게 달라였습니다 앞에 OAuth2 에서는
OAuth2 주소
1
2
3
http://localhost:8080/realms/Srping-Oauth2-Authorizaion-Project/protocol/openid-connect/auth?response_type=code&client_id=Spring-Oauth2-Authorizaion-client&scope=email%20profile&state=KvR7XkC1EIAIjMCYxLr4Ljs_gzTuprTn5_tHWMrljY4%3D&redirect_uri=http://localhost:8081/login/oauth2/code/keycloak
scope=email%20profile 가 이렇게 이어져 있는것을 확인할 수 있습니다 즉 oidc 요청을 할때에는 반드시 scope 에 openid 가 포함이 되어야 합니다
그럼 로그인을 진행하게되면 OAuth2LoginAuthenticationFilter 로 넘어가게 되는것이고 우리는 이 부분에서 자세히 보면됩니다
OAuth2LoginAuthenticationFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository
.removeAuthorizationRequest(request, response);
if (authorizationRequest == null) {
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
if (clientRegistration == null) {
OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
"Client Registration not found with Id: " + registrationId, null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
// @formatter:off
String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replaceQuery(null)
.build()
.toUriString();
// @formatter:on
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,
redirectUri);
Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration,
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authenticationRequest.setDetails(authenticationDetails);
OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
.getAuthenticationManager().authenticate(authenticationRequest);
OAuth2AuthenticationToken oauth2Authentication = this.authenticationResultConverter
.convert(authenticationResult);
Assert.notNull(oauth2Authentication, "authentication result cannot be null");
oauth2Authentication.setDetails(authenticationDetails);
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(), oauth2Authentication.getName(),
authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
return oauth2Authentication;
}
}
위에부분은 앞에와 마찬가지로 ClientRegistraion 을 찾아오고 redirect url 그리고 요청객체 , 응답객체 만드는것은 동일하나
실제로는 이 부분부터 달라질 예정입니다 자 그러면 이 부분터 보게 되면
ProviderManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
int currentPosition = 0;
int size = this.providers.size();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
...
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
...
}
if (result == null && this.parent != null) {
// Allow the parent to try.
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
...
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
ProviderManager 클래스의 목적은 현재 넘어오는 Oauth2 타입에 따라서 각기 다른 Provider 에서 처리하기 때문에 이곳에서 분기를 하게 됩니다 그래서 앞에서는 OAuth2LoginAuthenticationProvider 로 처리하게끔 만들어졌다면 이번에는
1
2
3
4
5
6
7
8
9
10
11
providers = {ArrayList} size = 3
...
2 = {OidcAuthorizationCodeAuthenticationProvider}
accessTokenResponseClient = {DefaultAuthorizationCodeTokenResponseClient}
userService = {OidcUserService}
jwtDecoderFactory = {OidcIdTokenDecoderFactory}
authoritiesMapper = {OidcAuthorizationCodeAuthenticationProvider$lambda}
총 3개가 준비가 되어 있고 이중에서 OidcAuthorizationCodeAuthenticationProvider 에서 처리될 예정입니다
OAuth2LoginAuthenticationProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2LoginAuthenticationToken loginAuthenticationToken = (OAuth2LoginAuthenticationToken) authentication;
if (loginAuthenticationToken.getAuthorizationExchange().getAuthorizationRequest().getScopes()
.contains("openid")) {
return null;
}
...
}
}
일단 한번은 무조건 호출이 될 예정인데 여기서 중요한것은
1
2
3
4
5
6
7
if (loginAuthenticationToken.getAuthorizationExchange().getAuthorizationRequest().getScopes()
.contains("openid")) {
return null;
}
즉 요청정보에 opendid를 포함하고 있으면 이곳에서 처리를 하지 않고 return 하게 되는것입니다
OidcAuthorizationCodeAuthenticationProvider
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class OidcAuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2LoginAuthenticationToken authorizationCodeAuthentication = (OAuth2LoginAuthenticationToken) authentication;
if (!authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest().getScopes()
.contains(OidcScopes.OPENID)) {
return null;
}
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationRequest();
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationResponse();
if (authorizationResponse.statusError()) {
throw new OAuth2AuthenticationException(authorizationResponse.getError(),
authorizationResponse.getError().toString());
}
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
OAuth2AccessTokenResponse accessTokenResponse = getResponse(authorizationCodeAuthentication);
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters();
if (!additionalParameters.containsKey(OidcParameterNames.ID_TOKEN)) {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE,
"Missing (required) ID Token in Token Response for Client Registration: "
+ clientRegistration.getRegistrationId(),
null);
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
}
OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
validateNonce(authorizationRequest, idToken);
OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration,
accessTokenResponse.getAccessToken(), idToken, additionalParameters));
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oidcUser.getAuthorities());
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(), oidcUser, mappedAuthorities,
accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken());
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult;
}
}
그렇기에 3번째 OidcAuthorizationCodeAuthenticationProvider 인 이곳에서 처리될 예정입니다
1
2
3
4
5
6
if (!authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest().getScopes()
.contains(OidcScopes.OPENID)) {
return null;
}
이곳도 마찬가지로 scope 에 opendid 가 포함이 되어 있지 않다면 이곳에서 처리 못하게끔 되어 있지만 우리는 이곳을 넘어갈 예정입니다
1
2
3
4
5
6
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationRequest();
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationResponse();
그리고 통신하기 위해 요청객체 응답객체를 각각 만들게 됩니다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
authorizationRequest = {OAuth2AuthorizationRequest}
authorizationUri = "http://localhost:8080/realms/Srping-Oauth2-Authorizaion-Project/protocol/openid-connect/auth"
authorizationGrantType = {AuthorizationGrantType}
responseType = {OAuth2AuthorizationResponseType}
clientId = "Spring-Oauth2-Authorizaion-client"
redirectUri = "http://localhost:8081/login/oauth2/code/keycloak"
scopes = {Collections$UnmodifiableSet@7841} size = 1
0 = "openid"
state = "5-1ELnPeEb15bKd5db7afsulqyYwU0EWKBLoxlKyAAo="
"nonce" -> "jxAd72Ms8h4kFuP_cR7ZplbiV4MNfURkn47ECcWbFDw"
authorizationRequestUri = "http://localhost:8080/realms/Srping-Oauth2-Authorizaion-Project/protocol/openid-connect/auth?response_type=code&client_id=Spring-Oauth2-Authorizaion-client&scope=openid&state=5-1ELnPeEb15bKd5db7afsulqyYwU0EWKBLoxlKyAAo%3D&redirect_uri=http://localhost:8081/login/oauth2/code/keycloak&nonce=jxAd72Ms8h4kFuP_cR7ZplbiV4MNfURkn47ECcWbFDw"
attributes =
"registration_id" -> "keycloak"
"nonce" -> "K7vZ7WvixvbO5ExawzScWoTmzqV8XLHhatZAQQEETXUuLtQa-uihBL2DxZ4nLX1HcRpCURF1rBMjjuovwJ237RykT-n8zAT39GPH18RF7pGNtxoHWNQ1cYJsJ44drhdC"
1
2
3
4
5
6
7
authorizationResponse = {OAuth2AuthorizationResponse}
redirectUri = "http://localhost:8081/login/oauth2/code/keycloak"
state = "5-1ELnPeEb15bKd5db7afsulqyYwU0EWKBLoxlKyAAo="
code = "34e46a46-0eca-4571-87f5-d9d95a80aeb5.82543964-bd21-47b4-bb02-f849253de542.4f8d60ae-7370-4bef-a356-95620f098f06"
error = null
여기서 중요한점은 이것도 마찬가지로 code 를 들고 있는 상태이고 이 code 를 던져서 id_token 을 받아올 예정인데 앞에서 Oauth2 에서는 code 를 던지면 access_token 이 넘어온다는 점하고는 다릅니다 이토큰의 차이점도 앞에서 다루어 봤구요
1
2
3
4
OAuth2AccessTokenResponse accessTokenResponse = getResponse(authorizationCodeAuthentication);
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
이곳에서 서버와 통신을 해서 accessTokenResponse 를 추출하게 되는데 이를 잘보면
1
2
3
4
5
6
7
8
9
10
11
12
13
accessTokenResponse = {OAuth2AccessTokenResponse}
accessToken = {OAuth2AccessToken}
tokenType = {OAuth2AccessToken$TokenType}
scopes = {Collections$UnmodifiableSet}
tokenValue = "..."
refreshToken = {OAuth2RefreshToken}
tokenValue = "..."
additionalParameters = {Collections$UnmodifiableMap}
"id_token" -> "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJkcDdscEZZUFktZG84aTlVNlZwM3NxYjRhdHl1dHN3MURVUXRaWml3SV9zIn0.eyJleHAiOjE2OTcyNTgzNTYsImlhdCI6MTY5NzI1ODA1NiwiYXV0aF90aW1lIjoxNjk3MjU3MDcxLCJqdGkiOiJiMjRlMTEwNy1jMzU5LTRjMmQtYTM5Ni0wNzVkNWFhYmM2NDQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL1NycGluZy1PYXV0aDItQXV0aG9yaXphaW9uLVByb2plY3QiLCJhdWQiOiJTcHJpbmctT2F1dGgyLUF1dGhvcml6YWlvbi1jbGllbnQiLCJzdWIiOiIzMDI4MzIyOC1mYTM2LTRkODgtODJjMy1jOTQ5NGRjNDRiZmQiLCJ0eXAiOiJJRCIsImF6cCI6IlNwcmluZy1PYXV0aDItQXV0aG9yaXphaW9uLWNsaWVudCIsIm5vbmNlIjoiaTBWNjBZdGdwNnY4d3p6a0Z5U2o3dFlZVEx5VU9hckF2TXdpZmtoeXd0YyIsInNlc3Npb25fc3RhdGUiOiI4MjU0Mzk2NC1iZDIxLTQ3YjQtYmIwMi1mODQ5MjUzZGU1NDIiLCJhdF9oYXNoIjoiU3hQaU4xb2JEWmtPcHcwWW4wdGpEUSIsImFjciI6IjAiLCJzaWQiOiI4MjU0Mzk2NC1iZDIxLTQ3YjQtYmIwMi1mODQ5MjUzZGU1NDIiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJ0aW1lIHVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMSIsImdpdmVuX25hbWUiOiJ0aW1lIiwiZmFtaWx5X25hbWUiOiJ1c2VyIiwiZW1haWwiOiJ1c2VyMUBnbWFpbC5jb20ifQ.D70n7bmPyaDTOwXiq"
여기서 잘보면 code 로 요청을 했기때문에 access_token 과 refresh_token 은 동일하게 오지만 추가적으로 이곳엔 id_token 이 하나가 발급이 됩니다 이 id 토큰을 jwt.io 에 분석해보면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"alg": "RS256",
"typ": "JWT",
"kid": "dp7lpFYPY-do8i9U6Vp3sqb4atyutsw1DUQtZZiwI_s"
}
{
"exp": 1697258356,
"iat": 1697258056,
"auth_time": 1697257071,
"jti": "b24e1107-c359-4c2d-a396-075d5aabc644",
"iss": "http://localhost:8080/realms/Srping-Oauth2-Authorizaion-Project",
"aud": "Spring-Oauth2-Authorizaion-client",
"sub": "30283228-fa36-4d88-82c3-c9494dc44bfd",
"typ": "ID",
"azp": "Spring-Oauth2-Authorizaion-client",
"nonce": "i0V60Ytgp6v8wzzkFySj7tYYTLyUOarAvMwifkhywtc",
"session_state": "82543964-bd21-47b4-bb02-f849253de542",
"at_hash": "SxPiN1obDZkOpw0Yn0tjDQ",
"acr": "0",
"sid": "82543964-bd21-47b4-bb02-f849253de542",
"email_verified": false,
"name": "time user",
"preferred_username": "user1",
"given_name": "time",
"family_name": "user",
"email": "user1@gmail.com"
}
인데 여기서 중요한점은 실제로는 우리는 앞에서 access_token 을 보내야 위와 같은 데이터를 받을 수 있는 반면에 현재 oidc 에서는 code 를 보냄과 동시에 인증이 완료되었다고 판단해서 keyclock 에서는 사용자 정보를 같이 access_token 과 refresh_token 를 반환을 해버립니다 이게 가능한 이유는 실제로 oidc 는 신원확인이 우선이기 때문에 code 가 발급이 된것은 이미 이 사람의 아이디 비밀번호는 일치한것이기 떄문에 id_token 으로 이 사람의 정보를 reutrn 하게 되는데 앞에서는 access_token 을 가지고 한번더 userInfo 엔트리 포인트에 접근해야 하는 것과 큰 차이가 발생하게 됩니다
1
2
3
4
5
6
7
Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters();
if (!additionalParameters.containsKey(OidcParameterNames.ID_TOKEN)) {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE,
"Missing (required) ID Token in Token Response for Client Registration: "
+ clientRegistration.getRegistrationId(),
null);
}
이제는 access_token 이 중요한것이 아니라 getAdditionalParameters 에서 ID_TOKEN 을 뽑아내는것이 더 중요하기 때문에 그것이 없으면 에러 처리 하게끔 설계가 되어 있습니다
1
OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
이 정보를 활용해서 oidc 토큰을 만들게 되고 이때 createOidcToken 살펴보게 되면
1
2
3
4
5
6
7
8
private OidcIdToken createOidcToken(ClientRegistration clientRegistration,
OAuth2AccessTokenResponse accessTokenResponse) {
JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
Jwt jwt = getJwt(accessTokenResponse, jwtDecoder);
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
jwt.getClaims());
return idToken;
}
JwtDecoder 객체를 활용하게 되는데 이때 우리가 앞에서 maven 으로 추가해준 라이브러리에 담겨 있는 것입니다 그래서 이 토큰을 jwtDecoder 돌려서 토큰의 value 와 , 토큰 발생시간 토큰 만료시간 토큰 클레임을 다 분리하고 그것 idToken 으로 만들어서 return 하게 됩니다
1
OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration,accessTokenResponse.getAccessToken(), idToken, additionalParameters));
그리고 oidcUser 를 만들게 되는데 이는 앞에서 보는 Oauth2User 객체와 동일한 방법입니다
OidcUserService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
Assert.notNull(userRequest, "userRequest cannot be null");
OidcUserInfo userInfo = null;
if (this.shouldRetrieveUserInfo(userRequest)) {
OAuth2User oauth2User = this.oauth2UserService.loadUser(userRequest);
Map<String, Object> claims = getClaims(userRequest, oauth2User);
userInfo = new OidcUserInfo(claims);
if (userInfo.getSubject() == null) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
if (!userInfo.getSubject().equals(userRequest.getIdToken().getSubject())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}
return getUser(userRequest, userInfo, authorities);
}
}
1
2
3
4
5
OAuth2User oauth2User = this.oauth2UserService.loadUser(userRequest);
Map<String, Object> claims = getClaims(userRequest, oauth2User);
userInfo = new OidcUserInfo(claims);
이것은 몰랐는데 User를 만들떄에는 OAuth2User 기반으로 OAuth2USerservice 를 호출하게 됩니다 그리고 그것을 기반으로 claims 를 만들게 되는데
1
2
3
4
5
6
7
8
9
10
11
userInfo = {OidcUserInfo@8509}
claims = {Collections$UnmodifiableMap@8511} size = 7
"sub" -> "30283228-fa36-4d88-82c3-c9494dc44bfd"
"email_verified" -> {Boolean@8493} false
"name" -> "time user"
"preferred_username" -> "user1"
"given_name" -> "time"
"family_name" -> "user"
"email" -> "user1@gmail.com"
유저 정보가 전부 이 claims 에 담기게 됩니다
1
2
3
4
5
6
7
8
9
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}
return getUser(userRequest, userInfo, authorities);
그리고 access_token 을 분리해서 권한을 만들어서 최종적으로 oidc 유저 타입의 객체를 만들게 됩니다
1
2
3
4
5
6
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(), oidcUser, mappedAuthorities,
accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken());
그리고 되돌아와서는 이제까지 모인 정보로 authenticationResult 를 만들게 되고
다시 OAuth2LoginAuthenticationFilter 로 돌아와서
1
2
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
이제까지 모인 정보 들을 시큐리티 컨텍스트에 저장하면 끝이나게 됩니다 이렇게 되면 oidc 인증은 끝이나게 됩니다 그리고 앞에서 만든 user 정보 가져오기를 비교해보면
1
2
3
4
5
6
7
8
## oidcUser 의 정보
Name: [30283228-fa36-4d88-82c3-c9494dc44bfd], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile]], User Attributes: [{at_hash=QdvsTOa2FxbYEDFsFx5B_Q, sub=30283228-fa36-4d88-82c3-c9494dc44bfd, email_verified=false, iss=http://localhost:8080/realms/Srping-Oauth2-Authorizaion-Project, typ=ID, preferred_username=user1, given_name=time, nonce=4FOVWRwROxsXBOP3Mi3XssqsmgnP8GYfYuJ1mMoQa-U, sid=82543964-bd21-47b4-bb02-f849253de542, aud=[Spring-Oauth2-Authorizaion-client], acr=0, azp=Spring-Oauth2-Authorizaion-client, auth_time=2023-10-14T04:17:51Z, name=time user, exp=2023-10-14T04:49:20Z, session_state=82543964-bd21-47b4-bb02-f849253de542, family_name=user, iat=2023-10-14T04:44:20Z, email=user1@gmail.com, jti=54e1d508-ebf3-4718-80c5-ba6642b05dcb}]
## oauth2 의 정보
Name: [30283228-fa36-4d88-82c3-c9494dc44bfd], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_profile]], User Attributes: [{sub=30283228-fa36-4d88-82c3-c9494dc44bfd, email_verified=false, name=time user, preferred_username=user1, given_name=time, family_name=user, email=user1@gmail.com}]
지금보면 oidc 는 oauth2 를 호출하긴 하지만 데이터 차이가 많이 나는 모습을 볼 수 있습니다 그래서 자신이 현재 사용하는 어플리케이션의 목적에 맞게 oidc 를 쓸지 oauth2 를 쓸지가 결정이 되는것이다