微信登录有很多第三方可以选择,比如ShareSDK、友盟等,同时能对接完成微博、QQ等登录方式。这篇只说明不使用其他第三方实现微信登录功能。
## 官方文档
微信登录的官方文档地址为<https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419317851&token=b62b923e6dd0f6b4a00f57e438e764138e36035b&lang=zh_CN>
其中,官方文档上建议将部分操作放到后台接口来实现,原因是
```bash
1、Appsecret 是应用接口使用密钥,泄漏后将可能导致应用数据泄漏、应用的用户数据泄漏等高风险后果;存储在客户端,极有可能被恶意窃取(如反编译获取Appsecret);
2、access_token 为用户授权第三方应用发起接口调用的凭证(相当于用户登录态),存储在客户端,可能出现恶意获取access_token 后导致的用户数据泄漏、用户微信相关接口功能被恶意发起等行为;
3、refresh_token 为用户授权第三方应用的长效凭证,仅用于刷新access_token,但泄漏后相当于access_token 泄漏,风险同上。
```
所以,本篇代码会同时涉及安卓部分和java后台部分。
## 安卓开发准备
首先需要进入开发者平台注册账号、申请开发者资质等,这部分不是本篇重点内容,略过。访问网址是<https://open.weixin.qq.com/>
进入导航栏中的管理中心,创建移动应用。提交审核的时候说7天内,一般一天就能过。审核通过后可以拿到AppID和AppSecret。以下内容假设这两个数值已经得到。
下载分享需要的jar包并导入项目,下载地址<https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419319167&token=b62b923e6dd0f6b4a00f57e438e764138e36035b&lang=zh_CN>
新建包.wxapi,添加登录页的WXEntryActivity到这个包中。比如包名是com.fymod.easemob,那么WXEntryActivity就位于包com.fymod.easemob.wxapi下。这个是微信登录之后的回调显示页面。
在AndroidManifest.xml中添加的代码一定要加上android:exported="true"和android:launchMode="singleTop"。
```bash
<activity android:name=".wxapi.WXEntryActivity"
android:exported="true"
android:launchMode="singleTop" >
</activity>
```
另外,添加访问网络权限。要通过后台接口获取数据的。
```bash
<uses-permission android:name="android.permission.INTERNET" />
```
## WXEntryActivity代码
WXEntryActivity是一个基本的Activity,不同的是,它需要实现IWXAPIEventHandler接口。
```bash
public class WXEntryActivity extends Activity implements IWXAPIEventHandler {
//点击之后跳转到微信页的button
private ButtonRectangle btn_login;
private IWXAPI api;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//给IWXAPI赋值,第二个参数是AppID
api = WXAPIFactory.createWXAPI(this, "wxd167f2424d470758", false);
//注册到微信,参数是AppID
api.registerApp("wxd167f2424d470758");
//这个必须加上,否则回调显示会出问题
api.handleIntent(getIntent(), this);
//这个是登录按钮的点击事件
btn_login = (ButtonRectangle)findViewById(R.id.btn_login);
btn_login.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
//固定方式,写入下面4行代码即可,点击按钮就能跳转到微信了
SendAuth.Req req = new SendAuth.Req();
req.scope = "snsapi_userinfo";
req.state = "STATE";
api.sendReq(req);
}
});
}
//启动微信之前调用这个方法,可以不写代码
@Override
public void onReq(BaseReq req) {
}
//在微信端点击确定登录之后或者取消登录之后,回调方法是onResp
@Override
public void onResp(BaseResp resp) {
switch (resp.errCode) {
case BaseResp.ErrCode.ERR_OK: //点击确定登录
//得到code值,需要使用该值和后台交互
String code = JSONObject.parseObject(JSON.toJSONString(resp)).getString("code");
Toast.makeText(WXEntryActivity.this, "登录成功,获取到code:" + code, Toast.LENGTH_LONG).show();
//以下代码需要发送code到后台接口,来获取用户信息,不再赘述
// post to interface, post(code)
break;
default:
//点击的取消,根据自身业务处理
break;
}
}
}
```
## 获取用户信息
这部分是请求微信端,使用APP获取到的code值来请求详细用户信息。操作分两步:
1 使用code获取到access_token和openid,其中,access_token在下一步请求中作为参数使用,openid是改应用下这个微信号登录的唯一值,用于区分是否是相同用户,也是下一步请求的参数。
2 使用access_token获取详细的用户信息
```bash
public void login(String code, PrintWriter pw) {
//自定义的类,就两个参数,int类型处理是否成功,Object类型存放放回的数据
ReturnValue ret = new ReturnValue();
//第一步请求的地址,appid和secret替换成自己申请下来的即可,code就是手机获取到的参数
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="
+ "wxd167f2424d470758&secret=a0669d1ae25004db9de44520c519abf5&code="
+ code + "&grant_type=authorization_code";
//使用http请求数据,其中,HttpUtil是自己的辅助类,代码会放到后面
String result = HttpUtil.sendGet(url, "utf-8");
System.out.println(result);
//我使用的fastjson,可以根据自己使用的jar包调整数据获取格式
JSONObject resultJson = JSONObject.parseObject(result);
//处理不正确的code情况,我只判断了400029,如果有完整微信错误字典,可以走框架流程
if(resultJson.containsKey("errcode") && resultJson.getIntValue("errcode") == 40029) {
ret.setResult(ReturnValue.FAIL);
ret.setData("code不合法");
pw.write(JSON.toJSONString(ret));
return;
}
if(resultJson.containsKey("access_token")) {
//下一步请求需要使用的access_token
String access_token = resultJson.getString("access_token");
//该APP下使用微信登录获取到的唯一值
String openid = resultJson.getString("openid");
//使用两个参数请求用户信息
String url2 = "https://api.weixin.qq.com/sns/userinfo?access_token="
+ access_token + "&openid=" + openid;
//userInfo就是需要的数据,为了方便比对,提供下我获取到的数据
//{"openid":"oHNVDwA8k3H2VQ3mHbXcplVmqm_U","nickname":"昵称改了下","sex":0,"language":"zh_CN","city":"","province":"","country":"CN","headimgurl":"http:\/\/wx.qlogo.cn\/mmopen\/80xzo4yia4MU5IKoUgylvFfRT1QMoVIdlZWs1icEVo3Klkknvy3MOJZuqbetdnqEw1Oibweg7jxBJT3DImlulVf5PYktSUx1QM7\/0","privilege":[],"unionid":"oJT7XwLrV90jbhO9BJSPmMkQ5-8I"}
//各个参数的含义可以参见文档。获取到参数之后,根据自己的业务需要选择即可
String userInfo = HttpUtil.sendGet(url2, "utf-8");
ret.setResult(ReturnValue.SUCCESS);
ret.setData(userInfo);
pw.write(JSON.toJSONString(ret));
}
}
```
处理好用户信息后,APP跳转到主页面即可。
以下是我使用的HttpUtil
```bash
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Map;
public class HttpUtil {
public static String sendGet(String url, String charset) {
String result = "";
BufferedReader in = null;
try {
URL realUrl = new URL(url);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(
connection.getInputStream(), charset));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
public static String sendPostUrl(String url, String param, String charset) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
in = new BufferedReader(new InputStreamReader(
conn.getInputStream(), charset));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
@SuppressWarnings("deprecation")
public static String sendPost(String url, Map<String, String> param,
String charset) {
StringBuffer buffer = new StringBuffer();
if (param != null && !param.isEmpty()) {
for (Map.Entry<String, String> entry : param.entrySet()) {
buffer.append(entry.getKey()).append("=")
.append(URLEncoder.encode(entry.getValue()))
.append("&");
}
}
buffer.deleteCharAt(buffer.length() - 1);
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
out.print(buffer);
out.flush();
in = new BufferedReader(new InputStreamReader(
conn.getInputStream(), charset));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}
```
