微信公众号开发:玩转OAuth2.0授权

什么是OAuth2.0协议(它能干什么)?

OAuth(开放授权)是一个开放授权标准,允许用户让第三方应用通过指定方式获取短期有效的访问令牌,代替用户密码,在令牌权限范围内访问资源服务器上的特定保护资源,从而用户无需与第三方应用共享用户名和密码。

那什么是开放授权,最直接的例子就是在登录一些网站的时候使用第三方账户进行登录

OAuth 2.0提供了四种具体的授权流程:

  1. 授权码流程(authorization code)
  2. 隐式许可流程(implicit)
  3. 用户密码流程(resource owner password credentials)
  4. 客户机凭据流程(client credentials)

OAuth 2.0设计上的安全性考虑:

为何引入authorization_code?

协议设计中,为什么要使用authorization_code来交换access_token?这是读者容易想到的一个问题。也就是说,在协议的第3步,为什么不直接将access_token通过重定向方式返回给Client呢?比如:

HTTP/1.1 302

Location:

https://www.facebook.com/?access_token=ya29.AHES6ZSXVKYTW2VAGZtnMjD&token_type=Bearer&expires_in=3600

如果直接返回access_token,协议将变得更加简洁,而且少一次Client与AS之间的交互,性能也更优。那为何不这么设计呢?协议文档[1]中并没有给出这样设计的理由,但也不难分析:

  1. 浏览器的redirect_uri是一个不安全信道,此方式不适合于传递敏感数据(如access_token)。因为uri可能通过HTTP referrer被传递给其它恶意站点,也可能存在于浏览器cacher或log文件中,这就给攻击者盗取access_token带来了很多机会。另外,此协议也不应该假设RO用户代理的行为是可信赖的,因为RO的浏览器可能早已被攻击者植入了跨站脚本用来监听access_token。因此,access_token通过RO的用户代理传递给Client,会显著扩大access_token被泄露的风险。 但authorization_code可以通过redirect_uri方式来传递,是因为authorization_code并不像access_token一样敏感。即使authorization_code被泄露,攻击者也无法直接拿到access_token,因为拿authorization_code去交换access_token是需要验证Client的真实身份。也就是说,除了Client之外,其他人拿authorization_code是没有用的。 此外,access_token应该只颁发给Client使用,其他任何主体(包括RO)都不应该获取access_token。协议的设计应能保证Client是唯一有能力获取access_token的主体。引入authorization_code之后,便可以保证Client是access_token的唯一持有人。当然,Client也是唯一的有义务需要保护access_token不被泄露。
  2. 引入authorization_code还会带来如下的好处。由于协议需要验证Client的身份,如果不引入authorization_code,这个Client的身份认证只能通过第1步的redirect_uri来传递。同样由于redirect_uri是一个不安全信道,这就额外要求Client必须使用数字签名技术来进行身份认证,而不能用简单的密码或口令认证方式。引入authorization_code之后,AS可以直接对Client进行身份认证,而且可以支持任意的Client认证方式(比如,简单地直接将Client端密钥发送给AS)。

在我们理解了上述安全性考虑之后,读者也许会有豁然开朗的感觉,懂得了引入authorization_code的妙处。那么,是不是一定要引入authorization_code才能解决这些安全问题呢?当然不是。笔者将会在另一篇博文给出一个直接返回access_token的扩展授权类型解决方案,它在满足相同安全性的条件下,使协议更简洁,交互次数更少。

基于Web安全的考虑:

OAuth协议设计不同于简单的网络安全协议的设计,因为OAuth需要考虑各种Web攻击,比如CSRF (Cross-Site Request Forgery), XSS (Cross Site Script), Clickjacking。要理解这些攻击原理,读者需要对浏览器安全(eg, Same Origin Policy, 同源策略)有基本理解。比如,在redirect_uri中引入state参数就是从浏览器安全角度考虑的,有了它就可以抵制CSRF攻击。如果没有这个参数,攻击者便可以在redirect_uri中注入攻击者提供的authorization_code或access_token,结果可能导致Client访问错误的资源(比如,将款项汇到一个错误的帐号)。

微信公众号授权案例:

官方文档:网页授权 | 微信开放文档

获取openid: https://api.weixin.qq.com/sns/jscode2session?appid=wxa6417cc7dd07745b&secret=cda9d51073b3e58d68d7fe8650ed0ca2&js_code=081TFDll2IlCca41UWml2aigWE0TFDl9&grant_type=authorization_code

获取access_token:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wxa6417cc7dd07745b&secret=cda9d51073b3e58d68d7fe8650ed0ca2

JAVA实现微信授权登录(授权码流程)

第一步:(前期设置)登录微信公众号接口测试平台设置信息

测试号申请链接: 微信公众平台

登录成功后可以看到测试用的appid和appsecret,稍后再后台我们要用到这两个ID,如下图

紧接着需要设置网页授权(体验接口权限表 —》 网页服务 —》网页帐号 —》 网页授权获取用户基本信息)

没有域名的话可以用内网穿透动态解析一个域名。

