openID login (on localhost) at google app engine - google-app-engine

I've written openID login for google App engine.
static {
openIdProviders = new HashMap<String, String>();
openIdProviders.put("Google", "https://www.google.com/accounts/o8/id");
openIdProviders.put("Yahoo", "yahoo.com");
}
#Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser(); // or req.getUserPrincipal()
Set<String> attributes = new HashSet();
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
if (user != null) {
out.println("Hello <i>" + user.getNickname() + "</i>!");
out.println("[sign out]");
} else {
out.println("Sign in at: ");
for (String providerName : openIdProviders.keySet()) {
String providerUrl = openIdProviders.get(providerName);
String loginUrl = userService.createLoginURL(req.getRequestURI(), null, providerUrl, attributes);
out.println("[" + providerName+ "] ");
}
}
}
everything works very well when I deploy my app. No problems.
after choosing openID provider, I'm redirected to that page:
sample.appspot.com/_ah/login_redir?claimid=[OPen ID provider]=[my sample page]/_ah/login_required
that's my servlet.
<servlet>
<servlet-name>Authorization</servlet-name>
<servlet-class>ge.eid.test.Authorization</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Authorization</servlet-name>
<url-pattern>/_ah/login_required</url-pattern>
</servlet-mapping>
ok. everything is fine! but, when I run the sampe app at localhost, I have another redirection URL:
http://localhost:8888/_ah/login?continue=/F_ah/Flogin_required
so I do not have OpenID login. I have something like that:
question 1) How to create openID login at localhost too.

This is a normal behaviour, because on localhost you might want to try many different accounts and it would be a mess if you had to actually login every time with a real one. So when on localhost you can just simulate an OpenID user just by providing any email you want and checking if you want to be an admin or not.

Related

How to authenticate users with Social Login (Facebook, Google) in a REST API backend with Spring Boot (separate React Frontend)

