WebClientReactiveJwtBearerTokenResponseClient failing to parse successful response - spring-security

I am assuming that its because of the specified body extractor, just wondering what part of the response is incorrect or if there is a different token response class.
var client = new WebClientReactiveJwtBearerTokenResponseClient();
client.setBodyExtractor(BodyExtractors.toMono(OAuth2AccessTokenResponse.class));
...
LOG.info("Response: {}", client
.getTokenResponse(request)
.onErrorMap(e -> {
LOG.error("Error is {}", e.getMessage());
return e;}
)
.block());
It fails with
Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.security.oauth2.core.OAuth2AccessToken.getScopes()" because the return value of "org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse.getAccessToken()" is null
at org.springframework.security.oauth2.client.endpoint.AbstractWebClientReactiveOAuth2AccessTokenResponseClient.populateTokenResponse(AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java:240) ~[spring-security-oauth2-client-5.7.6.jar:5.7.6]
at org.springframework.security.oauth2.client.endpoint.AbstractWebClientReactiveOAuth2AccessTokenResponseClient.lambda$readTokenResponse$3(AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java:228) ~[spring-security-oauth2-client-5.7.6.jar:5.7.6]
but, the access token is in the response which looks like
{
"access_token":"00D8K0000004cbe!AQgAQGO6yn3qEnI_QJJTsQHT5SyYt75M_o0QW9YdKDoj4L_r3wQir3P4zkCFal.I0oeNciySYsw52VazmZV_5LVy",
"scope":"api full",
"instance_url":"https://example.org",
"id":"00D8K0000004cbeUAA/0055f000008GSREAA4",
"token_type":"Bearer"
}

Related

"invalid_client" in Spring Security when using private_key_jwt instead of client_secret_basic

I have tried to switch from client_secret_basic to private_key_jwt, but I'm getting the following error when I'm sent back from the auth provider:
[invalid_client] Client authentication failed. No client authentication included
It's not a Spring Boot app, but this is what I have done so far:
private ClientRegistration idPortenClientRegistration() {
return ClientRegistrations
.fromIssuerLocation("the endpoint")
.clientId("the client id")
.registrationId("idporten")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("the redirect url")
.scope(Arrays.asList("the scopes"))
.userNameAttributeName(IdTokenClaimNames.SUB)
.clientName("idporten")
.clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
.build();
}
My SecurityConfig.class:
http.oauth2Client(oauth2 -> oauth2
.authorizationCodeGrant(codeGrant -> codeGrant
.accessTokenResponseClient(accessTokenResponseClient())));
[…]
private DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient() {
OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
requestEntityConverter.addParametersConverter(
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
tokenResponseClient.setRequestEntityConverter(requestEntityConverter);
return tokenResponseClient;
}
private Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
JKSKeyManager keyManager = getApplicationContext().getBean("keyManager", JKSKeyManager.class);
try {
RSAPublicKey publicKey = (RSAPublicKey) keyManager.getPublicKey("idporten1");
KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) keyManager.getKeyStore()
.getEntry("idporten1", new KeyStore.PasswordProtection(keyEntryPassword1.toCharArray()));
RSAPrivateKey privateKey = (RSAPrivateKey) pkEntry.getPrivateKey();
return new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();
} catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) {
logger.error("Failed to configure jwkResolver: " + e.getMessage(), e);
}
}
return null;
};
As mentioned, I'm successfully redirected to the auth provider, but getting the error described above when I'm sent back to the application. I have also tried to log accessTokenResponseClient() and jwkResolver. The former method gets called just before the error occurs, but nothing gets logged from the latter.
Some documentation from the provider:
https://docs.digdir.no/oidc_protocol_token.html
https://oidc-ver2.difi.no/idporten-oidc-provider/.well-known/openid-configuration

What kind of errors are returned by HttpServer stream in Dart

