[Spring Security] 유저의 ROLE별로 Authority 권한 부여하기

김태훈·2023년 10월 28일
0

Spring Security

목록 보기
6/7
post-thumbnail

우리의 서비스는 개발자를 대상으로한 질문/답변 커뮤니티와 기술블로그가 통합된 커뮤니티이다.

이전에는 유저별로 ROLE이 필요가 없었다. 그냥 '블로그를 사용하고자 하는 유저'가 우리 서비스의 유일한 사용자이기 때문이었다.

하지만 최근에 기능 하나를 추가하게 되었는데, 해당 서비스에서 활동하고 있는 개발자를 채용하고자 하는 '채용자'라는 사용자가 생겼다.
이렇게 되면서, 그들만의 특별한 기능을 마련해야 했다.

먼저 '채용자'는 그들이 특정 회사의 담당자인지 인증하는 과정도 거쳐야 해서,
1. 임시 채용자 권한
2. 인증받은 채용자 권한
이렇게 나누기도 해야했고, 일반 유저에게도 일반 사용자라는 권한을 추가하여야 했다.

1. 이전 상황

이전에는 Authentication확인을 위한 비교 대상 객체인 UserDetails의 권한 Authoritiy가 다 Empty List를 반환하게 했다.

그 뿐만 아니라, 코드가 좀 조잡하기도 했다.
getAuthorities 함수내에서 소셜로그인인지, 일반로그인인지 분기하는 것도 도메인 로직상 아주 불필요했다. 어차피 같은 Authentication 을위한 객체이기 때문이다.
소셜 로그인용 생성자 파라미터에는 User객체가 들어가 있는데 의미 없는 코드이기도 했다.

이전 CustomUserDetails 코드이다.
설명에 필요한 코드만 첨부하였다.

public class CustomUserDetails implements UserDetails, OAuth2User, Serializable {

    private static final long serialVersionUID = 174726374856727L;

    private String id;	// DB에서 PK 값
    private String loginId;		// 로그인용 ID 값
    private String password;	// 비밀번호
    private String email;	//이메일
    private boolean emailVerified;	//이메일 인증 여부
    private boolean locked;	//계정 잠김 여부
    private String nickname;	//닉네임
    private Collection<GrantedAuthority> authorities;	//권한 목록

    private User user;
    private Map<String, Object> attributes;

    //Social Login 용
    public CustomUserDetails(String id, Collection<GrantedAuthority> authorities,User user, Map<String, Object> attributes) {
        //PrincipalOauth2UserService 참고
        this.id = id;
        this.authorities = authorities; // social 회원가입 여부를 나타내는 것으로 사용됨
        this.user=user;
        this.attributes = attributes;
    }

    //Non Social Login 용
    public CustomUserDetails(Long authId, String userEmail, String userPw, boolean emailVerified,boolean locked) {
        this.id = String.valueOf(authId);
        this.email = userEmail;
        this.password = userPw;
        this.emailVerified = emailVerified;
        this.locked = !locked;
    }

