Keycloak harcoded claim for each user - microservices

I use the micro-services architecture. Each service uses bearer access type.
The users get token by public service.
Each service is business application with own privelegies.
Rabbit mq is used as interprocess communication.
Keycloak transfers user roles for each service by JWT.
But I would like to transfer user privelegies in the user's token.
Like this :
priveleges:[
{resource: subjects,
roles:[ADMIN,OPER,COMPLAINCE],
categories:[
read:[1,2,3,4,5],
write:[3,4,5],
delete:[3,4,5]
],
notes:[
read:[1,2,3,4,5],
write:[3,4,5],
delete:[3,4,5]
],
subject:[
read:true,
write:true,
delete:false
],
addresses:[
read:true,
write:true,
delete:false
]
},
{resource: cards,
roles:[ADMIN,OPER,COMPLAINCE],
card:[
read:true,
write:true,
delete:false
],
fin-operation:[1,2,3,4,5],
non-fin-operation:[1,2,3,4]
}
]
I have two questions:
Can I set different privileges for each user in keycloak?
I know it is possible to use a ProtocolMapper with a hardcoded mapper type in Keycloak. And in this case it will be one for all users but I need to have privileges claim for each user.
Is it correct to pass privileges in a token, so that later they can be processed on the backend and frontend?
Additional information:
But if I try to add string value more 255 chars, I catch the error because it is constraint of it field.
I have the error in logs jboss :
ERROR: value too long for type character varying(255)
I try to add this claim:
{ "priveleges":[ { "resource":"subjects", "roles":[ "ADMIN", "OPER", "COMPLAINCE" ], "categories":[ { "read":[ 1, 2, 3, 8, 5 ] }, { "write":[ 3, 9, 5 ] }, { "delete":[ 3, 6, 5 ] } ], "notes":[ { "read":[ 1, 2, 3, 7, 5 ] }, { "write":[ 3, 4, 5 ] }, { "delete":[ 3, 4, 5 ] } ] }, { "resource":"cards", "roles":[ "ADMIN", "OPER", "COMPLAINCE" ], "fin-operation":[ 1, 2, 3, 4, 5 ], "non-fin-operation":[ 1, 2, 3, 4 ] } ] }

