Repository: quarkusio/quarkus
Issue: QuarkusSecurityIdentity.builder(identity) loses isAnonymous flag

## Describe the bug
If QuarkusSecurityIdentity.builder(identity) is called with an identity that is anonymous then the identity returned from the builder returns always `false` on `isAnonymous()`
This typcially happens in a `SecurityIdentityAugmentor` that doesn't check the identity for anonymous.

### Expected behavior
If QuarkusSecurityIdentity.builder(identity) is called with an anonymous identity (`isAnonymous()` returns true) the produced identity must be an anonymous identity too, if not specified otherwise. Or if that's not supported builder(identity) should throw an exception if the specified identity is anonymous.
Since `SecurityIdentity` is an interface, QuarkusSecurityIdentity.builder should not also work with other implementations of the interface.

### Actual behavior
(Describe the actual behavior clearly and concisely.)


## To Reproduce

See above 

See also #15730

Changed files:
extensions/security/runtime/src/main/java/io/quarkus/security/runtime/QuarkusSecurityIdentity.java
extensions/security/runtime/src/test/java/io/quarkus/security/runtime/QuarkusSecurityIdentityTest.java

diff --git a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/QuarkusSecurityIdentity.java b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/QuarkusSecurityIdentity.java
index 54638f3d978..50bd92790f5 100644
--- a/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/QuarkusSecurityIdentity.java
+++ b/extensions/security/runtime/src/main/java/io/quarkus/security/runtime/QuarkusSecurityIdentity.java
@@ -22,6 +22,7 @@ public class QuarkusSecurityIdentity implements SecurityIdentity {
     private final Set<Credential> credentials;
     private final Map<String, Object> attributes;
     private final List<Function<Permission, Uni<Boolean>>> permissionCheckers;
+    private final boolean anonymous;
 
     private QuarkusSecurityIdentity(Builder builder) {
         this.principal = builder.principal;
@@ -29,6 +30,7 @@ private QuarkusSecurityIdentity(Builder builder) {
         this.credentials = Collections.unmodifiableSet(builder.credentials);
         this.attributes = Collections.unmodifiableMap(builder.attributes);
         this.permissionCheckers = Collections.unmodifiableList(builder.permissionCheckers);
+        this.anonymous = builder.anonymous;
     }
 
     @Override
@@ -38,7 +40,7 @@ public Principal getPrincipal() {
 
     @Override
     public boolean isAnonymous() {
-        return false;
+        return anonymous;
     }
 
     @Override
@@ -121,11 +123,13 @@ public static Builder builder() {
     }
 
     public static Builder builder(SecurityIdentity identity) {
-        return new Builder()
+        Builder builder = new Builder()
                 .addAttributes(identity.getAttributes())
                 .addCredentials(identity.getCredentials())
                 .addRoles(identity.getRoles())
-                .setPrincipal(identity.getPrincipal());
+                .setPrincipal(identity.getPrincipal())
+                .setAnonymous(identity.isAnonymous());
+        return builder;
     }
 
     public static class Builder {
@@ -135,6 +139,7 @@ public static class Builder {
         Set<Credential> credentials = new HashSet<>();
         Map<String, Object> attributes = new HashMap<>();
         List<Function<Permission, Uni<Boolean>>> permissionCheckers = new ArrayList<>();
+        private boolean anonymous;
         boolean built = false;
 
         public Builder setPrincipal(Principal principal) {
@@ -211,7 +216,25 @@ public Builder addPermissionChecker(Function<Permission, Uni<Boolean>> function)
             return this;
         }
 
+        /**
+         * Sets an anonymous identity status.
+         * 
+         * @param anonymous the anonymous status
+         * @return This builder
+         */
+        public Builder setAnonymous(boolean anonymous) {
+            if (built) {
+                throw new IllegalStateException();
+            }
+            this.anonymous = anonymous;
+            return this;
+        }
+
         public QuarkusSecurityIdentity build() {
+            if (principal == null && !anonymous) {
+                throw new IllegalStateException("Principal is null but anonymous status is false");
+            }
+
             built = true;
             return new QuarkusSecurityIdentity(this);
         }
diff --git a/extensions/security/runtime/src/test/java/io/quarkus/security/runtime/QuarkusSecurityIdentityTest.java b/extensions/security/runtime/src/test/java/io/quarkus/security/runtime/QuarkusSecurityIdentityTest.java
index c1704c16b5e..7d9dc11e67b 100644
--- a/extensions/security/runtime/src/test/java/io/quarkus/security/runtime/QuarkusSecurityIdentityTest.java
+++ b/extensions/security/runtime/src/test/java/io/quarkus/security/runtime/QuarkusSecurityIdentityTest.java
@@ -1,11 +1,23 @@
 package io.quarkus.security.runtime;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.security.Permission;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
 
 import org.junit.jupiter.api.Test;
 
+import io.quarkus.security.credential.Credential;
 import io.quarkus.security.credential.PasswordCredential;
 import io.quarkus.security.identity.SecurityIdentity;
+import io.smallrye.mutiny.Uni;
 
 public class QuarkusSecurityIdentityTest {
 
@@ -18,11 +30,132 @@ public void testCopyIdentity() throws Exception {
                 .addAttribute("key", "value")
                 .build();
 
+        assertFalse(identity1.isAnonymous());
+
         SecurityIdentity identity2 = QuarkusSecurityIdentity.builder(identity1).build();
+        assertFalse(identity1.isAnonymous());
 
         assertEquals(identity1.getAttributes(), identity2.getAttributes());
         assertEquals(identity1.getPrincipal(), identity2.getPrincipal());
         assertEquals(identity1.getCredentials(), identity2.getCredentials());
         assertEquals(identity1.getRoles(), identity2.getRoles());
     }
+
+    @Test
+    public void testAnonymousPrincipalWithCustomIdentity() throws Exception {
+        SecurityIdentity identity1 = new TestSecurityIdentityAnonymousPrincipal();
+        assertTrue(identity1.isAnonymous());
+        assertEquals("anonymous-principal", identity1.getPrincipal().getName());
+
+        SecurityIdentity identity2 = QuarkusSecurityIdentity.builder(identity1).build();
+        assertTrue(identity2.isAnonymous());
+        assertEquals("anonymous-principal", identity2.getPrincipal().getName());
+    }
+
+    @Test
+    public void testPrincipalNullAnonymousFalseWithBuilder() throws Exception {
+        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder()
+                .addRole("admin")
+                .addCredential(new PasswordCredential("password".toCharArray()))
+                .addAttribute("key", "value");
+        ;
+
+        assertThrows(IllegalStateException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testPrincipalNullAnonymousFalseWithCustomIdentity() throws Exception {
+        SecurityIdentity identity1 = new TestSecurityIdentityPrincipalNullAnonymousFalse();
+        assertFalse(identity1.isAnonymous());
+        assertNull(identity1.getPrincipal());
+
+        assertThrows(IllegalStateException.class, () -> QuarkusSecurityIdentity.builder(identity1).build());
+    }
+
+    @Test
+    public void testPrincipalNullAnonymousFalseWithCustomIdentityFixed() throws Exception {
+        SecurityIdentity identity1 = new TestSecurityIdentityPrincipalNullAnonymousFalse();
+        assertFalse(identity1.isAnonymous());
+        assertNull(identity1.getPrincipal());
+
+        SecurityIdentity identity2 = QuarkusSecurityIdentity.builder(identity1).setAnonymous(true).build();
+        assertTrue(identity2.isAnonymous());
+        assertNull(identity2.getPrincipal());
+    }
+
+    static class TestSecurityIdentityAnonymousPrincipal extends AbstractTestSecurityIdentity {
+
+        @Override
+        public Principal getPrincipal() {
+            return new Principal() {
+                @Override
+                public String getName() {
+                    return "anonymous-principal";
+                }
+            };
+        }
+
+        @Override
+        public boolean isAnonymous() {
+            return true;
+        }
+
+    }
+
+    static class TestSecurityIdentityPrincipalNullAnonymousFalse extends AbstractTestSecurityIdentity {
+
+        @Override
+        public Principal getPrincipal() {
+            return null;
+        }
+
+        @Override
+        public boolean isAnonymous() {
+            return false;
+        }
+
+    }
+
+    static abstract class AbstractTestSecurityIdentity implements SecurityIdentity {
+
+        @Override
+        public Set<String> getRoles() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public boolean hasRole(String role) {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public <T extends Credential> T getCredential(Class<T> credentialType) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Set<Credential> getCredentials() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public <T> T getAttribute(String name) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public Map<String, Object> getAttributes() {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public Uni<Boolean> checkPermission(Permission permission) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+    }
 }