BAEL-6620: RBAC Quarkus (#16416)

* BAEL-6620: RBAC quarkus

* BAEL-6620: fix permissions

* Code fix

* Fix test name
This commit is contained in:
Thiago dos Santos Hora 2024-04-23 23:48:51 +02:00 committed by GitHub
parent 0c1e41f3e8
commit de699e2386
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 869 additions and 0 deletions

View File

@ -0,0 +1,2 @@
## Relevant Articles

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung.quarkus</groupId>
<artifactId>quarkus-rbac</artifactId>
<version>1.0.0-SNAPSHOT</version>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>quarkus-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt-build</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security-jpa</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-jwt</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
<properties>
<compiler-plugin.version>3.12.1</compiler-plugin.version>
<failsafe.useModulePath>false</failsafe.useModulePath>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.9.3</quarkus.platform.version>
<surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
</properties>
</project>

View File

@ -0,0 +1,17 @@
package com.baeldung.quarkus.rbac.api;
import com.baeldung.quarkus.rbac.users.errors.DomainDataException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class ApiErrorHandler implements ExceptionMapper<DomainDataException> {
@Override
public Response toResponse(final DomainDataException exception) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(exception.getEntity() == null? exception.getEntity() : new Message(exception.getMessage()))
.build();
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.quarkus.rbac.api;
import jakarta.validation.constraints.NotNull;
public record LoginDto(@NotNull String username, @NotNull String password) {
@Override
public String toString() {
return "LoginDto{" +
"username='" + username + '\'' +
", password='*********'" +
'}';
}
}

View File

@ -0,0 +1,3 @@
package com.baeldung.quarkus.rbac.api;
public record Message(String message) { }

View File

@ -0,0 +1,37 @@
package com.baeldung.quarkus.rbac.api;
import io.quarkus.security.PermissionsAllowed;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/permission-based")
public class PermissionBasedController {
private final SecurityIdentity securityIdentity;
public PermissionBasedController(SecurityIdentity securityIdentity) {
this.securityIdentity = securityIdentity;
}
@GET
@Path("/resource/version")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@PermissionsAllowed("VIEW_ADMIN_DETAILS")
public String get() {
return "2.0.0";
}
@GET
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/resource/message")
@PermissionsAllowed(value = {"SEND_MESSAGE", "OPERATOR"}, inclusive = true)
public Message message() {
return new Message("Hello "+securityIdentity.getPrincipal().getName()+"!");
}
}

View File

@ -0,0 +1,85 @@
package com.baeldung.quarkus.rbac.api;
import com.baeldung.quarkus.rbac.users.Role;
import com.baeldung.quarkus.rbac.users.UserService;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.stream.Collectors;
@Path("/secured")
public class SecureResourceController {
private final UserService userService;
private final SecurityIdentity securityIdentity;
public SecureResourceController(UserService userService, SecurityIdentity securityIdentity) {
this.userService = userService;
this.securityIdentity = securityIdentity;
}
@GET
@Path("/resource")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({"VIEW_ADMIN_DETAILS"})
public String get() {
return "Hello world, here are some details about the admin!";
}
@GET
@Path("/resource/user")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({"VIEW_USER_DETAILS"})
public Message getUser() {
return new Message("Hello "+securityIdentity.getPrincipal().getName()+"!");
}
@POST
@Path("/resource")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("${customer.send.message.permission:SEND_MESSAGE}")
public Response getUser(Message message) {
return Response.ok(message).build();
}
@POST
@Path("/user")
@RolesAllowed({"CREATE_USER"})
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response postUser(@Valid final UserDto userDto) {
final var user = userService.createUser(userDto);
final var roles = user.getRoles().stream().map(Role::getName).collect(Collectors.toSet());
return Response.ok(new UserResponse(user.getUsername(), roles)).build();
}
@POST
@Path("/login")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@PermitAll
public Response login(@Valid final LoginDto loginDto) {
if (userService.checkUserCredentials(loginDto.username(), loginDto.password())) {
final var user = userService.findByUsername(loginDto.username());
final var token = userService.generateJwtToken(user);
return Response.ok().entity(new TokenResponse("Bearer " + token,"3600")).build();
} else {
return Response.status(Response.Status.UNAUTHORIZED).entity(new Message("Invalid credentials")).build();
}
}
}

View File

@ -0,0 +1,4 @@
package com.baeldung.quarkus.rbac.api;
public record TokenResponse(String token, String expiresIn){
}

View File

@ -0,0 +1,9 @@
package com.baeldung.quarkus.rbac.api;
import io.smallrye.common.constraint.NotNull;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Size;
import java.util.Set;
public record UserDto(@NotNull String username, @NotNull String password, @Size(min = 1) Set<String> roles, @Email String email) { }

View File

@ -0,0 +1,6 @@
package com.baeldung.quarkus.rbac.api;
import java.util.Set;
public record UserResponse(String username, Set<String> roles) { }

View File

@ -0,0 +1,10 @@
package com.baeldung.quarkus.rbac.users;
public enum Permission {
VIEW_ADMIN_DETAILS,
VIEW_USER_DETAILS,
SEND_MESSAGE,
CREATE_USER
}

View File

@ -0,0 +1,33 @@
package com.baeldung.quarkus.rbac.users;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
@Converter
public class PermissionConverter implements AttributeConverter<Set<Permission>, String> {
@Override
public String convertToDatabaseColumn(Set<Permission> attribute) {
if (attribute == null || attribute.isEmpty()) {
return null;
}
return attribute.stream()
.map(Permission::name)
.collect(Collectors.joining(","));
}
@Override
public Set<Permission> convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.isEmpty()) {
return Set.of();
}
return Arrays.stream(dbData.split(","))
.map(String::trim)
.map(Permission::valueOf)
.collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,39 @@
package com.baeldung.quarkus.rbac.users;
import io.quarkus.security.jpa.Roles;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "roles")
public class Role {
@Id
private String name;
@Roles
@Convert(converter = PermissionConverter.class)
private Set<Permission> permissions = new HashSet<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Permission> getPermissions() {
return permissions;
}
public void setPermissions(Set<Permission> permissions) {
this.permissions = permissions;
}
}

View File

@ -0,0 +1,87 @@
package com.baeldung.quarkus.rbac.users;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(unique = true)
private String username;
@Column
private String password;
@Column(unique = true)
private String email;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_name"))
private Set<Role> roles = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public void addRole(Role role) {
roles.add(role);
}
public void removeRole(Role role) {
roles.remove(role);
}
}

