
문제상황

회원가입한 유저의 권한을 DB에서 직접 ADMIN 으로 바꿔준 다음 ADMIN 권한을 가진 유저만 접근할 수 있는 api를 만들었는데, 여전히 유저의 권한이 USER 여서 접근할 수 없는 문제가 발생했다.
해결과정
디버깅을 돌려보니 DefaultOAuth2UserService
를 상속한 CustomOAuth2UserService
클래스의 loadUser
메소드에서 불러온
OAuth2User user = super.loadUser(userRequest);
의 user에서부터 이미 ROLE_TYPE 이미 USER 로 세팅되어 있었다.
해당 코드가 있는 전체 코드는 다음과 같다.
CustomOAuth2UserService.java
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// DefaultOAuth2UserService 의 loadUser 클래스를 사용하기 위해 super 키워드 사용. this 키워드는 내 클래스나 필드에 사용.
OAuth2User user = super.loadUser(userRequest);
try {
// 업데이트되거나 저장된 유저 정보 리턴
return this.process(userRequest, user);
} catch (AuthenticationException ex) {
throw ex;
} catch (Exception ex) {
ex.printStackTrace(); // 모든 에러 정보 출력
throw new InternalAuthenticationServiceException(ex.getMessage(), ex.getCause());
}
}
위에서 user를 만드는 함수는 super 키워드가 붙은 loadUser
를 사용하고 있고 CustomOAuth2UserService
클래스는 DefaultOAuth2UserService
클래스를 상속받고 있으므로 해당 클래스로 들어가 loadUser
메소드를 확인해 보았다.
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
Assert.notNull(userRequest, "userRequest cannot be null");
if (!StringUtils
.hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE,
"Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
.getUserNameAttributeName();
if (!StringUtils.hasText(userNameAttributeName)) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE,
"Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);
ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
Map<String, Object> userAttributes = response.getBody();
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}
return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
}
코드가 참 복잡하게 적혀있는데, 여기서 주목할 부분은
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
이 부분이다.
OAuth2UserAuthority
이 녀석이 ROLE_USER를 알아서 만들어서 넣어주고 있었다.
이 클래스를 타고 들어가보면
public OAuth2UserAuthority(Map<String,Object>attributes) {
this("ROLE_USER",attributes);
}
이렇게 기본 권한을 USER 로 넣어주는 생성자가 존재하는 걸 볼 수 있다.
그렇다면 기본으로 세팅된 ROLE_TYPE을 DB에서 바꿔준 ROLE_TYPE인 ADMIN으로 변경해주면 해결이 될 것 같다!
유저의 ROLE_TPYE 을 어디에서 변경해주는 게 좋을까? 다시 CustomOAuth2UserService
클래스로 가보자
private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) {
// 요청한 유저 정보에 들어있는 provider type을 가져온다.
ProviderType providerType = ProviderType.valueOf(userRequest.getClientRegistration().getRegistrationId().toUpperCase());
// 유저 정보를 가져온다.
OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, user.getAttributes());
// DB에 저장된 유저 정보를 가져온다.
User savedUser = userRepository.findByEmail(userInfo.getEmail());
if (savedUser != null) {
// DB에 유저 정보가 있을 때
if (providerType != savedUser.getProviderType()) {
throw new OAuthProviderMissMatchException(ErrorCode.ALREADY_LOGIN_ACCOUNT.getCode() + "," + providerType);
}
updateUser(savedUser, userInfo);
} else {
// DB에 유저 정보가 없을 때
savedUser = createUser(userInfo, providerType);
}
return UserPrincipal.create(savedUser, user.getAttributes());
}
해당 클래스의 process
메소드를 통해서 회원가입을 하지 않은 유저라면 DB에 저장하고, 회원가입을 했다면 변경된 값을 DB에 업데이트 해준다음, UserPrincipal
에 DB에 저장한 User 객체와 그 외 정보들(user.getAttributes)를 넣어 생성해주게 된다.
이제 여기서 만든 UserPrincipal
객체를 리턴하면 Authentication
에 담겨 successHandler
를 타고, jwt 토큰을 만들게 된다.
즉, UserPrincipal
을 생성할 때 ROLE_TYPE을 변경해주면 JWT 토큰에 담기는 ROLE_TYPE도 변경된다는 말이다.
이번엔 UserPrincipal
클래스의 create
메소드로 가보자.
public static UserPrincipal create(User user) {
return UserPrincipal.builder()
.userId(user.getSocialId())
.password(user.getPassword())
.providerType(user.getProviderType())
.roleType(user.getRoleType())
.authorities(Collections.singletonList(new SimpleGrantedAuthority(RoleType.USER.getCode())))
.build();
}
public static UserPrincipal create(User user, Map<String, Object> attributes) {
UserPrincipal userPrincipal = create(user);
userPrincipal.setAttributes(attributes);
return userPrincipal;
}
응? 위에서 클래스를 타고 타면서 봤던 ROLE_TYPE은 아무 상관없고 최종적으로 create
메소드에서 ROLE_TYPE 을 결정 짓고 있었다. 아…
최종적으로 위에서 첫번째 create 메소드를 수정해준 코드는 다음과 같다.
public static UserPrincipal create(User user) {
SimpleGrantedAuthority simpleGrantedAuthority;
if (user.getRoleType().getCode().equals("ROLE_ADMIN")) {
simpleGrantedAuthority = new SimpleGrantedAuthority(RoleType.ADMIN.getCode());
} else {
simpleGrantedAuthority = new SimpleGrantedAuthority(RoleType.USER.getCode());
}
return UserPrincipal.builder()
.userId(user.getSocialId())
.password(user.getPassword())
.providerType(user.getProviderType())
.roleType(user.getRoleType())
.authorities(Collections.singletonList(simpleGrantedAuthority))
.build();
}
파라미터로 넘겨받은 User는 DB에 저장되어있던 User 객체이고, DB의 ROLE_TYPE 컬럼 값이 ADMIN인지 판별해 그에 해당하는 권한을 authorities에 넣어주면 된다.
JWT 사이트에서 토큰을 복호화해보면 정상적으로 ADMIN 권한이 부여된걸 확인할 수 있다!!

