OAuth协议,是一种受权协议,不涉及具体的代码,只是表示一种约定的流程和规范。OAuth协议通常用于用户决定是否把本身在某个服务商上面的资源(好比:用户基本资料、照片、视频等)受权给第三方应用访问。此外,OAuth2.0协议是OAuth协议的升级版,如今已经逐渐成为单点登陆(SSO)和用户受权的标准。php
不知道你们有没有发现,目前主流的互联网网站除了可使用“用户名+密码”模式和“手机号+验证码”模式登陆外,不少还提供了第三方帐号登陆,好比最多见的QQ登陆、微博登陆、百度帐号登陆、GitHub登陆。而这些第三方登陆方式就是采用了OAuth2.0协议实现。html
第一,用户再也不须要注册大量帐号。在之前,咱们每使用一个新的网站或者APP就须要注册一个帐号,创建一套新的帐户体系才能使用网站 / APP提供的服务。可是如今咱们只须要拥有几个主流应用的帐号,而后经过他们提供的第三方帐号登陆就可使用一个新的网站/APP了(固然,咱们也能够不使用腾讯百度等公司提供的受权服务,开发本身的受权服务端,这方面的内容我将放在下篇文章中介绍)。java
第二,用于单点登陆。若是某个公司有不少个须要用户登陆才能提供服务的子产品(好比:官网、M网站、APP、微信公众号、使用同一套帐户体系的产品一、产品2等等),这种状况下为每一个产品都开发一个登陆、受权模块显然是不太优雅,所以比较好的解决方案就是全部须要登陆的产品都请求同一个登陆受权中心,进行统一登陆受权处理。而OAuth2.0协议就能够实现符合上述要求的单点登陆功能。git
第三,用于分布式系统的权限控制。由于基于OAuth2.0协议得到的令牌(Access Token
)同时关联了接入的第三方应用、受权用户、权限范围等信息。所以,在第三方应用拿着Token请求资源的时候,资源服务应用就能够很容易根据其访问权限返回相应的数据。web
Access Token
)。这种模式的特色是全部步骤都在浏览器中完成,Token对用户可见,且请求令牌的时候不须要传递client_secret
进行客户端认证。Access Token
)。采用Authorization Code获取Access Token的受权验证流程又被称为Web Server Flow,适用于全部有Server端的应用,如Web/Wap站点、有Server端的手机/桌面客户端应用等。通常来讲整体流程包含如下几个步骤:spring
client_id
请求受权服务端,获取Authorization Code。Authorization Code
、client_id
、client_secret
请求受权服务端,在验证完Authorization Code
是否失效以及接入的客户端信息是否有效(经过传递的client_id
和client_secret
信息和服务端已经保存的客户端信息进行匹配)以后,受权服务端生成Access Token和Refresh Token并返回给客户端。Access Token
请求资源服务应用,获取须要的且在申请的Access Token
权限范围内的资源信息。下面,我将经过基于受权码模式的百度OAuth2.0受权来详细介绍上面这三个步骤。固然,最后我会给出实际可运行的测试代码。apache
申请地址:developer.baidu.com/console#app…json
接着须要记录新建应用的API Key
和Secret Key
:api
以及须要在安全设置里面配置登陆的回调地址:浏览器
注:若是只是在浏览器中测试,能够把回调地址改为https://www.baidu.com
,这样就能够直观地在浏览器中看到重定向的结果了,好比请求https://openapi.baidu.com/oauth/2.0/authorize?client_id=n1pRXWNYFQ1MQLzpDfHyovFb&redirect_uri=https://www.baidu.com&response_type=code&scope=basic&display=popup
,返回结果以下:
其获取方式是经过重定向用户浏览器(或手机/桌面应用中的浏览器组件)到http://openapi.baidu.com/oauth/2.0/authorize
地址,并带上如下参数:
client_id
:必须参数,注册应用时得到的API Key。response_type
:必须参数,此值固定为“code”。redirect_uri
:必须参数,受权后要回调的URI,即接收Authorization Code的URI。scope
:非必须参数,以空格分隔的权限列表,若不传递此参数,表明请求用户的默认权限。state
:非必须参数,用于保持请求和回调的状态,受权服务器在回调时(重定向用户浏览器到“redirect_uri”时),会在Query Parameter中原样回传该参数。OAuth2.0标准协议建议,利用state参数来防止CSRF攻击。display
:非必须参数,登陆和受权页面的展示样式,默认为“page”,具体参数定义请参考“自定义受权页面”一节。force_login
:非必须参数,如传递“force_login=1”,则加载登陆页时强制用户输入用户名和口令,不会从cookie中读取百度用户的登录状态。confirm_login
:非必须参数,如传递“confirm_login=1”且百度用户已处于登录状态,会提示是否使用已当前登录用户对应用受权。login_type
:非必须参数,如传递“login_type=sms”,受权页面会默认使用短信动态密码注册登录方式。例如:client_id
为n1pRXWNYFa4MQLzpDfHyovFb
的应用要请求某个用户的默认权限和email访问权限,并在受权后需跳转到http://localhost:7080/login
,同时但愿在弹出窗口中展示用户登陆、受权界面,则应用须要重定向用户的浏览器到以下URL:
此时受权服务会根据应用传递参数的不一样,为用户展示不一样的受权页面。若是用户在此页面赞成受权,受权服务则将重定向用户浏览器到应用所指定的redirect_uri
,并附带上表示受权服务所分配的Authorization Code
的code参数,以及state参数(若是请求authorization code时带了这个参数)。
例如:继续上面的例子,假设受权服务在用户赞成受权后生成的 Authorization Code 为71c279ccd145a3dff977b38e6a8e34b4
,则受权服务将会返回以下响应包以重定向用户浏览器到http://localhost:7080/login
地址:
HTTP/1.1 302 Found Location: http://localhost:7080/login?code=71c279ccd145a3dff977b38e6a8e34b4
经过上面得到的Authorization Code
,接下来即可以用其换取一个Access Token
。获取方式是:应用在其服务端程序中发送请求(推荐使用POST)到 百度OAuth2.0受权服务的https://openapi.baidu.com/oauth/2.0/token
地址,并带上如下5个必须参数:
grant_type
:必须参数,此值固定为authorization_code
。code
:必须参数,经过上面第一步所得到的Authorization Code
。client_id
:必须参数,应用的API Key。client_secret
:必须参数,应用的Secret Key。redirect_uri
:必须参数,该值必须与获取Authorization Code
时传递的redirect_uri
保持一致。例如:
若参数无误,服务器将返回一段JSON文本,包含如下参数:
access_token
:要获取的Access Token。expires_in
:Access Token的有效期,以秒为单位(30天的有效期)。refresh_token
:用于刷新Access Token 的 Refresh Token,全部应用都会返回该参数(10年的有效期)。scope
:Access Token最终的访问范围,即用户实际授予的权限列表(用户在受权页面时,有可能会取消掉某些请求的权限)。session_key
:基于http调用Open API时所须要的Session Key,其有效期与Access Token一致。session_secret
:基于http调用Open API时计算参数签名用的签名密钥。例如:
{
"expires_in": 2592000,
"refresh_token": "22.247946a05a327ia929b74354c3670cb2.315360000.1847863585.321432378-13484254",
"access_token": "21.e2eb8577t4a68a32y23b61300eda8811.2592000.1536795385.321432378-13484254",
"session_secret": "e8f9ee40de92862cc35c343n5da2fcfb",
"session_key": "9mnRIQsyTR+0yfB3liSUjqGvk8F369TRfHJidz9iA0wDg\/KDBKZtGHACpXfULPjeX1YBWkKAtHSG\/OLXYKQHCuO4Zg2JiBwFtA==",
"scope": "basic"
}
复制代码
若请求错误,服务器将返回一段JSON文本,包含如下参数:
error
:错误码,关于错误码的详细信息请参考百度OAuth2.0错误响应。error_description
:错误描述信息,用来帮助理解和解决发生的错误。使用上面获得的Access Token
获取百度用户的基本资料,包括:用户名、性别、是否实名认证、是否验证手机号等等。
相关的REST API
接口能够参考官方文档:developer.baidu.com/wiki/index.…
请求示例(获取用户基本信息):
提示:下面只会给出关键代码逻辑,完整可用代码能够参考:gitee.com/zifangsky/B…
首先建立两个实体类,分别表示请求Access Token
的返回信息以及请求百度用户基本资料的返回信息。
AuthorizationResponse.java:
package cn.zifangsky.model;
/** * Authorization返回信息 * * @author zifangsky * @date 2018/7/25 * @since 1.0.0 */
public class AuthorizationResponse {
/** * 要获取的Access Token(30天的有效期) */
private String access_token;
/** * 用于刷新Access Token 的 Refresh Token(10年的有效期) */
private String refresh_token;
/** * Access Token最终的访问范围 */
private String scope;
/** * Access Token的有效期,以秒为单位(30天的有效期) */
private Long expires_in;
/** * 基于http调用Open API时所须要的Session Key,其有效期与Access Token一致 */
private String session_key;
/** * 基于http调用Open API时计算参数签名用的签名密钥 */
private String session_secret;
/** * 错误信息 */
private String error;
/** * 错误描述 */
private String error_description;
//省略setter和getter
@Override
public String toString() {
return "AuthorizationResponse{" +
"access_token='" + access_token + '\'' +
", refresh_token='" + refresh_token + '\'' +
", scope='" + scope + '\'' +
", expires_in=" + expires_in +
", session_key='" + session_key + '\'' +
", session_secret='" + session_secret + '\'' +
", error='" + error + '\'' +
", error_description='" + error_description + '\'' +
'}';
}
}
复制代码
BaiduUser.java:
package cn.zifangsky.model;
/** * 百度返回的用户基本信息 * * @author zifangsky * @date 2018/7/25 * @since 1.0.0 */
public class BaiduUser {
/** * 百度的userId */
private String userid;
/** * 用户名 */
private String username;
/** * 用户性别,0表示女性,1表示男性 */
private Integer sex;
/** * 用户生日 */
private String birthday;
/** * 用户描述 */
private String userdetail;
/** * 是否绑定手机号 */
private Integer is_bind_mobile;
/** * 是否已经实名认证 */
private Integer is_realname;
//省略setter和getter
@Override
public String toString() {
return "BaiduUser{" +
"userid='" + userid + '\'' +
", username='" + username + '\'' +
", sex=" + sex +
", birthday='" + birthday + '\'' +
", userdetail='" + userdetail + '\'' +
", is_bind_mobile=" + is_bind_mobile +
", is_realname=" + is_realname +
'}';
}
}
复制代码
最后就是最关键的用户登陆逻辑了:
package cn.zifangsky.controller;
import cn.zifangsky.common.Constants;
import cn.zifangsky.model.AuthorizationResponse;
import cn.zifangsky.model.BaiduUser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.text.MessageFormat;
/** * 登陆 * @author zifangsky * @date 2018/7/9 * @since 1.0.0 */
@Controller
public class LoginController {
@Autowired
private RestTemplate restTemplate;
@Value("${baidu.oauth2.client-id}")
private String clientId;
@Value("${baidu.oauth2.scope}")
private String scope;
@Value("${baidu.oauth2.client-secret}")
private String clientSecret;
@Value("${baidu.oauth2.user-authorization-uri}")
private String authorizationUri;
@Value("${baidu.oauth2.access-token-uri}")
private String accessTokenUri;
@Value("${baidu.oauth2.resource.userInfoUri}")
private String userInfoUri;
/** * 登陆验证(实际登陆调用认证服务器) * @author zifangsky * @date 2018/7/25 16:42 * @since 1.0.0 * @param request HttpServletRequest * @return org.springframework.web.servlet.ModelAndView */
@RequestMapping("/login")
public ModelAndView login(HttpServletRequest request){
//当前系统登陆成功以后的回调URL
String redirectUrl = request.getParameter("redirectUrl");
//当前系统请求认证服务器成功以后返回的Authorization Code
String code = request.getParameter("code");
//最后重定向的URL
String resultUrl = "redirect:";
HttpSession session = request.getSession();
//当前请求路径
String currentUrl = request.getRequestURL().toString();
//code为空,则说明当前请求不是认证服务器的回调请求,则重定向URL到百度OAuth2.0登陆
if(StringUtils.isBlank(code)){
//若是存在回调URL,则将这个URL添加到session
if(StringUtils.isNoneBlank(redirectUrl)){
session.setAttribute("redirectUrl",redirectUrl);
}
resultUrl += authorizationUri + MessageFormat.format("?client_id={0}&response_type=code&scope=basic&display=popup&redirect_uri={1}"
,clientId,currentUrl);
}else{
//1. 经过Authorization Code获取Access Token
AuthorizationResponse response = restTemplate.getForObject(accessTokenUri + "?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}"
,AuthorizationResponse.class
, clientId, clientSecret, code,currentUrl);
//2. 若是正常返回
if(response != null && StringUtils.isNoneBlank(response.getAccess_token())){
System.out.println(response);
//2.1 将Access Token存到session
session.setAttribute(Constants.SESSION_ACCESS_TOKEN,response.getAccess_token());
//2.2 再次查询用户基础信息,并将用户ID存到session
BaiduUser baiduUser = restTemplate.getForObject(userInfoUri + "?access_token={1}"
,BaiduUser.class
,response.getAccess_token());
if(baiduUser != null && StringUtils.isNoneBlank(baiduUser.getUserid())){
System.out.println(baiduUser);
session.setAttribute(Constants.SESSION_USER_ID,baiduUser.getUserid());
}
}
//3. 从session中获取回调URL,并返回
redirectUrl = (String) session.getAttribute("redirectUrl");
session.removeAttribute("redirectUrl");
if(StringUtils.isNoneBlank(redirectUrl)){
resultUrl += redirectUrl;
}else{
resultUrl += "/user/userIndex";
}
}
return new ModelAndView(resultUrl);
}
}
复制代码
上面代码里面的注释已经很详细了,这里我就很少作解释了,详细代码能够自行参考上面给出的示例源码。本篇文章到此结束,我将在下篇文章中介绍如何本身手动实现OAuth2.0受权服务端,敬请期待!
参考: