前言: 如今大多數網站項目都支持微信登陸,付款,以及支付寶登陸付款,這種方式也是可以讓用戶很快速便捷的註冊本網站的帳號,進行登陸,以及後續的操做。相信小夥伴們看完以後,會對怎麼與微信或者支付寶服務器打交道有很深的理解,就當作是一個敲門磚吧。那麼本篇主要針對微信的驗證登陸來打開通往微信服務器的大門,下一篇會主要講解一下支付寶付款驗證對接。html
本篇爲原創,轉載請標出處:http://www.cnblogs.com/gudu1/p/8087130.html 前端
首先呢,須要有一個微信公衆號,固然是收費的,不過呢,微信給咱們開發人員提供的有微信公衆測試號來進行開發測試,雖然提供功能不是很全,開發測試是夠用了,仍是很到位的java
微信測試號地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/logingit
掃碼登錄後呢,就是下面這個樣子了:web
上面這個appID 和 appsecret 很重要,正常的公衆號appsecret 必定不要暴露出去,否則是很危險的。spring
>> URL: 規則 http://服務器地址或者域名/項目驗證接口路由,好比: http://www.mmm.cn/testProject/weChat.do,這個weChat.do 就是要跟微信服務器驗證對接的接口json
>> Token:這個是由用戶本身來定義,主要是咱們的項目跟微信服務器對接時候須要進行SHA1加密和解密的字段之一,詳細請看官方文檔:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319api
>> 域名: 這裏就須要使用你本身的域名了,固然域名是要指向可以被外網訪問到的服務器地址。數組
當咱們在接口配置信息這裏點擊提交,微信服務器就會向咱們的服務器接口發送消息來進行驗證是否正確。瀏覽器
固然還須要修改一處,這裏須要的仍是服務器域名:
這裏呢,我使用的 Spring 來作:
import java.io.IOException; import java.io.PrintWriter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.util.wechat.SignUtil; @Controller @RequestMapping("wechat") public class WechatController { private static Logger log = LoggerFactory.getLogger(WechatController.class); @RequestMapping(method = { RequestMethod.GET }) public void doGet(HttpServletRequest request, HttpServletResponse response) { log.debug("weixin get..."); // 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。
String signature = request.getParameter("signature"); // 時間戳
String timestamp = request.getParameter("timestamp"); // 隨機數
String nonce = request.getParameter("nonce"); // 隨機字符串
String echostr = request.getParameter("echostr"); // 經過檢驗signature對請求進行校驗,若校驗成功則原樣返回echostr,表示接入成功,不然接入失敗
PrintWriter out = null; try { out = response.getWriter(); if (SignUtil.checkSignature(signature, timestamp, nonce)) { log.debug("weixin get success....");
// 咱們驗證經過,肯定是微信發過來的消息,就將 隨機字符串原樣返回給微信 out.print(echostr); } } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) out.close(); } } }
微信發過來的消息會到這裏,能夠看到咱們的Controller的RequestMappiing 路由爲 "wechat", 這就是咱們在微信測試號中配置的接口路由地址,很明顯,這是一個Servlet ,請求的是doGet 方法。
上面代碼只是咱們的入口,主要驗證邏輯在下面:
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** * 微信請求校驗工具類 */
public class SignUtil { // 與接口配置信息中的Token要一致
private static String token = "mytoken"; /** * 驗證簽名 * * @param signature * @param timestamp * @param nonce * @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) { String[] arr = new String[] { token, timestamp, nonce }; // 將token、timestamp、nonce三個參數進行字典序排序
Arrays.sort(arr); // 將三個字符串排序以後合併爲一個
StringBuilder content = new StringBuilder(); for (int i = 0; i < arr.length; i++) { content.append(arr[i]); } MessageDigest md = null; String tmpStr = null; try { // 得到SHA-1 加密的對象
md = MessageDigest.getInstance("SHA-1"); // 將三個參數字符串拼接成一個字符串進行sha1加密
byte[] digest = md.digest(content.toString().getBytes()); // 將字節數組轉換爲十六進制字符串
tmpStr = byteToStr(digest); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } content = null; // 將sha1加密後的字符串可與signature對比,標識該請求來源於微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false; } /** * 將字節數組轉換爲十六進制字符串 * * @param byteArray * @return
*/
private static String byteToStr(byte[] byteArray) { String strDigest = ""; for (int i = 0; i < byteArray.length; i++) { strDigest += byteToHexStr(byteArray[i]); } return strDigest; } /** * 將字節轉換爲十六進制字符串 * * @param mByte * @return
*/
private static String byteToHexStr(byte mByte) { char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] tempArr = new char[2]; tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; tempArr[1] = Digit[mByte & 0X0F]; String s = new String(tempArr); return s; } }
細心看的小夥伴確定已經注意到了, 在方法上面有一個字段就是token,這裏必定要跟微信測試號中配置的保持 一致,不相信的同窗能夠測試着改一個不一致的,保證過不了。
這裏的流程就是,微信首先 把咱們的 token+時間戳+隨機數 進行一次SHA-1 加密,而後發送給咱們的程序,而後咱們再進行一次SHA-1加密,而後跟微信發送過來的加密數據進行匹配。咱們想象一下,有人故意破壞咱們的程序,而後模擬發送數據,能嗎?答案是能,不過他首先得拿到咱們的token字段,才能使咱們的程序給出正確響應,因此這裏的token 配置必定要和微信測試號中配置保持一致。
好了,既然極影和微信服務器連通了,那麼就開始咱們的業務需求登陸操做吧。
用戶使用微信登陸,確定是須要向微信服務器發送登陸請求,而後微信再回調咱們的接口,接口獲取微信服務器傳遞過來的信息,肯定用戶是否登陸成功,包括用戶的一些數據,好比:暱稱,頭像地址 等等。
首先要知道這些接口是誰定義的,確定是微信定義的了,那咱們怎麼知道向哪一個接口發送消息,這就須要查看微信給咱們提供的官方文檔了。
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect , 該地址是程序向微信發送的第一條請求,請求參數中攜帶code
下面來分析一下這條URL:
appid:應用惟一標識,也就是微信測試號中的第一條 appid
redirect_uri:使用urlEncode對連接進行處理,也就是微信服務器回調地址,地址就指向咱們程序中處理用戶是否登陸的接口
response_type: 就填寫 code
scope:應用受權做用域,擁有多個做用域用逗號(,)分隔,網頁應用目前僅填寫snsapi_login便可
state:用於保持請求和回調的狀態,受權請求後原樣帶回給第三方。該參數可用於防止csrf攻擊(跨站請求僞造攻擊),建議第三方帶上該參數,可設置爲簡單的隨機數加session進行校驗
接下來就貼一下個人程序代碼幫助理解:
import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod;import com.util.wechat.WechatUtil; /** * 獲取關注公衆號以後的微信用戶信息的接口,若是在微信瀏覽器裏訪問 * https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd7f6c5b8899fba83&redirect_uri=http://o2o.yitiaojieinfo.com/o2o/wechatlogin/logincheck&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect * 則這裏將會獲取到code,以後再能夠經過code獲取到access_token 進而獲取到用戶信息 * @author xiangze * */ @Controller @RequestMapping("wechatlogin") public class WechatLoginController { private static Logger log = LoggerFactory.getLogger(WechatLoginController.class); private static final String FRONTEND = "1"; private static final String SHOPEND = "2"; @Autowired private PersonInfoService personInfoService; @Autowired private WechatAuthService wechatAuthService; @RequestMapping(value = "/logincheck", method = { RequestMethod.GET }) public String doGet(HttpServletRequest request, HttpServletResponse response) { log.debug("weixin login get..."); // 獲取微信公衆號傳輸過來的code,經過code可獲取access_token,進而獲取用戶信息
String code = request.getParameter("code"); // 這個state能夠用來傳咱們自定義的信息,方便程序調用,這裏也能夠不用
String roleType = request.getParameter("state"); log.debug("weixin login code:" + code); WechatUser user = null; String openId = null; WechatAuth auth = null; if (null != code) { UserAccessToken token; try { // 經過code獲取access_token
token = WechatUtil.getUserAccessToken(code); log.debug("weixin login token:" + token.toString()); // 經過token獲取accessToken
String accessToken = token.getAccessToken(); // 經過token獲取openId
openId = token.getOpenId(); // 經過access_token和openId獲取用戶暱稱等信息
user = WechatUtil.getUserInfo(accessToken, openId); log.debug("weixin login user:" + user.toString()); request.getSession().setAttribute("openId", openId); auth = wechatAuthService.getWechatAuthByOpenId(openId); } catch (IOException e) { log.error("error in getUserAccessToken or getUserInfo or findByOpenId: " + e.toString()); e.printStackTrace(); } } // 若微信賬號爲空則須要註冊微信賬號,同時註冊用戶信息
if (auth == null) { PersonInfo personInfo = WechatUtil.getPersonInfoFromRequest(user); auth = new WechatAuth(); auth.setOpenId(openId); if (FRONTEND.equals(roleType)) { personInfo.setUserType(1); } else { personInfo.setUserType(2); } auth.setPersonInfo(personInfo); WechatAuthExecution we = wechatAuthService.register(auth); if (we.getState() != WechatAuthStateEnum.SUCCESS.getState()) { return null; } else { personInfo = personInfoService.getPersonInfoById(auth.getPersonInfo().getUserId()); request.getSession().setAttribute("user", personInfo); } } else { request.getSession().setAttribute("user", auth.getPersonInfo()); } // 若用戶點擊的是前端展現系統按鈕則進入前端展現系統
if (FRONTEND.equals(roleType)) { return "frontend/index"; } else { return "shop/shoplist"; } } }
調用接口:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
代碼:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper;/** * 微信工具類 * * @author xiangze * */
public class WechatUtil { private static Logger log = LoggerFactory.getLogger(WechatUtil.class); /** * 獲取UserAccessToken實體類 * * @param code * @return * @throws IOException */
public static UserAccessToken getUserAccessToken(String code) throws IOException { // 測試號信息裏的appId
String appId = "你的微信測試號appid"; log.debug("appId:" + appId); // 測試號信息裏的appsecret
String appsecret = "你的微信測試號appsecret"; log.debug("secret:" + appsecret); // 根據傳入的code,拼接出訪問微信定義好的接口的URL
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + appsecret + "&code=" + code + "&grant_type=authorization_code"; // 向相應URL發送請求獲取token json字符串
String tokenStr = httpsRequest(url, "GET", null); log.debug("userAccessToken:" + tokenStr); UserAccessToken token = new UserAccessToken(); ObjectMapper objectMapper = new ObjectMapper(); try { // 將json字符串轉換成相應對象
token = objectMapper.readValue(tokenStr, UserAccessToken.class); } catch (JsonParseException e) { log.error("獲取用戶accessToken失敗: " + e.getMessage()); e.printStackTrace(); } catch (JsonMappingException e) { log.error("獲取用戶accessToken失敗: " + e.getMessage()); e.printStackTrace(); } catch (IOException e) { log.error("獲取用戶accessToken失敗: " + e.getMessage()); e.printStackTrace(); } if (token == null) { log.error("獲取用戶accessToken失敗。"); return null; } return token; } /** * 發起https請求並獲取結果 * * @param requestUrl * 請求地址 * @param requestMethod * 請求方式(GET、POST) * @param outputStr * 提交的數據 * @return json字符串 */
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) { StringBuffer buffer = new StringBuffer(); try { // 建立SSLContext對象,並使用咱們指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從上述SSLContext對象中獲得SSLSocketFactory對象
SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); // 設置請求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod); if ("GET".equalsIgnoreCase(requestMethod)) httpUrlConn.connect(); // 當有數據須要提交時
if (null != outputStr) { OutputStream outputStream = httpUrlConn.getOutputStream(); // 注意編碼格式,防止中文亂碼
outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 將返回的輸入流轉換成字符串
InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 釋放資源
inputStream.close(); inputStream = null; httpUrlConn.disconnect(); log.debug("https buffer:" + buffer.toString()); } catch (ConnectException ce) { log.error("Weixin server connection timed out."); } catch (Exception e) { log.error("https request error:{}", e); } return buffer.toString(); } }
由於咱們發送的是https請求,因此還須要有這麼一個工具方法,發起 https 請求。。。
OK,以上就是微信驗證以及登陸的全過程,可能並非很詳細,不過理解這個過程應該是能夠了,那麼下篇會講解一下支付寶付款的流程以及代碼。