    /**
     * 해당 유저의 권한 목록
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (user ==null){ //non social
            return Collections.emptyList();
        }
        else { //social
            return user.getAuthorities();
        }
    }
}

이 authority객체에 유저 역할별로 다른 ROLE에 해당하는 권한을 부여하는 작업이 필요했다.

2. ROLE 설정

@Getter
@RequiredArgsConstructor
public enum Role {
    USER ("ROLE_USER"),
    EMPLOYER("ROLE_EMPLOYER,ROLE_USER"),
    ADMIN ("ROLE_ADMIN,ROLE_EMPLOYER,ROLE_USER"),
    TEMP_EMPLOYER("ROLE_TEMPORARY_EMPLOYER"),
    TEMP_USER ("ROLE_TEMPORARY_USER"),
    OAUTH_FIRST_JOIN ("ROLE_FIRST_JOIN_OAUTH_USER");

    private final String roles;
    public static String getIncludingRoles(String role){
        return Role.valueOf(role).getRoles();
    }
    public static String addRole(Role role, String addRole){
        String priorRoles = role.getRoles();
        priorRoles += ","+addRole;
        return priorRoles;
    }
    public static String addRole(String roles, Role role){
        return roles + "," + role.getRoles();

    }
}

유저별로 다른 권한을 부여하기 위해 Role이라는 enum클래스를 생성하고, enum 인스턴스 별로, 권한들을 부여하였다.

  1. getIncludingRoles(String role)
    DB에 User Role 정보가 enum 인스턴스들의 이름 그대로 저장되어 있고,
    해당 enum클래스의 필드인 roles를 가져오기 위한 함수
  2. addRole(String roles, Role role)
    권한들이 모여있는 String에 추가하고 싶은 Role의 roles들을 추가하여 해당 권한들을 String으로 반환하는 함수

1) Role별로 가지고 있는 Roles

이 때, enum 클래스 별로 '다수'의 권한이 존재한다.
예를들어 'USER'의 Role의 경우 딱 한가지 권한인 'ROLE_USER'가 부여된다.
하지만 'EMPLOYER'의 Role의 경우 'ROLE_USER' 와 'ROLE_EMPLOYER" 권한을 부여하여, 유저의 권한도, 채용자의 권한도 갖게 한다.
즉, 넓은 범위의 권한을 포함할 수 있게끔, 작은 권한들 부터 큰 권한들까지 모두를 포함하게 코드를 작성하였다
이렇게 해야, 권한별로 접근할 수 있는 resource를 정하기 편하다.

왜냐하면 Security Config에서 채용자의 권한이 필요한 특정 엔드 포인트에 'hasRole("EMPLOYER") 이렇게 설정하는데, 우리의 로직이 채용자의 경우에도, USER권한을 행세할 수 있을 때, Role 별로 한가지의 권한만 부여하게된다면, 여러 권한을 hasRole로 나열해야할 것이기 때문이다.

2) 'ROLE_'은 필수적이다.

1. 기본 배경 지식

Employer의 권한을 가진 유저만 접근할 수 있는 엔드포인트가 GET /users/employer 라고 하자.
그런 설정을 하기 위해서는 SecurityConfig에 다음 설정을 추가해야 한다.

httpSecurity.requestMatchers(HttpMethod.GET,"/users/employer")
    .hasRole("EMPLOYER")

그러면 SecurityFilter는 'ROLE_EMPLOYER' 권한을 가지고 있는 유저인지 확인한다.

SecurityFilterChain의 설정정보를 등록하는 AuthorizeHttpRequestConfigurer 의 'hasRole'관련 설정이다.

설명을 읽어보면, 'ROLE_' 이라는 prefix가 자동으로 붙게 된다.

즉 엔드포인트마다 예를들어 'ROLE_USER', 'ROLE_EMPLOYER' 의 권한을 가지고 있는 Authentication 객체인지 확인한다는 것이다.

저 요구사항에 맞추기 위해서, Role enum에 roles 필드들의 앞에 ROLE_ 접두어를 붙여주었다.

2. 서비스 로직에 어떻게 적용시킬 것인지 계획해보자

회원가입을 하거나, 채용자 인증을 거칠 때, enum타입의 이름 그대로를 DB에 저장할 것이다.
즉 일반 유저는 'USER'로, 인증받은 채용자는 'EMPLOYER', 인증 대기중인 채용자는 'TEMP_EMPLOYER' 이다.
그 이후, 로그인 로직을 처리할 때, DB에 저장되어있는 ROLE을 가져와서, CustomUserDetailsauthority필드에 해당 Role 인스턴스가 가지고 있는 roles들을 하나하나 추가해주면 된다.

그 이후, jwt token을 만들고 클라이언트로 응답해야 한다. 이 token에는 당연히 authority관련 정보들이 들어 있어야, 나중에 클라이언트에서 정보를 요청할 때 jwt filter에서 해당 token을 복호화 하고, authentication 을 확인하고 해당 유저의 authority를 가져올 수 있을 것이다.

기존의 jwt token을 만들 때에는 username(우리 서비스에서는 이게 PK값이다) 과 빈(empty list) authority객체를 사용하여 jwt토큰을 암호화 하였다. 하지만 이제는 실제 authentication 객체 내에 들어있는 authorities들을 불러와서 jwt토큰을 만들어야 하는 것이 목표이다.

정리한 순서는 다음과 같다.

<일반 로그인> (회원가입을 해야 로그인이 가능하다)

  1. 로그인 성공시, DB에 저장되어 있는 ROLE 'USER'를 가져와서 해당 Role 이 가지고 있는 Roles를 authentication 의 authorities에 추가
  2. 인증받은 Authentication 객체를 확인하고 Authentication Success Handler 호출이 됨
  3. Token Provider 에서 access token과 refresh 토큰을 Authentication객체 정보를 파싱해서 만들고 cookie 에 담아 Client에 전송

<소셜 로그인> 최초 로그인의 경우 서비스 로직상 회원가입과 동일하다.

  1. DB에서 해당 소셜로그인의 계정으로 등록된 계정이 있는지 확인
    2-1. 계정이 있으면 회원가입된 유저이므로, 일반 로그인과 동일하게 Authentication 객체의 authority에 DB에 저장된 ROLE 'USER'정보를 가져오고 authorities에 해당 ROLE의 roles를 추가
    2-2. 계정이 없으면 회원가입 해야하는 유저이므로, DB에 해당 유저 정보를 ROLE 'USER'로 저장하고, authority에 최초 로그인을 위한 권한을 임시로 추가하고 DB에 저장된 ROLE의 roles들에 대한 authority도 추가
  2. 인증받은 Authentication 객체를 확인하고 Authentication Success Handler 호출이 됨
  3. Handler에서 authentication 객체의 authorities에 최초 로그인의 권한이 존재하는지 아닌지를 판단하고, redirect url을 분기함

3. 코드 살펴보기

1) CustomUserDetails

public class CustomUserDetails implements UserDetails, OAuth2User, Serializable {

    private static final long serialVersionUID = 174726374856727L;

    private String id;	// DB에서 PK 값
    private String loginId;		// 로그인용 ID 값
    private String password;	// 비밀번호
    private String email;	//이메일
    private boolean emailVerified;	//이메일 인증 여부
    private boolean locked;	//계정 잠김 여부
    private String nickname;	//닉네임
    private Collection<GrantedAuthority> authorities;	//권한 목록

    private User user;
    private Map<String, Object> attributes;

    //Social Login 용
    public CustomUserDetails(String id, String roles, Map<String, Object> attributes) {
        //PrincipalOauth2UserService 참고
        this.id = id;
        this.authorities = createAuthorities(roles);
        this.attributes = attributes;
    }

    //Non Social + Employer 로그인 용도
    public CustomUserDetails(Long authId, String roles, String userEmail, String userPw, boolean emailVerified, boolean locked) {
        this.id = String.valueOf(authId);
        this.authorities = createAuthorities(roles);
        this.email = userEmail;
        this.password = userPw;
        this.emailVerified = emailVerified;
        this.locked = !locked;
    }

    private Collection<GrantedAuthority> createAuthorities(String roles){
        Collection<GrantedAuthority> authorities = new ArrayList<>();

        for(String role : roles.split(",")){
            if (!StringUtils.hasText(role)) continue;
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }

    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }

    /**
     * 해당 유저의 권한 목록
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    /**
     * 비밀번호
     */
    @Override
    public String getPassword() {
        return password;
    }


