java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode - spring-boot

I am trying to get access token and access token secret of a user after authorization. getting this exception when i click on Authorize App. below is the code in callback controller. I have configured callback url in the consumer application setting.
#RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void connect(#RequestParam("oauth_token") String oauthToken,#RequestParam("oauth_verifier") String oauthVerifier){
TwitterConnectionFactory connectionFactory = new TwitterConnectionFactory( "XXX", "XXX" );
OAuth1Operations oauthOperations = connectionFactory.getOAuthOperations();
OAuthToken requestToken = oauthOperations.fetchRequestToken("htt*://localhost:8080/svc/v1/authorize",null);
///String authorizeUrl = oauthOperations.buildAuthorizeUrl(requestToken.getValue(),null);
OAuthToken accessToken = oauthOperations.exchangeForAccessToken(new AuthorizedRequestToken(requestToken, oauthVerifier), null);
String consumerKey = "..."; // The application's consumer key
String consumerSecret = "..."; // The application's consumer secret
String token = accessToken.getValue();
String tokenSecret = accessToken.getSecret();
System.out.println("token: "+token);
Twitter twitter = new TwitterTemplate( consumerKey, consumerSecret, token, tokenSecret );
}

I could fetch the accesstoken and accesstokensecret after authorization.
The problem was with creating requestToken. replaced the object creation of OAuthToken as follows.
OAuthToken requestToken = new OAuthToken(oauthToken, oauthVerifier);
and it worked.

Related

EWS modern authentication using oauth2.0 : The remote server returned an error: (401)Unauthorized

