You can do this, if speed is important, in a middleware, checking if the request has no session and then block right away or in a request event subscriber after authentication checking for the path. Or a bit slower after routing if you want to check that the matched route is the canonical entity route and use the node entity in your logic.
The easiest but slowest is in a page preprocess hook. You can even do this in a theme.
function mytheme_preprocess_page(&$variables) {
if (\Drupal::currentUser()->isAnonymous()) {
if (isset($variables['node'])) {
$node = $variables['node'];
if ($node instanceof \Drupal\node\NodeInterface) {
if ($node->getType() == 'article') {
// use PathInfo instead of RequestUri to ignore query strings
$path = \Drupal::request()->getPathInfo();
if (preg_match('#/node/\d+/*$#', $path)) {
throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
}
}
}
}
}
}
The cached page is varied by path and user permission by default and so the not found exception.
This doesn't work if you have installed the Redirect module with the setting Global Redirect - Enforce clean and canonical URLs enabled. In this case you need a request event subscriber with a priority higher than 33.