Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions bom/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,11 @@
<artifactId>quarkus-spring-web-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-security-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-data-deployment</artifactId>
Expand Down
33 changes: 33 additions & 0 deletions bom/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
<wiremock.version>2.24.1</wiremock.version>
<spring.version>5.1.8.RELEASE</spring.version>
<spring-data.version>2.1.9.RELEASE</spring-data.version>
<spring-security.version>5.2.0.RELEASE</spring-security.version>
<mvel2.version>2.4.4.Final</mvel2.version>
<mockito.version>3.0.0</mockito.version>
<jna.version>5.3.1</jna.version>
Expand Down Expand Up @@ -606,6 +607,11 @@
<artifactId>quarkus-spring-di</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web</artifactId>
Expand Down Expand Up @@ -2390,6 +2396,33 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring-security.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- Keycloak -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public final class FeatureBuildItem extends MultiBuildItem {
public static final String SPRING_DI = "spring-di";
public static final String SPRING_WEB = "spring-web";
public static final String SPRING_DATA_JPA = "spring-data-jpa";
public static final String SPRING_SECURITY = "spring-security";
public static final String SWAGGER_UI = "swagger-ui";
public static final String TIKA = "tika";
public static final String UNDERTOW_WEBSOCKETS = "undertow-websockets";
Expand Down
1 change: 1 addition & 0 deletions extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
<module>spring-di</module>
<module>spring-web</module>
<module>spring-data-jpa</module>
<module>spring-security</module>

<!-- Security -->
<module>security</module>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.quarkus.security.deployment;

import static io.quarkus.security.deployment.SecurityTransformerUtils.DENY_ALL;
import static io.quarkus.security.deployment.SecurityTransformerUtils.hasSecurityAnnotation;
import static io.quarkus.security.deployment.SecurityTransformerUtils.hasStandardSecurityAnnotation;

import java.util.List;

Expand All @@ -25,8 +25,8 @@ public boolean appliesTo(AnnotationTarget.Kind kind) {
public void transform(TransformationContext transformationContext) {
ClassInfo classInfo = transformationContext.getTarget().asClass();
List<MethodInfo> methods = classInfo.methods();
if (!hasSecurityAnnotation(classInfo)
&& methods.stream().anyMatch(SecurityTransformerUtils::hasSecurityAnnotation)) {
if (!SecurityTransformerUtils.hasStandardSecurityAnnotation(classInfo)
&& methods.stream().anyMatch(SecurityTransformerUtils::hasStandardSecurityAnnotation)) {
transformationContext.transform().add(DENY_ALL).done();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.security.deployment;

import java.util.Collection;
import java.util.Optional;
import java.util.Set;

import javax.annotation.security.DenyAll;
Expand All @@ -16,23 +18,38 @@ public class SecurityTransformerUtils {
public static final DotName DENY_ALL = DotName.createSimple(DenyAll.class.getName());
private static final Set<DotName> SECURITY_ANNOTATIONS = SecurityAnnotationsRegistrar.SECURITY_BINDINGS.keySet();

public static boolean hasSecurityAnnotation(MethodInfo methodInfo) {
for (AnnotationInstance annotation : methodInfo.annotations()) {
if (SECURITY_ANNOTATIONS.contains(annotation.name())) {
public static boolean hasStandardSecurityAnnotation(MethodInfo methodInfo) {
return hasStandardSecurityAnnotation(methodInfo.annotations());
}

public static boolean hasStandardSecurityAnnotation(ClassInfo classInfo) {
return hasStandardSecurityAnnotation(classInfo.classAnnotations());
}

private static boolean hasStandardSecurityAnnotation(Collection<AnnotationInstance> instances) {
for (AnnotationInstance instance : instances) {
if (SECURITY_ANNOTATIONS.contains(instance.name())) {
return true;
}
}

return false;
}

public static boolean hasSecurityAnnotation(ClassInfo classInfo) {
for (AnnotationInstance classAnnotation : classInfo.classAnnotations()) {
if (SECURITY_ANNOTATIONS.contains(classAnnotation.name())) {
return true;
public static Optional<AnnotationInstance> findFirstStandardSecurityAnnotation(MethodInfo methodInfo) {
return findFirstStandardSecurityAnnotation(methodInfo.annotations());
}

public static Optional<AnnotationInstance> findFirstStandardSecurityAnnotation(ClassInfo classInfo) {
return findFirstStandardSecurityAnnotation(classInfo.classAnnotations());
}

private static Optional<AnnotationInstance> findFirstStandardSecurityAnnotation(Collection<AnnotationInstance> instances) {
for (AnnotationInstance instance : instances) {
if (SECURITY_ANNOTATIONS.contains(instance.name())) {
return Optional.of(instance);
}
}

return false;
return Optional.empty();
}

}
4 changes: 4 additions & 0 deletions extensions/security/test-utils/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
</dependency>
</dependencies>

</project>
72 changes: 72 additions & 0 deletions extensions/spring-security/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-security-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>

<artifactId>quarkus-spring-security-deployment</artifactId>
<name>Quarkus - Spring Security - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-security</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus.security</groupId>
<artifactId>quarkus-security</artifactId>
<exclusions>
<exclusion>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security-test-utils</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkus.spring.security.deployment;

import org.jboss.jandex.DotName;
import org.springframework.security.access.annotation.Secured;

public final class DotNames {

static final DotName SPRING_SECURED = DotName.createSimple(Secured.class.getName());

private DotNames() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.spring.security.deployment;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.jboss.jandex.DotName;
import org.springframework.security.access.annotation.Secured;

import io.quarkus.arc.processor.InterceptorBindingRegistrar;

public class SpringSecurityAnnotationsRegistrar implements InterceptorBindingRegistrar {

public static final Map<DotName, Set<String>> SECURITY_BINDINGS = new HashMap<>();

static {
SECURITY_BINDINGS.put(DotName.createSimple(Secured.class.getName()), Collections.singleton("value"));
}

@Override
public Map<DotName, Set<String>> registerAdditionalBindings() {
return SECURITY_BINDINGS;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package io.quarkus.spring.security.deployment;

import static io.quarkus.security.deployment.SecurityTransformerUtils.findFirstStandardSecurityAnnotation;
import static io.quarkus.security.deployment.SecurityTransformerUtils.hasStandardSecurityAnnotation;

import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.MethodInfo;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.InterceptorBindingRegistrarBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.ApplicationIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.security.deployment.AdditionalSecurityCheckBuildItem;
import io.quarkus.security.deployment.SecurityCheckInstantiationUtil;
import io.quarkus.security.deployment.SecurityTransformerUtils;
import io.quarkus.spring.security.runtime.interceptor.SpringSecuredInterceptor;

class SpringSecurityProcessor {

@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FeatureBuildItem.SPRING_SECURITY);
}

@BuildStep
void registerSecurityInterceptors(BuildProducer<InterceptorBindingRegistrarBuildItem> registrars,
BuildProducer<AdditionalBeanBuildItem> beans) {
registrars.produce(new InterceptorBindingRegistrarBuildItem(new SpringSecurityAnnotationsRegistrar()));
beans.produce(new AdditionalBeanBuildItem(SpringSecuredInterceptor.class));
}

@BuildStep
void addSpringSecuredSecurityCheck(ApplicationIndexBuildItem index,
BuildProducer<AdditionalSecurityCheckBuildItem> additionalSecurityCheckBuildItems) {

Set<MethodInfo> methodsWithSecurityAnnotation = new HashSet<>();
for (AnnotationInstance instance : index.getIndex().getAnnotations(DotNames.SPRING_SECURED)) {
if (instance.value() == null) {
continue;
}
String[] rolesAllowed = instance.value().asStringArray();

if (instance.target().kind() == AnnotationTarget.Kind.METHOD) {
MethodInfo methodInfo = instance.target().asMethod();
checksStandardSecurity(instance, methodInfo);
checksStandardSecurity(instance, methodInfo.declaringClass());
additionalSecurityCheckBuildItems.produce(new AdditionalSecurityCheckBuildItem(methodInfo,
SecurityCheckInstantiationUtil.rolesAllowedSecurityCheck(rolesAllowed)));
methodsWithSecurityAnnotation.add(methodInfo);
} else if (instance.target().kind() == AnnotationTarget.Kind.CLASS) {
ClassInfo classInfo = instance.target().asClass();
checksStandardSecurity(instance, classInfo);
for (MethodInfo methodInfo : classInfo.methods()) {
if (!isPublicNonStaticNonConstructor(methodInfo)) {
continue;
}
checksStandardSecurity(instance, methodInfo);
if (!methodsWithSecurityAnnotation.contains(methodInfo)) {
additionalSecurityCheckBuildItems.produce(new AdditionalSecurityCheckBuildItem(methodInfo,
SecurityCheckInstantiationUtil.rolesAllowedSecurityCheck(rolesAllowed)));
}
}
}
}
}

//Validates that there is no @Secured with the standard security annotations at class level
private void checksStandardSecurity(AnnotationInstance instance, ClassInfo classInfo) {
if (hasStandardSecurityAnnotation(classInfo)) {
Optional<AnnotationInstance> firstStandardSecurityAnnotation = findFirstStandardSecurityAnnotation(classInfo);
if (firstStandardSecurityAnnotation.isPresent()) {
String securityAnnotationName = findFirstStandardSecurityAnnotation(classInfo).get().name()
.withoutPackagePrefix();
throw new IllegalArgumentException("An invalid security annotation combination was detected: Found @"
+ instance.name().withoutPackagePrefix() + " and @" + securityAnnotationName + " on class "
+ classInfo.simpleName());
}
}
}

//Validates that there is no @Secured with the standard security annotations at method level
private void checksStandardSecurity(AnnotationInstance instance, MethodInfo methodInfo) {
if (SecurityTransformerUtils.hasStandardSecurityAnnotation(methodInfo)) {
Optional<AnnotationInstance> firstStandardSecurityAnnotation = SecurityTransformerUtils
.findFirstStandardSecurityAnnotation(methodInfo);
if (firstStandardSecurityAnnotation.isPresent()) {
String securityAnnotationName = SecurityTransformerUtils.findFirstStandardSecurityAnnotation(methodInfo).get()
.name()
.withoutPackagePrefix();
throw new IllegalArgumentException("An invalid security annotation combination was detected: Found "
+ instance.name().withoutPackagePrefix() + " and " + securityAnnotationName + " on method "
+ methodInfo.name());
}
}
}

private boolean isPublicNonStaticNonConstructor(MethodInfo methodInfo) {
return Modifier.isPublic(methodInfo.flags()) && !Modifier.isStatic(methodInfo.flags())
&& !"<init>".equals(methodInfo.name());
}
}
Loading