View File

@ -0,0 +1,20 @@
package com.baeldung.quarkus.rbac.users;
import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.Collection;
import java.util.List;
@ApplicationScoped
class UserRepository implements PanacheRepository<User> {
public List<Role> findRoles(final Collection<String> roles) {
if (roles == null || roles.isEmpty()) {
return List.of();
}
return find("SELECT r FROM Role r WHERE r.name in ?1", roles).project(Role.class).list();
}
}

View File

@ -0,0 +1,76 @@
package com.baeldung.quarkus.rbac.users;
import com.baeldung.quarkus.rbac.api.UserDto;
import com.baeldung.quarkus.rbac.users.errors.EntityNotFoundException;
import com.baeldung.quarkus.rbac.users.errors.InvalidRolesProvidedException;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.smallrye.jwt.build.Jwt;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.jwt.Claims;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
@ApplicationScoped
public class UserService {
private final UserRepository repository;
private final String issuer;
public UserService(final UserRepository repository,
final @ConfigProperty(name = "mp.jwt.verify.issuer") String issuer) {
this.repository = repository;
this.issuer = issuer;
}
public String generateJwtToken(final User user) {
final Set<String> permissions = user.getRoles()
.stream()
.flatMap(role -> role.getPermissions().stream())
.map(Permission::name)
.collect(Collectors.toSet());
return Jwt.issuer(issuer)
.upn(user.getUsername())
.groups(permissions)
.expiresIn(3600)
.claim(Claims.email_verified.name(), user.getEmail())
.sign();
}
@Transactional
public User createUser(final UserDto userDto) {
final var roles = repository.findRoles(userDto.roles());
if (roles.size() != userDto.roles().size()) {
throw new InvalidRolesProvidedException("Unknown role provided");
}
final var user = new User();
user.setUsername(userDto.username());
user.setPassword(BcryptUtil.bcryptHash(userDto.password()));
user.setRoles(new HashSet<>(roles));
user.setEmail(userDto.email());
repository.persist(user);
return user;
}
public boolean checkUserCredentials(String username, String password) {
final User user = findByUsername(username);
return BcryptUtil.matches(password, user.getPassword());
}
public User findByUsername(String username) {
return repository.find("username", username).firstResultOptional()
.orElseThrow(() -> new EntityNotFoundException(username));
}
}