    /**
     * PK값
     */
    @Override
    public String getUsername() {
        return id;
    }

    /**
     * 계정 만료 여부
     * true : 만료 안됨
     * false : 만료
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 계정 잠김 여부
     * true : 잠기지 않음
     * false : 잠김
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return locked;
    }

    /**
     * 비밀번호 만료 여부
     * true : 만료 안됨
     * false : 만료
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }


    /**
     * 사용자 활성화 여부
     * ture : 활성화
     * false : 비활성화
     * @return
     */
    @Override
    public boolean isEnabled() {
        //이메일이 인증되어 있고 계정이 잠겨있지 않으면 true
        //상식과 조금 벗어나서, Customizing 하였음
        return (emailVerified && locked);

    }

    @Override
    public String getName() {
        String sub = attributes.get("sub").toString();
        return sub;
    }
}

OAuth와 일반 로그인 인증 대상 객체를 담기 위하여 OAuth2User, UserDetails를 둘다 구현함.

DB에 저장된 role을 꺼내고, 해당 role의 roles들을 파라미터로 받는
createAuthorities 로 authority를 생성한다.
roles들은 쉼표로 나열되어 있으므로, 이를 parsing하고, simpleGrantedAuthority 함수로 String의 role들을 하나씩 추가한다.

2) 일반 로그인