위의 코드 대부분은 다음 글을 참고했으므로, 해당 글을 보면서 위의 로직을 확인하는 걸 추천한다!
[Spring Boot] OAuth2 소셜 로그인 가이드 (구글, 페이스북, 네이버, 카카오)
[Spring Boot] OAuth2 소셜 로그인 가이드 (구글, 페이스북, 네이버, 카카오)
스프링부트를 이용하여 구글, 페이스북, 네이버, 카카오 OAuth2 로그인 구현하는 방법에 대해서 소개합니다.
deeplify.dev
'Study > Trouble Shooting' 카테고리의 다른 글
[삽질로그] 배포 후, cookie가 넘어가지 않는 문제 (0) | 2022.07.21 |
---|---|
[삽질로그] CORS 정책 위반 (0) | 2022.06.19 |
[삽질로그] 소스트리 강제 종료 에러 (0) | 2022.06.11 |
[삽질로그] JPA 지연로딩 (0) | 2022.06.08 |
[삽질로그] Entity 관련 삽질 (0) | 2022.06.08 |

문제상황

회원가입한 유저의 권한을 DB에서 직접 ADMIN 으로 바꿔준 다음 ADMIN 권한을 가진 유저만 접근할 수 있는 api를 만들었는데, 여전히 유저의 권한이 USER 여서 접근할 수 없는 문제가 발생했다.
해결과정
디버깅을 돌려보니 DefaultOAuth2UserService
를 상속한 CustomOAuth2UserService
클래스의 loadUser
메소드에서 불러온
OAuth2User user = super.loadUser(userRequest);
의 user에서부터 이미 ROLE_TYPE 이미 USER 로 세팅되어 있었다.
해당 코드가 있는 전체 코드는 다음과 같다.
CustomOAuth2UserService.java
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// DefaultOAuth2UserService 의 loadUser 클래스를 사용하기 위해 super 키워드 사용. this 키워드는 내 클래스나 필드에 사용.
OAuth2User user = super.loadUser(userRequest);
try {
// 업데이트되거나 저장된 유저 정보 리턴
return this.process(userRequest, user);
} catch (AuthenticationException ex) {
throw ex;
} catch (Exception ex) {
ex.printStackTrace(); // 모든 에러 정보 출력
throw new InternalAuthenticationServiceException(ex.getMessage(), ex.getCause());
}
}
위에서 user를 만드는 함수는 super 키워드가 붙은 loadUser
를 사용하고 있고 CustomOAuth2UserService
클래스는 DefaultOAuth2UserService
클래스를 상속받고 있으므로 해당 클래스로 들어가 loadUser
메소드를 확인해 보았다.
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
Assert.notNull(userRequest, "userRequest cannot be null");
if (!StringUtils
.hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE,
"Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
.getUserNameAttributeName();
if (!StringUtils.hasText(userNameAttributeName)) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE,
"Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);
ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
Map<String, Object> userAttributes = response.getBody();
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}
return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
}
코드가 참 복잡하게 적혀있는데, 여기서 주목할 부분은
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
이 부분이다.
OAuth2UserAuthority
이 녀석이 ROLE_USER를 알아서 만들어서 넣어주고 있었다.
이 클래스를 타고 들어가보면
public OAuth2UserAuthority(Map<String,Object>attributes) {
this("ROLE_USER",attributes);
}
이렇게 기본 권한을 USER 로 넣어주는 생성자가 존재하는 걸 볼 수 있다.
그렇다면 기본으로 세팅된 ROLE_TYPE을 DB에서 바꿔준 ROLE_TYPE인 ADMIN으로 변경해주면 해결이 될 것 같다!
유저의 ROLE_TPYE 을 어디에서 변경해주는 게 좋을까? 다시 CustomOAuth2UserService
클래스로 가보자
private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) {
// 요청한 유저 정보에 들어있는 provider type을 가져온다.
ProviderType providerType = ProviderType.valueOf(userRequest.getClientRegistration().getRegistrationId().toUpperCase());
// 유저 정보를 가져온다.
OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, user.getAttributes());
// DB에 저장된 유저 정보를 가져온다.
User savedUser = userRepository.findByEmail(userInfo.getEmail());
if (savedUser != null) {
// DB에 유저 정보가 있을 때
if (providerType != savedUser.getProviderType()) {
throw new OAuthProviderMissMatchException(ErrorCode.ALREADY_LOGIN_ACCOUNT.getCode() + "," + providerType);
}
updateUser(savedUser, userInfo);
} else {
// DB에 유저 정보가 없을 때
savedUser = createUser(userInfo, providerType);
}
return UserPrincipal.create(savedUser, user.getAttributes());
}
해당 클래스의 process
메소드를 통해서 회원가입을 하지 않은 유저라면 DB에 저장하고, 회원가입을 했다면 변경된 값을 DB에 업데이트 해준다음, UserPrincipal
에 DB에 저장한 User 객체와 그 외 정보들(user.getAttributes)를 넣어 생성해주게 된다.
이제 여기서 만든 UserPrincipal
객체를 리턴하면 Authentication
에 담겨 successHandler
를 타고, jwt 토큰을 만들게 된다.
즉, UserPrincipal
을 생성할 때 ROLE_TYPE을 변경해주면 JWT 토큰에 담기는 ROLE_TYPE도 변경된다는 말이다.
이번엔 UserPrincipal
클래스의 create
메소드로 가보자.
public static UserPrincipal create(User user) {
return UserPrincipal.builder()
.userId(user.getSocialId())
.password(user.getPassword())
.providerType(user.getProviderType())
.roleType(user.getRoleType())
.authorities(Collections.singletonList(new SimpleGrantedAuthority(RoleType.USER.getCode())))
.build();
}
public static UserPrincipal create(User user, Map<String, Object> attributes) {
UserPrincipal userPrincipal = create(user);
userPrincipal.setAttributes(attributes);
return userPrincipal;
}
응? 위에서 클래스를 타고 타면서 봤던 ROLE_TYPE은 아무 상관없고 최종적으로 create
메소드에서 ROLE_TYPE 을 결정 짓고 있었다. 아…
최종적으로 위에서 첫번째 create 메소드를 수정해준 코드는 다음과 같다.
public static UserPrincipal create(User user) {
SimpleGrantedAuthority simpleGrantedAuthority;
if (user.getRoleType().getCode().equals("ROLE_ADMIN")) {
simpleGrantedAuthority = new SimpleGrantedAuthority(RoleType.ADMIN.getCode());
} else {
simpleGrantedAuthority = new SimpleGrantedAuthority(RoleType.USER.getCode());
}
return UserPrincipal.builder()
.userId(user.getSocialId())
.password(user.getPassword())
.providerType(user.getProviderType())
.roleType(user.getRoleType())
.authorities(Collections.singletonList(simpleGrantedAuthority))
.build();
}
파라미터로 넘겨받은 User는 DB에 저장되어있던 User 객체이고, DB의 ROLE_TYPE 컬럼 값이 ADMIN인지 판별해 그에 해당하는 권한을 authorities에 넣어주면 된다.
JWT 사이트에서 토큰을 복호화해보면 정상적으로 ADMIN 권한이 부여된걸 확인할 수 있다!!

위의 코드 대부분은 다음 글을 참고했으므로, 해당 글을 보면서 위의 로직을 확인하는 걸 추천한다!
[Spring Boot] OAuth2 소셜 로그인 가이드 (구글, 페이스북, 네이버, 카카오)
[Spring Boot] OAuth2 소셜 로그인 가이드 (구글, 페이스북, 네이버, 카카오)
스프링부트를 이용하여 구글, 페이스북, 네이버, 카카오 OAuth2 로그인 구현하는 방법에 대해서 소개합니다.
deeplify.dev
'Study > Trouble Shooting' 카테고리의 다른 글
[삽질로그] 배포 후, cookie가 넘어가지 않는 문제 (0) | 2022.07.21 |
---|---|
[삽질로그] CORS 정책 위반 (0) | 2022.06.19 |
[삽질로그] 소스트리 강제 종료 에러 (0) | 2022.06.11 |
[삽질로그] JPA 지연로딩 (0) | 2022.06.08 |
[삽질로그] Entity 관련 삽질 (0) | 2022.06.08 |