NATAPP链接:点击注册

注册登录成功后可以看到下图,选择免费隧道

购买免费的隧道之后,可以直接按照官方的一分钟教程完成内网穿透,这样我们就拿到了域名

第二步:代码实现微信授权。

简单来说,微信授权分为四步:

  1. 授权登录接口。
  2. 用户点击授权。
  3. 微信授权回调接口。
  4. 在回调接口中获取openid、access_token、获取用户信息。

第一步:先上工具类AuthUtil

public class AuthUtil {
	public static JSONObject doGetJson(String url) throws ClientProtocolException, IOException {
		JSONObject jsonObject = null;
		DefaultHttpClient client = new DefaultHttpClient();
		HttpGet httpGet = new HttpGet(url);
		HttpResponse response = client.execute(httpGet);
		HttpEntity entity = response.getEntity();
		if (entity != null) {
			String result = EntityUtils.toString(entity, "UTF-8");
			jsonObject = JSONObject.fromObject(result);
		}
		httpGet.releaseConnection();
		return jsonObject;
	}
}

第二步:WxAuthorizeController的微信授权接口

   /**
    * Tea微信登录接口
    * @throws IOException 
    */
   @ApiOperation(value = "微信登录接口")
   @IgnoreAuth
   @RequestMapping("wx_login")
   public void wxLogin(HttpServletResponse response) throws IOException{
   	//域名(暂时写死的)
   	String sym = "http://c8d3v2.natappfree.cc";
       //这里是回调的url
       String redirect_uri = URLEncoder.encode(sym+"/front/auth/callBack", "UTF-8");
       String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
               "appid=APPID" +
               "&redirect_uri=REDIRECT_URI"+
               "&response_type=code" +
               "&scope=SCOPE" +
               "&state=123#wechat_redirect";
       response.sendRedirect(url.replace("APPID",WxConstant.APPID).replace("REDIRECT_URI",redirect_uri).replace("SCOPE","snsapi_userinfo"));
   }

参数说明如下:

拓展:

response_type:必选,希望授权服务器采用哪种Oauth 2.0流程来响应,code代表授权码流程。

state:推荐,不透明字符串,当授权服务器重定向到redirect_uri时,会原样返回给客户机应用,用于防止跨站请求伪造攻击(CSRF、 XSRF)。由于授权服务器会原样返回此参数,可将state值与用户在客户机应用最后浏览的URI绑定,便于授权完成后将用户重定向回最后浏览的页面。

第三步:WxAuthorizeController微信授权登录回调接口

	/**
	 * Tea微信授权成功的回调函数
	 * 
	 * @param request
	 * @param response
	 * @throws ClientProtocolException
	 * @throws IOException
	 * @throws ServletException
	 */
    @ApiOperation(value = "微信授权回调接口")
    @IgnoreAuth
	@RequestMapping("/callBack")
	protected void deGet(HttpServletRequest request, HttpServletResponse response)throws Exception {
    	//获取回调地址中的code
		String code = request.getParameter("code");
		//拼接url
		String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + WxConstant.APPID + "&secret="
				+ WxConstant.APPSECRET + "&code=" + code + "&grant_type=authorization_code";
		JSONObject jsonObject = AuthUtil.doGetJson(url);
		//1.获取微信用户的openid
		String openid = jsonObject.getString("openid");
		//2.获取获取access_token
		String access_token = jsonObject.getString("access_token");
		String infoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=" + access_token + "&openid=" + openid
				+ "&lang=zh_CN";
		//3.获取微信用户信息
		JSONObject userInfo = AuthUtil.doGetJson(infoUrl);
		//至此拿到了微信用户的所有信息,剩下的就是业务逻辑处理部分了
		//保存openid和access_token到session
		request.getSession().setAttribute("openid", openid);
		request.getSession().setAttribute("access_token", access_token);
		//去数据库查询此微信是否绑定过手机
		UserVo user = userService.queryByOpenId(openid);
		String mobile=user==null?"":user.getMobile();
		
		if(null == mobile || "".equals(mobile)){
			//如果无手机信息,则跳转手机绑定页面
			response.sendRedirect("/front/register.html");
		}else{
			//否则直接跳转首页
			response.sendRedirect("/front/index.html");
		}
	}

注意:code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。

至此,后台的代码暂时就这么多,剩下的需要前台页面了。

前端页面JS:index.js

var vm = new Vue({
    el: '#rrapp',
    data: {
    },
    methods: {
    //方法说明:首页加载时去查询Session中有没有存储openid,如果没有存储说明未经过授权,需要去授权页面
        getOpenId: function () {
            ApiAjax.request({
            	//此接口部分代码过于简单就不展示了
                url: '/front/auth/getOpenId',
                params: "",
                type: "POST",
                async: true,
                successCallback: function (r) {
                	if(r.data.openid == null){
                		//返回的openid如果为空就进入微信授权接口
                		window.location.href = "/front/auth/wx_login";
                	}
                }
            });
        }
    },
    mounted: function() {
		//页面渲染完成后执行该方法
    	this.getOpenId();
    }
});
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值