@Override
public UserDetails loadUserByUsername(String userEmail) throws UsernameNotFoundException {
    NonSocialMember member = nonSocialMemberRepository.findNonSocialMemberByEmail(userEmail)
            .orElseThrow(() -> new UsernameNotFoundException("이 이메일과 매칭되는 유저가 존재하지 않습니다 : " + userEmail));
    // non social, social 섞어있기 때문에, user_id를 CustomUserDetail 의 id로 생성합니다. -> 토큰의 getName의 user_id가 들어갑니다.
    return new CustomUserDetails(member.getUserId(), Role.getIncludingRoles(member.getRole()), member.getUserEmail(), member.getUserPw(), true, false);
}

CustomUserDetails를 만들기 위해서, Role.getIncludingRoles()를 활용하여, DB에서 가져온 Role 에 해당하는 roles들을 파라미터로 건네주고 유효한 UserDetails객체를 만든다.

이를 통해, authenticate의 과정을 거친다.

3) 소셜 로그인

@Slf4j
@Service
@RequiredArgsConstructor
public class OAuthService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final MemberRepository memberRepository;


    @Override
    @Transactional
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        OAuth2UserService delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);
        String email = oAuth2User.getAttribute("email");
        String nickname = UUID.randomUUID().toString().substring(0,15);

        Optional<SocialMember> socialMember = memberRepository.findSocialMemberByEmail(email);

        if(socialMember.isEmpty()){
            SocialMember savedSocialMember = SocialMember.createSocialMember(email, nickname);
            SaveMemberResponseDto savedResponse = memberRepository.save(savedSocialMember);
            String roles = Role.addRole(Role.getIncludingRoles(savedResponse.getRole()), Role.OAUTH_FIRST_JOIN);// 최초 회원가입을 위한 임시 role 추가
            return new CustomUserDetails(String.valueOf(savedResponse.getId()),roles,oAuth2User.getAttributes());
        }
        else{
            return new CustomUserDetails(String.valueOf(socialMember.get().getUserId()),Role.getIncludingRoles(socialMember.get().getRole()),oAuth2User.getAttributes());
        }
    }
}

최초 회원가입의 경우 socialMember.isEmpty()
CustomUserDetails에 회원가입을 처리하기 위한 임시 권한 부여

4) SuccessHandler

