From ec03ae7dc060a9c7609c603f49e09711c5456806 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Sun, 29 May 2016 14:52:56 +0800 Subject: [PATCH 01/24] add client-side test cases --- documents/test-cases/client-test-cases.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 documents/test-cases/client-test-cases.md diff --git a/documents/test-cases/client-test-cases.md b/documents/test-cases/client-test-cases.md new file mode 100644 index 0000000..740479f --- /dev/null +++ b/documents/test-cases/client-test-cases.md @@ -0,0 +1,20 @@ +# Client-Side Test Cases + + +## Authentication + +### Registration + +### Local Login + +### Social Login + +* Google +* Facebook + +### Invoke Business + +* Try Protected Resource without Logging in + * Expectation: User redirected to login UI + +* Try Protected Resource after Logging in \ No newline at end of file From c35307bb3193cac1ce34536ad2e3e8e5ae9eb2af Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Tue, 31 May 2016 17:01:01 +0800 Subject: [PATCH 02/24] temp submit --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6f06cbb..56d9a71 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ #Srbj4j -__Srb4j__ (pronounced "/srəb/ for J") is an open-source jax-rs backend code skeleton, with full-fledged authentication support based on OAuth2. +__Srb4j__ (pronounced "/srəb/ for J") is a Java RESTFul backend code skeleton, with common response data structures, __user/access-token persistence__, social login support and __API document generation__. -With __srb4j__ you can quickly launch a restful backend in several minutes. - +It can collaborate with __html clients__, __mobile clients__ and other types of clients such as __desktop applications__. + +__With Srb4j you can launch a restful backend in several minutes.__ + +__Checkout a demo client right away__ at http://srb4jclient.chenjianjx.com:8000/#/ , or download an __Android__ client on https://github.com/chenjianjx/Srb4jAndroidClient, or download a __desktop__ client on https://github.com/chenjianjx/srb4j-desktop-client . # Summary of Features From 0590518310f42e6be11aea743ae5e73145f0b045 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Tue, 31 May 2016 17:31:44 +0800 Subject: [PATCH 03/24] temp submit --- README.md | 273 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 167 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 56d9a71..9f330a4 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,23 @@ #Srbj4j -__Srb4j__ (pronounced "/srəb/ for J") is a Java RESTFul backend code skeleton, with common response data structures, __user/access-token persistence__, social login support and __API document generation__. +__Srb4j__ (pronounced "/srəb/ for J") is a Java RESTFul backend code skeleton, with common response data structures, __user/password/access-token support__, social login and __API document generation__. -It can collaborate with __html clients__, __mobile clients__ and other types of clients such as __desktop applications__. +It can collaborate with __html clients__, __mobile clients__ and other types of clients such as __desktop applications__ at the same time. __With Srb4j you can launch a restful backend in several minutes.__ -__Checkout a demo client right away__ at http://srb4jclient.chenjianjx.com:8000/#/ , or download an __Android__ client on https://github.com/chenjianjx/Srb4jAndroidClient, or download a __desktop__ client on https://github.com/chenjianjx/srb4j-desktop-client . +__Checkout a demo client right away__ at http://srb4jclient.chenjianjx.com:8000/ , or download an __Android__ client on https://github.com/chenjianjx/Srb4jAndroidClient, or download a __desktop__ client on https://github.com/chenjianjx/srb4j-desktop-client . + +__You can also see its out-of-box RESTFul APIs [here](https://srb4jdemo.chenjianjx.com/fo-rest-doc) .__ # Summary of Features -1. Registration/login based on standard OAuth2 with grant_type = 'password' +1. Registration/login based on standard OAuth2 password flow (access tokens, refresh tokens, etc.) 2. Social account login support (Google,Facebook...) 3. Password resetting and random code login -4. [Swagger](http://swagger.io/)-based document generation and client stub generation -5. Popular J2EE Stack: Jersey2 + Spring + MyBatis + MySQL +4. [Swagger](http://swagger.io/)-based API document generation and client stub generation +5. Popular J2EE Stack: JAX-RS + Spring + MyBatis + MySQL 6. Modularized structure design enforcing loose coupling between components 7. An out-of-box back office web portal @@ -24,7 +26,7 @@ __Checkout a demo client right away__ at http://srb4jclient.chenjianjx.com:8000/ 2. Servlet 3.0+ Container such as Tomcat 7 3. MySQL Server -# Quick Start +# Quick Start for Backend Developers ### Generate a Java project @@ -32,7 +34,7 @@ __Checkout a demo client right away__ at http://srb4jclient.chenjianjx.com:8000/ cd /path/to/your/workspace mvn -X archetype:generate \ --DarchetypeGroupId=com.github.chenjianjx -DarchetypeArtifactId=srb4j -DarchetypeVersion=1.1.1 \ +-DarchetypeGroupId=com.github.chenjianjx -DarchetypeArtifactId=srb4j -DarchetypeVersion=1.1.2 \ -DgroupId=your.groupId \ -DartifactId=yourArtifactid \ -Dpackage=your.pkg.name \ @@ -90,120 +92,179 @@ mvn jetty:run -Djetty.port=yourPort Open http://locahost:yourPort in a browser to verify the startup -# What's next - -## Give instructions to your client-side developer - -* Give them the __API doc__. - -Go to http://localhost:8080/fo-rest-doc, download this html file and give it to your client-side counterpart. - -* Show them the __sample code for registration and login__: - -````java - //Registration in srb4j is implemented as OAuth2 login flow with grant_type = password. - //After registration you are automatically logged in. - //This demo uses apache oltu OAuth2 library. +# Quick Start for Client-Side Developers + +## Refer to the API doc + +The API doc has been generated on your backend at https://your-backend/fo-rest-doc + +## Sample Code For HTML/Javascript Developers + +````javascript + //login + $.ajax({ + async: false, + url: "http://localhost:8080/fo/rest/token/new/local", + type: "POST", + contentType: 'application/x-www-form-urlencoded', + data: "grant_type=password&username=chenjianjx@gmail.com&password=abc123", + success: function(data, statusText, xhr){ + console.log(data.access_token); //You can save this token to cookie or LocalStorage + console.log(data.refresh_token); + console.log(data.expires_in); + console.log(data.user_principal); // the full user name + + + }, + error: function(xhr,statusText, e){ + console.log(xhr.status) + var response = $.parseJSON(xhr.responseText); + console.log(response.error); // "the error code" + console.log(response.error_description); // "the error description for developers" + console.log(response.error_description_for_user); // "user-friendly error desc for users" + console.log(response.exception_id); // "the server side developer can use this id to do troubleshooting" + } + }); + + ... + + // call a business web service + $.ajax({ + async: false, + url: "http://localhost:8080/fo/rest/bbs/posts/new", + type: "POST", + contentType: 'application/json', + headers: { + 'Authorization': "Bearer " + accessToken + }, + data: JSON.stringify({content:"my-first-post"}), + success: function(data, statusText, xhr){ + console.log(data); + }, + error: function(xhr,statusText, e){ + console.log(xhr.status); + + if(xhr.status == 400 || xhr.status == 401 || xhr.status == 403){// "token error" + var authHeader = xhr.getResponseHeader("WWW-Authenticate");// "See https://tools.ietf.org/html/rfc6750#page-7" + console.log(authHeader); + //in this case, you can redirect the user to login + } + else if (xhr.status == 460) { // "biz error" + var response = $.parseJSON(xhr.responseText); + console.log(response.error); // "the error code" + console.log(response.error_description); // "the error description for developers" + console.log(response.error_description_for_user); // "user-friendly error desc for users" + console.log(response.exception_id); // "the server side developer can use this id to do troubleshooting" + }else{ + console.log(xhr.responseText); + } + + } + + - OAuthClientRequest request = OAuthClientRequest - .tokenLocation( - "http://localhost:8080/fo/rest/token/new/by-register/local") - .setGrantType(GrantType.PASSWORD).setClientId(null) - .setClientSecret(null) - .setUsername(emailYouAreGoingToRegisterWith) - .setPassword(password).buildBodyMessage(); - - OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); - - OAuthJSONAccessTokenResponse response = oAuthClient.accessToken(request); - - System.out.println("access token:" + response.getAccessToken()); - System.out.println("refresh token:" + response.getRefreshToken()); - System.out.println("expire in:" + response.getExpiresIn()); - System.out.println("the user's principal:" + response.getParam("user_principal")); - -```` - - -````java - //log in. - OAuthClientRequest request = OAuthClientRequest - .tokenLocation("http://localhost:8080/fo/rest/token/new/local") - .setGrantType(GrantType.PASSWORD).setClientId(null) - .setClientSecret(null).setUsername(emailYouRegisterWith) - .setPassword(password).buildBodyMessage(); - OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); - - OAuthJSONAccessTokenResponse response = oAuthClient.accessToken(request); - - System.out.println("access token:" + response.getAccessToken()); - System.out.println("refresh token:" + response.getRefreshToken()); - System.out.println("expire in:" + response.getExpiresIn()); - System.out.println("the user's principal:" + response.getParam("user_principal")); - + }); + + ... + + //logout + $.ajax({ + async: false, + url: "http://localhost:8080/fo/rest/token/delete", + type: "POST", + contentType: 'application/json', + headers: { + 'Authorization': "Bearer " + accessToken + } + }); + + }); + ```` -* Show them the __sample code of invoking business web services__ +Check out more code [here](https://github.com/chenjianjx/srb4j-html-client) . + +## Sample Code For Desktop/Mobile Developers + ````java - Builder restRequest = restClient - .target("http://localhost:8080/fo/rest") - .path("/bbs/posts/new").request(); - restRequest = restRequest.header("Authorization", "Bearer " + token); +import com.mashape.unirest.http.HttpResponse; +import com.mashape.unirest.http.JsonNode; +import com.mashape.unirest.http.Unirest; +... + + // do login + HttpResponse loginResponse = Unirest + .post("http://localhost:8080/fo/rest/token/new/local") + .header("Content-Type", "application/x-www-form-urlencoded") + .field("grant_type", "password") + .field("username", "chenjianjx@gmail.com") + .field("password", "abc123").asJson(); + + if (loginResponse.getStatus() == 200) { + JSONObject token = loginResponse.getBody().getObject(); + System.out.println(token.get("access_token")); //You can save the token for later use + System.out.println(token.get("refresh_token")); + System.out.println(token.get("expires_in")); + System.out.println(token.get("user_principal")); // "the full user name" + } else { + System.out.println(loginResponse.getStatus()); + System.out.println(loginResponse.getStatusText()); + JSONObject error = loginResponse.getBody().getObject(); + System.out.println(error.get("error")); // "the error code" + System.out.println(error.get("error_description")); // "the error description for developers" + System.out.println(error.get("error_description_for_user")); // "user-friendly error desc for users" + System.out.println(error.get("exception_id")); // "the server side developer can use this id to do troubleshooting" + } + ... + + + // call a business web service NewPostRequest bizRequest = new NewPostRequest(); - bizRequest.setContent("Hi, welcome"); - Response restResponse = restRequest.post(Entity.entity(bizRequest, MediaType.APPLICATION_JSON)); - - if (restResponse.getStatus() == 200) { - Post post = restResponse.readEntity(Post.class); - System.out.println("successful. the newly created post is :" + post); + bizRequest.setContent("my-first-post"); + HttpResponse bizResponse = Unirest + .post("http://localhost:8080/fo/rest/bbs/posts/new") + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + accessToken) + .body(toJson(bizRequest)).asString(); + + if (bizResponse.getStatus() == 200) { + Post post = fromJson(bizResponse.getBody(), Post.class); + System.out.println(post); } - if (Arrays.asList(400, 401, 403).contains(restResponse.getStatus())) { - String wwwAuthHeader = restResponse.getHeaderString("www-authenticate"); - Map headerValues = decodeOAuthHeader(wwwAuthHeader); - System.err.println("OAuth2 Error"); - System.err.println(headerValues.get("error")); - System.err.println(headerValues.get("error_description")); - System.err.println(headerValues.get("error_description_for_user")); - System.err.println("exception_id"); + else if (Arrays.asList(400, 401, 403).contains(bizResponse.getStatus())) { // "token error" + String authHeader = bizResponse.getHeaders() + .get("WWW-Authenticate").get(0);// "See https://tools.ietf.org/html/rfc6750#page-7" + System.out.println(bizResponse.getStatus()); + System.out.println(authHeader); //You can also further parse auth header if needed. Search "decodeOAuthHeader" in this repository. + //You should then redirect the user to login UI } - if (460 == restResponse.getStatus()) { - ErrorResult errorResult = restResponse.readEntity(ErrorResult.class); - System.err.println("Biz Error: " + errorResult); + else if (bizResponse.getStatus() == 460) { // "biz error" + JSONObject error = new JSONObject(bizResponse.getBody()); + System.out.println(error.get("error")); // "the error code" + System.out.println(error.get("error_description")); // "the error description for developers" + System.out.println(error.get("error_description_for_user")); // "user-friendly error desc for users" + System.out.println(error.get("exception_id")); // "the server side developer can use this id to do troubleshooting" + } else { + System.out.println(bizResponse.getStatus()); + System.out.println(bizResponse.getBody()); } -```` - -and the code of method decodeOAuthHeader() is + + ... -````java - private static Map decodeOAuthHeader(String header) { - Map headerValues = new HashMap(); - if (header != null) { - Matcher m = Pattern.compile("\\s*(\\w*)\\s+(.*)").matcher(header); - if (m.matches()) { - if ("Bearer".equalsIgnoreCase(m.group(1))) { - for (String nvp : m.group(2).split("\\s*,\\s*")) { - m = Pattern.compile("(\\S*)\\s*\\=\\s*\"([^\"]*)\"") - .matcher(nvp); - if (m.matches()) { - String name = DemoClientUtils.urlDecode(m.group(1)); - String value = DemoClientUtils - .urlDecode(m.group(2)); - headerValues.put(name, value); - } - } - } - } - } - return headerValues; - } + // logout + Unirest.post("http://localhost:8080/fo/rest/bbs/posts/delete") + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + accessToken).asJson(); ```` -Note all client sample code can be found in the generated "yourArtifactId-demo-client" project. +Check out more code [here](https://github.com/chenjianjx/srb4j-desktop-client) or [here](https://github.com/chenjianjx/Srb4jAndroidClient) . + + ## The code organization From ee5b403fdbc6150f8c086b6a453ec582192f0d85 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Tue, 31 May 2016 17:41:16 +0800 Subject: [PATCH 04/24] temp submit --- README.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9f330a4..9ff500a 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,21 @@ import com.mashape.unirest.http.Unirest; Check out more code [here](https://github.com/chenjianjx/srb4j-desktop-client) or [here](https://github.com/chenjianjx/Srb4jAndroidClient) . +# Things the Backend Developers should Know + +## How to create a business module + +A business module called "bbs" is already there to demonstrate how to develop biz logic in srb4j. You can create your own by referring to "bbs" code files: + +1. Table 'Post' in ddl.sql +2. Biz entity and repository(DAO) classes in package 'yourpackage.impl.biz.bbs' +3. App-layer beans and managers in package 'yourpackage.intf.fo.bbs' and 'yourpackage.impl.fo.bbs' +4. RESTFul Resources in package 'yourpackage.webapp.fo.rest.bbs' + + +Note: +1. You can delete package 'yourpackage.impl.biz.bbs' if you don't it any more. +2. For layers and maven artifacts in srb4j, see below. ## The code organization @@ -274,7 +289,7 @@ Check out more code [here](https://github.com/chenjianjx/srb4j-desktop-client) o ![layering](documents/project-organization/layering-ppt/ultimate-layering/Slide2.jpg) - + * And you get these maven projects: @@ -285,17 +300,7 @@ Check out more code [here](https://github.com/chenjianjx/srb4j-desktop-client) o * "intf.bo" depends on "intf.fo" since back office users also need common-user perspectives. * Check full explanation [here](http://www.chenjianjx.com/myblog/entry/layering-in-java-webapps-final) - -## Now create your own business module - -A business module called "bbs" is generated to demonstrate how to develop biz logic in srb4j. You can create your own by referring to "bbs" code files: - -1. Table 'Post' in ddl.sql -2. Biz entity and repository(DAO) classes in package 'yourpackage.impl.biz.bbs' -3. App-layer beans and managers in package 'yourpackage.intf.fo.bbs' and 'yourpackage.impl.fo.bbs' -4. RESTFul Resources in package 'yourpackage.webapp.fo.rest.bbs' - -Note: You can delete package 'yourpackage.impl.biz.bbs' if you don't it any more. +# Social Login Integration (For Backend and Client Developers) # Introduction to the features From fff4a7514ed6f10b7363d0b137cf6fe3dde3e8cb Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Tue, 31 May 2016 18:10:18 +0800 Subject: [PATCH 05/24] temp submit --- README.md | 61 ++++++++++++++++++++----------------------------------- 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 9ff500a..cb61157 100644 --- a/README.md +++ b/README.md @@ -267,7 +267,12 @@ Check out more code [here](https://github.com/chenjianjx/srb4j-desktop-client) o # Things the Backend Developers should Know -## How to create a business module +## User Model + +1. Every user has a "source" property to indicate where this user is from. "local" means the user registers here, "facebook" means the user is created when he logged into the backend with a facebook account. +2. source + email make up of a username, the business key. + +## Create a business module A business module called "bbs" is already there to demonstrate how to develop biz logic in srb4j. You can create your own by referring to "bbs" code files: @@ -302,47 +307,25 @@ Note: # Social Login Integration (For Backend and Client Developers) +## Basic Flow -# Introduction to the features - -## OAuth2-based login flow with grant_type = password - -### User login with username/password -1. Username is email -2. Both registration and login are treated as OAuth2 token request as shown above. - -### Login with Google/Facebook account -Logging in with social account is also an OAuth2 process with grant_type=password. - -#### Login with social sites's tokens (suitable for mobile clients) - -1. Your client logs in with google/facebook sdk and obtain an OpenId token (Google) or an access token -2. Your client then logs in srb4j backend using the social token obtained above, with username = the-social-token and password = anything -3. The srb4j backend will verify the token by calling google/facebook, then create a user account with the returned email if it is a new user, and finally returns a srb4j-hosted access token to the client. -4. The client will then talk to the srb4j backend using srb4j-hosted token. - -For client-side samples, see [this demo](https://github.com/chenjianjx/srb4jfullsample/blob/master/demo-client/src/test/java/com/github/chenjianjx/srb4jfullsample/democlient/fo/rest/auth/DemoClientFoAuthUiMain.java). +1. The client obtains an auth code or an access token/id token from the social website +2. The client then exchanges this code or token with the backend for srb4j's access token +3. The backend will verify the code or token against the social website's server, before it sends an access token to the client -#### Login with social sites's auth codes (suitable for desktop clients) - -1. Your client starts an OAuth2 code flow with the social sites, launch a webview and finally obtain an authoration code. -2. Your client then logs in srb4j backend using the auth code above, with username = code and password = anything -3. The srb4j backend will exchange the code with a token by calling google/facebook, then create a user account with the returned email if it is a new user, and finally returns a srb4j-hosted access token to the client. -4. The client will then talk to the srb4j backend using srb4j-hosted token. - -For client-side samples, see [this demo](https://github.com/chenjianjx/srb4j-desktop-client). - -### Source of user - -Every user has a property called "source" decided by where this user is from, such as "google", "facebook" and "local". "local" means the user is registered on the srb4j backend. - -### Other authentication features -See [FoAuthTokenResource] (https://github.com/chenjianjx/srb4jfullsample/blob/master/webapp/src/main/java/com/github/chenjianjx/srb4jfullsample/webapp/fo/rest/auth/FoAuthTokenResource.java ) for more authentication features, such as token refreshing, password resetting and random code login. - -Note: A refresh token can be used only once. +## Integrate with this or that social site +* [Google + Mobile Client](userguide/social-integration/google_mobile.md) +* [Google + Html Client](userguide/social-integration/google_mobile.md) +* [Google + Desktop Client](userguide/social-integration/google_mobile.md) +* [Facebook + Mobile Client](userguide/social-integration/google_mobile.md) +* [Facebook + Html Client](userguide/social-integration/google_mobile.md) +* [Facebook + Desktop Client](userguide/social-integration/google_mobile.md) +* ... +* [Integrate a new social site](userguide/social-integration/google_mobile.md) + -## API documentation and client support +# API documentation and client support Thanks to [swagger](http://swagger.io/), you can have a WSDL-like API document of your restful web services with [swagger-ui](https://github.com/swagger-api/swagger-ui), generate client stubs[swagger-codegen](https://github.com/swagger-api/swagger-codegen) and test the services with a web-ui[swagger-ui](https://github.com/swagger-api/swagger-ui). @@ -355,7 +338,7 @@ It may be vulnerable to expose the API doc or testing-web-ui in a PROD system. Y enableSwagger=false ```` -## The back office code +# The back office code The back office code is just a way to demonstrate how a back office web portal can interact with the app layer. It enforces the code organization so that back-office code is separated from front end code. From 16ecf9d7b96f721b99050d93b5fd9f3115542b04 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Tue, 31 May 2016 18:25:15 +0800 Subject: [PATCH 06/24] temp submit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index cb61157..9a8ac25 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,9 @@ __You can also see its out-of-box RESTFul APIs [here](https://srb4jdemo.chenjian 2. Servlet 3.0+ Container such as Tomcat 7 3. MySQL Server + + + # Quick Start for Backend Developers ### Generate a Java project From 985fe3afb8c13e2f74ed13ece69fdd9622af5e8a Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 13:07:41 +0800 Subject: [PATCH 07/24] temp submit --- README.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a8ac25..097fb91 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,36 @@ __Checkout a demo client right away__ at http://srb4jclient.chenjianjx.com:8000/ __You can also see its out-of-box RESTFul APIs [here](https://srb4jdemo.chenjianjx.com/fo-rest-doc) .__ + +Table of Contents +================= + + * [Srbj4j](#srbj4j) + * [Table of Contents](#table-of-contents) + * [Summary of Features](#summary-of-features) + * [Prerequisites](#prerequisites) + * [Quick Start for Backend Developers](#quick-start-for-backend-developers) + * [Generate a Java project](#generate-a-java-project) + * [Create a MySQL database and its tables](#create-a-mysql-database-and-its-tables) + * [Setup Env\-specific properties](#setup-env-specific-properties) + * [Build the Java project](#build-the-java-project) + * [Verify the installation](#verify-the-installation) + * [Quick Start for Client\-Side Developers](#quick-start-for-client-side-developers) + * [Refer to the API doc](#refer-to-the-api-doc) + * [Sample Code For HTML and Javascript Developers](#sample-code-for-html-and-javascript-developers) + * [Sample Code For Desktop and Mobile Developers](#sample-code-for-desktop-and-mobile-developers) + * [Things the Backend Developers should Know](#things-the-backend-developers-should-know) + * [User Model](#user-model) + * [Create a business module](#create-a-business-module) + * [The code organization](#the-code-organization) + * [Social Login Integration (For Backend and Client Developers)](#social-login-integration-for-backend-and-client-developers) + * [Basic Flow](#basic-flow) + * [Integrate with this or that social site](#integrate-with-this-or-that-social-site) + * [API documentation and client support](#api-documentation-and-client-support) + * [The back office code](#the-back-office-code) + + + # Summary of Features 1. Registration/login based on standard OAuth2 password flow (access tokens, refresh tokens, etc.) @@ -101,7 +131,7 @@ Open http://locahost:yourPort in a browser to verify the startup The API doc has been generated on your backend at https://your-backend/fo-rest-doc -## Sample Code For HTML/Javascript Developers +## Sample Code For HTML and Javascript Developers ````javascript //login @@ -188,7 +218,7 @@ The API doc has been generated on your backend at https://your-backend/fo-rest-d Check out more code [here](https://github.com/chenjianjx/srb4j-html-client) . -## Sample Code For Desktop/Mobile Developers +## Sample Code For Desktop and Mobile Developers ````java @@ -349,3 +379,8 @@ If you don't need the back office web portal, please delete the following conten * BoAllInOneServlet.java * Its occurrence in web.xml + + + + + From f75999d209b35b9fd42375ce23374fa94c7153ef Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 13:12:04 +0800 Subject: [PATCH 08/24] temp submit --- README.md | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 097fb91..1de8301 100644 --- a/README.md +++ b/README.md @@ -14,31 +14,13 @@ __You can also see its out-of-box RESTFul APIs [here](https://srb4jdemo.chenjian Table of Contents ================= - - * [Srbj4j](#srbj4j) - * [Table of Contents](#table-of-contents) - * [Summary of Features](#summary-of-features) * [Prerequisites](#prerequisites) * [Quick Start for Backend Developers](#quick-start-for-backend-developers) - * [Generate a Java project](#generate-a-java-project) - * [Create a MySQL database and its tables](#create-a-mysql-database-and-its-tables) - * [Setup Env\-specific properties](#setup-env-specific-properties) - * [Build the Java project](#build-the-java-project) - * [Verify the installation](#verify-the-installation) * [Quick Start for Client\-Side Developers](#quick-start-for-client-side-developers) - * [Refer to the API doc](#refer-to-the-api-doc) - * [Sample Code For HTML and Javascript Developers](#sample-code-for-html-and-javascript-developers) - * [Sample Code For Desktop and Mobile Developers](#sample-code-for-desktop-and-mobile-developers) * [Things the Backend Developers should Know](#things-the-backend-developers-should-know) - * [User Model](#user-model) - * [Create a business module](#create-a-business-module) - * [The code organization](#the-code-organization) * [Social Login Integration (For Backend and Client Developers)](#social-login-integration-for-backend-and-client-developers) - * [Basic Flow](#basic-flow) - * [Integrate with this or that social site](#integrate-with-this-or-that-social-site) - * [API documentation and client support](#api-documentation-and-client-support) - * [The back office code](#the-back-office-code) - + * [API Documentation and Client Stub Generation](#api-documentation-and-client-stub-generation) + * [The Back Office](#the-back-office) # Summary of Features @@ -358,7 +340,7 @@ Note: * [Integrate a new social site](userguide/social-integration/google_mobile.md) -# API documentation and client support +# API Documentation and Client Stub Generation Thanks to [swagger](http://swagger.io/), you can have a WSDL-like API document of your restful web services with [swagger-ui](https://github.com/swagger-api/swagger-ui), generate client stubs[swagger-codegen](https://github.com/swagger-api/swagger-codegen) and test the services with a web-ui[swagger-ui](https://github.com/swagger-api/swagger-ui). @@ -371,16 +353,11 @@ It may be vulnerable to expose the API doc or testing-web-ui in a PROD system. Y enableSwagger=false ```` -# The back office code +# The Back Office The back office code is just a way to demonstrate how a back office web portal can interact with the app layer. It enforces the code organization so that back-office code is separated from front end code. If you don't need the back office web portal, please delete the following content: * BoAllInOneServlet.java -* Its occurrence in web.xml - - - - - +* Its occurrence in web.xml \ No newline at end of file From 3f9cd398efa9383705f3888b250e1d2ff1ae4541 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 13:15:31 +0800 Subject: [PATCH 09/24] temp --- README.md | 13 ++++++------- .../userguide/social-integration/google_html.md | 3 +++ 2 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 documents/userguide/social-integration/google_html.md diff --git a/README.md b/README.md index 1de8301..f2869d8 100644 --- a/README.md +++ b/README.md @@ -330,15 +330,14 @@ Note: ## Integrate with this or that social site +* [Google + Html Client](userguide/social-integration/google_html.md) * [Google + Mobile Client](userguide/social-integration/google_mobile.md) -* [Google + Html Client](userguide/social-integration/google_mobile.md) -* [Google + Desktop Client](userguide/social-integration/google_mobile.md) -* [Facebook + Mobile Client](userguide/social-integration/google_mobile.md) -* [Facebook + Html Client](userguide/social-integration/google_mobile.md) -* [Facebook + Desktop Client](userguide/social-integration/google_mobile.md) +* [Google + Desktop Client](userguide/social-integration/google_desktop.md) +* [Facebook + Html Client](userguide/social-integration/facebook_html.md) +* [Facebook + Mobile Client](userguide/social-integration/facebook_mobile.md) +* [Facebook + Desktop Client](userguide/social-integration/facebook_desktop.md) * ... -* [Integrate a new social site](userguide/social-integration/google_mobile.md) - +* [Integrate a new social site](userguide/social-integration/new_site.md) # API Documentation and Client Stub Generation diff --git a/documents/userguide/social-integration/google_html.md b/documents/userguide/social-integration/google_html.md new file mode 100644 index 0000000..fd3e650 --- /dev/null +++ b/documents/userguide/social-integration/google_html.md @@ -0,0 +1,3 @@ +# Overview + +# Come o \ No newline at end of file From 6c428f68ca7e5879bcdde39fd79f980897a59e49 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 14:01:46 +0800 Subject: [PATCH 10/24] google html + mobile + doc --- README.md | 14 ++++++------- .../social-integration/google_html.md | 9 ++++++-- .../social-integration/google_mobile.md | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 documents/userguide/social-integration/google_mobile.md diff --git a/README.md b/README.md index f2869d8..63c3273 100644 --- a/README.md +++ b/README.md @@ -330,14 +330,14 @@ Note: ## Integrate with this or that social site -* [Google + Html Client](userguide/social-integration/google_html.md) -* [Google + Mobile Client](userguide/social-integration/google_mobile.md) -* [Google + Desktop Client](userguide/social-integration/google_desktop.md) -* [Facebook + Html Client](userguide/social-integration/facebook_html.md) -* [Facebook + Mobile Client](userguide/social-integration/facebook_mobile.md) -* [Facebook + Desktop Client](userguide/social-integration/facebook_desktop.md) +* [Google + Html Client](documents/social-integration/google_html.md) +* [Google + Mobile Client](documents/social-integration/google_mobile.md) +* [Google + Desktop Client](documents/social-integration/google_desktop.md) +* [Facebook + Html Client](documents/social-integration/facebook_html.md) +* [Facebook + Mobile Client](documents/social-integration/facebook_mobile.md) +* [Facebook + Desktop Client](documents/social-integration/facebook_desktop.md) * ... -* [Integrate a new social site](userguide/social-integration/new_site.md) +* [Integrate a new social site](documents/social-integration/new_site.md) # API Documentation and Client Stub Generation diff --git a/documents/userguide/social-integration/google_html.md b/documents/userguide/social-integration/google_html.md index fd3e650..d01a783 100644 --- a/documents/userguide/social-integration/google_html.md +++ b/documents/userguide/social-integration/google_html.md @@ -1,3 +1,8 @@ -# Overview +# Google Login for Html Clients -# Come o \ No newline at end of file +You will obtain Google's OAuth2 code according to [Google Sign-in's Server Side Flow](https://developers.google.com/identity/sign-in/web/server-side-flow#implementing_the_one-time-code_flow) and send it to http://your-backend/token/new/social/by-auth-code/google/web + +# Notes + +* You can refer to [index.html](https://github.com/chenjianjx/srb4j-html-client/blob/master/index.html) and [SocialLoginGoogleController](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) for client-side code +* [getEmailFromToken](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoGoogleAuthHelper.java) is the server-side code. You don't have to change it, but you will set "googleWebClientId" and "googleWebClientSecret" on app.properties. \ No newline at end of file diff --git a/documents/userguide/social-integration/google_mobile.md b/documents/userguide/social-integration/google_mobile.md new file mode 100644 index 0000000..0675e91 --- /dev/null +++ b/documents/userguide/social-integration/google_mobile.md @@ -0,0 +1,21 @@ +# Google Login for Mobile Clients + +You will integrate Google's Android/IOS SDK, get the id token after user has signed-in, and send the token to http://your-backend/token/new/social/by-token/google/mobile + + +# Steps for Android +1. [Sign your APP](https://developer.android.com/studio/publish/app-signing.html) first. You will get a key store file. +2. run ````keytool -exportcert -keystore the-keys-store.jks -list -v```` to get the sha-1 fingerprint +3. [Get the configuration file](https://developers.google.com/identity/sign-in/android/start-integrating#get-config). You will input the sha-1 fingerprint obtained during previous steps. +4. [Go on with the integration] (https://developers.google.com/identity/sign-in/android/start-integrating) + * You can use a simple button instead of the button provided by the SDK. See [google_sign_in_button](https://github.com/chenjianjx/Srb4jAndroidClient/blob/master/app/src/main/res/layout/content_dashboard.xml) + * See [DashboardActivity](https://github.com/chenjianjx/Srb4jAndroidClient/blob/master/app/src/main/java/org/srb4j/androidclient/DashboardActivity.java) for front end sample code +6. Backend code is [getEmailFromToken](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoGoogleAuthHelper.java). You don't have to make any change, but you will set "googleWebClientId" and "googleWebClientSecret" on app.properties.(Yes, we use web client id on the back end even it's for mobile sign-in, as Google says so). + + +# Steps for IOS + +It's similar with the Android one. Can anybody help write the steps for IOS ? + + + \ No newline at end of file From 98539cc23bc0be1f986a6d8cad3e0543bde689fe Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 14:22:57 +0800 Subject: [PATCH 11/24] google desktop integration --- .../userguide/social-integration/google_desktop.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 documents/userguide/social-integration/google_desktop.md diff --git a/documents/userguide/social-integration/google_desktop.md b/documents/userguide/social-integration/google_desktop.md new file mode 100644 index 0000000..c572336 --- /dev/null +++ b/documents/userguide/social-integration/google_desktop.md @@ -0,0 +1,13 @@ +# Google Login for Desktop Clients + +Note: A Desktop is a system that doesn't have google's SDK support and is able to launch a web view, Java GUI, .NET GUI etc. + +You will follow [Google's Installed Applications](https://developers.google.com/identity/protocols/OAuth2InstalledApp) flow, get the auth code and and send it to http://your-backend/token/new/social/by-auth-code/google/desktop . + + +# Steps +1. [Create Google Client ID](https://console.developers.google.com/apis/credentials). Pick "Other" on "Application type" selection. +2. See [googleCodeLoginBtn.addActionListener()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/MainForm.java). You will construct the google auth url, launch it with a web view inside your client application. +3. See [WebEngineChangeListener.changed()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/auth/SocialLoginBrowser.java). You will monitor the browser's URL until you get the auth code, and then send the code to srb4j's back end. +4. The back end's code is [getEmailFromCode](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoGoogleAuthHelper.java). You don't need to change it, but you will set "googleClientId" and "googleClientSecret" on app.properties. + \ No newline at end of file From 941808819061b1d9e4bbd66a6df0bff5fa843f26 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 15:33:58 +0800 Subject: [PATCH 12/24] facebook + html --- .../userguide/social-integration/facebook_html.md | 12 ++++++++++++ .../userguide/social-integration/google_html.md | 2 +- .../userguide/social-integration/google_mobile.md | 7 +++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 documents/userguide/social-integration/facebook_html.md diff --git a/documents/userguide/social-integration/facebook_html.md b/documents/userguide/social-integration/facebook_html.md new file mode 100644 index 0000000..72d58ed --- /dev/null +++ b/documents/userguide/social-integration/facebook_html.md @@ -0,0 +1,12 @@ +# Facebook Login for Html Clients + +You will obtain Facebook's OAuth2 code according to [Manually Build a Login Flow](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow) and send it to http://your-backend/token/new/social/by-auth-code/facebook/web + +# Steps + +* [Create a facebook APP](https://developers.facebook.com/apps/) if you haven't got one. +* Follow [the manual flow](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow), and check out our sample code. + * Make a [button](https://github.com/chenjianjx/srb4j-html-client/blob/master/dashboard.html) + * See [$scope.facebookLogin](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) to go to facebook dialog page. You will create a facebook url inside your html client, and add it to "Valid OAuth redirect URIs" on [facebook's app center](https://developers.facebook.com/apps/). + * After the user has signed in, he will land onto the redirected URL inside your html client. Extract the code from the redirected URL and send it to srb4j's backend. See [SocialLoginByFbCodeController](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) +* [getEmailFromCode](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoFacebookAuthHelper.java) is the server-side code. You don't have to change it, but you will set "facebookClientId" and "facebookClientId" on app.properties. \ No newline at end of file diff --git a/documents/userguide/social-integration/google_html.md b/documents/userguide/social-integration/google_html.md index d01a783..54ee243 100644 --- a/documents/userguide/social-integration/google_html.md +++ b/documents/userguide/social-integration/google_html.md @@ -5,4 +5,4 @@ You will obtain Google's OAuth2 code according to [Google Sign-in's Server Side # Notes * You can refer to [index.html](https://github.com/chenjianjx/srb4j-html-client/blob/master/index.html) and [SocialLoginGoogleController](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) for client-side code -* [getEmailFromToken](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoGoogleAuthHelper.java) is the server-side code. You don't have to change it, but you will set "googleWebClientId" and "googleWebClientSecret" on app.properties. \ No newline at end of file +* [getEmailFromCode](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoGoogleAuthHelper.java) is the server-side code. You don't have to change it, but you will set "googleWebClientId" and "googleWebClientSecret" on app.properties. \ No newline at end of file diff --git a/documents/userguide/social-integration/google_mobile.md b/documents/userguide/social-integration/google_mobile.md index 0675e91..ec9347a 100644 --- a/documents/userguide/social-integration/google_mobile.md +++ b/documents/userguide/social-integration/google_mobile.md @@ -13,6 +13,13 @@ You will integrate Google's Android/IOS SDK, get the id token after user has sig 6. Backend code is [getEmailFromToken](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoGoogleAuthHelper.java). You don't have to make any change, but you will set "googleWebClientId" and "googleWebClientSecret" on app.properties.(Yes, we use web client id on the back end even it's for mobile sign-in, as Google says so). +## Not Working during Development? +The previous setup if for the release-version of your Android APP. You will encounter "unknown status code: 12501" (or 12500) errors during development, because you are running a debug version. + +To get your debug version running, +1. Find out the sha-1 fingerprint for debug. ````keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore````, input “android” as the password. +2. Go to the google developer’s console, create another clientId under the same project, input the debug fingerprint for it, and update the client-id and fingerprint on the google-services.json accordingly. See the comments in the [sample code](https://github.com/chenjianjx/Srb4jAndroidClient/blob/master/app/google-services.json). + # Steps for IOS It's similar with the Android one. Can anybody help write the steps for IOS ? From 725009200614eb1f31420c7f15aca46ccd33d05d Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 15:42:59 +0800 Subject: [PATCH 13/24] some more --- .../social-integration/facebook_desktop.md | 16 ++++++++++++++++ .../social-integration/google_desktop.md | 6 +++--- 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 documents/userguide/social-integration/facebook_desktop.md diff --git a/documents/userguide/social-integration/facebook_desktop.md b/documents/userguide/social-integration/facebook_desktop.md new file mode 100644 index 0000000..3c933fb --- /dev/null +++ b/documents/userguide/social-integration/facebook_desktop.md @@ -0,0 +1,16 @@ +# Facebook Login for Desktop Clients + +Note: A Desktop is a system that doesn't have facebook's SDK support and is able to launch a web view, such as Java GUI, .NET GUI and so on. + +You will obtain Facebook's OAuth2 code according to [Manually Build a Login Flow](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow) and send it to http://your-backend/token/new/social/by-auth-code/facebook/desktop . + + +# Steps +* [Create a facebook APP](https://developers.facebook.com/apps/) if you haven't got one. +* Add "https://www.facebook.com/connect/login_success.html" to the APP's "Valid OAuth redirect URIs". +* Follow [the manual flow](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow), and check out our sample code. + * Make a simple button + * See [facebookCodeLoginBtn.addActionListener()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/MainForm.java). You will construct the facebook auth url, launch it with a web view inside your client application. + * After the user has signed in, he will land onto the redirected URL inside your html client. Extract the code from the redirected URL and send it to srb4j's backend. See [SocialLoginByFbCodeController](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) +* [getEmailFromCode](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoFacebookAuthHelper.java) is the server-side code. You don't have to change it, but you will set "facebookClientId" and "facebookClientId" on app.properties. + \ No newline at end of file diff --git a/documents/userguide/social-integration/google_desktop.md b/documents/userguide/social-integration/google_desktop.md index c572336..ff36bea 100644 --- a/documents/userguide/social-integration/google_desktop.md +++ b/documents/userguide/social-integration/google_desktop.md @@ -1,13 +1,13 @@ # Google Login for Desktop Clients -Note: A Desktop is a system that doesn't have google's SDK support and is able to launch a web view, Java GUI, .NET GUI etc. +Note: A Desktop is a system that doesn't have google's SDK support and is able to launch a web view, such as Java GUI, .NET GUI and so on. You will follow [Google's Installed Applications](https://developers.google.com/identity/protocols/OAuth2InstalledApp) flow, get the auth code and and send it to http://your-backend/token/new/social/by-auth-code/google/desktop . # Steps 1. [Create Google Client ID](https://console.developers.google.com/apis/credentials). Pick "Other" on "Application type" selection. -2. See [googleCodeLoginBtn.addActionListener()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/MainForm.java). You will construct the google auth url, launch it with a web view inside your client application. +2. See [googleCodeLoginBtn.addActionListener()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/MainForm.java). You will construct the facebook auth url, launch it with a web view inside your client application. 3. See [WebEngineChangeListener.changed()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/auth/SocialLoginBrowser.java). You will monitor the browser's URL until you get the auth code, and then send the code to srb4j's back end. -4. The back end's code is [getEmailFromCode](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoGoogleAuthHelper.java). You don't need to change it, but you will set "googleClientId" and "googleClientSecret" on app.properties. +4. The back end's code is [getEmailFromCode](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoFacebookAuthHelper.java). You don't need to change it, but you will set "googleClientId" and "googleClientSecret" on app.properties. \ No newline at end of file From cf1aba18e4fbfa392abdb7bdadf181ec41fded58 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 15:54:57 +0800 Subject: [PATCH 14/24] all doc roughly done --- .../social-integration/facebook_mobile.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 documents/userguide/social-integration/facebook_mobile.md diff --git a/documents/userguide/social-integration/facebook_mobile.md b/documents/userguide/social-integration/facebook_mobile.md new file mode 100644 index 0000000..9e57f42 --- /dev/null +++ b/documents/userguide/social-integration/facebook_mobile.md @@ -0,0 +1,19 @@ +# Facebook Login for Mobile Clients + +You will integrate Google's Android/IOS SDK, get the access token after user has signed-in, and send the token to http://your-backend/token/new/social/by-token/google/mobile + + +# Steps for Android +1. Integrate Facebook SDK according to https://developers.facebook.com/quickstarts/ and https://developers.facebook.com/docs/android/getting-started/ +2. Integrate facebook sign-in according to [this tutorial](http://code.tutsplus.com/tutorials/quick-tip-add-facebook-login-to-your-android-app--cms-23837) and our sample code: + * You can use a simple button instead of the button provided by the SDK. See [facebook_sign_in_button](https://github.com/chenjianjx/Srb4jAndroidClient/blob/master/app/src/main/res/layout/content_dashboard.xml) + * See [DashboardActivity](https://github.com/chenjianjx/Srb4jAndroidClient/blob/master/app/src/main/java/org/srb4j/androidclient/DashboardActivity.java) for front end sample code +3. Backend code is [getEmailFromToken](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoFacebookAuthHelper.java). You don't need to make any change, but you will set "facebookClientId" and "facebookClientId" on app.properties. + + +# Steps for IOS + +It's similar with the Android one. Can anybody help write the steps for IOS ? + + + \ No newline at end of file From b44ef9c0147927c844d163c3cf140ba139015b1e Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 16:02:18 +0800 Subject: [PATCH 15/24] clean typos --- documents/userguide/social-integration/google_desktop.md | 4 ++-- documents/userguide/social-integration/google_html.md | 2 +- documents/userguide/social-integration/google_mobile.md | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/documents/userguide/social-integration/google_desktop.md b/documents/userguide/social-integration/google_desktop.md index ff36bea..01c3d39 100644 --- a/documents/userguide/social-integration/google_desktop.md +++ b/documents/userguide/social-integration/google_desktop.md @@ -2,11 +2,11 @@ Note: A Desktop is a system that doesn't have google's SDK support and is able to launch a web view, such as Java GUI, .NET GUI and so on. -You will follow [Google's Installed Applications](https://developers.google.com/identity/protocols/OAuth2InstalledApp) flow, get the auth code and and send it to http://your-backend/token/new/social/by-auth-code/google/desktop . +You will follow [Google's Installed Applications](https://developers.google.com/identity/protocols/OAuth2InstalledApp) flow, get the auth code and send it to http://your-backend/token/new/social/by-auth-code/google/desktop . # Steps -1. [Create Google Client ID](https://console.developers.google.com/apis/credentials). Pick "Other" on "Application type" selection. +1. [Create a Google Client ID](https://console.developers.google.com/apis/credentials). Pick "Other" on "Application type" selection. 2. See [googleCodeLoginBtn.addActionListener()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/MainForm.java). You will construct the facebook auth url, launch it with a web view inside your client application. 3. See [WebEngineChangeListener.changed()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/auth/SocialLoginBrowser.java). You will monitor the browser's URL until you get the auth code, and then send the code to srb4j's back end. 4. The back end's code is [getEmailFromCode](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoFacebookAuthHelper.java). You don't need to change it, but you will set "googleClientId" and "googleClientSecret" on app.properties. diff --git a/documents/userguide/social-integration/google_html.md b/documents/userguide/social-integration/google_html.md index 54ee243..5630f24 100644 --- a/documents/userguide/social-integration/google_html.md +++ b/documents/userguide/social-integration/google_html.md @@ -1,6 +1,6 @@ # Google Login for Html Clients -You will obtain Google's OAuth2 code according to [Google Sign-in's Server Side Flow](https://developers.google.com/identity/sign-in/web/server-side-flow#implementing_the_one-time-code_flow) and send it to http://your-backend/token/new/social/by-auth-code/google/web +You will obtain Google's OAuth2 code according to [Google Sign-in's Server Side Flow](https://developers.google.com/identity/sign-in/web/server-side-flow#implementing_the_one-time-code_flow) and send the code to http://your-backend/token/new/social/by-auth-code/google/web # Notes diff --git a/documents/userguide/social-integration/google_mobile.md b/documents/userguide/social-integration/google_mobile.md index ec9347a..2272350 100644 --- a/documents/userguide/social-integration/google_mobile.md +++ b/documents/userguide/social-integration/google_mobile.md @@ -14,11 +14,11 @@ You will integrate Google's Android/IOS SDK, get the id token after user has sig ## Not Working during Development? -The previous setup if for the release-version of your Android APP. You will encounter "unknown status code: 12501" (or 12500) errors during development, because you are running a debug version. +The previous setup is for the release version of your Android APP. You will encounter "unknown status code: 12501" (or 12500) errors during development, because you are running a debug version. To get your debug version running, -1. Find out the sha-1 fingerprint for debug. ````keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore````, input “android” as the password. -2. Go to the google developer’s console, create another clientId under the same project, input the debug fingerprint for it, and update the client-id and fingerprint on the google-services.json accordingly. See the comments in the [sample code](https://github.com/chenjianjx/Srb4jAndroidClient/blob/master/app/google-services.json). +* Find out the sha-1 fingerprint for debug. ````keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore````, input “android” as the password. +* Go to the google developer’s console, create another clientId under the same project, input the debug fingerprint for it, and update the client-id and fingerprint on the google-services.json accordingly. See the comments in the [sample code](https://github.com/chenjianjx/Srb4jAndroidClient/blob/master/app/google-services.json). # Steps for IOS From 54e3a61656b086c3f69c85d332ade83c0704e764 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 16:08:57 +0800 Subject: [PATCH 16/24] social documents done --- documents/userguide/social-integration/facebook_desktop.md | 2 +- documents/userguide/social-integration/facebook_html.md | 4 ++-- documents/userguide/social-integration/facebook_mobile.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documents/userguide/social-integration/facebook_desktop.md b/documents/userguide/social-integration/facebook_desktop.md index 3c933fb..862db3e 100644 --- a/documents/userguide/social-integration/facebook_desktop.md +++ b/documents/userguide/social-integration/facebook_desktop.md @@ -11,6 +11,6 @@ You will obtain Facebook's OAuth2 code according to [Manually Build a Login Flow * Follow [the manual flow](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow), and check out our sample code. * Make a simple button * See [facebookCodeLoginBtn.addActionListener()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/MainForm.java). You will construct the facebook auth url, launch it with a web view inside your client application. - * After the user has signed in, he will land onto the redirected URL inside your html client. Extract the code from the redirected URL and send it to srb4j's backend. See [SocialLoginByFbCodeController](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) + * See [WebEngineChangeListener.changed()](https://github.com/chenjianjx/srb4j-desktop-client/blob/master/src/main/java/org/srb4j/desktopclient/view/auth/SocialLoginBrowser.java). You will monitor the browser's URL until you get the auth code, and then send the code to srb4j's back end. * [getEmailFromCode](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoFacebookAuthHelper.java) is the server-side code. You don't have to change it, but you will set "facebookClientId" and "facebookClientId" on app.properties. \ No newline at end of file diff --git a/documents/userguide/social-integration/facebook_html.md b/documents/userguide/social-integration/facebook_html.md index 72d58ed..70df712 100644 --- a/documents/userguide/social-integration/facebook_html.md +++ b/documents/userguide/social-integration/facebook_html.md @@ -7,6 +7,6 @@ You will obtain Facebook's OAuth2 code according to [Manually Build a Login Flow * [Create a facebook APP](https://developers.facebook.com/apps/) if you haven't got one. * Follow [the manual flow](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow), and check out our sample code. * Make a [button](https://github.com/chenjianjx/srb4j-html-client/blob/master/dashboard.html) - * See [$scope.facebookLogin](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) to go to facebook dialog page. You will create a facebook url inside your html client, and add it to "Valid OAuth redirect URIs" on [facebook's app center](https://developers.facebook.com/apps/). - * After the user has signed in, he will land onto the redirected URL inside your html client. Extract the code from the redirected URL and send it to srb4j's backend. See [SocialLoginByFbCodeController](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) + * See [$scope.facebookLogin()](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) to go to facebook dialog page. You will create a facebook url inside your html client, and add it to "Valid OAuth redirect URIs" on [facebook's app center](https://developers.facebook.com/apps/). + * After the user has signed in, he will land onto the redirected_uri page inside your html client. Extract the code from current URL and send it to srb4j's backend. See [SocialLoginByFbCodeController](https://github.com/chenjianjx/srb4j-html-client/blob/master/home.js) * [getEmailFromCode](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoFacebookAuthHelper.java) is the server-side code. You don't have to change it, but you will set "facebookClientId" and "facebookClientId" on app.properties. \ No newline at end of file diff --git a/documents/userguide/social-integration/facebook_mobile.md b/documents/userguide/social-integration/facebook_mobile.md index 9e57f42..49c38c3 100644 --- a/documents/userguide/social-integration/facebook_mobile.md +++ b/documents/userguide/social-integration/facebook_mobile.md @@ -1,6 +1,6 @@ # Facebook Login for Mobile Clients -You will integrate Google's Android/IOS SDK, get the access token after user has signed-in, and send the token to http://your-backend/token/new/social/by-token/google/mobile +You will integrate Facebook's Android/IOS SDK, get the access token after user has signed-in, and send the token to http://your-backend/token/new/social/by-token/google/mobile # Steps for Android From 6ac01c7ba01a41c989a987a3777092ddaef63c8e Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 16:20:41 +0800 Subject: [PATCH 17/24] typos --- README.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 63c3273..b88e272 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,27 @@ -#Srbj4j +#Srb4j -__Srb4j__ (pronounced "/srəb/ for J") is a Java RESTFul backend code skeleton, with common response data structures, __user/password/access-token support__, social login and __API document generation__. +__Srb4j__ (pronounced "/srəb/ for J") is a Java RESTFul backend code skeleton, with __common response data structures__, __user/password/access-token support__, __social login__ and __API document generation__. It can collaborate with __html clients__, __mobile clients__ and other types of clients such as __desktop applications__ at the same time. __With Srb4j you can launch a restful backend in several minutes.__ -__Checkout a demo client right away__ at http://srb4jclient.chenjianjx.com:8000/ , or download an __Android__ client on https://github.com/chenjianjx/Srb4jAndroidClient, or download a __desktop__ client on https://github.com/chenjianjx/srb4j-desktop-client . +__Checkout a demo client right away__ at http://srb4jclient.chenjianjx.com:8000/ , or install an __Android__ client on https://github.com/chenjianjx/Srb4jAndroidClient, or download a __desktop__ client on https://github.com/chenjianjx/srb4j-desktop-client . __You can also see its out-of-box RESTFul APIs [here](https://srb4jdemo.chenjianjx.com/fo-rest-doc) .__ Table of Contents ================= + * [Summary of Features](#summary-of-features) * [Prerequisites](#prerequisites) * [Quick Start for Backend Developers](#quick-start-for-backend-developers) * [Quick Start for Client\-Side Developers](#quick-start-for-client-side-developers) * [Things the Backend Developers should Know](#things-the-backend-developers-should-know) * [Social Login Integration (For Backend and Client Developers)](#social-login-integration-for-backend-and-client-developers) * [API Documentation and Client Stub Generation](#api-documentation-and-client-stub-generation) - * [The Back Office](#the-back-office) + * [The Back Office](#the-back-office) # Summary of Features @@ -111,7 +112,7 @@ Open http://locahost:yourPort in a browser to verify the startup ## Refer to the API doc -The API doc has been generated on your backend at https://your-backend/fo-rest-doc +The API doc has been generated on your backend at http://your-backend/fo-rest-doc ## Sample Code For HTML and Javascript Developers @@ -304,11 +305,13 @@ Note: ## The code organization * The layers: - * Front End: Encapsulate use case-specific logic from business services. The users are common users such customers. - * Back Office: Encapsulate use case-specific logic from business services. The users are administrators, staff and so on. - + ![layering](documents/project-organization/layering-ppt/ultimate-layering/Slide2.jpg) +Notes: + * Front End: Encapsulate use case-specific logic from business services. The users are common users such customers. + * Back Office: Encapsulate use case-specific logic from business services. The users are administrators, staff and so on. + * And you get these maven projects: @@ -324,20 +327,20 @@ Note: ## Basic Flow -1. The client obtains an auth code or an access token/id token from the social website +1. The client obtains an auth code or an access token/id token from the social website after the user has loggged into a social website 2. The client then exchanges this code or token with the backend for srb4j's access token 3. The backend will verify the code or token against the social website's server, before it sends an access token to the client ## Integrate with this or that social site -* [Google + Html Client](documents/social-integration/google_html.md) -* [Google + Mobile Client](documents/social-integration/google_mobile.md) -* [Google + Desktop Client](documents/social-integration/google_desktop.md) -* [Facebook + Html Client](documents/social-integration/facebook_html.md) -* [Facebook + Mobile Client](documents/social-integration/facebook_mobile.md) -* [Facebook + Desktop Client](documents/social-integration/facebook_desktop.md) +* [Google + Html Client](documents/userguide/social-integration/google_html.md) +* [Google + Mobile Client](documents/userguide/social-integration/google_mobile.md) +* [Google + Desktop Client](documents/userguide/social-integration/google_desktop.md) +* [Facebook + Html Client](documents/userguide/social-integration/facebook_html.md) +* [Facebook + Mobile Client](documents/userguide/social-integration/facebook_mobile.md) +* [Facebook + Desktop Client](documents/userguide/social-integration/facebook_desktop.md) * ... -* [Integrate a new social site](documents/social-integration/new_site.md) +* [Integrate a new social site](documents/userguide/social-integration/new_site.md) # API Documentation and Client Stub Generation From 961cd15d346cc26980aac624b5bab989230be43d Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 16:23:05 +0800 Subject: [PATCH 18/24] typos --- README.md | 2 +- documents/userguide/social-integration/new_site.md | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 documents/userguide/social-integration/new_site.md diff --git a/README.md b/README.md index b88e272..b1cf62f 100644 --- a/README.md +++ b/README.md @@ -344,7 +344,7 @@ Notes: # API Documentation and Client Stub Generation -Thanks to [swagger](http://swagger.io/), you can have a WSDL-like API document of your restful web services with [swagger-ui](https://github.com/swagger-api/swagger-ui), generate client stubs[swagger-codegen](https://github.com/swagger-api/swagger-codegen) and test the services with a web-ui[swagger-ui](https://github.com/swagger-api/swagger-ui). +Thanks to [swagger](http://swagger.io/), you can have a WSDL-like API document of your restful web services with [swagger-ui](https://github.com/swagger-api/swagger-ui), generate client stubs with [swagger-codegen](https://github.com/swagger-api/swagger-codegen) and test the services with a [web-ui](https://github.com/swagger-api/swagger-ui). Srb4j has embedded swagger support. The document endpoint is http://localhost:8080/fo/rest/swagger.json . If you know swagger well, you know what to do. diff --git a/documents/userguide/social-integration/new_site.md b/documents/userguide/social-integration/new_site.md new file mode 100644 index 0000000..e69de29 From e89515baf853de4f6b4f27cd5a1f4d507ca5c3ca Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 16:32:35 +0800 Subject: [PATCH 19/24] new site integration guide --- README.md | 13 ++++++------- documents/userguide/social-integration/new_site.md | 5 +++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b1cf62f..513febf 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,9 @@ Table of Contents * [Quick Start for Backend Developers](#quick-start-for-backend-developers) * [Quick Start for Client\-Side Developers](#quick-start-for-client-side-developers) * [Things the Backend Developers should Know](#things-the-backend-developers-should-know) - * [Social Login Integration (For Backend and Client Developers)](#social-login-integration-for-backend-and-client-developers) - * [API Documentation and Client Stub Generation](#api-documentation-and-client-stub-generation) + * [Social Login Integration](#social-login-integration) + * [API Documentation and Client Stub Generation and Online Testing](#api-documentation-and-client-stub-generation-and-online-testing) * [The Back Office](#the-back-office) - # Summary of Features @@ -323,7 +322,7 @@ Notes: * "intf.bo" depends on "intf.fo" since back office users also need common-user perspectives. * Check full explanation [here](http://www.chenjianjx.com/myblog/entry/layering-in-java-webapps-final) -# Social Login Integration (For Backend and Client Developers) +# Social Login Integration ## Basic Flow @@ -342,13 +341,13 @@ Notes: * ... * [Integrate a new social site](documents/userguide/social-integration/new_site.md) -# API Documentation and Client Stub Generation +# API Documentation and Client Stub Generation and Online Testing Thanks to [swagger](http://swagger.io/), you can have a WSDL-like API document of your restful web services with [swagger-ui](https://github.com/swagger-api/swagger-ui), generate client stubs with [swagger-codegen](https://github.com/swagger-api/swagger-codegen) and test the services with a [web-ui](https://github.com/swagger-api/swagger-ui). -Srb4j has embedded swagger support. The document endpoint is http://localhost:8080/fo/rest/swagger.json . If you know swagger well, you know what to do. +Srb4j has embedded swagger support. The document endpoint is http://your-backend/fo/rest/swagger.json . If you know swagger well, you know what to do. -If you just want to see a download-able API doc, check http://localhost:8080/fo-rest-doc , which is generated by [swagger2html] (https://github.com/chenjianjx/swagger2html). +If you just want to see a download-able API doc, check http://your-backend/fo-rest-doc , which is generated by [swagger2html] (https://github.com/chenjianjx/swagger2html). It may be vulnerable to expose the API doc or testing-web-ui in a PROD system. You can disable swagger (and the API doc) by editing app.properties and restart: ```` diff --git a/documents/userguide/social-integration/new_site.md b/documents/userguide/social-integration/new_site.md index e69de29..8565f0d 100644 --- a/documents/userguide/social-integration/new_site.md +++ b/documents/userguide/social-integration/new_site.md @@ -0,0 +1,5 @@ +# Integrate New Social Sites + +* Do what you have to do according to the social site's guide +* Add a source to [User] class(https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/biz/user/User.java) +* Create a class to handle auth code or acess token. This class must implement [FoSocialSiteAuthHelper](https://github.com/chenjianjx/srb4jfullsample/blob/master/impl/src/main/java/com/github/chenjianjx/srb4jfullsample/impl/fo/auth/socialsite/FoSocialSiteAuthHelper.java) and register the class in FoSocialSiteAuthHelper.Factory \ No newline at end of file From 61701e1c8418ef15aa10e045eb8317ef42b6c73f Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 16:33:37 +0800 Subject: [PATCH 20/24] type --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 513febf..6ac89ba 100644 --- a/README.md +++ b/README.md @@ -307,9 +307,9 @@ Note: ![layering](documents/project-organization/layering-ppt/ultimate-layering/Slide2.jpg) -Notes: - * Front End: Encapsulate use case-specific logic from business services. The users are common users such customers. - * Back Office: Encapsulate use case-specific logic from business services. The users are administrators, staff and so on. +* Notes: + * Front End: Encapsulate use case-specific logic from business services. The users are common users such customers. + * Back Office: Encapsulate use case-specific logic from business services. The users are administrators, staff and so on. From 310ece900478e2c242868a3df84fa270441c44c9 Mon Sep 17 00:00:00 2001 From: "chenjianjx@gmail.com" Date: Wed, 1 Jun 2016 17:32:48 +0800 Subject: [PATCH 21/24] Integrate changes from srb4jfullsample --- README.md | 2 +- .../archetype-resources/demo-client/pom.xml | 15 +- .../bo/portal/DemoClientBoPortalTest.java | 2 +- .../fo/rest/auth/DemoClientFoAuthTest.java | 4 +- .../fo/rest/auth/DemoClientFoAuthUiMain.java | 148 +------------ .../fo/rest/biz/DemoClientFoBizResponse.java | 2 +- .../fo/rest/biz/DemoClientFoBizTest.java | 2 +- .../fo/rest/fodoc/DemoClientForDocTest.java | 122 ++++++++++ .../java/democlient/util/DemoClientUtils.java | 21 +- .../doc/app.properties.sample | 13 +- .../src/main/java/impl/biz/client/Client.java | 24 ++ .../java/impl/fo/auth/FoAuthManagerImpl.java | 208 ++---------------- .../auth/socialsite/FoFacebookAuthHelper.java | 107 +++++++++ .../auth/socialsite/FoGoogleAuthHelper.java | 192 ++++++++++++++++ .../socialsite/FoSocialSiteAuthHelper.java | 64 ++++++ .../fo/auth/FoSocialAuthCodeLoginRequest.java | 22 +- .../fo/auth/FoSocialLoginByTokenRequest.java | 12 +- .../main/java/intf/fo/basic/FoConstants.java | 16 +- .../resources/archetype-resources/pom.xml | 13 +- .../archetype-resources/webapp/pom.xml | 5 +- .../fo/rest/auth/FoAuthTokenResource.java | 33 ++- .../webapp/fo/rest/support/FoCorsFilter.java | 46 ++++ .../fo/rest/support/FoRestApplication.java | 20 ++ .../rest/support/FoRestExceptionMapper.java | 7 +- .../webapp/fo/rest/support/FoRestUtils.java | 2 +- .../webapp/src/main/webapp/WEB-INF/web.xml | 86 ++++++++ 26 files changed, 823 insertions(+), 365 deletions(-) create mode 100644 src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/fodoc/DemoClientForDocTest.java create mode 100644 src/main/resources/archetype-resources/impl/src/main/java/impl/biz/client/Client.java create mode 100644 src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoFacebookAuthHelper.java create mode 100644 src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoGoogleAuthHelper.java create mode 100644 src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoSocialSiteAuthHelper.java create mode 100644 src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoCorsFilter.java create mode 100644 src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestApplication.java diff --git a/README.md b/README.md index 6ac89ba..2ac3304 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Table of Contents cd /path/to/your/workspace mvn -X archetype:generate \ --DarchetypeGroupId=com.github.chenjianjx -DarchetypeArtifactId=srb4j -DarchetypeVersion=1.1.2 \ +-DarchetypeGroupId=com.github.chenjianjx -DarchetypeArtifactId=srb4j -DarchetypeVersion=1.2.0 \ -DgroupId=your.groupId \ -DartifactId=yourArtifactid \ -Dpackage=your.pkg.name \ diff --git a/src/main/resources/archetype-resources/demo-client/pom.xml b/src/main/resources/archetype-resources/demo-client/pom.xml index bc43490..ae17dec 100644 --- a/src/main/resources/archetype-resources/demo-client/pom.xml +++ b/src/main/resources/archetype-resources/demo-client/pom.xml @@ -55,10 +55,7 @@ - - org.glassfish.jersey.media - jersey-media-json-jackson - + io.swagger @@ -80,6 +77,16 @@ org.glassfish.jersey.core jersey-client + + + com.mashape.unirest + unirest-java + + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + diff --git a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/bo/portal/DemoClientBoPortalTest.java b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/bo/portal/DemoClientBoPortalTest.java index 681daf0..f114720 100644 --- a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/bo/portal/DemoClientBoPortalTest.java +++ b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/bo/portal/DemoClientBoPortalTest.java @@ -18,7 +18,7 @@ public class DemoClientBoPortalTest { private static final String BBSADMIN_EMAIL = "bbsadmin@nonexist.com"; - private static Client mockClient = ClientBuilder.newClient(); + private static Client mockClient = DemoClientUtils.createRestClient(); @Test public void loginFormTest() { diff --git a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/auth/DemoClientFoAuthTest.java b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/auth/DemoClientFoAuthTest.java index 6eb1c2b..cf29a83 100644 --- a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/auth/DemoClientFoAuthTest.java +++ b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/auth/DemoClientFoAuthTest.java @@ -37,7 +37,7 @@ */ public class DemoClientFoAuthTest { - private static Client restClient = ClientBuilder.newClient(); + private static Client restClient = DemoClientUtils.createRestClient(); @Test public void testProtectedResourceWithoutLogin() { @@ -45,7 +45,7 @@ public void testProtectedResourceWithoutLogin() { .path(DemoClientConstants.PROCTED_RESOURCE_URL) .request(MediaType.APPLICATION_JSON_TYPE).get(); Assert.assertEquals(400, restResponse.getStatus()); - Assert.assertNotNull(restResponse.getHeaderString("www-authenticate")); + Assert.assertNotNull(restResponse.getHeaderString("WWW-Authenticate")); } @Test diff --git a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/auth/DemoClientFoAuthUiMain.java b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/auth/DemoClientFoAuthUiMain.java index 9935b99..2c57394 100644 --- a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/auth/DemoClientFoAuthUiMain.java +++ b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/auth/DemoClientFoAuthUiMain.java @@ -17,6 +17,7 @@ import org.junit.Assert; import ${package}.democlient.util.DemoClientConstants; +import ${package}.democlient.util.DemoClientUtils; import ${package}.restclient.model.GenRandomLoginCodeRequest; import com.github.scribejava.apis.GoogleApi20; import com.github.scribejava.apis.google.GoogleToken; @@ -40,18 +41,10 @@ * */ public class DemoClientFoAuthUiMain { - private static Client restClient = ClientBuilder.newClient(); + private static Client restClient = DemoClientUtils.createRestClient(); public static void main(String[] args) throws Exception { - facebookLoginByTokenFlow(); - googleLoginByTokenFlow(); - - /** - * for social login by code, see here - */ - randomCodeLoginFlow(); } @@ -114,141 +107,4 @@ public static void randomCodeLoginFlow() throws IOException { } - private static void googleLoginByTokenFlow() throws IOException { - BufferedReader consoleInput = new BufferedReader(new InputStreamReader( - System.in)); - - System.out.print("Please input your google client id: \n >>"); - String googleClientId = consoleInput.readLine(); - - System.out.print("Please input your google client screct: \r\n >>"); - String googleClientSecret = consoleInput.readLine(); - - final OAuth20Service service = new ServiceBuilder() - .apiKey(googleClientId).apiSecret(googleClientSecret) - .scope("email") - // replace with desired scope - .callback("urn:ietf:wg:oauth:2.0:oob") - .build(GoogleApi20.instance()); - - final String authorizationUrl = service.getAuthorizationUrl(); - System.out - .println("Please copy the following url and visit it on your browser. Copy the code on the page and paste it here"); - System.out.println(authorizationUrl); - System.out.print(">>"); - final Verifier verifier = new Verifier(consoleInput.readLine()); - System.out.println(); - - final GoogleToken accessToken = (GoogleToken) service - .getAccessToken(verifier); - String googleIdToken = accessToken.getOpenIdToken(); - System.out.println("the idToken is: " + googleIdToken); - - WebTarget target = restClient.target(BACKEND_FO_REST_URL) - .path(DemoClientConstants.SOCIAL_LOGIN_BY_TOKEN_URL_PREFIX - + "google"); - Form form = new Form(); - form.param("grant_type", "password"); - form.param("username", googleIdToken); - form.param("password", "anything"); - form.param("long_session", "false"); - - Response restResponse = target.request(MediaType.APPLICATION_JSON_TYPE) - .accept(MediaType.APPLICATION_JSON_TYPE) - .post(Entity.form(form)); - - DemoClientFoAuthTokenResponse clientResponse = DemoClientFoAuthTokenResponse - .parseRestResponse(restResponse); - - DemoClientFoAuthTest.assertAuthResponseSuccessful(clientResponse); - - } - - private static void facebookLoginByTokenFlow() throws IOException { - BufferedReader consoleInput = new BufferedReader(new InputStreamReader( - System.in)); - - System.out.print("Please input your facebook app id: \n >>"); - String clientId = consoleInput.readLine(); - - System.out.print("Please input your facebook app screct: \r\n >>"); - String clientSecret = consoleInput.readLine(); - - final OAuth20Service service = new ServiceBuilder() - .apiKey(clientId) - .apiSecret(clientSecret) - .scope("email") - // replace with desired scope - .callback("https://www.facebook.com/connect/login_success.html") - .build(new DemoClientFacebookApi()); - - final String authorizationUrl = service.getAuthorizationUrl(); - System.out - .println("Now open your browser and show the 'inspect' window to monitor the url redirection"); - System.out - .println("Copy the following url and visit it on your browser. And then extract the code accoring to\n https://raw.githubusercontent.com/chenjianjx/srb4j/master/src/main/resources/archetype-resources/demo-client/doc/fo/extract-facebook-code.png: "); - System.out.println(authorizationUrl); - System.out.print(">>"); - Verifier verifier = new Verifier(consoleInput.readLine()); - Token accessToken = service.getAccessToken(verifier); - - System.out.println("the access token is: " + accessToken.getToken()); - - WebTarget target = restClient.target(BACKEND_FO_REST_URL).path( - DemoClientConstants.SOCIAL_LOGIN_BY_TOKEN_URL_PREFIX - + "facebook"); - Form form = new Form(); - form.param("grant_type", "password"); - form.param("username", accessToken.getToken()); - form.param("password", "anything"); - form.param("long_session", "false"); - - Response restResponse = target.request(MediaType.APPLICATION_JSON_TYPE) - .accept(MediaType.APPLICATION_JSON_TYPE) - .post(Entity.form(form)); - - DemoClientFoAuthTokenResponse clientResponse = DemoClientFoAuthTokenResponse - .parseRestResponse(restResponse); - - DemoClientFoAuthTest.assertAuthResponseSuccessful(clientResponse); - - } - - public static class DemoClientFacebookApi extends DefaultApi20 { - - private static final String AUTHORIZE_URL = "https://www.facebook.com/v2.5/dialog/oauth?client_id=%s&redirect_uri=%s"; - - @Override - public String getAccessTokenEndpoint() { - return "https://graph.facebook.com/v2.5/oauth/access_token"; - } - - @Override - public String getAuthorizationUrl(final OAuthConfig config) { - Preconditions - .checkValidUrl(config.getCallback(), - "Must provide a valid url as callback. Facebook does not support OOB"); - final StringBuilder sb = new StringBuilder(String.format( - AUTHORIZE_URL, config.getApiKey(), - OAuthEncoder.encode(config.getCallback()))); - if (config.hasScope()) { - sb.append('&').append(OAuthConstants.SCOPE).append('=') - .append(OAuthEncoder.encode(config.getScope())); - } - - final String state = config.getState(); - if (state != null) { - sb.append('&').append(OAuthConstants.STATE).append('=') - .append(OAuthEncoder.encode(state)); - } - return sb.toString(); - } - - @Override - public AccessTokenExtractor getAccessTokenExtractor() { - return new JsonTokenExtractor(); - } - - } - } diff --git a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/biz/DemoClientFoBizResponse.java b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/biz/DemoClientFoBizResponse.java index 7b2285a..2fcc1da 100644 --- a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/biz/DemoClientFoBizResponse.java +++ b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/biz/DemoClientFoBizResponse.java @@ -90,7 +90,7 @@ private static DemoClientFoBizResponse doParseResponse( if (OAUTH2_HTTP_ERROR_CODES.contains(restResponse.getStatus())) { String wwwAuthHeader = restResponse - .getHeaderString("www-authenticate"); + .getHeaderString("WWW-Authenticate"); Map headerValues = decodeOAuthHeader(wwwAuthHeader); result.oauth2Error = new ErrorResult(); result.oauth2Error.setError(headerValues.get("error")); diff --git a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/biz/DemoClientFoBizTest.java b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/biz/DemoClientFoBizTest.java index 87a276f..ecc2c4d 100644 --- a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/biz/DemoClientFoBizTest.java +++ b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/biz/DemoClientFoBizTest.java @@ -23,7 +23,7 @@ */ public class DemoClientFoBizTest { private static final String BIZ_RESOURCE_URL = "/bbs/posts/new"; - private Client restClient = ClientBuilder.newClient(); + private Client restClient = DemoClientUtils.createRestClient(); @Test public void bizTest() { diff --git a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/fodoc/DemoClientForDocTest.java b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/fodoc/DemoClientForDocTest.java new file mode 100644 index 0000000..1ffb586 --- /dev/null +++ b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/fodoc/DemoClientForDocTest.java @@ -0,0 +1,122 @@ +package ${package}.democlient.fo.rest.fordoc; + +import static ${package}.democlient.util.DemoClientUtils.fromJson; +import static ${package}.democlient.util.DemoClientUtils.toJson; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.json.JSONObject; +import org.junit.Test; + +import ${package}.democlient.util.DemoClientUtils; +import ${package}.restclient.model.NewPostRequest; +import ${package}.restclient.model.Post; +import com.mashape.unirest.http.HttpResponse; +import com.mashape.unirest.http.JsonNode; +import com.mashape.unirest.http.Unirest; + +/** + * The code's target is for it to be copied to documents. So there should be as + * little abstraction as possible. + * + * @author chenjianjx@gmail.com + * + */ +public class DemoClientForDocTest { + + @Test + public void allInOneTest() throws Exception { + String accessToken = null; + + // do login + HttpResponse loginResponse = Unirest + .post("http://localhost:8080/fo/rest/token/new/local") + .header("Content-Type", "application/x-www-form-urlencoded") + .field("grant_type", "password") + .field("username", "chenjianjx@gmail.com") + .field("password", "abc123").asJson(); + + if (loginResponse.getStatus() == 200) { + JSONObject token = loginResponse.getBody().getObject(); + System.out.println(token.get("access_token")); //You can save the token for later use + System.out.println(token.get("refresh_token")); + System.out.println(token.get("expires_in")); + System.out.println(token.get("user_principal")); // "the full user name" + accessToken = (String) token.get("access_token"); + } else { + System.out.println(loginResponse.getStatus()); + System.out.println(loginResponse.getStatusText()); + JSONObject error = loginResponse.getBody().getObject(); + System.out.println(error.get("error")); // "the error code" + System.out.println(error.get("error_description")); // "the error description for developers" + System.out.println(error.get("error_description_for_user")); // "user-friendly error desc for users" + System.out.println(error.get("exception_id")); // "the server side developer can use this id to do troubleshooting" + } + + // call a business web service + NewPostRequest bizRequest = new NewPostRequest(); + bizRequest.setContent("my-first-post"); + HttpResponse bizResponse = Unirest + .post("http://localhost:8080/fo/rest/bbs/posts/new") + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + accessToken) + .body(toJson(bizRequest)).asString(); + + if (bizResponse.getStatus() == 200) { + Post post = fromJson(bizResponse.getBody(), Post.class); + System.out.println(post); + } + + else if (Arrays.asList(400, 401, 403).contains(bizResponse.getStatus())) { // "token error" + String authHeader = bizResponse.getHeaders() + .get("WWW-Authenticate").get(0);// "See https://tools.ietf.org/html/rfc6750#page-7" + System.out.println(bizResponse.getStatus()); + System.out.println(authHeader); //You can also further parse auth header if needed. Search "decodeOAuthHeader" in this repository. + //You should then redirect the user to login UI + } + + else if (bizResponse.getStatus() == 460) { // "biz error" + JSONObject error = new JSONObject(bizResponse.getBody()); + System.out.println(error.get("error")); // "the error code" + System.out.println(error.get("error_description")); // "the error description for developers" + System.out.println(error.get("error_description_for_user")); // "user-friendly error desc for users" + System.out.println(error.get("exception_id")); // "the server side developer can use this id to do troubleshooting" + } else { + System.out.println(bizResponse.getStatus()); + System.out.println(bizResponse.getBody()); + } + + // logout + Unirest.post("http://localhost:8080/fo/rest/bbs/posts/delete") + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + accessToken).asJson(); + + } + + private static Map decodeOAuthHeader(String header) { + Map headerValues = new HashMap(); + if (header != null) { + Matcher m = Pattern.compile("\\s*(\\w*)\\s+(.*)").matcher(header); + if (m.matches()) { + if ("Bearer".equalsIgnoreCase(m.group(1))) { + for (String nvp : m.group(2).split("\\s*,\\s*")) { + m = Pattern.compile("(\\S*)\\s*\\=\\s*\"([^\"]*)\"") + .matcher(nvp); + if (m.matches()) { + String name = DemoClientUtils.urlDecode(m.group(1)); + String value = DemoClientUtils + .urlDecode(m.group(2)); + headerValues.put(name, value); + } + } + } + } + } + return headerValues; + } + +} \ No newline at end of file diff --git a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/util/DemoClientUtils.java b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/util/DemoClientUtils.java index f82c0ae..2e7ad0c 100644 --- a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/util/DemoClientUtils.java +++ b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/util/DemoClientUtils.java @@ -2,10 +2,13 @@ import java.net.URLDecoder; -import ${package}.restclient.model.ErrorResult; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; +import ${package}.restclient.model.ErrorResult; /** * @@ -14,6 +17,11 @@ */ public class DemoClientUtils { + public static Client createRestClient() { + return ClientBuilder.newBuilder() + .register(JacksonJaxbJsonProvider.class).build(); + } + public static String toJson(Object response) { ObjectMapper mapper = new ObjectMapper(); try { @@ -25,6 +33,15 @@ public static String toJson(Object response) { } } + public static T fromJson(String json, Class clazz) { + ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.readValue(json, clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public static String urlDecode(String s) { try { return URLDecoder.decode(s, "UTF-8"); @@ -32,7 +49,7 @@ public static String urlDecode(String s) { throw new RuntimeException(wow); } } - + public static void printErrorResult(ErrorResult errorResult) { System.out.println("error code: " + errorResult.getError()); System.out.println("error msg for developer: " diff --git a/src/main/resources/archetype-resources/doc/app.properties.sample b/src/main/resources/archetype-resources/doc/app.properties.sample index 85620a0..e9e1847 100644 --- a/src/main/resources/archetype-resources/doc/app.properties.sample +++ b/src/main/resources/archetype-resources/doc/app.properties.sample @@ -28,7 +28,18 @@ enableSwagger=true #IF you want to let the client login with social sites' auth code, enable the following; otherwise, leave them as they are +##for desktop clients (non-web, non-mobile) googleClientId=placeholder googleClientSecret=placeholder + + +##for web clients which uses Google Sign-in's javascript SDK +googleWebClientId=placeholder +googleWebClientSecret=placeholder + facebookClientId=placeholder -facebookClientSecret=placeholder \ No newline at end of file +facebookClientSecret=placeholder + +#The header of "Access-Control-Allow-Origin" to allow ajax access from html clients. +#Set it as "*" if you allow access from everywhere. If you don't allow cors, leave this value empty +corsOriginHeader= \ No newline at end of file diff --git a/src/main/resources/archetype-resources/impl/src/main/java/impl/biz/client/Client.java b/src/main/resources/archetype-resources/impl/src/main/java/impl/biz/client/Client.java new file mode 100644 index 0000000..ca4d57c --- /dev/null +++ b/src/main/resources/archetype-resources/impl/src/main/java/impl/biz/client/Client.java @@ -0,0 +1,24 @@ +package ${package}.impl.biz.client; + +import java.util.Arrays; +import java.util.List; + +/** + * Representing the clients of this backend + * + * @author chenjianjx@gmail.com + * + */ +public class Client { + + public static final String TYPE_DESKTOP = "desktop"; + public static final String TYPE_WEB = "web"; + public static final String TYPE_MOBILE = "mobile"; + + public static final List CLIENT_TYPES = Arrays.asList(TYPE_DESKTOP, + TYPE_WEB, TYPE_MOBILE); + + public static boolean isValidClientType(String clientType) { + return CLIENT_TYPES.contains(clientType); + } +} \ No newline at end of file diff --git a/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/FoAuthManagerImpl.java b/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/FoAuthManagerImpl.java index fcb6568..667b101 100644 --- a/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/FoAuthManagerImpl.java +++ b/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/FoAuthManagerImpl.java @@ -2,15 +2,11 @@ import static ${package}.intf.fo.basic.FoConstants.NULL_REQUEST_BEAN_TIP; -import java.io.IOException; -import java.security.GeneralSecurityException; - import javax.annotation.Resource; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import ${package}.impl.biz.auth.AccessToken; @@ -18,8 +14,10 @@ import ${package}.impl.biz.auth.AuthService; import ${package}.impl.biz.auth.RandomLoginCode; import ${package}.impl.biz.auth.RandomLoginCodeRepo; +import ${package}.impl.biz.client.Client; import ${package}.impl.biz.user.User; import ${package}.impl.biz.user.UserRepo; +import ${package}.impl.fo.auth.socialsite.FoSocialSiteAuthHelper; import ${package}.impl.fo.common.FoManagerImplBase; import ${package}.impl.util.infrahelp.beanvalidae.MyValidator; import ${package}.impl.util.tools.lang.MyDuplet; @@ -34,23 +32,6 @@ import ${package}.intf.fo.auth.FoSocialLoginByTokenRequest; import ${package}.intf.fo.basic.FoConstants; import ${package}.intf.fo.basic.FoResponse; -import com.github.scribejava.apis.FacebookApi; -import com.github.scribejava.apis.GoogleApi20; -import com.github.scribejava.apis.google.GoogleToken; -import com.github.scribejava.core.builder.ServiceBuilder; -import com.github.scribejava.core.model.Token; -import com.github.scribejava.core.model.Verifier; -import com.github.scribejava.core.oauth.OAuth20Service; -import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; -import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; -import com.google.api.client.http.HttpTransport; -import com.google.api.client.http.apache.ApacheHttpTransport; -import com.google.api.client.json.JsonFactory; -import com.google.api.client.json.jackson2.JacksonFactory; -import com.restfb.DefaultFacebookClient; -import com.restfb.FacebookClient; -import com.restfb.Parameter; -import com.restfb.Version; /** * @@ -79,14 +60,8 @@ public class FoAuthManagerImpl extends FoManagerImplBase implements @Resource AuthService authService; - HttpTransport googleHttpTransport = new ApacheHttpTransport(); - JsonFactory googleJsonFactory = new JacksonFactory(); - - private String googleClientId; - private String googleClientSecret; - - private String facebookClientId; - private String facebookClientSecret; + @Resource + FoSocialSiteAuthHelper.Factory socialSiteAuthHelperFactory; @Override public FoResponse localOauth2Login( @@ -305,8 +280,10 @@ public FoResponse socialLoginByToken( boolean longSession = request.isLongSession(); String socialToken = request.getToken(); - MyDuplet> emailOrErrResp = getEmailFromSocialSiteToken( - source, socialToken); + FoSocialSiteAuthHelper helper = this.socialSiteAuthHelperFactory + .getHelper(source); + MyDuplet> emailOrErrResp = helper + .getEmailFromToken(socialToken, request.getClientType()); return handleSocialSiteEmailResult(source, longSession, emailOrErrResp); } @@ -327,11 +304,22 @@ public FoResponse socialLoginByAuthCode( "unsupported source: " + source, null); } + String clientType = request.getClientType(); + if (!Client.isValidClientType(clientType)) { + return FoResponse.devErrResponse( + FoConstants.FEC_OAUTH2_INVALID_REQUEST, + "unsupported client type: " + clientType, null); + } + + String redirectUri = request.getRedirectUri(); + boolean longSession = request.isLongSession(); String authCode = request.getAuthCode(); - MyDuplet> emailOrErrResp = getEmailFromSocialSiteAuthCode( - source, authCode); + FoSocialSiteAuthHelper helper = this.socialSiteAuthHelperFactory + .getHelper(source); + MyDuplet> emailOrErrResp = helper + .getEmailFromCode(authCode, clientType, redirectUri); return handleSocialSiteEmailResult(source, longSession, emailOrErrResp); } @@ -359,140 +347,6 @@ private FoResponse handleSocialSiteEmailResult( return buildAuthTokenResponse(existingUser, longSession); } - /** - * - * @param source - * @param socialToken - * left = email, right = error response if any - * @return - */ - private MyDuplet> getEmailFromSocialSiteToken( - String source, String socialToken) { - - if (User.SOURCE_GOOGLE.equals(source)) { - return getEmailFromGoogleToken(socialToken); - } - - if (User.SOURCE_FACEBOOK.equals(source)) { - return getEmailFromFacebookToken(socialToken); - } - - throw new IllegalStateException("Unreachable code"); - } - - /** - * - * @param source - * @param authCode - * left = email, right = error response if any - * @return - */ - private MyDuplet> getEmailFromSocialSiteAuthCode( - String source, String authCode) { - if (User.SOURCE_GOOGLE.equals(source)) { - return getEmailFromGoogleAuthCode(authCode); - } - - if (User.SOURCE_FACEBOOK.equals(source)) { - return getEmailFromFacebookAuthCode(authCode); - } - - throw new IllegalStateException("Unreachable code"); - } - - private MyDuplet> getEmailFromFacebookAuthCode( - String authCode) { - - // exchange the code for token - final OAuth20Service service = new ServiceBuilder() - .apiKey(facebookClientId) - .apiSecret(facebookClientSecret) - .scope("email") - .callback("https://www.facebook.com/connect/login_success.html") - .build(FacebookApi.instance()); - Token facebookTokenObj = service.getAccessToken(new Verifier(authCode)); - String token = facebookTokenObj.getToken(); - // get email by token - return this.getEmailFromFacebookToken(token); - - } - - private MyDuplet> getEmailFromFacebookToken( - String socialToken) { - FacebookClient facebookClient = new DefaultFacebookClient(socialToken, - Version.VERSION_2_5); - com.restfb.types.User user = facebookClient.fetchObject("me", - com.restfb.types.User.class, - Parameter.with("fields", "id,name,email")); - if (user == null) { - FoResponse errResp = FoResponse.devErrResponse( - FoConstants.FEC_OAUTH2_INVALID_REQUEST, - "invalid facebook access token", null); - return MyDuplet.newInstance(null, errResp); - } - - String email = user.getEmail(); - if (email == null) { - FoResponse errResp = FoResponse - .devErrResponse( - FoConstants.FEC_OAUTH2_INVALID_REQUEST, - "cannot extract email from this facebook access token. please check the scope used for facebook connect", - null); - return MyDuplet.newInstance(null, errResp); - } - return MyDuplet.newInstance(email, null); - } - - private MyDuplet> getEmailFromGoogleToken( - String idToken) { - GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder( - googleHttpTransport, googleJsonFactory).build(); - GoogleIdToken git = verifyGoogleIdToken(verifier, idToken); - if (git == null) { - FoResponse errResp = FoResponse.devErrResponse( - FoConstants.FEC_OAUTH2_INVALID_REQUEST, - "invalid google open id token", null); - return MyDuplet.newInstance(null, errResp); - } - String email = git.getPayload().getEmail(); - if (email == null) { - FoResponse errResp = FoResponse - .devErrResponse( - FoConstants.FEC_OAUTH2_INVALID_REQUEST, - "cannot extract email from this open id token. please check the scope used for google sign-in", - null); - return MyDuplet.newInstance(null, errResp); - } - return MyDuplet.newInstance(email, null); - } - - private MyDuplet> getEmailFromGoogleAuthCode( - String authCode) { - // exchange the code for token - final OAuth20Service service = new ServiceBuilder() - .apiKey(googleClientId).apiSecret(googleClientSecret) - .scope("email") - // replace with desired scope - .callback("urn:ietf:wg:oauth:2.0:oob") - .build(GoogleApi20.instance()); - GoogleToken googleTokenObj = (GoogleToken) service - .getAccessToken(new Verifier(authCode)); - String idToken = googleTokenObj.getOpenIdToken(); - // get email by token - return this.getEmailFromGoogleToken(idToken); - } - - private GoogleIdToken verifyGoogleIdToken(GoogleIdTokenVerifier verifier, - String idToken) { - try { - return verifier.verify(idToken); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - } - @Override public FoResponse oauth2RefreshToken( FoRefreshTokenRequest request) { @@ -526,24 +380,4 @@ public FoResponse oauth2RefreshToken( } - @Value("${googleClientId}") - public void setGoogleClientId(String googleClientId) { - this.googleClientId = StringUtils.trimToNull(googleClientId); - } - - @Value("${googleClientSecret}") - public void setGoogleClientSecret(String googleClientSecret) { - this.googleClientSecret = StringUtils.trimToNull(googleClientSecret); - } - - @Value("${facebookClientId}") - public void setFacebookClientId(String facebookClientId) { - this.facebookClientId = StringUtils.trimToNull(facebookClientId); - } - - @Value("${facebookClientSecret}") - public void setFacebookClientSecret(String facebookClientSecret) { - this.facebookClientSecret = StringUtils - .trimToNull(facebookClientSecret); - } } diff --git a/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoFacebookAuthHelper.java b/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoFacebookAuthHelper.java new file mode 100644 index 0000000..3c866fe --- /dev/null +++ b/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoFacebookAuthHelper.java @@ -0,0 +1,107 @@ +package ${package}.impl.fo.auth.socialsite; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import ${package}.impl.biz.client.Client; +import ${package}.impl.util.tools.lang.MyDuplet; +import ${package}.intf.fo.auth.FoAuthTokenResult; +import ${package}.intf.fo.basic.FoConstants; +import ${package}.intf.fo.basic.FoResponse; +import com.github.scribejava.apis.FacebookApi; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.Token; +import com.github.scribejava.core.model.Verifier; +import com.github.scribejava.core.oauth.OAuth20Service; +import com.restfb.DefaultFacebookClient; +import com.restfb.FacebookClient; +import com.restfb.Parameter; +import com.restfb.Version; + +/** + * + * @author chenjianjx@gmail.com + * + */ +@Service +public class FoFacebookAuthHelper implements FoSocialSiteAuthHelper { + + private String facebookClientId; + private String facebookClientSecret; + + @Value("${facebookClientId}") + public void setFacebookClientId(String facebookClientId) { + this.facebookClientId = StringUtils.trimToNull(facebookClientId); + } + + @Value("${facebookClientSecret}") + public void setFacebookClientSecret(String facebookClientSecret) { + this.facebookClientSecret = StringUtils + .trimToNull(facebookClientSecret); + } + + @Override + public MyDuplet> getEmailFromToken( + String token, String clientType) { + String accessToken = token; + FacebookClient facebookClient = new DefaultFacebookClient(accessToken, + Version.VERSION_2_5); + com.restfb.types.User user = facebookClient.fetchObject("me", + com.restfb.types.User.class, + Parameter.with("fields", "id,name,email")); + if (user == null) { + FoResponse errResp = FoResponse.devErrResponse( + FoConstants.FEC_OAUTH2_INVALID_REQUEST, + "invalid facebook access token", null); + return MyDuplet.newInstance(null, errResp); + } + + String email = user.getEmail(); + if (email == null) { + FoResponse errResp = FoResponse + .devErrResponse( + FoConstants.FEC_OAUTH2_INVALID_REQUEST, + "cannot extract email from this facebook access token. please check the scope used for facebook connect", + null); + return MyDuplet.newInstance(null, errResp); + } + return MyDuplet.newInstance(email, null); + } + + @Override + public MyDuplet> getEmailFromCode( + String authCode, String clientType, String redirectUri) { + + if (Client.TYPE_DESKTOP.equals(clientType)) { + if (redirectUri == null) { + redirectUri = FoConstants.FACEBOOK_REDIRECT_URI_LOGIN_SUCCESS; + } + } else if (Client.TYPE_WEB.equals(clientType)) { + if (redirectUri == null) { + FoResponse errResp = FoResponse + .devErrResponse( + FoConstants.FEC_OAUTH2_INVALID_REQUEST, + "redirect uri must not be empty for facebook + web ", + null); + return MyDuplet.newInstance(null, errResp); + } + } else { + throw new IllegalArgumentException( + "Currently we don't support google login with client type = " + + clientType); + } + + // exchange the code for token + final OAuth20Service service = new ServiceBuilder() + .apiKey(facebookClientId).apiSecret(facebookClientSecret) + .scope("email").callback(redirectUri) + .build(FacebookApi.instance()); + Token facebookTokenObj = service.getAccessToken(new Verifier(authCode)); + String token = facebookTokenObj.getToken(); + // get email by token + return this.getEmailFromToken(token, clientType); + + } + +} diff --git a/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoGoogleAuthHelper.java b/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoGoogleAuthHelper.java new file mode 100644 index 0000000..1319747 --- /dev/null +++ b/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoGoogleAuthHelper.java @@ -0,0 +1,192 @@ +package ${package}.impl.fo.auth.socialsite; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Arrays; + +import org.apache.commons.lang.StringUtils; +import org.apache.http.NoHttpResponseException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import ${package}.impl.biz.client.Client; +import ${package}.impl.util.tools.lang.MyDuplet; +import ${package}.intf.fo.auth.FoAuthTokenResult; +import ${package}.intf.fo.basic.FoConstants; +import ${package}.intf.fo.basic.FoResponse; +import com.github.scribejava.apis.GoogleApi20; +import com.github.scribejava.apis.google.GoogleToken; +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.Verifier; +import com.github.scribejava.core.oauth.OAuth20Service; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.apache.ApacheHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; + +/** + * + * @author chenjianjx@gmail.com + * + */ +@Service +public class FoGoogleAuthHelper implements FoSocialSiteAuthHelper { + + /** + * for desktop clients (non-web, non-mobile) + */ + private String googleClientId; + private String googleClientSecret; + + /** + * for web clients which uses google's javascript SDK + */ + private String googleWebClientId; + private String googleWebClientSecret; + + HttpTransport googleHttpTransport = new ApacheHttpTransport(); + JsonFactory googleJsonFactory = new JacksonFactory(); + + @Override + public MyDuplet> getEmailFromToken( + String token, String clientType) { + String idToken = token; + MyDuplet> gitResult = verifyGoogleIdToken( + idToken, clientType); + GoogleIdToken git = gitResult.left; + FoResponse gitError = gitResult.right; + if (gitError != null) { + return MyDuplet.newInstance(null, gitError); + } + if (git == null) { + FoResponse errResp = FoResponse.devErrResponse( + FoConstants.FEC_OAUTH2_INVALID_REQUEST, + "invalid google open id token", null); + return MyDuplet.newInstance(null, errResp); + } + String email = git.getPayload().getEmail(); + if (email == null) { + FoResponse errResp = FoResponse + .devErrResponse( + FoConstants.FEC_OAUTH2_INVALID_REQUEST, + "cannot extract email from this open id token. please check the scope used for google sign-in", + null); + return MyDuplet.newInstance(null, errResp); + } + return MyDuplet.newInstance(email, null); + + } + + private MyDuplet> verifyGoogleIdToken( + String idToken, String clientType) { + MyDuplet> gitResult = verifyGoogleIdToken( + idToken, clientType, "https://accounts.google.com"); + if (gitResult.left == null && gitResult.right == null) { + // stupid compatibility google bug. You have to try both issuers + gitResult = verifyGoogleIdToken(idToken, clientType, + "accounts.google.com"); + } + return gitResult; + } + + /** + * + * @param idToken + * @param clientType + * @param issuer + * @return token + error (Note token can be null) + */ + private MyDuplet> verifyGoogleIdToken( + String idToken, String clientType, String issuer) { + GoogleIdTokenVerifier.Builder vb = new GoogleIdTokenVerifier.Builder( + googleHttpTransport, googleJsonFactory).setIssuer(issuer); + + if (clientType.equals(Client.TYPE_DESKTOP)) { + vb.setAudience(Arrays.asList(this.googleClientId)); + } + + if (clientType.equals(Client.TYPE_WEB) + || clientType.equals(Client.TYPE_MOBILE)) { + // according to + // https://developers.google.com/identity/sign-in/android/backend-auth#send-the-id-token-to-your-server, + // the web client Id will be used even for mobile login + vb.setAudience(Arrays.asList(this.googleWebClientId)); + } + + GoogleIdTokenVerifier verifier = vb.build(); + try { + GoogleIdToken token = verifier.verify(idToken); + return MyDuplet.newInstance(token, null); + } catch (NoHttpResponseException e) { + FoResponse errResp = FoResponse.userErrResponse( + FoConstants.FEC_ERR_BUT_CAN_RETRY, + "Google didn't response. Please try again."); + return MyDuplet.newInstance(null, errResp); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + @Override + public MyDuplet> getEmailFromCode( + String authCode, String clientType, String redirectUri) { + + String clientId = null; + String clientSecret = null; + + if (Client.TYPE_DESKTOP.equals(clientType)) { + clientId = googleClientId; + clientSecret = googleClientSecret; + if (redirectUri == null) { + redirectUri = FoConstants.GOOGLE_REDIRECT_URI_OOB; + } + } else if (Client.TYPE_WEB.equals(clientType)) { + clientId = googleWebClientId; + clientSecret = googleWebClientSecret; + if (redirectUri == null) { + redirectUri = FoConstants.GOOGLE_REDIRECT_URI_POSTMESSAGE; + } + } else { + throw new IllegalArgumentException( + "Currently we don't support google login with client type = " + + clientType); + } + + // exchange the code for token + final OAuth20Service service = new ServiceBuilder().apiKey(clientId) + .apiSecret(clientSecret).scope("email").callback(redirectUri) + .build(GoogleApi20.instance()); + GoogleToken googleTokenObj = (GoogleToken) service + .getAccessToken(new Verifier(authCode)); + String idToken = googleTokenObj.getOpenIdToken(); + // get email by token + return this.getEmailFromToken(idToken, clientType); + + } + + @Value("${googleWebClientId}") + public void setGoogleWebClientId(String googleWebClientId) { + this.googleWebClientId = StringUtils.trimToNull(googleWebClientId); + } + + @Value("${googleWebClientSecret}") + public void setGoogleWebClientSecret(String googleWebClientSecret) { + this.googleWebClientSecret = StringUtils + .trimToNull(googleWebClientSecret); + } + + @Value("${googleClientId}") + public void setGoogleClientId(String googleClientId) { + this.googleClientId = StringUtils.trimToNull(googleClientId); + } + + @Value("${googleClientSecret}") + public void setGoogleClientSecret(String googleClientSecret) { + this.googleClientSecret = StringUtils.trimToNull(googleClientSecret); + } + +} \ No newline at end of file diff --git a/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoSocialSiteAuthHelper.java b/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoSocialSiteAuthHelper.java new file mode 100644 index 0000000..e8bdaab --- /dev/null +++ b/src/main/resources/archetype-resources/impl/src/main/java/impl/fo/auth/socialsite/FoSocialSiteAuthHelper.java @@ -0,0 +1,64 @@ +package ${package}.impl.fo.auth.socialsite; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; + +import org.springframework.stereotype.Service; + +import ${package}.impl.biz.user.User; +import ${package}.impl.util.tools.lang.MyDuplet; +import ${package}.intf.fo.auth.FoAuthTokenResult; +import ${package}.intf.fo.basic.FoResponse; + +/** + * To get something from social site's token or auth code + * + * @author chenjianjx + * + */ +public interface FoSocialSiteAuthHelper { + + /** + * + * @param token + * @param clientType + * @return left = email, right = error response if any + */ + MyDuplet> getEmailFromToken( + String token, String clientType); + + /** + * + * @param authCode + * @param clientType + * @param redirectUri + * @return left = email, right = error response if any + */ + MyDuplet> getEmailFromCode( + String authCode, String clientType, String redirectUri); + + @Service + public static final class Factory { + @Resource + FoGoogleAuthHelper googleAuthHelper; + + @Resource + FoFacebookAuthHelper facebookAuthHelper; + + private Map helpers = new HashMap(); + + @PostConstruct + private void init() { + helpers.put(User.SOURCE_GOOGLE, googleAuthHelper); + helpers.put(User.SOURCE_FACEBOOK, facebookAuthHelper); + } + + public FoSocialSiteAuthHelper getHelper(String source) { + return helpers.get(source); + } + + } +} \ No newline at end of file diff --git a/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/auth/FoSocialAuthCodeLoginRequest.java b/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/auth/FoSocialAuthCodeLoginRequest.java index 4fa23e3..2838754 100644 --- a/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/auth/FoSocialAuthCodeLoginRequest.java +++ b/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/auth/FoSocialAuthCodeLoginRequest.java @@ -22,6 +22,18 @@ public class FoSocialAuthCodeLoginRequest { private Boolean longSession; + private String clientType; + + private String redirectUri; + + public String getClientType() { + return clientType; + } + + public void setClientType(String clientType) { + this.clientType = clientType; + } + public String getSource() { return source; } @@ -42,6 +54,14 @@ public void setAuthCode(String authCode) { this.authCode = authCode; } + public String getRedirectUri() { + return redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + @JsonIgnore public boolean isLongSession() { return longSession != null && longSession; @@ -57,4 +77,4 @@ public String toString() { ToStringStyle.SHORT_PREFIX_STYLE); } -} +} \ No newline at end of file diff --git a/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/auth/FoSocialLoginByTokenRequest.java b/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/auth/FoSocialLoginByTokenRequest.java index d141f2f..a52be48 100644 --- a/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/auth/FoSocialLoginByTokenRequest.java +++ b/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/auth/FoSocialLoginByTokenRequest.java @@ -20,8 +20,10 @@ public class FoSocialLoginByTokenRequest { @NotNull(message = "social site token cannot be empty") private String token; + private String clientType; + private Boolean longSession; - + public String getSource() { return source; } @@ -51,6 +53,14 @@ public void setLongSession(Boolean longSession) { this.longSession = longSession; } + public String getClientType() { + return clientType; + } + + public void setClientType(String clientType) { + this.clientType = clientType; + } + @Override public String toString() { return ToStringBuilder.reflectionToString(this, diff --git a/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/basic/FoConstants.java b/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/basic/FoConstants.java index 142c020..98cff99 100644 --- a/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/basic/FoConstants.java +++ b/src/main/resources/archetype-resources/intf-fo/src/main/java/intf/fo/basic/FoConstants.java @@ -34,8 +34,16 @@ public interface FoConstants { public static final String ERROR_DESC_FN = "error_description"; /* Other fields */ - public static final String LONG_SESSION_PARAM = "long_session"; + public static final String LONG_SESSION_PARAM = "long_session"; public static final String SOCIAL_SITE_SOURCE_PARAM = "source"; + public static final String CLIENT_TYPE_PARAM = "clientType"; + public static final String REDIRECT_URI_PARAM = "redirectUri"; + + /*social login redirect uris*/ + public static final String GOOGLE_REDIRECT_URI_POSTMESSAGE = "postmessage"; + public static final String GOOGLE_REDIRECT_URI_OOB = "urn:ietf:wg:oauth:2.0:oob"; + public static final String FACEBOOK_REDIRECT_URI_LOGIN_SUCCESS = "https://www.facebook.com/connect/login_success.html"; + /* biz error http code */ public static final int FO_SC_BIZ_ERROR = 460; @@ -51,6 +59,8 @@ public interface FoConstants { public static final String FEC_INVALID_INPUT = "INVALID_INPUT"; public static final String FEC_RECORD_ALREADY_EXISTS = "RECORD_ALREADY_EXISTS"; + + public static final String FEC_ERR_BUT_CAN_RETRY = "ERR_BUT_CAN_RETRY"; /** * won't be visible to restful users because an oauth2 response will be sent @@ -74,5 +84,7 @@ public interface FoConstants { //public static final String PASSWORD_ERR_TIP = "Password should contain at least 1 digit and 1 English letter, 6-20 characters long and should only digits, English letters and punctuations."; public static final String PASSWORD_ERR_TIP = "Password too weak."; public static final String NULL_REQUEST_BEAN_TIP = "Please input the required fields."; + public static final String SOCIAL_LOGIN_CLIENT_TYPE_TIP = "The client type, including 'desktop', 'web' and 'mobile'."; + public static final String SOCIAL_LOGIN_SOURCE_TIP = "Currently it supports: 'google' and 'facebook' ."; -} +} \ No newline at end of file diff --git a/src/main/resources/archetype-resources/pom.xml b/src/main/resources/archetype-resources/pom.xml index 174edb3..8f39786 100644 --- a/src/main/resources/archetype-resources/pom.xml +++ b/src/main/resources/archetype-resources/pom.xml @@ -75,9 +75,9 @@ - org.glassfish.jersey.media - jersey-media-json-jackson - ${jersey.version} + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + 2.5.4 org.glassfish.jersey.media @@ -333,6 +333,13 @@ 1.0 test + + + com.mashape.unirest + unirest-java + 1.4.9 + test + diff --git a/src/main/resources/archetype-resources/webapp/pom.xml b/src/main/resources/archetype-resources/webapp/pom.xml index 139937f..081c0ae 100644 --- a/src/main/resources/archetype-resources/webapp/pom.xml +++ b/src/main/resources/archetype-resources/webapp/pom.xml @@ -94,9 +94,8 @@ - org.glassfish.jersey.media - jersey-media-json-jackson - + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider org.glassfish.jersey.media diff --git a/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/auth/FoAuthTokenResource.java b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/auth/FoAuthTokenResource.java index 51db211..12d5339 100644 --- a/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/auth/FoAuthTokenResource.java +++ b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/auth/FoAuthTokenResource.java @@ -3,7 +3,11 @@ import static ${package}.intf.fo.basic.FoConstants.ACCESS_TOKEN_HEADER_DEFAULT; import static ${package}.intf.fo.basic.FoConstants.ACCESS_TOKEN_HEADER_KEY; import static ${package}.intf.fo.basic.FoConstants.BIZ_ERR_TIP; +import static ${package}.intf.fo.basic.FoConstants.CLIENT_TYPE_PARAM; +import static ${package}.intf.fo.basic.FoConstants.FACEBOOK_REDIRECT_URI_LOGIN_SUCCESS; import static ${package}.intf.fo.basic.FoConstants.FO_SC_BIZ_ERROR; +import static ${package}.intf.fo.basic.FoConstants.GOOGLE_REDIRECT_URI_OOB; +import static ${package}.intf.fo.basic.FoConstants.GOOGLE_REDIRECT_URI_POSTMESSAGE; import static ${package}.intf.fo.basic.FoConstants.LONG_SESSION_PARAM; import static ${package}.intf.fo.basic.FoConstants.LONG_SESSION_TIP; import static ${package}.intf.fo.basic.FoConstants.OAUTH2_ACCESS_TOKEN_NAME_TIP; @@ -12,6 +16,9 @@ import static ${package}.intf.fo.basic.FoConstants.OAUTH2_TOKEN_ENDPOINT_ERR_TIP; import static ${package}.intf.fo.basic.FoConstants.OAUTH2_TOKEN_ENDPOINT_TIP; import static ${package}.intf.fo.basic.FoConstants.OK_TIP; +import static ${package}.intf.fo.basic.FoConstants.REDIRECT_URI_PARAM; +import static ${package}.intf.fo.basic.FoConstants.SOCIAL_LOGIN_CLIENT_TYPE_TIP; +import static ${package}.intf.fo.basic.FoConstants.SOCIAL_LOGIN_SOURCE_TIP; import static ${package}.intf.fo.basic.FoConstants.SOCIAL_SITE_SOURCE_PARAM; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_OK; @@ -38,6 +45,7 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import org.apache.commons.lang.StringUtils; import org.apache.oltu.oauth2.as.request.OAuthUnauthenticatedTokenRequest; import org.apache.oltu.oauth2.as.response.OAuthASResponse; import org.apache.oltu.oauth2.common.OAuth; @@ -123,7 +131,7 @@ public FoResponse doAuth( } @POST - @Path("/new/social/by-token/{source}") + @Path("/new/social/by-token/{source}/{clientType}") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @ApiOperation(value = OAUTH2_TOKEN_ENDPOINT_TIP + " login with social sites's token. The backend will verify this token and obtain the user's email. Mainly used for mobile clients which can obtain token directly. " @@ -136,9 +144,10 @@ public FoResponse doAuth( @ApiResponses(value = { @ApiResponse(code = SC_OK, message = OK_TIP, response = FoAuthTokenResult.class), @ApiResponse(code = SC_BAD_REQUEST, message = OAUTH2_TOKEN_ENDPOINT_ERR_TIP, response = FoErrorResult.class) }) - public Response facebookLogin( + public Response socialLoginByToken( @Context HttpServletRequest rawRequest, - final @ApiParam(required = true, value = "Currently it supports: 'google' and 'facebook' . For google, plaease pass the id token; for facebook, please pass the access token") @PathParam(SOCIAL_SITE_SOURCE_PARAM) String source, + final @ApiParam(required = true, value = SOCIAL_LOGIN_SOURCE_TIP + "For google, plaease pass the id token; for facebook, please pass the access token") @PathParam(SOCIAL_SITE_SOURCE_PARAM) String source, + final @ApiParam(required = true, value = SOCIAL_LOGIN_CLIENT_TYPE_TIP) @PathParam(CLIENT_TYPE_PARAM) String clientType, MultivaluedMap form) throws OAuthSystemException { OAuth2RequestWrapper servletRequest = new OAuth2RequestWrapper( @@ -156,6 +165,7 @@ public FoResponse doAuth( foRequest.setToken(oltuRequest.getUsername()); foRequest.setLongSession(longSession); foRequest.setSource(source); + foRequest.setClientType(clientType); FoResponse foResponse = foAuthManager .socialLoginByToken(foRequest); return foResponse; @@ -167,24 +177,30 @@ public FoResponse doAuth( } @POST - @Path("/new/social/by-auth-code/{source}") + @Path("/new/social/by-auth-code/{source}/{clientType}") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @ApiOperation(value = OAUTH2_TOKEN_ENDPOINT_TIP + " login with social sites authorization code. The backend will exchange the code for access token, and extracts the user's email. " - + " This is mainly used for desktop clients where there are no corresponding login SDK. " + + " This is mainly used for desktop clients and web clients. " + " Note that you must set up social clientId/clientSecret on the backend, and set up social clientId on the client side " + "", notes = OAUTH2_FLOW_TIP) @ApiImplicitParams({ @ApiImplicitParam(name = "grant_type", value = OAUTH2_GRANT_TYPE_TIP, required = true, dataType = "string", paramType = "form", defaultValue = "password"), @ApiImplicitParam(name = "username", value = "The authorization code you obtained from social sites after an OAuth2 code flow with them", required = true, dataType = "string", paramType = "form"), @ApiImplicitParam(name = "password", value = "anything but null", required = true, dataType = "string", paramType = "form"), - @ApiImplicitParam(name = LONG_SESSION_PARAM, value = LONG_SESSION_TIP, required = true, dataType = "boolean", paramType = "form") }) + @ApiImplicitParam(name = LONG_SESSION_PARAM, value = LONG_SESSION_TIP, required = true, dataType = "boolean", paramType = "form"), + @ApiImplicitParam(name = "redirectUri", value = "The redirect uri for this social login. \n " + + "1. For google + desktop, it CAN be '"+ GOOGLE_REDIRECT_URI_OOB + "' \n" + + "2. For google + web, it MUST be '" + GOOGLE_REDIRECT_URI_POSTMESSAGE +"' \n " + + "3. For facebook + desktop, it CAN be '" + FACEBOOK_REDIRECT_URI_LOGIN_SUCCESS + "' \n" + + "4. For facebook + web, it is a url of your html client", required = true, dataType = "string", paramType = "form") }) @ApiResponses(value = { @ApiResponse(code = SC_OK, message = OK_TIP, response = FoAuthTokenResult.class), @ApiResponse(code = SC_BAD_REQUEST, message = OAUTH2_TOKEN_ENDPOINT_ERR_TIP, response = FoErrorResult.class) }) public Response socialLoginByAuthCode( @Context HttpServletRequest rawRequest, - final @ApiParam(required = true, value = "Currently it supports: 'google' and 'facebook' ") @PathParam(SOCIAL_SITE_SOURCE_PARAM) String source, + final @ApiParam(required = true, value = SOCIAL_LOGIN_SOURCE_TIP) @PathParam(SOCIAL_SITE_SOURCE_PARAM) String source, + final @ApiParam(required = true, value = SOCIAL_LOGIN_CLIENT_TYPE_TIP) @PathParam(CLIENT_TYPE_PARAM) String clientType, MultivaluedMap form) throws OAuthSystemException { OAuth2RequestWrapper servletRequest = new OAuth2RequestWrapper( @@ -198,10 +214,13 @@ public FoResponse doAuth( OAuthUnauthenticatedTokenRequest oltuRequest) { boolean longSession = Boolean.TRUE.toString().equals( servletRequest.getParameter(LONG_SESSION_PARAM)); + String redirectUri = StringUtils.trimToNull(servletRequest.getParameter(REDIRECT_URI_PARAM)); FoSocialAuthCodeLoginRequest foRequest = new FoSocialAuthCodeLoginRequest(); foRequest.setAuthCode(oltuRequest.getUsername()); foRequest.setLongSession(longSession); foRequest.setSource(source); + foRequest.setClientType(clientType); + foRequest.setRedirectUri(redirectUri); FoResponse foResponse = foAuthManager .socialLoginByAuthCode(foRequest); return foResponse; diff --git a/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoCorsFilter.java b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoCorsFilter.java new file mode 100644 index 0000000..a393f61 --- /dev/null +++ b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoCorsFilter.java @@ -0,0 +1,46 @@ +package ${package}.webapp.fo.rest.support; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.Provider; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * + * + * + */ +@Provider +@Component +public class FoCorsFilter implements ContainerResponseFilter { + + private String corsOriginHeader; + + @Override + public void filter(ContainerRequestContext request, + ContainerResponseContext response) { + if (corsOriginHeader == null) { + return; + } + + MultivaluedMap headers = response.getHeaders(); + headers.add("Access-Control-Allow-Origin", corsOriginHeader); + headers.add("Access-Control-Allow-Methods", + "GET, POST, PUT, DELETE, OPTIONS, HEAD"); + headers.add( + "Access-Control-Allow-Headers", + "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization"); + headers.add("Access-Control-Expose-Headers", "WWW-Authenticate"); + } + + @Value("${corsOriginHeader}") + public void setCorsOriginHeader(String corsOriginHeader) { + this.corsOriginHeader = StringUtils.trimToNull(corsOriginHeader); + } + +} \ No newline at end of file diff --git a/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestApplication.java b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestApplication.java new file mode 100644 index 0000000..c5e5406 --- /dev/null +++ b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestApplication.java @@ -0,0 +1,20 @@ +package ${package}.webapp.fo.rest.support; + +import org.glassfish.jersey.server.ResourceConfig; + +import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; + +/** + * This class is introduced to avoid problems as mentioned here + * + * @author chenjianjx@gmail.com + * + */ +public class FoRestApplication extends ResourceConfig { + + public FoRestApplication() { + register(JacksonJaxbJsonProvider.class); + } +} diff --git a/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestExceptionMapper.java b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestExceptionMapper.java index e75bb26..99aca62 100644 --- a/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestExceptionMapper.java +++ b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestExceptionMapper.java @@ -15,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; + import ${package}.intf.fo.basic.FoConstants; import ${package}.intf.fo.basic.FoResponse; @@ -37,7 +38,11 @@ public class FoRestExceptionMapper implements ExceptionMapper { public Response toResponse(Throwable ex) { if (ex instanceof javax.ws.rs.NotFoundException) {// do not log for 404 - return Response.status(Status.NOT_FOUND).build(); + FoResponse foResponse = FoResponse.devErrResponse( + FoConstants.FEC_UNKNOWN_SERVER_ERROR, + "This restful resource doesn't exist", null); + return FoRestUtils.fromFoResponse(foResponse, + Status.NOT_FOUND.getStatusCode()); } String exceptionId = Thread.currentThread().getName() + "-" diff --git a/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestUtils.java b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestUtils.java index a86d8a7..1820c7d 100644 --- a/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestUtils.java +++ b/src/main/resources/archetype-resources/webapp/src/main/java/webapp/fo/rest/support/FoRestUtils.java @@ -62,7 +62,7 @@ private static Response buildNotLoginRestResponse( - private static Response fromFoResponse(FoResponse foResponse, + public static Response fromFoResponse(FoResponse foResponse, int statusCodeIfErr) { if (foResponse == null) { return null; diff --git a/src/main/resources/archetype-resources/webapp/src/main/webapp/WEB-INF/web.xml b/src/main/resources/archetype-resources/webapp/src/main/webapp/WEB-INF/web.xml index ec1bf98..68c18a6 100644 --- a/src/main/resources/archetype-resources/webapp/src/main/webapp/WEB-INF/web.xml +++ b/src/main/resources/archetype-resources/webapp/src/main/webapp/WEB-INF/web.xml @@ -5,6 +5,92 @@ http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> + + fo-rest-doc-servlet + ${package}.webapp.root.FoRestDocServlet + + + + fo-rest-doc-servlet + /fo-rest-doc + + + + + + bo-portal-servlet + ${package}.webapp.bo.portal.BoAllInOneServlet + + + + bo-portal-servlet + /bo/portal/* + + + + + + + + fo-rest + org.glassfish.jersey.servlet.ServletContainer + + jersey.config.server.provider.packages + + io.swagger.jaxrs.listing, + ${package}.webapp.fo.rest + + + + javax.ws.rs.Application + ${package}.webapp.fo.rest.support.FoRestApplication + + 1 + + + + fo-rest + /fo/rest/* + + + + + fo-rest-swagger-init + ${package}.webapp.fo.rest.support.FoSwaggerJaxrsConfig + 2 + + + + + + log4jConfigLocation + /WEB-INF/log4j.xml + + + + org.springframework.web.util.Log4jConfigListener + + + + + contextConfigLocation + classpath:spring/applicationContext.xml + + + + org.springframework.web.context.ContextLoaderListener + + + + + + + + From 3b29a43a3130197efb8ecd0c1977b41d23c5077a Mon Sep 17 00:00:00 2001 From: "shaunyip@outlook.com" Date: Wed, 1 Jun 2016 17:48:07 +0800 Subject: [PATCH 22/24] temp submit --- pom.xml | 2 +- .../webapp/src/main/webapp/WEB-INF/web.xml | 82 ------------------- 2 files changed, 1 insertion(+), 83 deletions(-) diff --git a/pom.xml b/pom.xml index ef4c9eb..ab2d3ec 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ srb4j The archetype to generate code http://maven.apache.org - 1.1 + 1.2.0-SNAPSHOT diff --git a/src/main/resources/archetype-resources/webapp/src/main/webapp/WEB-INF/web.xml b/src/main/resources/archetype-resources/webapp/src/main/webapp/WEB-INF/web.xml index 68c18a6..4bc6e2b 100644 --- a/src/main/resources/archetype-resources/webapp/src/main/webapp/WEB-INF/web.xml +++ b/src/main/resources/archetype-resources/webapp/src/main/webapp/WEB-INF/web.xml @@ -83,86 +83,4 @@ http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" - - - - - - - - fo-rest-doc-servlet - ${package}.webapp.root.FoRestDocServlet - - - - fo-rest-doc-servlet - /fo-rest-doc - - - - - - bo-portal-servlet - ${package}.webapp.bo.portal.BoAllInOneServlet - - - - bo-portal-servlet - /bo/portal/* - - - - - - fo-rest - org.glassfish.jersey.servlet.ServletContainer - - jersey.config.server.provider.packages - - io.swagger.jaxrs.listing, - ${package}.webapp.fo.rest - - - 1 - - - - fo-rest - /fo/rest/* - - - - - fo-rest-swagger-init - ${package}.webapp.fo.rest.support.FoSwaggerJaxrsConfig - 2 - - - - - - log4jConfigLocation - /WEB-INF/log4j.xml - - - - org.springframework.web.util.Log4jConfigListener - - - - - contextConfigLocation - classpath:spring/applicationContext.xml - - - - org.springframework.web.context.ContextLoaderListener - - - - From 766501bdffc634e0171372a614e3de47b9cf0d4d Mon Sep 17 00:00:00 2001 From: "shaunyip@outlook.com" Date: Wed, 1 Jun 2016 18:12:00 +0800 Subject: [PATCH 23/24] read to integrate --- pom.xml | 2 +- .../test/java/democlient/bo/portal/DemoClientBoPortalTest.java | 2 +- .../fo/rest/{fodoc => fordoc}/DemoClientForDocTest.java | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/{fodoc => fordoc}/DemoClientForDocTest.java (100%) diff --git a/pom.xml b/pom.xml index ab2d3ec..475560b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ srb4j The archetype to generate code http://maven.apache.org - 1.2.0-SNAPSHOT + 1.1.5 diff --git a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/bo/portal/DemoClientBoPortalTest.java b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/bo/portal/DemoClientBoPortalTest.java index f114720..7a15ccd 100644 --- a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/bo/portal/DemoClientBoPortalTest.java +++ b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/bo/portal/DemoClientBoPortalTest.java @@ -1,7 +1,6 @@ package ${package}.democlient.bo.portal; import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Form; import javax.ws.rs.core.Response; @@ -9,6 +8,7 @@ import org.junit.Assert; import org.junit.Test; import ${package}.democlient.util.DemoClientConstants; +import ${package}.democlient.util.DemoClientUtils; /** * diff --git a/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/fodoc/DemoClientForDocTest.java b/src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/fordoc/DemoClientForDocTest.java similarity index 100% rename from src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/fodoc/DemoClientForDocTest.java rename to src/main/resources/archetype-resources/demo-client/src/test/java/democlient/fo/rest/fordoc/DemoClientForDocTest.java From 9614dc402e6b79af034e07a9dfa13bcdf9aa3bd9 Mon Sep 17 00:00:00 2001 From: "shaunyip@outlook.com" Date: Wed, 1 Jun 2016 18:17:42 +0800 Subject: [PATCH 24/24] final: 1.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 475560b..ac88a12 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ srb4j The archetype to generate code http://maven.apache.org - 1.1.5 + 1.2.0