I'm going through the Dart server documentation. I see I can await for an HttpRequest like this:
import 'dart:io';
Future main() async {
var server = await HttpServer.bind(
InternetAddress.loopbackIPv4,
4040,
);
print('Listening on localhost:${server.port}');
await for (HttpRequest request in server) {
request.response.write('Hello, world!');
await request.response.close();
}
}
That's because HttpServer implements Stream. But since a stream can return either a value or an error, I should catch exceptions like this, right:
try {
await for (HttpRequest request in server) {
request.response.write('Hello, world!');
await request.response.close();
}
} catch (e) {
// ???
}
But I'm not sure what kind of exceptions can be caught. Do the exceptions arise from the request (and warrant a 400 level response) or from the server (and warrant a 500 level response)? Or both?
Error status codes
On exception, a BAD_REQUEST status code will be set:
} catch (e) {
// Try to send BAD_REQUEST response.
request.response.statusCode = HttpStatus.badRequest;
(see source)
That would be 400 (see badRequest).
Stream errors
In that same catch block, the exceptions will be rethrown, which means that you will still receive all the errors on your stream. This happens in processRequest, which processes all requests in bind.
And you get the errors on your stream because they are forwarded to the sink in bind.
Kinds of errors
I could only find a single explicit exception type:
if (disposition == null) {
throw const HttpException(
"Mime Multipart doesn't contain a Content-Disposition header value");
}
if (encoding != null &&
!_transparentEncodings.contains(encoding.value.toLowerCase())) {
// TODO(ajohnsen): Support BASE64, etc.
throw HttpException('Unsupported contentTransferEncoding: '
'${encoding.value}');
}
(see source)
These are both HttpExceptions.

AcquireTokenSilentAsync fails to authenticate user

I am trying to get the silent token request to initialize the ConfidentialClientApp object as in the 'Microsoft Graph SDK ASPNET Connect' project and outlined in Add sign-in with Microsoft to an ASP.NET web app
With my code mirroring the samples above, I expect that my call will return a successful result with an access to.
var result = await cca.AcquireTokenSilentAsync(graphScopes, cca.Users.First());
return result.AccessToken;
However, I get an error where it says the user needs to be authenticated. I am not sure what I am missing from the examples that make this work in them.
You can only acquire the token silently if there is already a cached token for that user that includes the scopes you're requesting (it can have more, but it needs to have at least what you've asked for).
This is why AcquireTokenSilentAsync should always be wrapped in a Try/Catch block. If it fails to find a matching token, you need to launch an interactive flow. Here is an example from the MSAL Wiki:
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenSilentAsync(scopes, app.Users.FirstOrDefault());
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync.
// This indicates you need to call AcquireTokenAsync to acquire a token
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
result = await app.AcquireTokenAsync(scopes);
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{System.Environment.NewLine}{msalex}";
}
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token Silently:{System.Environment.NewLine}{ex}";
return;
}
if (result != null)
{
string accessToken = result.AccessToken;
// Use the token
}

Jenkins Docker container - 403 no valid crumb was included in the request

