-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make security.adoc the main Quarkus Security document
- Loading branch information
1 parent
100bc39
commit a01331c
Showing
6 changed files
with
682 additions
and
411 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
//// | ||
This guide is maintained in the main Quarkus repository | ||
and pull requests should be submitted there: | ||
https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc | ||
//// | ||
= Quarkus - Authorization of Web Endpoints | ||
|
||
include::./attributes.adoc[] | ||
|
||
Quarkus has an integrated pluggable web security layer. If security is enabled all HTTP requests will have a permission | ||
check performed to make sure they are allowed to continue. | ||
|
||
NOTE: Configuration authorization checks are executed before any annotation-based authorization check is done, so both | ||
checks have to pass for a request to be allowed. | ||
|
||
== Authorization using Configuration | ||
|
||
The default implementation allows you to define permissions using config in `application.properties`. An example | ||
config is shown below: | ||
|
||
[source,properties] | ||
---- | ||
quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin <1> | ||
quarkus.http.auth.permission.roles1.paths=/roles-secured/*,/other/*,/api/* <2> | ||
quarkus.http.auth.permission.roles1.policy=role-policy1 | ||
quarkus.http.auth.permission.permit1.paths=/public/* <3> | ||
quarkus.http.auth.permission.permit1.policy=permit | ||
quarkus.http.auth.permission.permit1.methods=GET | ||
quarkus.http.auth.permission.deny1.paths=/forbidden <4> | ||
quarkus.http.auth.permission.deny1.policy=deny | ||
---- | ||
<1> This defines a role based policy that allows users with the `user` and `admin` roles. This is referenced by later rules | ||
<2> This is a permission set that references the previously defined policy. `roles1` is an arbitrary name, you can call the permission sets whatever you want. | ||
<3> This permission references the default `permit` built in policy to allow `GET` methods to `/public`. This is actually a no-op in this example, as this request would have been allowed anyway. | ||
<4> This permission references the built in `deny` build in policy `/forbidden`. This is an exact path match as it does not end with *. | ||
|
||
Permissions are defined in config using permission sets. These are arbitrarily named permission grouping. Each permission | ||
set must specify a policy that is used to control access. There are three built in policies: `deny`, `permit` and `authenticated`, | ||
which permit all, deny all and only allow authenticated users respectively. | ||
|
||
It is also possible to define role based policies, as shown in the example. These policies will only allow users with the | ||
specified roles to access the resources. | ||
|
||
=== Matching on paths, methods | ||
|
||
Permission sets can also specify paths and methods as a comma separated list. If a path ends with '*' then it is considered | ||
to be a wildcard match and will match all sub paths, otherwise it is an exact match and will only match that specific path: | ||
|
||
[source,properties] | ||
---- | ||
quarkus.http.auth.permission.permit1.paths=/public/*,/css/*,/js/*,/robots.txt | ||
quarkus.http.auth.permission.permit1.policy=permit | ||
quarkus.http.auth.permission.permit1.methods=GET,HEAD | ||
---- | ||
|
||
=== Matching path but not method | ||
|
||
If a request would match one or more permission sets based on the path, but does not match any due to method requirements | ||
then the request is rejected. | ||
|
||
TIP: Given the above permission set, `GET /public/foo` would match both the path and method and thus be allowed, | ||
whereas `POST /public/foo` would match the path but not the method and would thus be rejected. | ||
|
||
=== Matching multiple paths: longest path wins | ||
|
||
Matching is always done on a longest path basis, less specific permission sets are not considered if a more specific one | ||
has been matched: | ||
|
||
[source,properties] | ||
---- | ||
quarkus.http.auth.permission.permit1.paths=/public/* | ||
quarkus.http.auth.permission.permit1.policy=permit | ||
quarkus.http.auth.permission.permit1.methods=GET,HEAD | ||
quarkus.http.auth.permission.deny1.paths=/public/forbidden-folder/* | ||
quarkus.http.auth.permission.deny1.policy=deny | ||
---- | ||
|
||
TIP: Given the above permission set, `GET /public/forbidden-folder/foo` would match both permission sets' paths, | ||
but because it matches the `deny1` permission set's path on a longer match, `deny1` will be chosen and the request will | ||
be rejected. | ||
|
||
[NOTE] Subpath permission always win against the root path permission as explained above in the `deny1` versus `permit1` permission example. | ||
Here is another example showing a subpath permission allowing a public resource access with the root path permission requiring the authorization: | ||
|
||
[source,properties] | ||
---- | ||
quarkus.http.auth.policy.user-policy.roles-allowed=user | ||
quarkus.http.auth.permission.roles.paths=/api/* | ||
quarkus.http.auth.permission.roles.policy=user-policy | ||
quarkus.http.auth.permission.public.paths=/api/noauth/* | ||
quarkus.http.auth.permission.public.policy=permit | ||
---- | ||
|
||
=== Matching multiple paths: most specific method wins | ||
|
||
If a path is registered with multiple permission sets then any permission sets that specify a HTTP method will take | ||
precedence and permissions sets without a method will not be considered (assuming of course the method matches). In this | ||
instance, the permission sets without methods will only come into effect if the request method does not match any of the | ||
sets with method permissions. | ||
|
||
[source,properties] | ||
---- | ||
quarkus.http.auth.permission.permit1.paths=/public/* | ||
quarkus.http.auth.permission.permit1.policy=permit | ||
quarkus.http.auth.permission.permit1.methods=GET,HEAD | ||
quarkus.http.auth.permission.deny1.paths=/public/* | ||
quarkus.http.auth.permission.deny1.policy=deny | ||
---- | ||
|
||
TIP: Given the above permission set, `GET /public/foo` would match both permission sets' paths, | ||
but because it matches the `permit1` permission set's explicit method, `permit1` will be chosen and the request will | ||
be accepted. `PUT /public/foo` on the other hand, will not match the method permissions of `permit1` and so | ||
`deny1` will be activated and reject the request. | ||
|
||
=== Matching multiple paths and methods: both win | ||
|
||
If multiple permission sets specify the same path and method (or multiple have no method) then both permissions have to | ||
allow access for the request to proceed. Note that for this to happen both have to either have specified the method, or | ||
have no method, method specific matches take precedence as stated above: | ||
|
||
[source,properties] | ||
---- | ||
quarkus.http.auth.policy.user-policy1.roles-allowed=user | ||
quarkus.http.auth.policy.admin-policy1.roles-allowed=admin | ||
quarkus.http.auth.permission.roles1.paths=/api/*,/restricted/* | ||
quarkus.http.auth.permission.roles1.policy=user-policy1 | ||
quarkus.http.auth.permission.roles2.paths=/api/*,/admin/* | ||
quarkus.http.auth.permission.roles2.policy=admin-policy1 | ||
---- | ||
|
||
TIP: Given the above permission set, `GET /api/foo` would match both permission sets' paths, | ||
so would require both the `user` and `admin` roles. | ||
|
||
=== Configuration Properties to Deny access | ||
|
||
There are two configuration settings that alter the RBAC Deny behavior: | ||
|
||
- `quarkus.security.jaxrs.deny-unannotated-endpoints=true|false` - if set to true, the access will be denied for all JAX-RS endpoints by default. | ||
That is if the security annotations do not define the access control. Defaults to `false` | ||
- `quarkus.security.deny-unannotated-members=true|false` - if set to true, the access will be denied to all CDI methods | ||
and JAX-RS endpoints that do not have security annotations but are defined in classes that contain methods with | ||
security annotations. Defaults to `false`. | ||
|
||
[#standard-security-annotations] | ||
== Authorization using Annotations | ||
|
||
Quarkus comes with built-in security to allow for Role-Based Access Control (link:https://en.wikipedia.org/wiki/Role-based_access_control[RBAC]) | ||
based on the common security annotations `@RolesAllowed`, `@DenyAll`, `@PermitAll` on REST endpoints and CDI beans. | ||
An example of an endpoint that makes use of both JAX-RS and Common Security annotations to describe and secure its endpoints is given in <<subject-example>>. Quarkus also provides | ||
the `io.quarkus.security.Authenticated` annotation that will permit any authenticated user to access the resource | ||
(equivalent to `@RolesAllowed("**")`). | ||
|
||
[#subject-example] | ||
.SubjectExposingResource Example | ||
[source,java] | ||
---- | ||
import java.security.Principal; | ||
import javax.annotation.security.DenyAll; | ||
import javax.annotation.security.PermitAll; | ||
import javax.annotation.security.RolesAllowed; | ||
import javax.ws.rs.GET; | ||
import javax.ws.rs.Path; | ||
import javax.ws.rs.core.Context; | ||
import javax.ws.rs.core.SecurityContext; | ||
@Path("subject") | ||
public class SubjectExposingResource { | ||
@GET | ||
@Path("secured") | ||
@RolesAllowed("Tester") <1> | ||
public String getSubjectSecured(@Context SecurityContext sec) { | ||
Principal user = sec.getUserPrincipal(); <2> | ||
String name = user != null ? user.getName() : "anonymous"; | ||
return name; | ||
} | ||
@GET | ||
@Path("unsecured") | ||
@PermitAll<3> | ||
public String getSubjectUnsecured(@Context SecurityContext sec) { | ||
Principal user = sec.getUserPrincipal(); <4> | ||
String name = user != null ? user.getName() : "anonymous"; | ||
return name; | ||
} | ||
@GET | ||
@Path("denied") | ||
@DenyAll<5> | ||
public String getSubjectDenied(@Context SecurityContext sec) { | ||
Principal user = sec.getUserPrincipal(); | ||
String name = user != null ? user.getName() : "anonymous"; | ||
return name; | ||
} | ||
} | ||
---- | ||
<1> This `/subject/secured` endpoint requires an authenticated user that has been granted the role "Tester" through the use of the `@RolesAllowed("Tester")` annotation. | ||
<2> The endpoint obtains the user principal from the JAX-RS SecurityContext. This will be non-null for a secured endpoint. | ||
<3> The `/subject/unsecured` endpoint allows for unauthenticated access by specifying the `@PermitAll` annotation. | ||
<4> This call to obtain the user principal will return null if the caller is unauthenticated, non-null if the caller is authenticated. | ||
<5> The `/subject/denied` endpoint disallows any access regardless of whether the call is authenticated by specifying the `@DenyAll` annotation. |
105 changes: 105 additions & 0 deletions
105
docs/src/main/asciidoc/security-built-in-authentication.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
//// | ||
This guide is maintained in the main Quarkus repository | ||
and pull requests should be submitted there: | ||
https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc | ||
//// | ||
= Quarkus - BuiltIn Authentication Support | ||
|
||
include::./attributes.adoc[] | ||
|
||
This document describes the Quarkus built in authentication mechanisms for HTTP based FORM, BASIC and Mutual TLS authentication as well as the proactive authentication. | ||
|
||
[[basic-auth]] | ||
== Basic Authentication | ||
|
||
To enable basic authentication set `quarkus.http.auth.basic=true`. You must also have at least one extension installed | ||
that provides a username/password based `IdentityProvider`, such as link:security-jdbc[Elytron JDBC]. | ||
|
||
Please see link:security#identity-providers[Security Identity Providers] for more information. | ||
|
||
[[form-auth]] | ||
== Form Based Authentication | ||
|
||
Quarkus provides form based authentication that works in a similar manner to traditional Servlet form based auth. Unlike | ||
traditional form authentication the authenticated user is not stored in an HTTP session, as Quarkus does not provide | ||
clustered HTTP session support. Instead the authentication information is stored in an encrypted cookie, which can | ||
be read by all members of the cluster (provided they all share the same encryption key). | ||
|
||
The encryption key can be set using the `quarkus.http.auth.session.encryption-key` property, and it must be at least 16 characters | ||
long. This key is hashed using SHA-256 and the resulting digest is used as a key for AES-256 encryption of the cookie | ||
value. This cookie contains a expiry time as part of the encrypted value, so all nodes in the cluster must have their | ||
clocks synchronised. At one minute intervals a new cookie will be generated with an updated expiry time if the session | ||
is in use. | ||
|
||
The following properties can be used to configure form based auth: | ||
|
||
include::{generated-dir}/config/quarkus-vertx-http-config-group-form-auth-config.adoc[opts=optional, leveloffset=+1] | ||
|
||
[[mutual-tls]] | ||
== Mutual TLS Authentication | ||
|
||
Quarkus provides mTLS authentication so that you can authenticate users based on their X.509 certificates. | ||
|
||
To use this authentication method, you should first enable SSL to your application. For more details, check the link:http-reference[Supporting secure connections with SSL] guide. | ||
|
||
Once your application is accepting secure connections, the next step is to configure a `quarkus.http.ssl.certificate.trust-store-file` | ||
holding all the certificates that your application should trust as well as how your application should ask for certificates when | ||
a client (e.g.: browser or another service) tries to access one of its protected resources. | ||
|
||
[source,properties] | ||
---- | ||
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks <1> | ||
quarkus.http.ssl.certificate.key-store-password=the_key_store_secret | ||
quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks <2> | ||
quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret | ||
quarkus.http.ssl.client-auth=required <3> | ||
quarkus.http.auth.permission.default.paths=/* <4> | ||
quarkus.http.auth.permission.default.policy=authenticated | ||
---- | ||
<1> Configures a key store where the server's private key is located. | ||
|
||
<2> Configures a trust store from where the trusted certificates are going to be loaded from. | ||
|
||
<3> Defines that the server should *always* ask certificates from clients. You can relax this behavior by using `REQUEST` so | ||
that the server should still accept requests without a certificate. Useful when you are also supporting authentication methods other than | ||
mTLS. | ||
|
||
<4> Defines a policy where only authenticated users should have access to resources from your application. | ||
|
||
Once the incoming request matches a valid certificate in the truststore, your application should be able to obtain the subject by | ||
just injecting a `SecurityIdentity` as follows: | ||
|
||
[#x509-subject-example] | ||
.Obtaining the subject | ||
[source,java] | ||
---- | ||
@Inject | ||
SecurityIdentity identity; | ||
@GET | ||
@Produces(MediaType.TEXT_PLAIN) | ||
public String hello() { | ||
return String.format("Hello, %s", identity.getPrincipal().getName()); | ||
} | ||
---- | ||
|
||
You should also be able to get the certificate as follows: | ||
|
||
[#x509-credential-example] | ||
.Obtaining the certificate | ||
[source,java] | ||
---- | ||
CertificateCredential credential = identity.getCredential(CertificateCredential.class); | ||
X509Certificate certificate = credential.getCertificate(); | ||
---- | ||
|
||
[[proactive-authentication]] | ||
== Proactive Authentication | ||
|
||
By default Quarkus does what we call proactive authentication. This means that if an incoming request has a | ||
credential then that request will always be authenticated (even if the target page does not require authentication). | ||
|
||
This means that requests with an invalid credential will always be rejected, even for public pages. You can change | ||
this behaviour and only authenticate when required by setting `quarkus.http.auth.proactive=false`. | ||
|
Oops, something went wrong.