大家好,今天聊聊微信登录

微信登录我写过的有三种登录方法(授权登录、静默登录、扫码登录)这里说一下我的看法(个人观点,欢迎拍砖)。
1、授权登录需要拉起需要用户确认的授权页(`・ω・´)
这里写图片描述
有些人可能会不喜欢,例如我老板(T_T),此方式可以获取比较多的信息,例如头像、昵称、openid、unionid、是否关注公众号等信息
个人认为此种授权方式比较适合纯在微信端运营的网站,如果不考虑脱离微信端的话。
2、静默登录,这个当前环境下很流行,用appid去获取code然后获取用户的openid,拿到openid之后就可以进行自己业务处理了,从体验上来说用户无感,没有上面那个授权页,个人认为此种授权方式比较适合较为独立或者正在考虑脱离微信的网站,
3、扫码登录,此种方式一般适用于电商网站的PC登录,如下图
这里写图片描述
好啦,开始介绍开发流程。

1、授权登录:
a)、首先需要在公众号取得相应的获取用户基本信息的权限。推荐适用拦截器的方式拦截部分需要授权的功能,例如钱包、个人中心等,对于网站介绍,或者一些推广页、首页这些不需要授权可以提升一下用户体验,虽然从开发的角度看意义不大,拦截代码如下

package com.xxx.filter;

import com.xxx.util.CookieUtil;
import com.xxx.util.IPUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class WeChatUserFilter implements Filter {

    protected static final Logger LOG = LoggerFactory.getLogger(WeChatUserFilter.class);

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest _request, ServletResponse _response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) _request;
        HttpServletResponse response = (HttpServletResponse) _response;
        // 获取当前访问路径
        String path = request.getRequestURI().toString();
        // 拦截地址配置
        String[] filterUrls = { "/home", "/XXXX/XXX", "/XXX/XXXX" };
        for (String url : filterUrls) {
            if (path.contains(url)) {
                // 拦截
                LOG.info("拦截的地址是:" + path + ",ip:" + IPUtil.getIpAddr(request));
                String openid = CookieUtil.getCookieValue(request, "openid");
                if (StringUtils.isNotBlank(openid)) {
                    chain.doFilter(_request, _response);
                    return;
                }
                if (StringUtils.isNotBlank(request.getQueryString())) {
                    path += "?" + request.getQueryString();
                }
                response.sendRedirect(response.encodeURL("/user/login?u=" + response.encodeURL(path)));
                return;
            }
        }
        chain.doFilter(_request, _response);
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub

    }

}

b)、如果用户没有进行过授权则会跳转到/user/login中进行登录,完整的登录代码如下

@RestController
@RequestMapping(value = "user")
public class LoginController {
    protected static final Logger LOG = LoggerFactory.getLogger(LoginController.class);

    // 公众号APPID
    public static final String WX_APPID = "XXXXXXXXXXXX";
    // 公众号SECRET
    public static final String WX_APP_SECRET = "XXXXXXXXXXXX";

    @RequestMapping(value = "login", method = RequestMethod.GET)
    public void toLogin(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 获取cookie中的openid
        String openid = CookieUtil.getCookieValue(request, "openid");
        String unionid = CookieUtil.getCookieValue(request, "unionid");
        // 定义微信授权之后的跳转地址命名方式为(http://你的网址+/user/login?u=)
        String reUrl = "";
        // 获取需要跳转的url
        String u = request.getParameter("u");
        LOG.error("需要跳转的url为:", u);
        // 判断如果openid不存在,则获取code
        // 如果拦截到的跳转的地址为空则给个默认值
        if (StringUtils.isBlank(u)) {
            u = "/indexController/index";
        }
        if (StringUtils.isBlank(openid) || StringUtils.isBlank(unionid)) {
            // 获取code
            String code = request.getParameter("code");
            LOG.error("code:", code);
            if (StringUtils.isBlank(code)) {
                // 跳转到微信端获取code
                String redrictUrl = response.encodeURL(reUrl + response.encodeURL(u));
                String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WX_APPID + "&redirect_uri="
                        + redrictUrl + "&response_type=code&scope=snsapi_userinfo&state=0#wechat_redirect";
                try {
                    response.sendRedirect(url);
                } catch (IOException e) {
                    // logger.error("url:重定向错误");
                }
                return;
            } else {
                // 获取用户信息
                JSONObject object = getSnsapiUserinfo(request, code);
                if (object != null) {
                    String user_unionid = object.getString("unionid");
                    String user_openid = object.getString("openid");
                    if (StringUtils.isBlank(user_unionid) || StringUtils.isBlank(user_openid)) {
                        return;
                    }
                    String headimgurl = object.getString("headimgurl");
                    String nickname = EmojiFilter.filterEmoji(object.getString("nickname"));
                }

            }
        }
        // 返回客户访问页面
        try {
            response.sendRedirect(u);
        } catch (IOException e) {
            // logger.error("url:重定向错误");
        }
    }

    public static JSONObject getSnsapiUserinfo(HttpServletRequest request, String code)
            throws org.apache.commons.httpclient.HttpException {
        JSONObject wxuser;

        String t_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + WX_APPID + "&secret="
                + WX_APP_SECRET + "&code=" + code + "&grant_type=authorization_code";
        Proxy httproxy = new Proxy();
        final String result = httproxy.get(t_url, null);
        LOG.info("sns/oauth2/access_token:" + result);
        JSONObject obj = JSON.parseObject(result);
        String openid = obj.getString("openid");
        // 判断用户是否存在
        String t_access_token = obj.getString("access_token");
        String t_url_2 = "https://api.weixin.qq.com/sns/userinfo?access_token=" + t_access_token + "&openid=" + openid;
        final String res2 = httproxy.get(t_url_2, null);
        wxuser = JSON.parseObject(res2);
        LOG.info("网页授权获取用户基本信息:" + wxuser.toString());
        return wxuser;
    }
}

2、静默登录 静默登录只需在授权登录的基础之上更改scope参数的值即可 如下

String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WX_APPID + "&redirect_uri="
                        + redrictUrl + "&response_type=code&scope=     snsapi_base&state=0#wechat_redirect";

3、扫码登录就简单很多了
只需拉起微信扫码页然后设置扫码成功之后的回调方法即可,具体如下:
a)、拉起微信扫码登录页

阿里云-推广AD
String WX_SCAN_CODE_URL = "https://open.weixin.qq.com/connect/qrconnect?appid={APPID}&redirect_uri={REUTL}&response_type=code&scope=snsapi_login&state={STATE}#wechat_redirect";
    // 千万要记住,这个是微信开放平台的APPID
    String WX_PLATFROM_APPID = "XXXXXX";
    // 你的回调地址
    String scanReUrl = "http://你的网址/user/wxLoginCallback";

    /**
     * 微信扫码登陆
     * 
     * @param request
     * @param response
     */
    @RequestMapping(value = "weixinScanLogin", method = RequestMethod.GET)
    public void weixinRetrun(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 获取回调url(非必填,只是附带上你扫码之前要进入的网址,具体看业务是否需要)
        String url = request.getParameter("reurl");
        // 拼接扫码登录url
        String wxLoginurl = WX_SCAN_CODE_URL;
        wxLoginurl = wxLoginurl.replace("{APPID}", WX_PLATFROM_APPID).replace("{REUTL}", scanReUrl).replace("{STATE}",
                url);
        wxLoginurl = response.encodeURL(wxLoginurl);
        response.sendRedirect(wxLoginurl);
    }

b)、微信扫码登录的回调地址

// 千万要记住,这个是微信开放平台的APPID
    String WX_PLATFROM_APPID = "XXXXXXXXX";
    // 千万要记住,这个是微信开放平台的APPSECRET
    String WX_PLATFORM_APPSECRET = "XXXXXXXXXX";
    // 拉起微信扫码页地址
    String WX_SCAN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
    // 微信扫码之后获取用户基本信息的地址
    String WX_SCAN_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";

    /**
     * 微信扫码登录回调
     * 
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "wxLoginCallback", method = RequestMethod.GET)
    public void loginCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String code = request.getParameter("code");
        if (code == null) {
            // 用户禁止授权
        }
        String url = WX_SCAN_URL.replace("APPID", WX_PLATFROM_APPID).replace("SECRET", WX_PLATFORM_APPSECRET)
                .replaceAll("CODE", code);
        url = response.encodeURL(url);
        try {
            String result = HttpClientUtils.get(url, null);
            Map<String, Object> resultMap = JsonHelper.toMap(result);
            String unionid = (String) resultMap.get("unionid");
            String access_token = (String) resultMap.get("access_token");
            String openid = (String) resultMap.get("openid");
            // 这里可以根据获取的信息去库中判断是否存在库中 如果不存在执行以下方法
            // 如果该用户不存在数据库中
            // 获取用户信息
            url = ConstantHelper.WX_SCAN_GET_USER_INFO.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid);
            url = response.encodeURL(url);
            String userResult = HttpClientUtils.get(url, null);
            Map<String, Object> userResultMap = JsonHelper.toMap(userResult);
            // 注册一个用户
            System.out.println("扫码登录返回值******************:" + userResult);
            String headimgurl = (String) userResultMap.get("headimgurl");
            // 处理微信名特殊符号问题 过滤图标
            String nickname = (String) userResultMap.get("nickname");
            // 把用户信息存入session中
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 返回地址
        try {
            String newUrl = request.getParameter("state");
            response.sendRedirect(newUrl);
        } catch (IOException e) {
            // logger.error("url:重定向错误");
        }
    }

下面附上几个工具类

public class Proxy {
    // const
    public final static int DEFAULT_CONNECT_TIMEOUT = 15000 * 2;
    public final static int DEFAULT_SO_TIMEOUT = 30000 * 2;
    public final static int DEFAULT_BUFFER_SIZE = 256;
    public final static int DEFAULT_MAX_CONNECTIONS = 200 * 2;

    private final static String CS_PREFIX = "charset=";
    private final static int CS_PREFIX_LEN = CS_PREFIX.length();

    // 数据编码格式
    private final String encoding;
    // client
    private final HttpClient client;
    // buffer
    private final int bufferSize;

    public Proxy() {
        this("UTF-8", DEFAULT_CONNECT_TIMEOUT, DEFAULT_SO_TIMEOUT, DEFAULT_BUFFER_SIZE, DEFAULT_MAX_CONNECTIONS);
    }

    public Proxy(final String encoding) {
        this(encoding, DEFAULT_CONNECT_TIMEOUT, DEFAULT_SO_TIMEOUT, DEFAULT_BUFFER_SIZE, DEFAULT_MAX_CONNECTIONS);
    }

    public Proxy(final int bufferSize) {
        this("UTF-8", DEFAULT_CONNECT_TIMEOUT, DEFAULT_SO_TIMEOUT, bufferSize, DEFAULT_MAX_CONNECTIONS);
    }

    public Proxy(final String encoding, final int connectTimeout, final int soTimeout) {
        this(encoding, connectTimeout, soTimeout, DEFAULT_BUFFER_SIZE, DEFAULT_MAX_CONNECTIONS);
    }

    public Proxy(final String encoding, final int connectTimeout, final int soTimeout, final int bufferSize) {
        this(encoding, connectTimeout, soTimeout, bufferSize, DEFAULT_MAX_CONNECTIONS);
    }

    public Proxy(final String encoding, final int connectTimeout, final int soTimeout, final int bufferSize, final int maxConnections) {
        this.encoding = encoding;
        // connect-parameters
        final HttpConnectionManagerParams mp = new HttpConnectionManagerParams();
        mp.setConnectionTimeout(connectTimeout);
        mp.setSoTimeout(soTimeout);
        mp.setStaleCheckingEnabled(true);
        mp.setTcpNoDelay(true);
        mp.setMaxTotalConnections(maxConnections);
        final HttpConnectionManager mgr = new SimpleHttpConnectionManager();
        mgr.setParams(mp);
        // client-init
        this.client = new HttpClient(mgr);
        // client-parameters
        final HttpClientParams cparams = new HttpClientParams();
        // 设置httpClient的连接超时,对连接管理器设置的连接超时是无用的
        cparams.setConnectionManagerTimeout(connectTimeout);
        this.client.setParams(cparams);
        this.bufferSize = bufferSize;
    }

    public HttpClient getClient() {
        return client;
    }

    public String getEncoding() {
        return encoding;
    }

    public String post(final String url, final Map<String, String> headers, final Map<String, String> data, final String encoding) throws HttpException {
        // post方式
        final PostMethod post = new PostMethod(url);
        // headers
        setHeaders(post, headers);
        // content
        if (data != null) {
            Set<Map.Entry<String, String>> postset = data.entrySet();
            final NameValuePair[] params = new NameValuePair[postset.size()];
            int i = 0;
            for (Iterator<Map.Entry<String, String>> it = postset.iterator(); it.hasNext();) {
                Map.Entry<String, String> p = it.next();
                params[i++] = new NameValuePair(p.getKey(), p.getValue());
            }
            post.setRequestBody(params);
        }
        try {
            return (execute(post, encoding));
        } finally {
            post.releaseConnection();
        }
    }

    public String post(final String url, final Map<String, String> data, final String encoding) throws HttpException {
        return post(url, null, data, encoding);
    }

    @SuppressWarnings("deprecation")
    public String post(final String url, final Map<String, String> headers, final String data, final String encoding) throws HttpException {
        // post方式
        final PostMethod post = new PostMethod(url);
        // headers
        setHeaders(post, headers);
        // content
        if (data != null) {
            try {
                final InputStream bin = new ByteArrayInputStream(data.getBytes(this.encoding));
                post.setRequestBody(bin);
            } catch (final UnsupportedEncodingException e) {
            }
        }
        try {
            return (execute(post, encoding));
        } finally {
            post.releaseConnection();
        }
    }

    public String post(final String url, final String data, final String encoding) throws HttpException {
        // post方式
        return post(url, null, data, encoding);
    }

    public String get(final String url, final Map<String, String> headers) throws HttpException {
        // get方式
        final GetMethod get = new GetMethod(url);
        try {
            // headers
            setHeaders(get, headers);
            return (execute(get, encoding));
        } finally {
            get.releaseConnection();
        }
    }

    private final HttpMethod setHeaders(final HttpMethod method, final Map<String, String> headers) {
        if (headers != null) {
            final Set<Map.Entry<String, String>> headset = headers.entrySet();
            for (Iterator<Map.Entry<String, String>> it = headset.iterator(); it.hasNext();) {
                Map.Entry<String, String> header = it.next();
                method.setRequestHeader(header.getKey(), header.getValue());
            }
        }
        return method;
    }

    private String execute(final HttpMethod method, final String encoding) throws HttpException {
        InputStream in = null;
        BufferedReader reader = null;
        try {
            client.executeMethod(method);
            // get-encoding
            String encode = encoding;
            final Header ctypeh = method.getResponseHeader("Content-Type");
            if (ctypeh != null) {
                final String cv = ctypeh.getValue();
                final String ctype;
                if (cv == null) {
                    ctype = null;
                } else {
                    ctype = cv.toLowerCase(Locale.ENGLISH);
                }
                final int i;
                if (ctype != null && (i = ctype.indexOf(CS_PREFIX)) != -1) {
                    encode = ctype.substring(i + CS_PREFIX_LEN).trim();
                    // test encoding
                    try {
                        "a".getBytes(encode);
                    } catch (UnsupportedEncodingException e) {
                        encode = encoding;
                    }
                    //
                }
            }
            //
            if (encode == null) {
                return (method.getResponseBodyAsString());
            }
            in = method.getResponseBodyAsStream();
            reader = new BufferedReader(new InputStreamReader(in, encode), bufferSize);
            final StringBuffer sbuf = new StringBuffer(bufferSize >>> 1);
            for (String line = reader.readLine(); line != null; line = reader.readLine()) {
                sbuf.append(line).append("\r\n");
            }
            return (sbuf.toString());
        } catch (IOException e) {
            throw new HttpException(e.getMessage());
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                }
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

下面这个工具类是去除微信名中的Emoji表情

public class EmojiFilter {

    /**
     * 检测是否有emoji字符
     * @param source
     * @return 一旦含有就抛出
     */
    public static boolean containsEmoji(String source) {
        if (StringUtils.isBlank(source)) {
            return false;
        }

        int len = source.length();

        for (int i = 0; i < len; i++) {
            char codePoint = source.charAt(i);

            if (isEmojiCharacter(codePoint)) {
                //do nothing,判断到了这里表明,确认有表情字符
                return true;
            }
        }

        return false;
    }

    private static boolean isEmojiCharacter(char codePoint) {
        return (codePoint == 0x0) ||
                (codePoint == 0x9) ||
                (codePoint == 0xA) ||
                (codePoint == 0xD) ||
                ((codePoint >= 0x20) && (codePoint <= 0xD7FF)) ||
                ((codePoint >= 0xE000) && (codePoint <= 0xFFFD)) ||
                ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF));
    }

    /**
     * 过滤emoji 或者 其他非文字类型的字符
     * @param source
     * @return
     */
    public static String filterEmoji(String source) {

        if (!containsEmoji(source)) {
            return source;//如果不包含,直接返回
        }
        //到这里铁定包含
        StringBuilder buf = null;

        int len = source.length();

        for (int i = 0; i < len; i++) {
            char codePoint = source.charAt(i);

            if (isEmojiCharacter(codePoint)) {
                if (buf == null) {
                    buf = new StringBuilder(source.length());
                }

                buf.append(codePoint);
            } else {
            }
        }

        if (buf == null) {
            return source;//如果没有找到 emoji表情,则返回源字符串
        } else {
            if (buf.length() == len) {//这里的意义在于尽可能少的toString,因为会重新生成字符串
                buf = null;
                return source;
            } else {
                return buf.toString();
            }
        }

    }
}

如果有什么问题咱们可以讨论一下(~ ̄▽ ̄)~
转载需注明出处,不然你会尝到正义的小拳拳。