@Component
@RequiredArgsConstructor
@Slf4j
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    private final TokenProvider tokenProvider;
    @Value("${jwt.domain}") private String domain;
    @Value("${oauth-signup-uri}") private String signUpURI;
    @Value("${oauth-signin-uri}") private String signInURI;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        String accessToken = tokenProvider.createAccessToken(authentication);
        String refreshToken = tokenProvider.createRefreshToken(authentication);

        /**
         * 일반 로그인일 경우 생성되는 Authentication 객체를 상속한 UsernamePasswordAuthenticationToken 으로 response 생성
         * 바로 jwt 토큰 발급하여 response 에 쿠키를 추가합니다.
         */
        if (authentication instanceof UsernamePasswordAuthenticationToken){
            makeSuccessResponseBody(response);
            resolveResponseCookieByOrigin(request, response, accessToken, refreshToken);
            return;
        }

        resolveResponseCookieByOrigin(request, response, accessToken, refreshToken);
        response.sendRedirect(redirectUriByFirstJoinOrNot(authentication));

    }

    private static void makeSuccessResponseBody(HttpServletResponse response) throws IOException {
        String successResponse = convertSuccessObjectToString();
        response.setStatus(response.SC_OK);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write(successResponse);
    }

    private static String convertSuccessObjectToString() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        IsSuccessResponseDto isSuccessResponseDto = new IsSuccessResponseDto(true, "로그인에 성공하였습니다.");
        String successResponse = objectMapper.writeValueAsString(isSuccessResponseDto);
        return successResponse;
    }

    private void resolveResponseCookieByOrigin(HttpServletRequest request, HttpServletResponse response, String accessToken, String refreshToken){
        if(request.getServerName().equals("localhost") || request.getServerName().equals("dev.inforum.me")){
            addCookie(accessToken, refreshToken, response,false);
        }
        else{
            addCookie(accessToken, refreshToken, response,true);
        }
    }

    private void addCookie(String accessToken, String refreshToken, HttpServletResponse response,boolean isHttpOnly) {
        String accessCookieString = makeAccessCookieString(accessToken, isHttpOnly);
        String refreshCookieString = makeRefreshCookieString(refreshToken, isHttpOnly);
        response.setHeader("Set-Cookie", accessCookieString);
        response.addHeader("Set-Cookie", refreshCookieString);
    }

    private String makeAccessCookieString(String token,boolean isHttpOnly) {
        if(isHttpOnly){
            return "accessToken=" + token + "; Path=/; Domain=" + domain + "; Max-Age=3600; SameSite=Lax; HttpOnly; Secure";
        }else{
            return "accessToken=" + token + "; Path=/; Domain=" + domain + "; Max-Age=3600;";
        }
    }

    private String makeRefreshCookieString(String token,boolean isHttpOnly) {
        if(isHttpOnly){
            return "refreshToken=" + token + "; Path=/; Domain=" + domain + "; Max-Age=864000; SameSite=Lax; HttpOnly; Secure";
        }else{
            return "refreshToken=" + token + "; Path=/; Domain=" + domain + "; Max-Age=864000;";
        }
    }

    private String redirectUriByFirstJoinOrNot(Authentication authentication){
        OAuth2User oAuth2User = (OAuth2User)authentication.getPrincipal();
        Collection<? extends GrantedAuthority> authorities = oAuth2User.getAuthorities();
        if(authorities.stream().filter(o -> o.getAuthority().equals(Role.OAUTH_FIRST_JOIN)).findAny().isPresent()){
            return UriComponentsBuilder.fromHttpUrl(signUpURI)
                    .path(authentication.getName())
                    .build().toString();

        }
        else{ // non social 로그인의 경우 회원가입한 유저이므로 else문으로 항상 들어감.
            return UriComponentsBuilder.fromHttpUrl(signInURI)
                    .build().toString();
        }
    }
}

소셜 로그인의 경우, 최초 로그인 여부에 따라 redirect를 하기 위해서, authentication.getAuthorities() 를 활용해 회원가입의 절차가 필요한 authentication인지 확인하고 redirect 시킴]

5) TokenProvider

@Slf4j
@Component
public class TokenProvider implements InitializingBean {

    private static final String AUTHORITIES_KEY = "auth";
    private final String secret;
    private final long accessTokenValidityInMilliseconds;
    private final long refreshTokenValidityInMilliseconds;

    private Key key;
    public TokenProvider(
            @Value("${jwt.secret}") String secret,
            @Value("${jwt.access-token-validity-in-seconds}") long accessTokenValidityInMilliseconds,
            @Value("${jwt.refresh-token-validity-in-seconds}") long refreshTokenValidityInMilliseconds) {
        this.secret = secret;
        this.accessTokenValidityInMilliseconds = accessTokenValidityInMilliseconds * 1000;
        this.refreshTokenValidityInMilliseconds = refreshTokenValidityInMilliseconds * 1000;
    }

