Description
Summary
Hello everybody,
currently iam building an Microservice-Architecture with and OIDC-Server, a Spring boot frontend app and some spring boot resource server apis. For the frontend app i configured springs security as follows:
server:
port: 8081
spring:
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
oidc:
client-id: spring_api_client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
provider:
oidc:
authorization-uri: http://localhost:8080/uma-server-webapp/authorize
token-uri: http://localhost:8080/uma-server-webapp/token
user-info-uri: http://localhost:8080/uma-server-webapp/userinfo
jwk-set-uri: http://localhost:8080/uma-server-webapp/jwk
user-name-attribute: sub
This works perfectly smooth and i can login using my OIDC-Server. Now i want to request a Resource Server which returns messages available for the user requesting. Therefore my frontend app requests the Resource Server (RS) with the access token received during the login. The Resource Server is also protected with a scope which needs to be included in the access token to limit access to the RS. The RS then validates the incoming token by calling the /introspect or /userinfo Endpoint of my OIDC-Server depending on how i configured my RS. Currently my RS is configuration is as follows:
server:
port: 8082
security:
oauth2:
client:
clientId: spring_api_client_backend
clientSecret: secret
resource:
tokenInfoUri: http://localhost:8080/uma-server-webapp/introspect
user-info-uri: http://localhost:8080/uma-server-webapp/userinfo
preferTokenInfo: true
Actual Behavior
Spring security states, that the token can be validated by using either the /introspect or the /userinfo endpoint. For the beginning i used the /intorspect endpoint. This endpoint returns a few user information and belonging scopes, so the RS can check if the token scopes fit to the RS scopes. To get the current username requesting the RS i called Principal.getName() Method which returns the name of the client requesting the RS instead of the username. After some debugging i figured out, that the name of the attribute for the username is hardcoded and does not fit to the username attribute name which is returned by the /introspect endpoint. Therefore it can't find the username and sets the client name instead. For now I changed the attribute name in my /introspect endpoint to make it work. (Username attribute name should be configurable in the yaml)
Since the /introspect endpoint is only returning a few information about the user and is mainly used to validate the token, i decided to use the /userinfo endpoint to receive more user information required in my RS. Therefore i configured the RS as follows:
server:
port: 8082
security:
oauth2:
client:
clientId: spring_api_client_backend
clientSecret: secret
resource:
tokenInfoUri: http://localhost:8080/uma-server-webapp/introspect
user-info-uri: http://localhost:8080/uma-server-webapp/userinfo
preferTokenInfo: false
Now when iam calling the RS with a valid token, i get a 403 or a 401 as answer... After some research i figured out, that the user scopes only get set when using the /introspect endpoint. When using the /userinfo endpoint the user scopes are ignored and are not set. Since my RS is protected by a scope which is not set for the current access token, i get the mentionied response codes. In conclusion this means, that i can't protect my RS with scopes when using the /userinfo endpoint. When i remove the Scopes from the RS everything works fine.
Expected Behavior
As stated by the OIDC Spec, the /introspect endpoint can be used to validate the incoming access tokens at the RS. The /userinfo endpoint is only used to receive further user information which are not included in the /introspect endpoint and are usally more sensitive data. Therefore the /userinfo endpoint should NOT be used to validate tokens. Ofcourse when receiving an answer from the /userinfo endpoint the token must be valid, but i think that was not the intended use by the OIDC Spec.
As mentionied above, i can only use the /introspect OR the /userinfo endpoint to validate tokens. Since the best practice is to use the /introspect endpoint to validate the tokens together with RS Scopes, i did that. The off trade is, that i don't get any more user information since i can't call the /userinfo endpoint. I think the actual process should be, to validate the token at the /introspect endpoint AND then call the /userinfo endpoint to receive further user information. The part with calling the /userinfo endpoint can also be optional, since not every RS requires the full user information. For now i problably have to configure the whole part of OIDC and Spring Security manually again. Hope this helps to improve the OIDC support in Spring Security in the future.
Version
Spring Boot 2.1.1
Spring Security 5