How to fix sender_id_mismatch using fcm notification - java

I am working on a java server which is sending Firebase Cloud Messaging notification to devices using OAUTH 2.0 authentication.
According to guide (https://firebase.google.com/docs/cloud-messaging/auth-server?authuser=0) I have generated key from an account service with editor role, but I keep getting 403 SENDER_ID_MISMATCH error.
Any suggestion?
public class FcmManager {
private static final String DEVICE_NOTIFICATION_URL = "https://fcm.googleapis.com/v1/projects/myprojectid/messages:send";
private static final String JSON_STR_DEVICE_NOTIFICATION = "{\"message\":{\"token\" : " +"\"%s\"" + ",\"notification\" : " +
"{\"body\" : \"%s\",\"title\" : \"%s\"}}}";
public static boolean sendNotificationDevice(DeviceNotification deviceNotification, String msgTitle,
String msgBody) throws Exception{
String accessToken = getAccessToken();
System.out.println(msgBody);
String jsonStr = String.format(JSON_STR_DEVICE_NOTIFICATION, deviceNotification.getToken()
,msgBody,msgTitle);
System.out.println(jsonStr);
F.Promise<String> response = WS.url(DEVICE_NOTIFICATION_URL)
.setContentType("application/json").setHeader("Authorization","Bearer " +
accessToken).post(jsonStr).map(
new F.Function<WS.Response, String>() {
public String apply(WS.Response response) {
String result = response.getBody();
System.out.println(result);
return result;
}
}
);
return true;
}
private static String getAccessToken() throws IOException {
GoogleCredential googleCredential = GoogleCredential
.fromStream(new FileInputStream("mygeneratedkeyfromaccountservice.json"))
.createScoped(Arrays.asList("https://www.googleapis.com/auth/firebase.messaging"));
googleCredential.refreshToken();
return googleCredential.getAccessToken();
}
}

Related

Getting Bad Request while trying to consume REST API in Java SpringBoot

Hi I am trying to consume a REST endpoint using POST, but I am getting the error below. The endpoint gives proper response in POSTMAN, but I am getting error in Java. Please let me know where the mistake is.
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: [{
"error":"unsupported_grant_type",
"error_description":"The given grant_type is not supported"
}]] with root cause
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: [{
"error":"unsupported_grant_type",
"error_description":"The given grant_type is not supported"
}]
Below is code:
Controller:
public class MemberController {
private static final Logger log = LoggerFactory.getLogger(MemberController.class);
#Autowired
MemberService memberService;
#PostMapping(value = "/token", headers = "Accept=application/json")
public String getToken() {
log.info("Test getToken method");
return memberService.callTokenService();
}
Service Class:
public class MemberService {
private static final Logger log = LoggerFactory.getLogger(MemberService.class);
private RestTemplate restTemplate = new RestTemplate();
final String tokenURL = "-------";
public String callTokenService() {
log.info("Inside Service");
TokenInput input = new TokenInput();
String clientId = "l7xxef159fc30ee8479e9a7dab859c458a4d";
String clientSecret = "a63d0b4a01b844c0b7e7eb724ef13959";
String grantType = "client_credentials";
input.setCLIENT_ID(clientId);
input.setCLIENT_SECRET(clientSecret);
input.setGRANT_TYPE(grantType);
ResponseEntity<TokenProperties> response = restTemplate.postForEntity(tokenURL, input, TokenProperties.class);
HttpStatus status = response.getStatusCode();
log.info("Status: "+status);
log.info("Response: "+response.toString());
return response.toString();
}
}
POJO class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class TokenProperties {
String access_token;
String token_type;
String expires_in;
String scope;
public String getAccess_token()
{
return access_token;
}
public void setAccess_token(String access_token)
{
this.access_token = access_token;
}
public String getToken_type()
{
return token_type;
}
public void setToken_type(String token_type)
{
this.token_type = token_type;
}
public String getExpires_in()
{
return expires_in;
}
public void setExpires_in(String expires_in)
{
this.expires_in = expires_in;
}
public String getScope()
{
return scope;
}
public void setScope(String scope)
{
this.scope = scope;
}
#Override
public String toString() {
return "{" + "access_token='" + access_token + '\'' + ", token_type=" + token_type + ", expires_in=" + expires_in + '\'' + "scope='" + scope + '}';
}
}
TokenInput POJO:
package com.memberservice_v2;
public class TokenInput {
String CLIENT_ID;
String CLIENT_SECRET;
String GRANT_TYPE;
public String getCLIENT_ID() {
return CLIENT_ID;
}
public void setCLIENT_ID(String cLIENT_ID) {
CLIENT_ID = cLIENT_ID;
}
public String getCLIENT_SECRET() {
return CLIENT_SECRET;
}
public void setCLIENT_SECRET(String cLIENT_SECRET) {
CLIENT_SECRET = cLIENT_SECRET;
}
public String getGRANT_TYPE() {
return GRANT_TYPE;
}
public void setGRANT_TYPE(String gRANT_TYPE) {
GRANT_TYPE = gRANT_TYPE;
}
}
Can anyone please help me out? Please let me know where the mistake is. Thanks in Advance!
Your request ResponseEntity<TokenProperties> response = restTemplate.postForEntity(tokenURL, null, TokenProperties.class); to token endpoint is incomplete. You're not passing the payload (token request) as far as I can see in the aforementioned code.
First, create the token request and set the appropriate attributes such as client id, grant type etc.
TokenRequest tokenRequest = new TokenRequest();
// set the attributes as per the token endpoint
ResponseEntity<TokenProperties> response = restTemplate.postForEntity(tokenURL, tokenRequest, TokenProperties.class);

Securing typesafe microprofile RestClientBuilder with Keycloak

I have the following Setup:
Keycloak 9.0.0 running on port 8180
Spring Boot server application running on port 8080
Demo client application using CxfTypeSafeClientBuilder to access server service
The Keycloak - Spring Boot interaction is working fine and I can receive tokens from Keycloak and the demo service is validating the token if I pass it as Authorization header.
How should I configure the CxfTypeSafeClientBuilder / RestClientBuilder to handle the JWT tokens I get from the Keycloak instance? Do I have to build my own ClientResponseFilter, if so how to handle expired tokens?
Are there any existing implementations / standards I didn't find?
JAX-RS webservice interface:
#Path("/demo")
public interface IDemoService {
#GET
#Path("/test")
String test();
}
Simple Spring Security configuration:
http.cors().and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionAuthenticationStrategy(sessionAuthenticationStrategy()).and().authorizeRequests().antMatchers("/**")
.authenticated();
EDIT: new workaround to get initial access- and refresh token from server:
AccessTokenResponse tokens = AuthUtil.getAuthTokens("http://localhost:8180/auth", "share-server", "test", "test", "share-server-service-login");
String accessToken = tokens.getToken();
String refreshToken = tokens.getRefreshToken();
Client doing service calls until the token expires:
URI apiUri = new URI("http://localhost:8080/services/");
RestClientBuilder client = new CxfTypeSafeClientBuilder().baseUri(apiUri).register(new TokenFilter(accessToken, refreshToken));
IDemoService service = client.build(IDemoService.class);
for (int i = 0; i < 200; i++) {
System.out.println("client: " + new Date() + " " + service.test());
Thread.sleep(10000);
}
TokenFilter which works until the access-token expires:
public static class TokenFilter implements ClientRequestFilter, ClientResponseFilter {
private String accessToken;
private String refreshToken;
public TokenFilter(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
#Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
if (responseContext.getStatus() == 401 && "invalid_token".equals(responseContext.getStatusInfo().getReasonPhrase())) {
// maybe handle send the refresh token... probalby should be handled earlier using the 'expires' value
}
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
if (accessToken != null && !accessToken.isEmpty()) {
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer" + " " + accessToken);
}
}
}
Found a better solution with only dependencies on keycloak-authz-client:
String serverUrl = "http://localhost:8180/auth";
String realm = "share-server";
String clientId = "share-server-service-login";
String clientSecret = "e70752a6-8910-4043-8926-03661f43398c";
String username = "test";
String password = "test";
Map<String, Object> credentials = new HashMap<>();
credentials.put("secret", clientSecret);
Configuration configuration = new Configuration(serverUrl, realm, clientId, credentials, null);
AuthzClient authzClient = AuthzClient.create(configuration);
AuthorizationResource authorizationResource = authzClient.authorization(username, password);
URI apiUri = new URI("http://localhost:8080/services/");
RestClientBuilder client = new CxfTypeSafeClientBuilder().baseUri(apiUri).register(new TokenFilter(authorizationResource));
IDemoService service = client.build(IDemoService.class);
for (int i = 0; i < 200; i++) {
System.out.println("client: " + new Date() + " " + service.test());
Thread.sleep(10000);
}
authorizationResource.authorize() will use org.keycloak.authorization.client.util.TokenCallable.call() in the background which validates the token expiration time and automatically refreshes the token if necessary.
so String accessToken = authorize.getToken(); will always be the current valid token.
#Priority(Priorities.AUTHENTICATION)
public static class TokenFilter implements ClientRequestFilter {
private AuthorizationResource authorizationResource;
public TokenFilter(AuthorizationResource authorizationResource) {
this.authorizationResource = authorizationResource;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
AuthorizationResponse authorize = authorizationResource.authorize();
String accessToken = authorize.getToken();
System.out.println(accessToken);
if (accessToken != null && !accessToken.isEmpty()) {
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer" + " " + accessToken);
}
}
}
I found a solution which automatically refreshes the access token but now I have a dependency to keycloak-client-registration-cli (which is actually intended to provide a console).
There might be better solutions with a less heavy dependencies.
Currently no handling if login fails or other exception handling implemented.
String serverUrl = "http://localhost:8180/auth";
String realm = "share-server";
String clientId = "share-server-service-login";
String username = "test";
String password = "test";
// initial token after login
AccessTokenResponse token = AuthUtil.getAuthTokens(serverUrl, realm, username, password, clientId);
String accessToken = token.getToken();
String refreshToken = token.getRefreshToken();
ConfigData configData = new ConfigData();
configData.setRealm(realm);
configData.setServerUrl(serverUrl);
RealmConfigData realmConfigData = configData.sessionRealmConfigData();
realmConfigData.setClientId(clientId);
realmConfigData.setExpiresAt(System.currentTimeMillis() + token.getExpiresIn() * 1000);
realmConfigData.setRefreshExpiresAt(System.currentTimeMillis() + token.getRefreshExpiresIn() * 1000);
realmConfigData.setToken(accessToken);
realmConfigData.setRefreshToken(refreshToken);
ConfigUtil.setupInMemoryHandler(configData);
URI apiUri = new URI("http://localhost:8080/services/");
RestClientBuilder client = new CxfTypeSafeClientBuilder().baseUri(apiUri).register(new TokenFilter(configData));
IDemoService service = client.build(IDemoService.class);
for (int i = 0; i < 200; i++) {
System.out.println("client: " + new Date() + " " + service.test());
Thread.sleep(10000);
}
Filter which automatically refreshes the access token if expired using AuthUtil.ensureToken(configData):
#Priority(Priorities.AUTHENTICATION)
public static class TokenFilter implements ClientRequestFilter {
private ConfigData configData;
public TokenFilter(ConfigData configData) {
this.configData = configData;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
String accessToken = AuthUtil.ensureToken(configData);
if (accessToken != null && !accessToken.isEmpty()) {
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer" + " " + accessToken);
}
}
}
More generic solution using Apache CXF OAuth2 (cxf-rt-rs-security-oauth2), without ClientRequestFilter.
The BearerAuthSupplier automatically handles refreshTokens and receives new accessTokens.
String serverUrl = "http://localhost:8180/auth";
String realm = "share-server";
String clientId = "share-server-service-login";
String clientSecret = "e70752a6-8910-4043-8926-03661f43398c";
String username = "test";
String password = "test";
String tokenUri = serverUrl + "/realms/" + realm + "/protocol/openid-connect/token";
Consumer consumer = new Consumer(clientId);
ResourceOwnerGrant grant = new ResourceOwnerGrant(username, password);
ClientAccessToken initial = OAuthClientUtils.getAccessToken(tokenUri, consumer, grant, true);
BearerAuthSupplier supplier = new BearerAuthSupplier();
supplier.setAccessToken(initial.getTokenKey());
supplier.setRefreshToken(initial.getRefreshToken());
supplier.setConsumer(consumer);
supplier.setAccessTokenServiceUri(tokenUri);
HTTPConduitConfigurer httpConduitConfigurer = new HTTPConduitConfigurer() {
#Override
public void configure(String name, String address, HTTPConduit c) {
c.setAuthSupplier(supplier);
}
};
Bus bus = BusFactory.getThreadDefaultBus();
bus.setExtension(httpConduitConfigurer, HTTPConduitConfigurer.class);
URI apiUri = new URI("http://localhost:8080/services/");
RestClientBuilder client = new CxfTypeSafeClientBuilder().baseUri(apiUri);
IDemoService service = client.build(IDemoService.class);
for (int i = 0; i < 200; i++) {
System.out.println("client: " + new Date() + " " + service.test());
Thread.sleep(5 * 60 * 1000);
}
Instead of login in with username and password (ResourceOwnerGrant) also possible to use client credentials with ClientCredentialsGrant.
ClientCredentialsGrant grant = new ClientCredentialsGrant();
grant.setClientId(clientId);
grant.setClientSecret(clientSecret);

Play Framework Basic Auth User Not Changing Between Sessions

I'm trying to implement Basic Auth with the play framework.
public class BasicAuth extends Action.Simple {
private static final String REALM = "Authorisation Needed";
private static final String AUTHORISATION = "authorization";
private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
private static final F.Promise<Result> UNAUTHORISED = F.Promise.pure((Result) unauthorized());
#Override
public F.Promise<Result> call(Http.Context context) throws Throwable {
Optional<String> auth_header = Optional.ofNullable(context.request().getHeader(AUTHORISATION));
if (!auth_header.isPresent()) {
context.response().setHeader(WWW_AUTHENTICATE, REALM);
return UNAUTHORISED;
}
String encoded_credentials = auth_header.get().substring(6);
byte[] decoded_credentials = Base64.getDecoder().decode(encoded_credentials);
String[] credentials = new String(decoded_credentials, "UTF-8").split(":");
if (credentials == null || credentials.length != 2) {
return UNAUTHORISED;
}
User user = authorise(credentials);
if (user == null) {
return UNAUTHORISED;
}
context.session().put("email", user.getEmail());
return delegate.call(context);
}
private User authorise(String[] credentials) {
String username = credentials[0];
String password = credentials[1];
return User.find.where().eq("email", username).eq("password", password).findUnique();
}
}
But the user doesn't change between requests. I.e. I log in with Joe Bloggs after initialising the server and it returns Joe as the current user.
Next request I log in with Bill Gates, and it returns Joe Bloggs as the current user.
I am returning the email stored in the session in the controller as so:
User logged_user = UserDao.findByEmail(context.session().get("email"));
I really need to fix this. Any help please?

GWT Facebook Integration

I am trying to write a server side Facebook Notification service in my GWT app. The idea is that I will run this as a timertask or cron job sort of.
With the code below, I get a login URL, I want to be able to Login programmatically as this is intended to be automated (Headless sort of way). I was gonna try do a submit with HTMLunit but I thought the FB API should cater for this.
Please advice.
public class NotificationServiceImpl extends RemoteServiceServlet implements NotificationService {
/**serialVersionUID*/
private static final long serialVersionUID = 6893572879522128833L;
private static final String FACEBOOK_USER_CLIENT = "facebook.user.client";
long facebookUserID;
public String sendMessage(Notification notification) throws IOException {
String api_key = notification.getApi_key();
String secret = notification.getSecret_key();
try {
// MDC.put(ipAddress, req.getRemoteAddr());
HttpServletRequest request = getThreadLocalRequest();
HttpServletResponse response = getThreadLocalResponse();
HttpSession session = getThreadLocalRequest().getSession(true);
// session.setAttribute("api_key", api_key);
IFacebookRestClient<Document> userClient = getUserClient(session);
if(userClient == null) {
System.out.println("User session doesn't have a Facebook API client setup yet. Creating one and storing it in the user's session.");
userClient = new FacebookXmlRestClient(api_key, secret);
session.setAttribute(FACEBOOK_USER_CLIENT, userClient);
}
System.out.println("Creating a FacebookWebappHelper, which copies fb_ request param data into the userClient");
FacebookWebappHelper<Document> facebook = new FacebookWebappHelper<Document>(request, response, api_key, secret, userClient);
String nextPage = request.getRequestURI();
nextPage = nextPage.substring(nextPage.indexOf("/", 1) + 1); //cut out the first /, the context path and the 2nd /
System.out.println(nextPage);
boolean redirectOccurred = facebook.requireLogin(nextPage);
if(redirectOccurred) {
return null;
}
redirectOccurred = facebook.requireFrame(nextPage);
if(redirectOccurred) {
return null;
}
try {
facebookUserID = userClient.users_getLoggedInUser();
if (userClient.users_hasAppPermission(Permission.STATUS_UPDATE)) {
userClient.users_setStatus("Im testing Facebook With Java! This status is written using my Java code! Can you see it? Cool :D", false);
}
} catch(FacebookException ex) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while fetching user's facebook ID");
System.out.println("Error while getting cached (supplied by request params) value " +
"of the user's facebook ID or while fetching it from the Facebook service " +
"if the cached value was not present for some reason. Cached value = {}" + userClient.getCacheUserId());
return null;
}
// MDC.put(facebookUserId, String.valueOf(facebookUserID));
// chain.doFilter(request, response);
} finally {
// MDC.remove(ipAddress);
// MDC.remove(facebookUserId);
}
return String.valueOf(facebookUserID);
}
public static FacebookXmlRestClient getUserClient(HttpSession session) {
return (FacebookXmlRestClient)session.getAttribute(FACEBOOK_USER_CLIENT);
}
}
Error message:
[ERROR] com.google.gwt.user.client.rpc.InvocationException: <script type="text/javascript">
[ERROR] top.location.href = "http://www.facebook.com/login.php?v=1.0&api_key=MY_KEY&next=notification";
[ERROR] </script>