I am currently working on a Spring Boot REST API. I have successfully added login using client credentials with Spring Oauth and Spring Security (I can successfully get access token and refresh token using /oauth/token endpoint).
But now I want to provide social login with Facebook and Google. As I understand, this is the flow.
User clicks Login with Social button in React frontend.
Then, he will be asked to grant access. (Still in React)
After that he will be redirected to the react front end with an access token.
Frontend sends that access token to the Spring Boot backend. (I don't know to what endpoint)
Then backend uses that access token to fetch details from the Facebook/Google and check whether a such user exists in our database.
If such user exists, backend will return access and refresh tokens to the frontend.
Now frontend can consume all the endpoints.
My problem is, I have no idea about the steps 4,5 and 6.
Do I have to make a custom endpoint to receive FB/Google access tokens?
How do I issue custom access and refresh tokens in Spring Boot?
I would really appreciate it if you could help me with this scenario.
The flow it's the following:
Front-End calls spring to /oauth2/authorization/facebook(or whatever client do you wanna use)
Back-end respond with a redirect to Facebook login page(including in the query params, client_id, scope, redirect_uri(must be the same present on your developer console) and state which is used to avoid XSRF attacks, according to OAuth2 Standards)
you can see more details here https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
state
RECOMMENDED. An opaque value used by the client to maintain
state between the request and callback. The authorization
server includes this value when redirecting the user-agent back
to the client. The parameter SHOULD be used for preventing
cross-site request forgery as described in Section 10.12.
3) Once the user log-in and accept whatever popup facebook or other services will show, the user will be redirected to the page present in "redirect_uri", this page should be a component of your ReactJs. The callback will come with some datas put in the query params, usually those params are two, state(it's the same you sent to facebook) and code(which is used from the BE to end the login flow).
Once facebook or whatever service, called you back, you have to take those 2 params, from the url(using JS for instance) and call the /login/oauth2/code/facebook/?code=CODE_GENERATED_BY_FACEBOOK&?state=STATE_GENERATED_BY_SPRING
Spring will call the facebook service(with an implementation of OAuth2AccessTokenResponseClient, using your secret_token, client_id, code and few other fields. Once facebook responds with the access_token and refresh_token, spring call an implementation of OAuth2UserService, used to get user info from facebook using the access_token created a moment before, at facebook's response a session will be created including the principal. (You can intercept the login success creating an implementation of SimpleUrlAuthenticationSuccessHandlerand adding it to your spring security configuration. (For facebook, google and otka in theory OAuth2AccessTokenResponseClient and OAuth2UserService implementations should already exist.
In that handler you can put the logic to add and look for an existing user.
coming back to the default behavior
Once spring created the new session and gave you the JSESSIONID cookie, it will redirect you to the root (I believe, I don't remember exactly which is the default path after the login, but you can change it, creating your own implementation of the handler I told you before)
Note: access_token and refresh_token will be stored in a OAuth2AuthorizedClient, stored in the ClientRegistrationRepository.
This is the end. From now then you can call your back end with that cookie and the be will see you as a logged user. My suggestion is once you got the simple flow working, you should implement a JWT token to use and store in the localstorage of your browser instead of using the cookie.
Hopefully I gave you the infos you were looking for, if I missed something, misunderstood something or something it's not clear let me know in the comment.
UPDATE (some java samples)
My OAuth2 SecurityConfig :
NOTE:
PROTECTED_URLS it's just : public static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
PUBLIC_URLS it's just: private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher( new AntPathRequestMatcher("/api/v1/login"));
Also note I'm using a dual HttpSecurity configuration. (But in this case it's useless to public that too)
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class OAuth2ClientSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JWTService jwtService;
private final TempUserDataService tempUserDataService;
private final OAuth2AuthorizedClientRepo authorizedClientRepo;
private final OAuth2AuthorizedClientService clientService;
private final UserAuthenticationService authenticationService;
private final SimpleUrlAuthenticationSuccessHandler successHandler; //This is the default one, this bean has been created in another HttpSecurity Configuration file.
private final OAuth2TokenAuthenticationProvider authenticationProvider2;
private final CustomOAuth2AuthorizedClientServiceImpl customOAuth2AuthorizedClientService;
private final TwitchOAuth2UrlAuthSuccessHandler oauth2Filter; //This is the success handler customized.
//In this bean i set the default successHandler and the current AuthManager.
#Bean("oauth2TokenAuthenticaitonFilter")
TokenAuthenticationFilter oatuh2TokenAuthenticationFilter() throws Exception {
TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationSuccessHandler(successHandler);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#PostConstruct
public void setFilterSettings() {
oauth2Filter.setRedirectStrategy(new NoRedirectStrategy());
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider2);
}
#Bean
public RestOperations restOperations() {
return new RestTemplate();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers("/twitch/**").authenticated()
.and().csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable().authenticationProvider(authenticationProvider2)
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.addFilterBefore(oatuh2TokenAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.oauth2Login().successHandler(oauth2Filter).tokenEndpoint()
.accessTokenResponseClient(new RestOAuth2AccessTokenResponseClient(restOperations()))
.and().authorizedClientService(customOAuth2AuthorizedClientService)
.userInfoEndpoint().userService(new RestOAuth2UserService(restOperations(), tempUserDataService, authorizedClientRepo));
}
#Bean
FilterRegistrationBean disableAutoRegistrationOAuth2Filter() throws Exception {
FilterRegistrationBean registration = new FilterRegistrationBean(oatuh2TokenAuthenticationFilter());
registration.setEnabled(false);
return registration;
}
}
By the fact that my SessionCreationPolicy.STATELESS the cookie created by spring after the end of the OAuth2 Flow is useless. So once the process its over I give to the user a TemporaryJWT Token used to access the only possible service (the register service)
My TokenAutheticationFilter:
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String AUTHORIZATION = "Authorization";
private static final String BEARER = "Bearer";
public TokenAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String token = Optional.ofNullable(httpServletRequest.getHeader(AUTHORIZATION))
.map(v -> v.replace(BEARER, "").trim())
.orElseThrow(() -> new BadCredentialsException("Missing authentication token."));
Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
return getAuthenticationManager().authenticate(auth);
}
#Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setStatus(401);
}
}
TwitchOAuth2UrlAuthSuccessHandler (This is where all the magic happens):
This handler is called once the userService and the userService is called when the user calls api.myweb.com/login/oauth2/code/facebook/?code=XXX&state=XXX. (please don't forget the state)
#Component
#RequiredArgsConstructor
public class TwitchOAuth2UrlAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final OAuth2AuthorizedClientRepo oAuth2AuthorizedClientRepo;
private final UserAuthenticationService authenticationService;
private final JWTService jwtService;
private final Gson gson;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
response.setStatus(200);
Cookie cookie = new Cookie("JSESSIONID", null);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
Optional<OAuth2AuthorizedClientEntity> oAuth2AuthorizedClient = oAuth2AuthorizedClientRepo.findById(new OAuth2AuthorizedClientId(((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(), authentication.getName()));
if (oAuth2AuthorizedClient.isPresent() && oAuth2AuthorizedClient.get().getUserDetails() != null) {
response.getWriter().write(gson.toJson(authenticationService.loginWithCryptedPassword(oAuth2AuthorizedClient.get().getUserDetails().getUsername(), oAuth2AuthorizedClient.get().getUserDetails().getPassword())));
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().flush();
} else {
response.setHeader("Authorization", jwtService.createTempToken(((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(), authentication.getName()));
}
}
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
return "";
}
}
RestOAuth2AccessTokenResponseClient (its responsable to take Access_token and refresh_token from FB)
public class RestOAuth2AccessTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
private final RestOperations restOperations;
public RestOAuth2AccessTokenResponseClient(RestOperations restOperations) {
this.restOperations = restOperations;
}
#Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
MultiValueMap<String, String> tokenRequest = new LinkedMultiValueMap<>();
tokenRequest.add("client_id", clientRegistration.getClientId());
tokenRequest.add("client_secret", clientRegistration.getClientSecret());
tokenRequest.add("grant_type", clientRegistration.getAuthorizationGrantType().getValue());
tokenRequest.add("code", authorizationGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode());
tokenRequest.add("redirect_uri", authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getRedirectUri());
tokenRequest.add("scope", String.join(" ", authorizationGrantRequest.getClientRegistration().getScopes()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add(HttpHeaders.USER_AGENT, "Discord Bot 1.0");
ResponseEntity<AccessResponse> responseEntity = restOperations.exchange(tokenUri, HttpMethod.POST, new HttpEntity<>(tokenRequest, headers), AccessResponse.class);
if (!responseEntity.getStatusCode().equals(HttpStatus.OK) || responseEntity.getBody() == null) {
throw new SecurityException("The result of token call returned error or the body returned null.");
}
AccessResponse accessResponse = responseEntity.getBody();
Set<String> scopes = accessResponse.getScopes().isEmpty() ?
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes() : accessResponse.getScopes();
return OAuth2AccessTokenResponse.withToken(accessResponse.getAccessToken())
.tokenType(accessResponse.getTokenType())
.expiresIn(accessResponse.getExpiresIn())
.refreshToken(accessResponse.getRefreshToken())
.scopes(scopes)
.build();
}
}
UserService
public class RestOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final RestOperations restOperations;
private final TempUserDataService tempUserDataService;
private final OAuth2AuthorizedClientRepo authorizedClientRepo;
public RestOAuth2UserService(RestOperations restOperations, TempUserDataService tempUserDataService, OAuth2AuthorizedClientRepo authorizedClientRepo) {
this.restOperations = restOperations;
this.tempUserDataService = tempUserDataService;
this.authorizedClientRepo = authorizedClientRepo;
}
#Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
String userInfoUrl = oAuth2UserRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", oAuth2UserRequest.getAccessToken().getTokenValue()));
headers.add(HttpHeaders.USER_AGENT, "Discord Bot 1.0");
if (oAuth2UserRequest.getClientRegistration().getClientName().equals("OAuth2 Twitch")) {
headers.add("client-id", oAuth2UserRequest.getClientRegistration().getClientId());
}
ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<Map<String, Object>>() {
};
ResponseEntity<Map<String, Object>> responseEntity = restOperations.exchange(userInfoUrl, HttpMethod.GET, new HttpEntity<>(headers), typeReference);
if (!responseEntity.getStatusCode().equals(HttpStatus.OK) || responseEntity.getBody() == null) {
throw new SecurityException("The result of token call returned error or the body returned null.");
}
Map<String, Object> userAttributes = responseEntity.getBody();
userAttributes = LinkedHashMap.class.cast(((ArrayList) userAttributes.get("data")).get(0));
OAuth2AuthorizedClientId clientId = new OAuth2AuthorizedClientId(oAuth2UserRequest.getClientRegistration().getRegistrationId(), String.valueOf(userAttributes.get("id")));
Optional<OAuth2AuthorizedClientEntity> clientEntity = this.authorizedClientRepo.findById(clientId);
if (!clientEntity.isPresent() || clientEntity.get().getUserDetails() == null) {
TempUserData tempUserData = new TempUserData();
tempUserData.setClientId(clientId);
tempUserData.setUsername(String.valueOf(userAttributes.get("login")));
tempUserData.setEmail(String.valueOf(userAttributes.get("email")));
tempUserDataService.save(tempUserData);
}
Set<GrantedAuthority> authorities = Collections.singleton(new OAuth2UserAuthority(userAttributes));
return new DefaultOAuth2User(authorities, userAttributes, oAuth2UserRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName());
}
As asked this is all the code you need, just to give you another hint. When you call /login/oauth2/code/facebook/?code=XXX&?state=XXX the chain is the following:
RestOAuth2AccessTokenResponseClient
RestOAuth2UserService
TwitchOAuth2UrlAuthSuccessHandler
I hope this can help you. Let me know if you need more explainations.

Proper way to post to a servlet on Google App Engine from a Java client application

I have deployed an to App Engine. I setup SSL, and associated it with a custom domain. When I was developing the app locally, sending to a servlet via http://localhost:8080/servlet, worked as expected, but when I deploy it to App Engine, I have yet to get the appropriate result. I've tried many things, and the response codes I keep getting are either 404 or 500.
I startied with a simple HTTPUrlConnection and DataOutputstream to send a JSON to the servlet, and get an appropriate response. Like so:
URL url;
HttpURLConnection connection = null;
try {
url = new URL(targetURL);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("custom-Header", "XYZ");
connection.setRequestProperty("Content-Length", Integer.toString(urlParameters.length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
//Send request
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
wr.write(urlParameters);
wr.flush ();
wr.close ();
//Get Response
BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuffer response = new StringBuffer();
while((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
return response.toString();
}
finally {
if(connection != null) {
connection.disconnect();
}
}
This works locally.
I've now tried Apache Common's HttpAsyncClient, to check if it maybe a timing issue:
final ResponseObject responseObject = new ResponseObject(); //my simple POJO
try(CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom()
.setSSLStrategy(sslSessionStrategy)
.build()) {
httpclient.start();
HttpPost post = new HttpPost(url);
post.setHeader("Content-Type", "application/json");
post.setHeader("custom-Header", "XYZ");
post.setHeader("Content-Language", "en-US");
HttpEntity entity = new ByteArrayEntity(urlParameters);
post.setEntity(entity);
final CountDownLatch latch1 = new CountDownLatch(1);
httpclient.execute(post, new FutureCallback<HttpResponse>() {
public void completed(final HttpResponse response) {
int status = response.getStatusLine().getStatusCode();
if (status >= 200 && status < 300) {
HttpEntity entity = response.getEntity();
try {
responseObject.message = entity != null ? EntityUtils.toString(entity) : null;
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
responseObject.exception = new ClientProtocolException("Unexpected response status: " + status);
}
latch1.countDown();
}
public void failed(final Exception ex) {
responseObject.exception = ex;
latch1.countDown();
}
public void cancelled() {
latch1.countDown();
}
});
latch1.await();
if(responseObject.exception != null) {
throw responseObject.exception;
} else {
return responseObject.message;
}
}
This also works locally, but when trying to reach AppEngine, still no go.
Here's my simple web.xml:
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>my.servlet.package.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>everything</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<welcome-file-list>
<welcome-file>home.jsp</welcome-file>
</welcome-file-list>
Locally, I post to http://localhost:8080/login. As for App Engine, I posted to the ff:
https://myapp.appspot.com/login
https://myapp.customdomain.com/login
I've tried changing up the url-pattern. I started with /login, then did login, then explicitly tried the both App Engine and custom domain urls (i.e. myapp.appspot.com/login and myapp.mydomain.com/login). I also tried having an actual jsp or html page to post to, after trying not having an actual page associated with the servlet i.e. login.jsp or login.html.
When I used HttpAsyncClient (my choice due to SSLContext), the best result I got was the HTML of the page associated with the servlet, but never the response I need from the Servlet.
Any ideas?
Found the answer on one of Google Cloud Platform's scattered docs:
https://cloud.google.com/appengine/docs/standard/java/how-requests-are-handled
https://cloud.google.com/appengine/docs/standard/java/how-requests-are-routed
Basically, you'll have to prepend your project's appspot url e.g. myapp.appspot.com, with the version ID of any actively serving instance of your app. You can find such IDs under the Versions page of your App Engine console. For example: https://versionId.myapp.appspot.com.

Google Plus credentials for application Login

I am creating an app and I want to confirm user using the Google credentials in Java environment. It can be done using google API but I am not sure how to code it as a servlet.
I found a code snippet to authorize the credentials but the AuthorizationCodeInstalledApp() is throwing an error and I am not sure which api to use.
private static Credential authorize() throws Exception {
// load client secrets
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
new InputStreamReader(Test.class.getResourceAsStream("/client_secrets.json")));
if (clientSecrets.getDetails().getClientId().startsWith("Enter")
|| clientSecrets.getDetails().getClientSecret().startsWith("Enter ")) {
System.out.println(
"Enter Client ID and Secret from https://code.google.com/apis/console/?api=plus "
+ "into plus-cmdline-sample/src/main/resources/client_secrets.json");
System.exit(1);
}
// set up authorization code flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets,
Collections.singleton(PlusScopes.PLUS_ME)).setDataStoreFactory(
dataStoreFactory).build();
// authorize
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}
Hope someone can help me with this and also let me know the process it'll be great...
If you really are writing a servlet to run under Google AppEngine, it is much much easier than that.
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
...
#Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
UserService userService = UserServiceFactory.getUserService();
User currentUser = userService.getCurrentUser();
if (currentUser == null) {
resp.sendRedirect(userService.createLoginURL(req.getRequestURI()));
}
else {
// show the view
}
}

GAE LoginAuthorization issue (not directed to WEB-INF)

I have OpenID login in GAE:
private static final Map<String, String> openIdProviders;
static {
openIdProviders = new HashMap<String, String>();
openIdProviders.put("Google", "https://www.google.com/accounts/o8/id");
}
#Override
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
UserService userService = UserServiceFactory.getUserService();
Principal princ = req.getUserPrincipal();
Set<String> attributes = new HashSet<String>();
String provider =req.getParameter("openIdProvider");
for (String providerName : openIdProviders.keySet()) {
String providerUrl = openIdProviders.get(providerName);
if(providerName.equals(provider)){
String loginUrl = userService.createLoginURL("/test/manager.jsp", null, providerUrl, attributes);
resp.sendRedirect(loginUrl);
return;
}
}
}
Everything Works greate!
BUT I need to redirect to WEB-INF/test/manager.jsp
I know that I cant do this without RequestDispatcher.
1 QUESTION when I use RequestDispatcher for loginUrl :
RequestDispatcher dispatch = req.getRequestDispatcher(loginUrl);
dispatch.forward(req, resp);
I have error
java.lang.NullPointerException at com.google.appengine.api.users.dev.LoginCookieUtils.encodeEmailAsUserId(LoginCookieUtils.java:91)
Quesiton 2.
Then I try another sulotuion (redirect to the servlet. in this UserProfilePanel servlet I' will use RequestDispatcher .
String loginUrl = userService.createLoginURL("/UserProfilePanel", null, providerUrl, attributes);
resp.sendRedirect(loginUrl);
But, I have then that error:
Problem accessing /UserProfilePanel. Reason:
Response has already been committed
Caused by:
java.lang.IllegalStateException: Response has already been committed
at com.google.appengine.repackaged.com.google.common.base.Preconditions.checkState(Preconditions.java:153)
I was testing at localhost: I was tedirected to localhost:8888/_ah/login?continue=/FUserProfilePanel when I enter my mail, error of "Response has already been committed".
what can I do? I cant find solutions
in a remember, things in WEB-INF folder couldn't be served like you seems to want to do.
Try to put you test.jsp file somewhere "normal" ( root or in a subfolder of root)

Accessing oauth protected resource on Google App Engine

I'm trying to access an OAuth-protected resource on Google App Engine using a Java/Groovy client. However the authentication is not working and my GET requests are just bringing back the Google Accounts login page HTML.
I get the same results with HTTPBuilder/signpost and with google-oauth-java-client.
Here's what I've done:
Set up an OAuth provider as described in http://ikaisays.com/2011/05/26/setting-up-an-oauth-provider-on-google-app-engine/
Created a 'hello world' servlet (actually a Gaelyk groovlet) mapped to http://<my-app>.appspot.com/rest/hello
Deployed the servlet to gae and confirmed I can GET via a browser.
Added a security constraint to my web.xml and redeployed.
<security-constraint>
<web-resource-collection>
<web-resource-name>Rest</web-resource-name>
<url-pattern>/rest/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
Confirmed that a browser GET requires a Google Accounts login and that after login I can access the servlet.
Did the 3-legged OAuth dance as described in http://groovy.codehaus.org/modules/http-builder/doc/auth.html to get the access and client secret tokens.
Use the tokens in a RESTClient as follows (following instructions in the link above)
def client = new RESTClient('http://<my-app>.appspot.com' )
def consumerKey = <my consumer key>
def consumerSecret = <my consumer secret>
def accessToken = <my access token>
def secretToken = <my secret token>
client.auth.oauth consumerKey, consumerSecret, accessToken, secretToken
def resp = client.get(path:'/rest/hello')
assert resp.data == 'Hello world'
The assert fails since the response is the Google Accounts login page.
I get the same behaviour when using google-oauth-java-client.
I've been through the process above several times, checking for copy/paste errors in the tokens and ensuring that I'm not getting the tokens mixed up.
This is with Groovy 1.8.2, OSX Java 1.6.0_29, HTTPBuilder 0.5.1, gaelyk 1.1.
Any ideas? Thanks.
OK, no response on this so here's how I worked around it.
I gave up on using oauth... google only claim 'experimental' status for this anyway so maybe it fundamentally doesn't work yet.
However I get good results using the ClientLogin protocol from my test client (equivalent to doing a manual login to Google Accounts like the one you do when accessing gmail)
I based this on the extremely useful article http://www.geekyblogger.com/2011/05/using-clientlogin-to-do-authentication.html. I had to extend in a few ways, code below:
import java.io.File;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import com.google.appengine.repackaged.com.google.common.io.Files;
import com.google.cloud.sql.jdbc.internal.Charsets;
public class Login {
public static void main(String[] args) throws Exception {
// This file contains my
// google password. Note that this has to be an app-specific
// password if you use 2-step verification
File passFile = new File("/Users/me/pass.txt");
String pass = Files.toString(passFile, Charsets.UTF_8);
String authCookie = loginToGoogle("myemail#gmail.com", pass,
"http://myapp.appspot.com");
DefaultHttpClient client = new DefaultHttpClient();
// A te
HttpGet get = new HttpGet("http://myapp.appspot.com/rest/blah");
get.setHeader("Cookie", authCookie);
HttpResponse response = client.execute(get);
response.getEntity().writeTo(System.out);
}
public static String loginToGoogle(String userid, String password,
String appUrl) throws Exception {
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(
"https://www.google.com/accounts/ClientLogin");
MultipartEntity reqEntity = new MultipartEntity();
reqEntity.addPart("accountType", new StringBody("HOSTED_OR_GOOGLE",
"text/plain", Charset.forName("UTF-8")));
reqEntity.addPart("Email", new StringBody(userid));
reqEntity.addPart("Passwd", new StringBody(password));
reqEntity.addPart("service", new StringBody("ah"));
reqEntity.addPart("source", new StringBody(
"YourCompany-YourApp-YourVersion"));
post.setEntity(reqEntity);
HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
InputStream input = response.getEntity().getContent();
String result = IOUtils.toString(input);
String authToken = getAuthToken(result);
post = new HttpPost(appUrl + "/_ah/login?auth=" + authToken);
response = client.execute(post);
Header[] cookies = response.getHeaders("SET-COOKIE");
for (Header cookie : cookies) {
if (cookie.getValue().startsWith("ACSID=")) {
return cookie.getValue();
}
}
throw new Exception("ACSID cookie cannot be found");
} else
throw new Exception("Error obtaining ACSID");
}
private static String getAuthToken(String responseText) throws Exception {
LineNumberReader reader = new LineNumberReader(new StringReader(
responseText));
String line = reader.readLine();
while (line != null) {
line = line.trim();
if (line.startsWith("Auth=")) {
return line.substring(5);
}
line = reader.readLine();
}
throw new Exception("Could not find Auth token");
}
}

Resources