I am trying to use keycloak for authorization in spring cloud gateway. Keycloak does not provide any spring based adapters for policy enforcement for reactive stack.However, it does provide an endpoint for policy evaluation.
http://localhost:8080/realms/myrealm/protocol/openid-connect/token -- POST
Request:
grant_type:urn:ietf:params:oauth:grant-type:uma-ticket
response_mode:decision
audience:b2b
permission:spm_audit#GET
Header:
Authorization : bearer <JWT>
# spm_audit is the resource that I have created in keycloak and GET is the scope(using HTTP methods as api scopes).
RESPONSE:
{
"result": true
}
My problem is that above endpoint does not accept URI as permission in request body and I don't have any resource-name to request URL mapping at gateway.
One possible solution could be to use gateway's route id as resource name and pass it in permission
cloud:
gateway:
routes:
- id: spm_audit
uri: http://localhost:8001
predicates:
- Path=/gateway/spm/api/v1/registrations/{regUUID}/audit
filters:
- StripPrefix=1
metadata:
custom_scope: "test scope"
#Fetch the route info in auth manager
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); //(ServerWebExchange exchange)
route.getId();
The problem with this approch is that the route matching filters are applied after authorization filter and exchange.getAttribute(GATEWAY_ROUTE_ATTR) is coming as null, plus I will have to map all api paths in route configuration and will end up with a huge configuration file.
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, @Qualifier("keycloKWebClient")WebClient kycloakWebClient) {
http
.authorizeExchange()
.pathMatchers(
"/gateway/*/public/**")
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.access(keyalokAuthManager(kycloakWebClient))....#this is where I call policy evaluation api
https://www.keycloak.org/docs/latest/authorization_services/index.html#_service_authorization_api
What about using spring-security for resource-servers with a JWT decoder? It would be far more efficient as it would save many round trips to authorization-server (JWT decoder validates access-token with authorization-server public key downloaded once when policy enforcer requires a call to authorization-server for each and every incoming "non public" request).
You can map Keycloak "roles" to spring "granted authorities" and apply Role Based Access Control either with:
http.authorizeExchange().pathMatchers("/protected-route/foo").hasAuthority("ROLE_IN_KEYCLOAK")
in your Java conf@PreAuthorize("hasAnyAuthority('ROLE_IN_KEYCLOAK')")
on your components methods.For that, all you have to do is provide with an authentication converter with custom authorities converter:
You should also have a look at this repo: https://github.com/ch4mpy/spring-addons
There is a
spring-addons-webflux-jwt-resource-server
spring-boot (2.7 or later) starter which would save you quite some configuration hassle. Tutorials in this repo are using servlet variants of the libs, but the 4 starters (servlet/reactive with JWT-decoder/introspection) work the same and you should easily find what to adapt for your reactive app.