在《螞蟻金服開放平臺開發前期準備》準備後,已經獲取應用AppID以及應用私鑰、支付寶公鑰、回調地址。可以進行網站應用的開發。
一、需求
用戶點擊登錄後,選擇第三方登錄中的“支付寶”,跳轉到登錄頁面使用支付寶掃碼進行授權登錄。用戶同意登錄後獲取到用戶的基本信息。
授權流程
1.採用IDEA2017 進行開發
2.基於JDK1.8,使用SpringBoot1.5.14進行快速開發,json庫採用阿里開源的fastjson1.2.4,對於數據緩存方面使用redis對臨時數據進行保存。
3.採用Maven進行管理開發
三、開發步驟
1.創建工程
創建一個SpringBoot的工程,打開IDEA,新建一個工程,選擇Spring Initializr,Next。然後填寫工程基本信息
選擇SpringBoot的版本已經需要添加的依賴,也可以直接跳過,手動添加依賴。由於是web工程,需要添加web相關依賴。模板引擎採用springboot推薦的thymeleaf。最後點擊確認,進入工程。
2.添加依賴
進入工程後還需要添加一些開發需要的依賴
Pom.xml中主要添加信息:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
<!-- 添加httpclient支持 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
<type>jar</type>
</dependency>
還需要添加阿里提供的開發包的依賴
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.3.4.ALL</version>
</dependency>
採用fastjson對json進行處理,使用HttpClient進行請求處理。使用redis對數據進行緩存
3.添加配置文件。
SpringBoot默認會將resources下的application名字開頭的properties作爲配置文件。所以只需要添加application.properties就可以了
1)配置視圖模板相關
由於是使用thymeleaf作爲模板引擎。可以添加相應的配置:
#thymelea模板配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
爲了讓系統更具有通用性,分別創建application-dev.properties、application-prod.properties對不同的環境進行配置。
在application.properties中,使用spring.profiles.active進行配置環境的選擇。
如:spring.profiles.active = prod
2)配置螞蟻金服開放平臺相關
開發環境下,可以使用阿里提供的支付寶錢包沙箱進行測試。沙箱環境和線上環境是不一樣的api.
I.配置沙箱環境。
application-dev.properties:
# 沙箱環境
alipay.serverUrl = https://openapi.alipaydev.com/gateway.do
alipay.qrUserInfoUrl = https://openauth.alipaydev.com/oauth2/publicAppAuthorize.htm
# 應用appid
alipay.appid =
# 應用私鑰
alipay.privatekey=
# 支付寶公鑰
alipay.publickey =
# 回調地址
alipay.redirect_userinfo_uri =
II.配置生產環境下的配置:
在application-prod.properties中配置:
# 網關
alipay.serverUrl = https://openapi.alipay.com/gateway.do
# 用於獲取用戶信息詳情
alipay.qrUserInfoUrl = https://openauth.alipay.com/oauth2/publicAppAuthorize.htm
alipay.appid =
# 應用私鑰
alipay.privatekey=
# 支付寶公鑰
alipay.publickey =
# 回調地址
alipay.redirect_userinfo_uri =
上述空白處填寫《螞蟻金服開放平臺開發前期準備.doc》中填寫的信息。包括appid、應用私鑰、支付寶公鑰、回調地址,
4.新建javabean:
新建一個javaBean來保存獲取到的用戶信息。這些信息是支付寶可以提供給我們的公開信息。
@Getter
@Setter
public class AliPayUserInfo {
private String userId;
private String nickName;
private String avatar;
private String city;
private String gender;
private String is_certified;
private String is_student_certified;
private String province;
private String user_status;
private String user_type;
}
5.Controller層
I.AlipayLoginController類
新建Controller包,添加AlipayLoginController類。這裏完成用戶授權登錄獲取用戶信息的邏輯。使用Spring的Environment用於獲取配置的值。
@Controller
@RequestMapping("/alipay")
public class AlipayLoginController {
private static Logger log = LoggerFactory.getLogger(AlipayLoginController.class);
@Autowired private Environment env;
1)引導用戶訪問支付寶授權頁面
對於引導用戶訪問支付寶授權頁面的url拼接。
1Scope目前有五種類型:分別爲auth_user(獲取用戶信息、網站支付寶登錄)、auth_base(用戶信息授權,僅僅用於靜默獲取用戶支付寶的uid)、auth_ecard(商戶會員卡)、auth_invoice_info(支付寶閃電開票)、auth_puc_charge(生活繳費)。
由於是授權登錄獲取用戶信息,這裏直接拼接scope=auth_user可以達到需求。
2 state是可選的參數,爲了安全,這裏採用redis去暫存uuid生成的隨機字符串作爲state,後續再支付寶回調的時候將帶上state參數,可以通過判斷state參數防止CSRF(跨站請求僞造)攻擊
/**
* 用戶信息授權
* 獲取用戶信息詳情
* @return
*/
@RequestMapping("/alipayUserInfo")
public String aLiPayUserInfo(HttpServletRequest httpServletRequest) {
String state = UUID.randomUUID().toString().replaceAll("-","");
RedisPoolUtil.setEx("alipay-state-"+httpServletRequest.getSession().getId(),state,1800);
String qrAliLoginUrl =
env.getProperty("alipay.qrUserInfoUrl") +
"?" +
"app_id=" +
env.getProperty("alipay.appid") +
"&scope=auth_user" + // 這裏硬編碼,就是爲了獲取用戶的信息
"&redirect_uri=" +
env.getProperty("alipay.redirect_userinfo_uri") +
"&state=" + state;
log.info("redirect url:"+qrAliLoginUrl);
return "redirect:"+qrAliLoginUrl;
}
2)用戶同意授權
用戶同意授權後,將回調到我們提供的url地址上。同時帶回appid、scope、state和code。
這個code可以用於請求,獲取到token。
/**
* 螞蟻金服回調地址,正確的回調將帶回app_id、scope和code
* @param httpServletRequest
* @param model
* @return
*/
@RequestMapping("/getalipayUserInfo")
public String getALiPayUserInfo(HttpServletRequest httpServletRequest, Model model) {
String appId = httpServletRequest.getParameter("app_id");
String scope = httpServletRequest.getParameter("scope");
String authCode = httpServletRequest.getParameter("auth_code");
String state = httpServletRequest.getParameter("state");
// auth_code
log.info("appID:" + appId + " scope:" + scope + " authCode:" + authCode+" state:"+state);
if (appId == null || scope == null || authCode == null || state == null){
throw new RuntimeException("參數爲空");
}
// 判斷是否是之前的請求,防止CSRF攻擊
String alipayState = RedisPoolUtil.get("alipay-state-"+httpServletRequest.getSession().getId());
if (alipayState!=null){
if (!state.equals(alipayState)){
throw new RuntimeException("非法請求");
}
}
// 判定appid是否是我們的
if(!appId.equals(env.getProperty("alipay.appid"))){
throw new RuntimeException("非法請求");
}
3)根據code來獲取token
根據阿里提供的開發包中的AlipaySystemOauthTokenRequest類,以及獲取到的code,執行請求成功後,獲取到token。有了token,就可以進一步獲取到用戶信息
AlipayClient alipayClient = new DefaultAlipayClient(
env.getProperty("alipay.serverUrl"),
env.getProperty("alipay.appid"),
env.getProperty("alipay.privatekey"),
"json", "UTF-8",
env.getProperty("alipay.publickey"),
"RSA2");
AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
request.setCode(authCode);
request.setGrantType("authorization_code");
try {
AlipaySystemOauthTokenResponse oauthTokenResponse = alipayClient.execute(request);
log.info("AccessToken:"+oauthTokenResponse.getAccessToken());
AliPayUserInfo aliPayUserInfo = getAliPayUserInfo(alipayClient,oauthTokenResponse.getAccessToken());
if (aliPayUserInfo==null){
throw new RuntimeException("獲取用戶信息失敗");
}
model.addAttribute(aliPayUserInfo);
} catch (AlipayApiException e) {
//處理異常
e.printStackTrace();
}
return "alipayUser";
}
4)根據Token獲取用戶信息
通過阿里提供的庫文件中的AlipayUserInfoShareResponse。根據token去向支付寶開放平臺發出請求,然後獲取用戶的基本信息。
private AliPayUserInfo getAliPayUserInfo(AlipayClient alipayClient,String token){
AlipayUserInfoShareRequest requestUser = new AlipayUserInfoShareRequest();
try {
// 根據獲取的TOKEN獲取用戶公開信息
AlipayUserInfoShareResponse userinfoShareResponse = alipayClient.execute(requestUser, token);
String body = userinfoShareResponse.getBody();
log.info(body.toString());
// 權限不足判斷 TODO:需要使用更好的辦法實現
// 獲取到用戶信息
JSONObject jsonObject = (JSONObject)JSONObject.parseObject(body).get("alipay_user_info_share_response");
if(((String)jsonObject.get("code")).equals("40006")){
throw new RuntimeException("ISV或用戶權限不足");
}
// 設置返回過來的信息
AliPayUserInfo aliPayUserInfo = new AliPayUserInfo();
aliPayUserInfo.setUserId(userinfoShareResponse.getUserId());
aliPayUserInfo.setNickName(userinfoShareResponse.getNickName());
aliPayUserInfo.setAvatar(userinfoShareResponse.getAvatar());//頭像Url
aliPayUserInfo.setCity(userinfoShareResponse.getCity());
aliPayUserInfo.setGender(userinfoShareResponse.getGender());
aliPayUserInfo.setIs_certified(userinfoShareResponse.getIsCertified());
aliPayUserInfo.setIs_student_certified(userinfoShareResponse.getIsStudentCertified());
aliPayUserInfo.setProvince(userinfoShareResponse.getProvince());
aliPayUserInfo.setUser_status(userinfoShareResponse.getUserStatus());
aliPayUserInfo.setUser_type(userinfoShareResponse.getUserType());
return aliPayUserInfo;
} catch (AlipayApiException e) {
//處理異常
e.printStackTrace();
}
return null;
}
}
II.IndexController類
@Controller
public class IndexController {
@RequestMapping("/")
public String index(){
return "login";
}
}
6.視圖模板層
在templates下添加login.html
添加引導支付寶登錄url鏈接。
7.其他重要的類
I.HttpClient封裝類HttpClientUtils
封裝HttpClient的post和get請求,
其中get請求封裝
public static JSONObject httpGet(String url) {
// get請求返回結果
JSONObject jsonResult = null;
CloseableHttpClient client = HttpClients.createDefault();
// 發送get請求
HttpGet request = new HttpGet(url);
request.setConfig(requestConfig);
try {
CloseableHttpResponse response = client.execute(request);
// 請求發送成功,並得到響應
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// 讀取服務器返回過來的json字符串數據
HttpEntity entity = response.getEntity();
String strResult = EntityUtils.toString(entity, "utf-8");
// 把json字符串轉換成json對象
jsonResult = JSONObject.parseObject(strResult);
} else {
logger.error("get請求提交失敗:" + url);
}
} catch (IOException e) {
logger.error("get請求提交失敗:" + url, e);
} finally {
request.releaseConnection();
}
return jsonResult;
}
II.Jedis封裝類RedisPoolUtil
封裝jedis的方法,讓操作redis更加易用
其中get方法實現:
public static String get(String key){
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.get(key);
} catch (Exception e) {
log.error("get key:{} error",key,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
RedisPool.java也是對redis進行封裝的類,主要對jedis的JedisPoolConfig進行配置。使用池管理Jedis。
四、功能測試
1.開發環境測試
開發環境使用阿里提供的沙箱
最終能夠獲取到測試賬號的信息。
2.線上環境測試
成功獲取用戶基本信息
五、開放平臺文檔鏈接
登錄授權:https://docs.open.alipay.com/289/105656
獲取用戶信息:https://docs.open.alipay.com/api_2/alipay.user.info.share
刷新token:https://docs.open.alipay.com/api_9/alipay.system.oauth.token
網站支付寶登陸:https://docs.open.alipay.com/263/105809/