View File

@ -0,0 +1,17 @@
package com.baeldung.quarkus.rbac.users.errors;
import java.io.Serializable;
public abstract class DomainDataException extends RuntimeException {
private final Serializable entity;
protected DomainDataException(final String message, final Serializable entity) {
super(message);
this.entity = entity;
}
public Serializable getEntity() {
return entity;
}
}

View File

@ -0,0 +1,8 @@
package com.baeldung.quarkus.rbac.users.errors;
public class EntityNotFoundException extends RuntimeException {
public EntityNotFoundException(String id) {
super("Entity with id " + id + " not found");
}
}

View File

@ -0,0 +1,14 @@
package com.baeldung.quarkus.rbac.users.errors;
import java.io.Serializable;
public class InvalidRolesProvidedException extends DomainDataException {
public InvalidRolesProvidedException(String message, Serializable entity) {
super(message, entity);
}
public InvalidRolesProvidedException(String message) {
super(message, null);
}
}

View File

@ -0,0 +1,19 @@
mp.jwt.verify.publickey.location=publicKey.pem
quarkus.native.resources.includes=publicKey.pem
mp.jwt.verify.issuer=my-issuer
smallrye.jwt.sign.key.location=privateKey.pem
quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb_;DB_CLOSE_DELAY=-1;MODE=MYSQL;DB_CLOSE_ON_EXIT=TRUE;DATABASE_TO_LOWER=TRUE
quarkus.hibernate-orm.dialect=org.hibernate.dialect.MySQLDialect
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.sql-load-script=import.sql
quarkus.http.auth.policy.role-policy1.permissions.VIEW_ADMIN_DETAILS=VIEW_ADMIN_DETAILS
quarkus.http.auth.policy.role-policy1.permissions.VIEW_USER_DETAILS=VIEW_USER_DETAILS
quarkus.http.auth.policy.role-policy1.permissions.SEND_MESSAGE=SEND_MESSAGE
quarkus.http.auth.policy.role-policy1.permissions.CREATE_USER=CREATE_USER
quarkus.http.auth.policy.role-policy1.permissions.OPERATOR=OPERATOR
quarkus.http.auth.permission.roles1.paths=/permission-based/*
quarkus.http.auth.permission.roles1.policy=role-policy1

View File

@ -0,0 +1,11 @@
insert into roles (name, permissions) values ('ADMIN', 'VIEW_ADMIN_DETAILS,CREATE_USER,SEND_MESSAGE,VIEW_USER_DETAILS');
insert into roles (name, permissions) values ('USER', 'VIEW_USER_DETAILS,SEND_MESSAGE');
insert into roles (name, permissions) values ('GUEST', 'SEND_MESSAGE');
insert into users (id, username, password, email) values (1, 'admin', '$2a$10$sWfRL1ruggfeebSfeMnToOeeuQTzSFY.khIlS/dzWY6qYOekisccS', 'admin@test.io');
insert into users (id, username, password, email) values (2, 'user', '$2a$16$6AZvwlL1PCJ7fgoNVBlezOnoB6WkZlmj6mvQPP5/0uWyso8nVOdXm', 'user@test.io');
insert into users (id, username, password, email) values (3, 'guest', '$2a$16$Sd0wA6le90dUTe3OAUiSZe.FmPL96XLvRYUUbhitF3.dmgF/dLgFm', 'guest@test.io');
insert into user_roles (user_id, role_name) values (1, 'ADMIN');
insert into user_roles (user_id, role_name) values (2, 'USER');
insert into user_roles (user_id, role_name) values (3, 'GUEST');

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa
PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H
OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN
qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh
nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM
uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6
oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv
6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY
URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6
96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB
Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3
zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF
KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP
iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B
m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS
34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG
5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2
tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL
WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y
b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09
nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB
MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d
Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe
Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt
FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8
f3cg+fr8aou7pr9SHhJlZCU=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq
Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR
TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e
UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9
AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn
sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x
nQIDAQAB
-----END PUBLIC KEY-----

View File

@ -0,0 +1,57 @@
package com.baeldung.quarkus;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.quarkus.test.security.jwt.Claim;
import io.quarkus.test.security.jwt.JwtSecurity;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
@QuarkusTest
class PermissionBasedControllerIntegrationTest {
@Test
@TestSecurity(user = "admin", roles = "VIEW_ADMIN_DETAILS")
@JwtSecurity(claims = {
@Claim(key = "email", value = "admin@test.io")
})
void givenSecureVersionApi_whenUserIsAuthenticated_thenShouldReturnVersion() {
given()
.contentType(ContentType.JSON)
.get("/permission-based/resource/version")
.then()
.statusCode(200)
.body(equalTo("2.0.0"));
}
@Test
@TestSecurity(user = "user", roles = "SEND_MESSAGE")
@JwtSecurity(claims = {
@Claim(key = "email", value = "user@test.io")
})
void givenSecureMessageApi_whenUserOnlyHasOnePermission_thenShouldNotAllowRequest() {
given()
.contentType(ContentType.JSON)
.get("/permission-based/resource/message")
.then()
.statusCode(403);
}
@Test
@TestSecurity(user = "new-operator", roles = {"SEND_MESSAGE", "OPERATOR"})
@JwtSecurity(claims = {
@Claim(key = "email", value = "operator@test.io")
})
void givenSecureMessageApi_whenUserOnlyHasBothPermissions_thenShouldAllowRequest() {
given()
.contentType(ContentType.JSON)
.get("/permission-based/resource/message")
.then()
.statusCode(200)
.body("message", equalTo("Hello new-operator!"));
}
}

View File

@ -0,0 +1,87 @@
package com.baeldung.quarkus;
import com.baeldung.quarkus.rbac.api.LoginDto;
import com.baeldung.quarkus.rbac.api.Message;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.quarkus.test.security.jwt.Claim;
import io.quarkus.test.security.jwt.JwtSecurity;
import io.restassured.http.ContentType;
import io.restassured.mapper.ObjectMapperType;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
@QuarkusTest
class SecureResourceControllerIntegrationTest {
@Test
void givenSecureLoginApi_whenAdminLogsIn_thenShouldReturnOK() {
given()
.contentType(ContentType.JSON)
.body(new LoginDto("admin", "admin"), ObjectMapperType.JACKSON_2)
.post("/secured/login")
.then()
.statusCode(200)
.body("token", notNullValue())
.body("expiresIn", equalTo("3600"));
}
@Test
@TestSecurity(user = "user", roles = "VIEW_USER_DETAILS")
@JwtSecurity(claims = {
@Claim(key = "email", value = "user@test.io")
})
void givenSecureUserApi_whenUserIsAuthenticated_thenShouldReturnCustomMessage() {
given()
.contentType(ContentType.JSON)
.get("/secured/resource/user")
.then()
.statusCode(200)
.body("message", equalTo("Hello user!"));;
}
@Test
@TestSecurity(user = "user", roles = "VIEW_USER_DETAILS")
@JwtSecurity(claims = {
@Claim(key = "email", value = "user@test.io")
})
void givenSecureAdminApi_whenUserTriesToAccessAdminApi_thenShouldNotAllowRequest() {
given()
.contentType(ContentType.JSON)
.get("/secured/resource")
.then()
.statusCode(403);
}
@Test
@TestSecurity(user = "admin", roles = "VIEW_ADMIN_DETAILS")
@JwtSecurity(claims = {
@Claim(key = "email", value = "admin@test.io")
})
void givenSecureAdminApi_whenAdminTriesAccessAdminApi_thenShouldAllowRequest() {
given()
.contentType(ContentType.JSON)
.get("/secured/resource")
.then()
.statusCode(200)
.body(equalTo("Hello world, here are some details about the admin!"));
}
@Test
@TestSecurity(user = "guest", roles = "SEND_MESSAGE")
@JwtSecurity(claims = {
@Claim(key = "email", value = "guest@test.io")
})
void givenSecureGuestApi_whenCallAsGuess_thenShouldReturnOk() {
given()
.contentType(ContentType.JSON)
.body(new Message("Hello Friend!"), ObjectMapperType.JACKSON_2)
.post("/secured/resource")
.then()
.statusCode(200)
.body("message", equalTo("Hello Friend!"));
}
}