Google translator toolkit API - google-api-java-client Client login 403 Forbidden

I just implemented Google translator toolkit API using google-api-java-client library.
The problem is, that I can authenticate using clientLogin with the old "gdata" client library, but I can't manage to do that with google-api-java-client.
It's quite straightforward, but I'm still getting 403 forbidden response. The requests (old / new) are almost the same, but only the auth tokens differ. Google just sends me a token that I cannot authenticate with...
Please anybody help, I spent an hour with the entire model implementation and then 3 hours of this hell.
public class GttClient {
public static void main(String[] args) {
Debug.enableLogging();
HttpTransport transport = setUpTransport();
try {
authenticateWithClientLogin(transport);
printResults(executeGet(transport, GttUrl.forDocuments()));
} catch (IOException e) {
e.printStackTrace();
}
}
private static HttpTransport setUpTransport() {
HttpTransport transport = GoogleTransport.create();
GoogleHeaders headers = (GoogleHeaders) transport.defaultHeaders;
headers.setApplicationName("Google-PredictionSample/1.0");
headers.gdataVersion = "2.0";
AtomParser parser = new AtomParser();
parser.namespaceDictionary = Namespace.DICTIONARY;
transport.addParser(parser);
return transport;
}
private static void authenticateWithClientLogin(HttpTransport transport)
throws IOException {
ClientLogin clientLogin = new ClientLogin();
clientLogin.authTokenType = "gtrans";
clientLogin.accountType = "HOSTED_OR_GOOGLE";
clientLogin.username = "user#gmail.com";
clientLogin.password = "password";
clientLogin.authenticate().setAuthorizationHeader(transport);
}
public static Feed executeGet(HttpTransport transport, GttUrl url)
throws IOException {
HttpRequest request = transport.buildGetRequest();
// url.fields = GData.getFieldsFor(Feed.class);
request.url = url;
return request.execute().parseAs(Feed.class);
}
}
public class GttUrl extends GoogleUrl {
static final String ROOT_URL = "https://translate.google.com/toolkit/feeds";
#Key("sharedwith")
public String sharedwith;
#Key("onlydeleted")
public String onlydeleted;
#Key("scope")
public String scope;
public GttUrl(String url) {
super(url);
if (Debug.ENABLED) {
this.prettyprint = true;
}
}
public static GttUrl forRoot() {
return new GttUrl(ROOT_URL);
}
public static GttUrl forDocuments() {
GttUrl result = forRoot();
result.pathParts.add("documents");
return result;
}
public static GttUrl forTranslMemories() {
GttUrl result = forRoot();
result.pathParts.add("tm");
return result;
}
public static GttUrl forGlossaries() {
GttUrl result = forRoot();
result.pathParts.add("glossary");
return result;
}
}
So, I implemented translator toolkit api in an hour and then I got
stuck for 4 hours on clientLogin authorization....
the proper setup of the request is
gdataVersion = "1.0";
and GET request
Unfortunately during the course of trying I had either
1.0 and POST
or
2.0 and GET
It means that gdataVersion = "2"; is working only for APIs for which the "new" client is already implemented...afaik

Categories