在《蚂蚁金服开放平台开发前期准备》准备后,已经获取应用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/