Can I set different privileges for each user in keycloak? I know it is
possible to use a ProtocolMapper with a hardcoded mapper type in
Keycloak. And in this case it will be one for all users but I need to
have privelegies claim for each user.
If I correctly understand what you mean by "privileges", you can implement it at least in the following two ways: 1) Create and assign Realm roles to the users, accordingly, and create a custom user attribute on each of your users. Alternatively, you can use Keycloak Authorization features to deal with that, but it will require some work.
Is it correct to pass privileges in a token, so that later they can be
processed on the backend and frontend?
Yes, it is common to use Keycloak for authentication and a backend to perform the authorization part based on the roles injected on the token requested on some user's behalf.
Let me provide you with an illustrative example. First create the Realm Roles by going to:
Your Realm;
Select Roles;
Add Role;
Now create a separate role for ADMIN, OPER and COMPLAINCE, for instance;
Save those roles;
Now let us assign the Realm Roles to the user and create an User Attribute by going to:
Your Realm;
Users;
Click on a user;
Switch the tab to Role Mappings;
Select the Realm Roles that you want to assign to the user, and click on Add selected;
Now go to Attributes;
Add the rest of the claims into the user. For example, Key = "priveleges" and value = {"resource": "subjects","categories": [{"read": [1,2,3,8,5]},{"write": [3,9,5]},{"delete": [3,6,5]}],"notes": [{"read": [1,2,3,7,5]},{"write": [3,4,5]},{"delete": [3,4,5]}]}.
To finalize, you need to created a Mapper to map the newly create user attribute into the token. For that, go to:
Your Realm;
Clients;
Select your client;
Mappers;
Create;
Fill up the fields, namely
Name : "priveleges"
As Mapper Type select "User Attribute";
User Attribute : priveleges;
Token Claim Name : priveleges;
Claim JSON Type : JSON;
Fill up the rest accordingly.
Click Save.
Now request a token to your client on the user's behalf. The decoded token will look like the following:
"iat": ......,
"jti": "......",
"iss": "......",
"aud": "......",
"sub": "......",
"typ": "......",
"azp": "......",
"session_state": "......",
"acr": "1",
"allowed-origins": [
"http://localhost:8080"
],
"realm_access": {
"roles": [
"COMPLAINCE",
"offline_access",
"uma_authorization",
"ADMIN",
"OPER",
"app-user"
]
},
"resource_access": {
"springboot-microservice": {
"roles": [
"user"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "profile email",
"email_verified": true,
"priveleges": {
"resource": "subjects",
"categories": [
{
"read": [
1,
2,
3,
8,
5
]
},
{
"write": [
3,
9,
5
]
},
{
"delete": [
3,
6,
5
]
}
],
"notes": [
{
"read": [
1,
2,
3,
7,
5
]
},
{
"write": [
3,
4,
5
]
},
{
"delete": [
3,
4,
5
]
}
]
},
"name": "e 1",
"preferred_username": "employee1",
"given_name": "e",
"family_name": "1",
}
The roles are under the claim:
"realm_access": {
"roles": [
"COMPLAINCE",
"offline_access",
"uma_authorization",
"ADMIN",
"OPER",
"app-user"
]
However, if you want the Realm Roles also under the priveleges claim, you need to add them into the user attribute claim that we have previously created.
Bear in mind, however, that there is a limit on how many characters one can save per user attribute (i.e., 255 characters). So you might have to split your claim into multiple claims (i.e., user attributes).

Related

Springdoc + OpenId Connect = missing client_id from Swagger request

I am integrating a Spring Boot application with OIDC. The customer has an OIDC manifest file, at https://example.biz/.well-known/openid-configuration, which is redacted as displayed later.
Problem
When I attempt to authenticate via Swagger, the authorization endpoint complains that a parameter is missing. I could see that Swagger does not send the client_id parameter in (it's actually empty).
Please note, I expect that 99% of the configuration is handled by Springdoc after the OIDC configuration URL.
Question
Why doesn't Springdoc/Swagger OIDC setup work according to the existing setup and OIDC manifest?
Set up
OIDC manifest I can retrieve from authorization endpoint
{
"client_id": "the-clientid",
"issuer": "https://...",
"authorization_endpoint": "https://...",
"token_endpoint": "https://...",
"userinfo_endpoint": "https://...",
"jwks_uri": "https://...",
"end_session_endpoint": "https://...",
"registration_endpoint": null,
"scopes_supported": [
"openid"
],
"response_types_supported": [
"token",
"id_token",
"id_token token"
],
"response_modes_supported": [
"form_post",
"fragment"
],
"grant_types_supported": [
""
],
"subject_types_supported": [
"pairwise"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"token_endpoint_auth_methods_supported": [
"client_secret_basic"
],
"claims_supported": [
"sub",
"iss",
"aud",
"exp",
"iat",
"auth_time",
"nonce"
]
}
I import this scheme using Springdoc's #SecurityScheme annotation
#ConditionalOnProperty(...) // I can switch between basic (dev) and oidc authentication (test, uat, prod)
#SecurityScheme(
name = "secured",
type = OPENIDCONNECT,
openIdConnectUrl = "${oidc-url}/.well-known/openid-configuration" //Parameterized URL works here
)
public class OidcConfiguration {
}
Swagger displays me the Authorize button on top of the page.
However, when I see the generated Swagger OpenAPI v3 schema, it looks like the following
{
"openapi": "3.0.1",
"info": {
"title": "API",
"version": "v1"
},
"servers": [
{
"url": "http://localhost:8080",
"description": "Generated server url"
}
],
"paths": {
"/api/v1/.....": {
"patch": {
"tags": [
"...."
],
"operationId": "...",
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"secured": []
}
]
}
},
"/health.check": {
"get": {
"tags": [
"probe-controller"
],
"operationId": "healthCheckProbe",
"responses": {
"200": {
"description": "OK",
"content": {
"*/*": {
"schema": {
"type": "boolean"
}
}
}
}
},
"security": [
{
"open": []
}
]
}
}
},
"components": {
"securitySchemes": {
"secured": {
"type": "openldConnect",
"openldConnectUrl": "https://...."
}
}
}
}
As you can see, the JSON file points to the remote OIDC configuration, and Swagger retrieves it.
But when I click on the Authentication button, I can see in the popup
secured (OAuth2, )
OpenId Connect URL: https://.....
Flow:
Scopes (select all, select none)
[ ] openid
According to this, I am not prompted with any client id. Clicking on Authorize opens a new window where I can't debug the original URL, as I am immediately redirected and DevTools is not open yet.
But after login request, I could see that the next URL displays ?client_id=&.....
If I manually navigate the URL with the client ID set, I can be redirected.
I also tried to set springdoc.swagger-ui.oauth.clientId=some client id according to linked issue, but it didn't display the input box.

Laravel intertwined relationship

while a user A is editing the permissions of user B, user A needs to see both the permissions it has and the permissions that user B has. For this, I thought of something like this and added something like and it gives me a nice output yes!
Controller:
PermissionCategoryResource::collection(PermissionCategory::with([
'permissions' => fn ($query) => $query->whereHas('adminUsers', fn ($query) => $query->where('admin_users.id', $this->user()->id)),
'selected' => fn ($query) => $query->whereHas('adminUsers', fn ($query) => $query->where('admin_users.id', $id)),
])
->select('id','name')
->get());
output:
{
"id": 2,
"name": "user",
"permissions": {
"permissions": [
[{
"id": 2,
"name": "userCreate"
},
{
"id": 3,
"name": "userUpdate"
},
{
"id": 4,
"name": "userDelete"
}
]
],
"selected": [
[{
"id": 2,
"name": "userCreate"
},
{
"id": 3,
"name": "userUpdate"
}
]
]
}
},
selected: The permissions of user B, edited by user A.
However, there is a situation like this. In order to compare the permissions in permissions with the permissions in selected, I need to put them both in a foreach loop. I don't like using a nested foreach loop. And I think Laravel has a solution for this. I'm new to Laravel and I'm trying to learn something so forgive me. Actually, I want an output like this. Let's say we loop the permissions in Permissions. Inside the loop: Does the permission in Permissions also exist in selected ? If it exists, I need to give the selected: true key and value to its permission in Permissions. So, to explain briefly, it is as follows:
{
"id": 2,
"name": "user",
"permissions": {
"permissions": [
[{
"id": 2,
"name": "userCreate"
"selected":true
},
{
"id": 3,
"name": "userUpdate"
"selected":true
},
{
"id": 4,
"name": "userDelete"
}
]
],
"selected": [
[{
"id": 2,
"name": "userCreate"
},
{
"id": 3,
"name": "userUpdate"
}
]
]
}
},
yes, it is better to explain with this example.
I tried this with resources and array map but failed. Do you have a solution suggestion for this issue?

How do i test create lead user case in Jmeter for muliple users

When user there is a new user (Identified through new Mobile No) then system store details and create an Unique ID for Customer,
But how to do performance testing for this scenario for 1000 Virtual Users, Only getting 1 Successful and others unsuccessful due to It required different Mobile No for each user
Request
{
"firstName": "Ankit",
"lastName": "Singh",
"mobileNumber": "8169089997",
"vehicleData": [
{
"registrationNumber": "UP32MX0505",
"vehicleType": "CAR"
},
{
"registrationNumber": "KA01HP8748",
"vehicleType": "BIKE"
}
],
"otherVehicles": [
{
"vehicleRegistrationNumber": "UP43Z0064",
"make": "Volvo",
"model": "Xc90",
"fuelType": "Hybrid (Electric + Petrol)",
"vehicleType": "CAR"
}
]
}
Response
{
"id": 331,
"mobileNo": "8169089997",
"firstName": "Ankit",
"lastName": "Singh",
"emailAddress": null,
"avatarId": null,
"profileImageUrl": null,
"gender": null,
"city": null,
"failedToSaveVehicles": []
}
How about using different mobile numbers for different users? For example you can consider using __threadNum() and __longSum() functions combination like replacing your 8169089997 with ${__longSum(8169000000,${__threadNum},)}
More information on JMeter Functions concept: Apache JMeter Functions - An Introduction

Show only the latest record using laravel query builder

I have two tables a payments table and a tenants table. Each payments table can have many payments for a single tenant (one to many relationship)
Payments
id tenant_id amount
1 1 5000
2 1 6000
3 2 7000
4 2 8000
tenants
id name email
1 peter peter#info.com
2 grace grace#info.com
When i run the following in my controller
public function getPaymentsList(Request $request)
{
$payments = DB::table("payments")
->join('tenants','tenants.id','=','tenant_id')
->select('payments.id','payments.tenant_id','tenants.name','payments.rent_to')
->groupBy('tenant_id')->get()
;
return response()->json($payments);
}
}
I get json content of the oldest record in the payments table not the latest record.
[
{
"id": 1,
"tenant_id": 1,
"name": "peter",
"amount": "5000"
},
{
"id": 3,
"tenant_id": 2,
"name": "grace",
"amount": "7000"
},
]
The output i want is
[
{
"id": 2,
"tenant_id": 1,
"name": "peter",
"amount": "6000"
},
{
"id": 4,
"tenant_id": 2,
"name": "grace",
"amount": "8000"
},
]
How to improve the above query so that it can display the desired output?
you can use something like:
public function getPaymentsList(Request $request)
{
$payments = DB::table("payments")
->join('tenants','tenants.id','=','tenant_id')
->select('payments.id','payments.tenant_id','tenants.name','payments.rent_to')
->groupBy('tenant_id')->latest()->first()
;
return response()->json($payments);
}
}
check the larvel documents for useing latest()->first()

Spring pagination - request parameters

My REST contorller:
#GetMapping("/test")
public Page<MyObject> pathParamTest(Pageable pageable) {
return myService.getPage(pageable);
}
I send a request like following:
localhost:8091/endpoint/test?page=0&size=3&sort=id&direction=DESC
It's my response from server:
{
"content": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
],
"last": true,
"totalPages": 1,
"totalElements": 3,
"first": true,
"sort": [
{
"direction": "ASC",
"property": "id",
"ignoreCase": false,
"nullHandling": "NATIVE",
"descending": false,
"ascending": true
}
],
"numberOfElements": 3,
"size": 3,
"number": 0
}
but the request has still direction = ASC.
How can I send to server direction = DESC?
And why response has a field "last" = true, because next page has one element more?
try
localhost:8091/endpoint/test?page=0&size=3&sort=id,DESC
from spring data rest 6.2. Sorting
curl -v
"http://localhost:8080/people/search/nameStartsWith?name=K&sort=name,desc"
sort Properties that should be sorted by in the format
property,property(,ASC|DESC). Default sort direction is ascending. Use
multiple sort parameters if you want to switch directions, e.g.
?sort=firstname&sort=lastname,asc.

Resources