Spring Security ‐ OAuth 2.0 Resource Server 권한 구현 - dnwls16071/Backend_Summary GitHub Wiki

📚 Scope 기반 권한 매핑 구현

  • 클라이언트가 인가 서버로 OAuth 2.0 권한 부여 요청을 할 때 사용자 리소스에 대한 접근 범위를 제한하기 위해 마련한 장치이다.
  • 클라이언트는 하나 이상의 scope를 요청할 수 있으며 동의 화면에서 사용자가 scope를 지정하게 되면 scope 범위에 제한된 토큰이 발행된다.

Scope로 리소스 접근 제어

  • 권한 부여 요청시 인가서버에 지정했던 Scope가 리소스 서버의 권한 범위에 포함되지 않으면 접근이 거부되고 포함되면 접근이 허용된다.

📚 권한 구성 커스터마이징 - JwtAuthenticationConverter

  • 인가 서버가 Scope 속성 대신 자체 커스텀 속성을 사용하거나 리소스 서버에서 속성을 내부 권한에 맞게 조정해야 할 경우 사용한다.
  • JwtAuthenticationConverter는 JWT 객체를 Authentication으로 변환하는 클래스이며 권한을 변경하는 JwtGrantedAuthoritiesConverter를 가지고 있다.
@Component
@RequiredArgsConstructor
public class VaonAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    private final AccountMapper accountMapper;
    private final ObjectMapper objectMapper;

    private static final String ACCESS_TOKEN_TYPE = "access";

    private static final String TOKEN_USE_CLAIM = "token_use";
    private static final String USERNAME_CLAIM = "username";
    private static final String PERMISSION_CLAIM = "permission";

    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        String tokenUse = jwt.getClaimAsString(TOKEN_USE_CLAIM);

        if (tokenUse.isEmpty()) {
            throw new OAuth2AuthenticationException("invalid token");
        }

        if (!tokenUse.equals(ACCESS_TOKEN_TYPE)) {
            throw new OAuth2AuthenticationException("invalid token");
        }
        
        String accountId = jwt.getClaimAsString(USERNAME_CLAIM);
        VaonAccount vaonAccount = accountMapper.findVaonAccount(accountId);
        List<GrantedAuthority> authorities = assignAuthorities(jwt);
        return new VaonAuthenticatedToken(vaonAccount, null, authorities);
    }

    private List<GrantedAuthority> assignAuthorities(Jwt jwt) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        String permissionsFromClaim = jwt.getClaim(PERMISSION_CLAIM);

        if (permissionsFromClaim == null) {
            throw new VaonRuntimeException(INVALID_TOKEN);
        }
        String permissions = GzipUtils.decompressFromGzipBase64(permissionsFromClaim);

        try {
            String[] permissionArray = objectMapper.readValue(permissions, String[].class);
            for (String permission : permissionArray) {
                authorities.add(new SimpleGrantedAuthority(permission.trim()));
            }
        } catch (JsonProcessingException e) {
            throw new OAuth2AuthenticationException("Failed to parse permissions");
        }
        return Collections.unmodifiableList(authorities);
    }
}