    // 빈이 생성되고 주입을 받은 후에 secret값을 Base64 Decode해서 key 변수에 할당하기 위해
    @Override
    public void afterPropertiesSet() {
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    public String createAccessToken(Authentication authentication) {
        String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        // 토큰의 expire 시간을 설정
        long now = (new Date()).getTime();
        Date validity = new Date(now + this.accessTokenValidityInMilliseconds);
        log.info("[Login] User Id = {}, authority = {}",authentication.getName(), authorities);
        return Jwts.builder()
                .setSubject(authentication.getName()) // user_id가 반환됨
                .claim(AUTHORITIES_KEY, authorities) // 정보 저장
                .signWith(key, SignatureAlgorithm.HS512) // 사용할 암호화 알고리즘과 , signature 에 들어갈 secret값 세팅
                .setExpiration(validity) // set Expire Time 해당 옵션 안넣으면 expire안함
                .compact();
    }
    public String createRefreshToken(Authentication authentication) {
        String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        // 토큰의 expire 시간을 설정
        long now = (new Date()).getTime();
        Date validity = new Date(now + this.refreshTokenValidityInMilliseconds);
        return Jwts.builder()
                .setSubject(authentication.getName()) // user_id가 반환됨
                .claim(AUTHORITIES_KEY, authorities) // 정보 저장
                .signWith(key, SignatureAlgorithm.HS512) // 사용할 암호화 알고리즘과 , signature 에 들어갈 secret값 세팅
                .setExpiration(validity) // set Expire Time 해당 옵션 안넣으면 expire안함
                .compact();
    }

    // 토큰으로 클레임을 만들고 이를 이용해 유저 객체를 만들어서 최종적으로 authentication 객체를 리턴
    public Authentication getAuthentication(String token) {
        Claims claims = Jwts
                .parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();
        List<SimpleGrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());

        User principal = new User(claims.getSubject(), "", authorities);

        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }
    // 토큰의 유효성 검증을 수행
    public boolean validateToken(String token){
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            throw new BadTokenException("잘못된 JWT 서명입니다.", e);
        } catch (ExpiredJwtException e ){
            return false;
        } catch (UnsupportedJwtException e) {
            throw new BadTokenException("지원되지 않는 JWT 토큰입니다.", e);
        } catch (IllegalArgumentException e) {
            throw new BadTokenException("JWT 토큰이 잘못되었습니다.", e);
        }
    }

}
  1. createAccessToken() 메서드로 authentication에 존재하는 authority 정보들을 쉼표로 파싱후, jwt builder로 jwt token 생성

  2. getAuthnetication(String token) 메서드를 이용하여, 발급된 token정보를 쿠키 값으로 계속 사용하는 유저들의 authorities들을 파악.
    그후 UsernamePasswordAuthenticationToken의 authentication 구현 객체를 SecurityContextHolder 객체에 내부적으로 저장함 -> 사실상 세션임 언제든지 꺼내서 확인할 수 있다. (같은 Request - Response 사이클 내에서만)

번외

@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
    return (authorities) -> {
        Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

        authorities.forEach(authority -> {
            if (OidcUserAuthority.class.isInstance(authority)) {
                OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;

                OidcIdToken idToken = oidcUserAuthority.getIdToken();
                OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();

                // Map the claims found in idToken and/or userInfo
                // to one or more GrantedAuthority's and add it to mappedAuthorities

            } else if (OAuth2UserAuthority.class.isInstance(authority)) {
                OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority) authority;

                Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();

                // Map the attributes found in userAttributes
                // to one or more GrantedAuthority's and add it to mappedAuthorities

            }
        });

        return mappedAuthorities;


    };
}

해당 정보를 Security Config에 Bean으로 등록시켰다가,
OAuth관련 authority에 어떠한 authority도 등록되지 않았음.
공식문서에서 advanced setting이라고 해서 OAuth관련해서 뭐 해주는 설정인가보다 하고 그냥 긁어왔다가, 이로 인해 디버깅을 오래했었다.

내가 등록했던 것들은 SimpleGrantedAuthority타입의 authority class인데 여기에서 내부적으로 OAuth2UserSecurty 클래스 타입으로 바뀌면서 mapping이 이상하게 된것으로 추정된다.
아무거나 긁어오지 말자..

profile
기록하고, 공유합시다

0개의 댓글