I'm setting up my Jenkins server, and on simple requests in the web interface, like creating a folder, a pipeline, a job, etc., I periodically get the following error:
HTTP ERROR 403
Problem accessing /job/Mgmt/createItem. Reason:
No valid crumb was included in the request
The server is using the Jenkins/Jenkins container, orchestrated by Kubernetes on a cluster on AWS created with kops. It sits behind a class ELB.
Why might I be experiencing this? I thought the crumb was to combat certain CSRF requests, but all I'm doing is using the Jenkins web interface.
Enabling proxy compatibility may help to solve this issue.
Go to Settings -> Security -> Enable proxy compatibility in CSRF Protection section
Some HTTP proxies filter out information that the default crumb issuer uses to calculate the nonce value. If an HTTP proxy sits between your browser client and your Jenkins server and you receive a 403 response when submitting a form to Jenkins, checking this option may help. Using this option makes the nonce value easier to forge.
After a couple of hours of struggling, I was able to make it work with curl:
export JENKINS_URL=http://localhost
export JENKINS_USER=user
export JENKINS_TOKEN=mytoken
export COOKIE_JAR=/tmp/cookies
JENKINS_CRUMB=$(curl --silent --cookie-jar $COOKIE_JAR $JENKINS_URL'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)' -u $JENKINS_USER:$JENKINS_TOKEN)
echo $JENKINS_CRUMB
curl --cookie $COOKIE_JAR $JENKINS_URL/createItem?name=yourJob --data-binary #jenkins/config.xml -H $JENKINS_CRUMB -H "Content-Type:text/xml" -u $JENKINS_USER:$JENKINS_TOKEN -v
when calling the http://JENKINS_SERVER:JENKINS_PORT/JENKINS_PREFIX/crumbIssuer/api/json you receive a header ("Set-Cookie") to set a JSESSIONID, so you must supply it in the upcoming requests you issue,
the reason is that jenkins test for valid crumb in this manner: comparing the crumb you send in the request with a crumb it generates on the server side (using your session id),
you can see it in jenkins code: scroll down to method:
public boolean validateCrumb(ServletRequest request, String salt, String crumb)
it means you HAVE to include a session in the next requests (after fetching the crumb)!
so the curl --cookie must be used as ThiagoAlves stated in his solution
i use java so i used this next tester (HTTPClient would be prefered, but i wanted a simple java only example):
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class JobRunner
{
String jenkinsUser = "tester";
String jenkinsPassword = "1234"; // password or API token
String jenkinsServer = "localhost";
String jenkinsPort = "8080";
String jenkinsPrefix = "/jenkins";
String jSession = null;
String crumb = null;
HttpURLConnection connection = null;
String responseBody = "";
public void openConnection(String requestMethod, String relativeURL) throws Exception
{
// prepare the authentication string
String authenticationString = jenkinsUser + ":" + jenkinsPassword;
String encodedAuthenticationString = Base64.getEncoder().encodeToString(authenticationString.getBytes("utf-8"));
// construct the url and open a connection to it
URL url = new URL("http://" + jenkinsServer + ":" + jenkinsPort + jenkinsPrefix + relativeURL);
connection = (HttpURLConnection) url.openConnection();
// set the login info as a http header
connection.setRequestProperty("Authorization", "Basic " + encodedAuthenticationString);
// set the request method
connection.setRequestMethod(requestMethod);
}
public void readResponse() throws Exception
{
// get response body and set it in the body member
int responseCode = connection.getResponseCode();
switch (responseCode)
{
case 401:
System.out.println("server returned 401 response code - make sure your user/password are correct");
break;
case 404:
System.out.println("server returned 404 response code - make sure your url is correct");
break;
case 201:
case 200:
System.out.println("server returned " + responseCode + " response code");
InputStream responseBodyContent = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(responseBodyContent));
String currentLine;
while ((currentLine = bufferedReader.readLine()) != null)
{
responseBody = responseBody + currentLine + "\n";
}
break;
default:
System.out.println("server returned error response code: " + responseCode);
break;
}
}
public void setSessionCookie() throws Exception
{
jSession = connection.getHeaderField("Set-Cookie");
System.out.println("jSession: " + jSession);
}
public void disconnect() throws Exception
{
if(connection!=null)
{
connection.disconnect();
connection = null;
responseBody = "";
}
}
public void getCrumb() throws Exception
{
try
{
openConnection("GET", "/crumbIssuer/api/json");
readResponse();
setSessionCookie();
int crumbIndex = responseBody.indexOf("crumb\":\"");
if(crumbIndex!=-1)
{
int crumbIndexEnd = responseBody.indexOf("\",\"", crumbIndex);
crumb = responseBody.substring(crumbIndex + "crumb\":\"".length(), crumbIndexEnd);
System.out.println(crumb);
}
}
finally
{
disconnect();
}
}
public void runJob() throws Exception
{
try
{
openConnection("POST", "/job/test/build");
connection.setDoOutput(true);
connection.setRequestProperty("Cookie", jSession);
connection.setRequestProperty("Jenkins-Crumb", crumb);
readResponse();
System.out.println("Post response: " + responseBody);
}
finally
{
disconnect();
}
}
public static void main(String[] args)
{
JobRunner jobRunner = new JobRunner();
try
{
jobRunner.getCrumb();
jobRunner.runJob();
}
catch (Exception err)
{
err.printStackTrace();
}
}
}

Cant connect to survey monkey API

I am trying to connect to the survey monekey API with this code, which is not working. It says "Invalid API key" even though I got the API from the API console.
public void fetch() {
String url = "https://api.surveymonkey.net/v2/surveys/get_survey_list?api_key=" + apiKey;
System.out.println("request being sent");
System.out.println(url);
JSONObject obj = new JSONObject();
try {
// byte[] postDataBytes = obj.toJSONString().getBytes("UTF-8");
URL ourl = new URL(url.toString());
HttpURLConnection conn = (HttpURLConnection) ourl.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "bearer " + accessToken);
conn.setRequestProperty("Content-Type", "application/json");
conn.getRequestProperty(obj.toString().getBytes("UTF-8").toString());
int k = conn.getResponseCode();
System.out.println("The response code received is " + k);
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ conn.getResponseCode());
}
BufferedReader br = new BufferedReader(new InputStreamReader(
(conn.getInputStream())));
String output;
System.out.println("Output from Server .... \n");
output = br.readLine();
System.out.println(output);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Here's the error:
request being sent
https://api.surveymonkey.net/v2/surveys/get_survey_list?api_key=---API-KEY----
The response code received is 200
Output from Server ....
{"status":3,"errmsg":"Expected object or value"}
I just got this url from the API console.
Ensure you are using the API key associated with your developer account registered at http://developer.surveymonkey.com, not the sample API key the console uses to let you try requests. The sample api key is not meant to be used with apps, only on the API console.
That particular error is generated when an empty string is sent for the POST data. The API expects an empty object at minimum ("{}"). If the issue pointed out by Miles above was just a typo (using 'getRequestProperty' instead of 'setRequestProperty',) check if toString on an empty JSONObject is returning "" or "{}".

Resources