I am trying to do modern authentication for outlook mailbox(which is used to send mail from applications) using microsoft graph access token.
I am succssfully getting the access token from the below code:
public class AuthTokenAccess {
public AuthTokenAccess() {}
public static String getAccessToken(String tenantId, String clientId, String clientSecret, String scope)
{
String endpoint = String.format("https://login.microsoftonline.com/%s/oauth2/token", tenantId);
String postBody = String.format("grant_type=client_credentials&client_id=%s&client_secret=%s&resource=%s&scope=%s",
clientId, clientSecret, "https://management.azure.com/", scope);
String accessToken = null;
try{
HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection();
conn.setRequestMethod("POST");
conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);
conn.getOutputStream().write(postBody.getBytes());
conn.connect();
JsonFactory factory = new JsonFactory();
JsonParser parser = factory.createParser(conn.getInputStream());
//String accessToken = null;
while (parser.nextToken() != JsonToken.END_OBJECT) {
String name = parser.getCurrentName();
if ("access_token".equals(name)) {
parser.nextToken();
accessToken = parser.getText();
}
}
}catch(Exception e) {
}
return accessToken;
}
after getting the access token I am sending this to ExchangeService:
public ExchangeService getExchangeServiceObj(String emailId, String token, String emailServerURI) throws URISyntaxException {
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
if(service != null) {
service.getHttpHeaders().put("Authorization", "Bearer " + token);
service.getHttpHeaders().put("X-AnchorMailbox", emailId);
service.setUrl(new URI(emailServerURI)); //https://outlook.office365.com/EWS/Exchange.asmx
}
LOGGER.debug("getExchangeServiceObj() {}.", "ends");
return service;
}
Here, I am getting the ExchangeService object but when I am trying send mail microsoft.exchange.webservices.data.core.service.item.EmailMessage.sendAndSaveCopy() throws Exception
public void sendMail(String toMail, String ccMail, String subject, String body, String pathOfFileToAttach) {
ExchangeService emailService = getExchangeServiceObj(
ResourceUtils.getPropertyValue("email_user"),
token,
ResourceUtils.getPropertyValue("ews_server"));
if(!StringUtils.hasText(toMail)) {
toMail = ccMail;
}
EmailMessage emessage = new EmailMessage(emailService);
emessage.setSubject(subject);
String strBodyMessage = body;
strBodyMessage = strBodyMessage + "<br /><br />";
LOGGER.info("Body: {} ", body);
MessageBody msg = new MessageBody(BodyType.HTML, strBodyMessage);
emessage.setBody(msg);
emessage.sendAndSaveCopy();
LOGGER.info("Email send {}", "sucessfully");
} catch(Exception e) {
LOGGER.error(Constants.ERROR_STACK_TRACE, e);
throw new CommonException(e);
}
}
Tried with below scopes:
"https://outlook.office.com/EWS.AccessAsUser.All",
"https://graph.microsoft.com/.default"
Below is the access token I am getting using the above code:
{"aud": "https://management.azure.com/",
"iss": "https://sts.windows.net/3863b7d0-213d-40f3-a4d0-6cd90452245a/",
"iat": 1628068305,
"nbf": 1628068305,
"exp": 1628072205,
"aio": "E2ZgYEjcvsaipUV1wxwxrne/9F4XAAA=",
"appid": "055eb578-4716-4901-861b-92f2469dac9c",
"appidacr": "1",
"idp": "https://sts.windows.net/3863b7d0-213d-40f3-a4d0-6cd90452245a/",
"oid": "33688cee-e16e-4d11-8ae0-a804805ea007",
"rh": "0.AUYA0LdjOD0h80Ck0GzZBFIkWni1XgUWRwFJhhuS8kadrJxGAAA.",
"sub": "33688cee-e16e-4d11-8ae0-a804805ea007",
"tid": "3863b7d0-213d-40f3-a4d0-6cd90452245a",
"uti": "nZUVod_e3EuO_T-Ter-_AQ",
"ver": "1.0",
"xms_tcdt": 1626687774
}
as you could see scope is not included in the token. Do I need to pass any other thing while getting the token.
Azure active directory set up:
registered application
2.Create client secret
3.Added redirect URL
added permission
Can someone please help me here, where I am doing mistake or is other any other way to make it work. Thank you
I can see a few issue here first your using the client credentials flow which requires that you assign Application Permission and you only have Delegate permission, with EWS the only Application permission that will work is full_access_as_app see https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth (app-only section)
String endpoint = String.format("https://login.microsoftonline.com/%s/oauth2/token", tenantId);
String postBody = String.format("grant_type=client_credentials&client_id=%s&client_secret=%s&resource=%s&scope=%s",
clientId, clientSecret, "https://management.azure.com/", scope);
Your mixing V1 and V2 authentication (see https://nicolgit.github.io/AzureAD-Endopoint-V1-vs-V2-comparison/) here which won't work (scope will just be ignored) for the v1 endpoint eg what you have in https://login.microsoftonline.com/%s/oauth2/token is the V1 auth endpoint so your request shouldn't include the scope just the resource and that resource should be https://outlook.office.com

Service that checks if token hasnt expired

so I have a authentication bean which provides access tokens from client credentials.
public class AuthServiceBean {
#Value("${some.url}")
private String someUrl;
#Value("${some.clientId}")
private String someClientId;
#Value("${some.secret}")
private String someSecret;
#Value("${some.username}")
private String someUsername;
#Value("${some.password}")
private String somePassword;
public AuthInfo getPrevAuth() {
return prevAuth;
}
public void setPrevAuth(AuthInfo prevAuth) {
this.prevAuth = prevAuth;
}
private AuthInfo prevAuth;
public AuthInfo getAuthInfo() throws IOException {
if (this.prevAuth != null && this.prevAuth.isNotExpired()) {
return this.prevAuth;
}
return this.Authenticate();
}
private AuthInfo Authenticate() throws IOException {
final String url = this.someUrl + "/api/oauth/v1/token";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
String clientIdSecret = this.someClientId +":"+ this.someSecret;
String authString = Base64.getEncoder().encodeToString(clientIdSecret.getBytes());
headers.add("Authorization", "Basic " + authString);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("username", this.someUsername);
map.add("password", this.somePassword);
map.add("grant_type", "password");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<?> response = restTemplate.postForEntity(url, request, String.class);
String bodyString = response.getBody().toString();
ObjectMapper mapper = new ObjectMapper();
try {
AuthInfo authInfo = mapper.readValue(bodyString, AuthInfo.class);
this.prevAuth = authInfo;
return this.prevAuth;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
And now how do I need to create service which checks if that access token valid if it hasn't expired and how to use refresh token. When access token expires I could ask new token with refresh token? Would be good to get any examples.
First of all, As I see in your code, you are using password grant type, not client credentials, and because of this, you pass also user credentials (username and password) in addition to the client credentials, client id and client secret.
Anyway, the reason because all the examples you found to check expiration are using jwt tokens is because these tokens have this information coded in the token itself, so you can parse it using some kind of library like Nimbus Jose and get the "exp" claim and check directly if that date is before or after the actual date.
If the token is an opaque one (not jwt). You don't have any way to check the expiration without call the server who issued that token. Normally the server (an oauth2 server) provides and endpoint called introspect in which you pass a token and it responds indicating if this token is valid or is not, because it has expired or it is revoked etc..

Share SPRING_SECURITY_CONTEXT between two applications

I have two different Spring Boot Applications that run on localhost on different ports (8080, 8081) and different configs (application.yml). These apps use SSO with OAuth 2.0 to get authorization token from Authorization Server. I log in to my first application, get authorization and everything works great here. Now I need to share these authentication details with second Spring Boot App (on port 8081) to authorize second app in Authorization Server. Googled and found 2 aproaches: I can try to share HttpSession between two apps (but I think it's redundant) OR HttpSessionSecurityContextRepository as SecurityContextRepository which seems more convenient. The problem here is that I can't manage to do so and I'm still not sure that it's a good idea to share Security Context between 2 apps.
What I tried for now:
Share authorization token from first app via headers in GET request (custom-built in accordance with specification for requests for Authorization Server), but it didn't work - second app doesn't take in mind this token.
Share authorized cookie from first app to second, but it didn't work, too.
I can't do authorization through Authorization Server on second app because it may be not a Spring Boot App with #Controller but any other app without HTML forms, so I need to authorize on first app (with UI), get all the data which is needed to perform authorized requests and pass it to second app (third, fourth...) so they will be able to do authorized requests too.
Thanks in advance!
I presume that your authorization/resource server is external application.And you can login successfully with your first application so flow is working.You have two client application with own client_id, client_secret and etc. parameters.If these parameters are different then authorization/resource server will return different bareer token and sessionid cookie for first and second client application.Otherwise you need to authorize both of them in authorization/resource server.
I would offer when user do login to first app then in background you do login also for second application.
For automatically authorizing second application you can try to do oauth2 login flow manually for second application with own parameters when after successful first application login and send cookies to frontend which you got from oauth2 login.
For manual oauth2 login you can try below code:
private Cookie oauth2Login(String username, String password, String clientId, String clientSecret) {
try {
String oauthHost = InetAddress.getByName(OAUTH_HOST).getHostAddress();
HttpHeaders headers = new HttpHeaders();
RestTemplate restTemplate = new RestTemplate();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
// Basic Auth
String plainCreds = clientId + ":" + clientSecret;
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = org.apache.commons.net.util.Base64.encodeBase64(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
headers.add("Authorization", "Basic " + base64Creds);
// form param
map.add("username", username);
map.add("password", password);
map.add("grant_type", GRANT_TYPE);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map,
headers);
// CALLING TOKEN URL
OauthTokenRespone res = null;
try {
res = restTemplate.postForObject(OAUTH_HOST, request,
OauthTokenRespone.class);
} catch (Exception ex) {
ex.printStackTrace();
}
Optional<OauthTokenRespone> optRes = Optional.ofNullable(res);
String accessToken = optRes.orElseGet(() -> new OauthTokenRespone("", "", "", "", "", ""))
.getAccess_token();
// CALLING RESOURCE
headers.clear();
map.clear();
headers.setContentType(MediaType.APPLICATION_JSON);
map.add("access_token", accessToken);
request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
Cookie oauthCookie = null;
if (accessToken.length() > 0) {
HttpEntity<String> response = restTemplate.exchange(
OAUTH_RESOURCE_URL.replace(OAUTH_HOST, oauthHost) + "?access_token=" + accessToken,
HttpMethod.POST, request, String.class);
String cookie = Optional.ofNullable(response.getHeaders().get("Set-Cookie"))
.orElseGet(() -> Arrays.asList(new String(""))).get(0);
if (cookie.length() > 0) {
String[] c = cookie.split(";")[0].split("=");
oauthCookie = new Cookie(c[0], c[1]);
oauthCookie.setHttpOnly(true);
}
}
return Optional.ofNullable(oauthCookie).orElseGet(() -> new Cookie("Ops", ""));
} catch (Throwable t) {
return new Cookie("Ops", "");
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class OauthTokenRespone {
private String access_token;
private String token_type;
private String refresh_token;
private String expires_in;
private String scope;
private String organization;
// getter and setter
}
And call this method after first app login as follows :
Cookie oauthCookie = oauth2Login(authenticationRequest.getUsername(), authenticationRequest.getPassword(),
CLIENT_ID, CLIENT_SECRET);
After getting cookie you need change its name (for example JSESSIONID-SECOND) because same cookies will override each other and also need to change its domain path to second app domain.
response.addCookie(oauthCookie);
Last you need add cookie to response (it is HttpServletResponse reference).
Hope it helps!

Store owin oauth bearer token

I am creating a simple authentication server using the default owin oauth server. After supplying the correct credentials a bearer token is generated and returned to the client. I used among others this tutorial by Taiseer
I would like to store the token in a database before the token is send to the client.
Maybe I completely overlooked it, but where can I get the token before it is send? As far as I know the token is generated after the ticket is validated in the GrantResourceOwnerCredentials method.
I am guessing the token is stored in the context. How can I get it out?
Startup.cs
private void ConfigureAuthServer(IAppBuilder app) {
// Configure the application for OAuth based flow
var oAuthServerOptions = new OAuthAuthorizationServerOptions {
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
Provider = new ApplicationOAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14)
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
ApplicationOAuthProvider
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) {
//Dummy check here
if (context.UserName != context.Password) {
context.SetError("invalid_grant", "The user name or password is incorrect");
return Task.FromResult<object>(null);
}
var claims = new List<Claim> {
new Claim(ClaimTypes.NameIdentifier, context.UserName),
new Claim(ClaimTypes.Name, context.UserName)
};
var oAuthIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
context.Validated(ticket);
return Task.FromResult<object>(null);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context) {
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary) {
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
Note: for those who wonder why I want to store the tokens.. it is a requirement I have to fulfill.
To fetch the token before it is sent to the client you must override TokenEndpointResponse:
public override Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
return base.TokenEndpointResponse(context);
}
the context object has a property AccessToken which will contains the representation of the token as a string.
OAuthTokenEndpointResponseContext contains a dictionary of objects
IDictionary<string, object> in AdditionalResponseParameters which allows us to find all the claims for the indentity.
If we wanted to fetch the expiration of the token we would find the claim .expires in the dictionary:
context.AdditionalResponseParameters[".expires"]
There's a github repository if someone is interested to play with a simple integration of client and server interaction.

Spring Social Reddit Extension - OAuth 2 Access_token retrieval. 401 Error

I am trying to create an extension for Reddit's Api. Reddit follows OAuth 2 for obtaining an access_token. I am using springs RestTemplate to make all POST requests to Reddit. I am able to successfully complete the first stage according to the documentation. The user is redirected to Reddit where he/she allows my application, Reddit then redirects me back to my application with a code. However, the second stage doesn't seem to work. I must use that code to make another post request to :
https://ssl.reddit.com/api/v1/access_token
Here is my attempt for obtaining an AccessGrant (SpringSocial wrapper for accesstoken sent back from Reddit). Spring Social requires you to extend OAuth2Template and implement the authentication process from there. In a typical spring application, a controller will use a helper to make a call to RedditOAuth2Template.exchangeForAccess and save the returned AccessGrant into a database.
According to the Reddit API Documentaiton a 401 response occurs due to a lack of client credentials via HTTP basic Auth. However, I am doing that in the createHeaders(String username, String password) method.
public class RedditOAuth2Template extends OAuth2Template {
private static final Logger LOG = LogManager.getLogger(RedditOAuth2Template.class);
private String client_id;
private String client_secret;
public RedditOAuth2Template(String clientId, String clientSecret) {
super(clientId, clientSecret, RedditPaths.OAUTH_AUTH_URL, RedditPaths.OAUTH_TOKEN_URL);
this.client_id = clientId;
this.client_secret = clientSecret;
setUseParametersForClientAuthentication(true);
}
#Override
#SuppressWarnings({"unchecked", "rawtypes"})
protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
HttpHeaders headers = createHeaders(client_id, client_secret);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.set(accessTokenUrl, accessTokenUrl);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(parameters, headers);
ResponseEntity<Map> responseEntity = getRestTemplate().exchange(accessTokenUrl, HttpMethod.POST, requestEntity, Map.class);
Map<String, Object> responseMap = responseEntity.getBody();
return extractAccessGrant(responseMap);
}
/*
Reddit requires client_id and client_secret be
placed via HTTP basic Auth when retrieving the access_token
*/
private HttpHeaders createHeaders(String username, String password) {
String auth = username + ":" + password;
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(Charset.forName("US-ASCII")));
HttpHeaders headers = new HttpHeaders();
String authHeader = "Basic " + new String(encodedAuth);
headers.set("Authorization", authHeader);
return headers;
}
private AccessGrant extractAccessGrant(Map<String, Object> result) {
String accessToken = (String) result.get("access_token");
String scope = (String) result.get("scope");
String refreshToken = (String) result.get("refresh_token");
// result.get("expires_in") may be an Integer, so cast it to Number first.
Number expiresInNumber = (Number) result.get("expires_in");
Long expiresIn = (expiresInNumber == null) ? null : expiresInNumber.longValue();
return createAccessGrant(accessToken, scope, refreshToken, expiresIn, result);
}
}
If you're getting a 401 response for that endpoint, you're doing one of a small number of things wrong, all related to sending the client ID & secret as HTTP Basic Authorization:
Not including a properly formatted Authorization header (i.e., Authorization: basic <b64 encoded credentials>)
Not properly base 64 encoding your credentials
Not including a client_id that for a valid OAuth2 client
Not including a semicolon between the client ID and secret
Not including the secret, or including the WRONG secret
You should check each stage of the Basic client auth, and log your output (or use a debugger to inspect it) at each stage to ensure you're not missing anything. You should also inspect the actual HTTP request you generate, and verify that the header is being sent (some HTTP libraries like to take liberties with headers)

Resources