oauth2.0搭建

一、协议流程
(A)用户打开客户端以后,客户端要求用户给予授权。

(B)用户同意给予客户端授权。

(C)客户端使用上一步获得的授权,向认证服务器申请令牌。

(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

(E)客户端使用令牌,向资源服务器申请获取资源。

(F)资源服务器确认令牌无误,同意向客户端开放资源。
  
二、客户端的授权模式

----客户端获取授权的四种模式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。

授权码模式(authorization code)
简化模式(implicit)
密码模式(resource owner password credentials)
客户端模式(client credentials)
(1)授权码模式(authorization code)

功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。

(2)简化模式(implicit grant type)

不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

(3)密码模式(Resource Owner Password Credentials Grant)

用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。

(4)客户端模式(Client Credentials Grant)

指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

注:web资源:其实就是接口。
授权码模式

—以使用微博账号登录简书为示例详细讲解其工作原理

1、原理概要

新浪微博作为服务提供商,拥有用户的头像、昵称、邮箱、好友以及所有的微博内容,简书希望获取用户存储在微博的头像和昵称,假设它们是三个人:

简书问新浪微博:我想要获取用户 A 的头像和昵称,请你提供
微博说:我需要经过用户A 本人的许可,然后去问用户 A 是否要授权简书访问自己的头像和昵称
用户 A 对微博说:我给简书一个临时的钥匙,如果他给你出示了这把钥匙,你就把我的资料给他
简书使用户给它的钥匙获取用户头像和昵称信息。
以上是 OAuth 认证的大概流程。在使用微博授权之前,简书需要先在微博开放平台上注册应用,填写自己的名称、logo、用途等信息,微博开放平台颁发给简书一个应用 ID 和叫 APP Secret 的密钥,在实际对接中,会使用到这两个参数。

注:
新浪微博开放平台即是认证服务器
用户发放code(时间极短)
认证服务器发放 token(时间长)
资源服务器提供用户资源

2、详细流程

接下来分步详细解释上图中每步都做了什么:

1.用户点击简书上的微博登录按钮,跳转到微博授权页面。微博登录按钮的链接形如下方的 URL:

https://api.weibo.com/oauth2/authorize?client_id=123050457758183&redirect_uri=http://jianshu.com/callback
  URL 中要包含以下参数:

client_id:在微博开放平台申请的应用 ID
redirect_uri:授权成功后要跳转到的地址
  点击以上链接后跳转到微博的授权页面如下图:

这个页面会告诉用户第三方应用要获取用户的哪些数据,以及拥有什么权限,比如在上图中简书会要求获得个人信息、好友关系、分享内容到微博以及获得评论的权限,用户点击“允许”则表示允许简书获得这些数据,进行下一步。

2.页面自动跳转到初始参数中redirect_uri 定义的那个URL,并自动在 URL 末尾添加一个 code 参数,实际跳转的地址形如:

http://jianshu.com/callback?code=2559200ecd7ea433f067a2cf67d6ce6c
  3.第三步,简书通过上一步获取的 code 参数换取 Token,Token 就是前文中说到的钥匙。简书请求如下的接口获取 Token:

POST https://api.weibo.com/oauth2/access_token
  要包含以下参数:

client_id:在微博开放平台申请的应用 ID
client_secret:在微博开放平台申请时提供的APP Secret
grant_type:需要填写authorization_code
code:上一步获得的 code
redirect_uri:回调地址,需要与注册应用里的回调地址以及第一步的 redirect_uri 参数一致
 注:获取token十分重要,采用POST方式比较安全。

4.通过第三步的请求,接口返回 Token 和相关数据:
{
“access_token”: “ACCESS_TOKEN”,//Token 的值
“expires_in”: 1234,//过期时间
“uid”:“12341234”//当前授权用户的UID。
}
  5.在第四步中获取了access_token ,使用它,就可以去获取用户的资源了,要获取用户昵称和头像,请求如下接口:

GET https://api.weibo.com/2/users/show.json

携带参数:

access_token:上一步获取的access_token
uid:用户的账号 id,上一步的接口有返回
  注:access_token使用时间有限,若失效,常用的解决方法:
重新授权登录
使用refresh_token,重新申请。(一般用在不间断连续在线)

6.最后一步,微博返回用户信息,简书进行处理,整个流程结束。
  通过以上的方式,在简书和新浪微博中间建立了一个独立的权限层,这个权限由用户赋予,可以被用户随时取消,不同第三方应用之间相互独立,互不干扰,这样就彻底解决了明文存放账号密码的问题。

三、代码实现

实现主要涉及参数配置如下:
授权码设置(code)
第三方通过code进行获取 access_token的时候需要用到,code的超时时间为10分钟,一个code只能成功换取一次access_token即失效。
授权作用域(scope)
作用域代表用户授权给第三方的接口权限,第三方应用需要向服务端申请使用相应scope的权限后,经过用户授权,获取到相应access_token后方可对接口进行调用。
令牌有效期(access_token)
access_token是调用授权关系接口的调用凭证,由于access_token有效期(目前为2个小时)较短,当access_token超时后,可以使用refresh_token进行刷新,access_token刷新结果有两种:
1. 若access_token已超时,那么进行refresh_token会获取一个新的access_token,新的超时时间;
2. 若access_token未超时,那么进行refresh_token不会改变access_token,但超时时间会刷新,相当于续期access_token。
refresh_token拥有较长的有效期(30天),当refresh_token失效的后,需要用户重新授权。

项目介绍
项目结构如下:
AuthzController:获取授权码
TokenController:获得令牌
ResourceController:资源服务
ClientController:客户端

基础代码放到github:https://github.com/zhouyongtao/homeinns-web

oauth2.0搭建_第1张图片

确保项目8080端口运行,可以手动调试

获得授权码
http://localhost:8080/oauth2/authorize?client_id=fbed1d1b4b1449daa4bc49397cbe2350&response_type=code&redirect_uri=http://localhost:8080/oauth_callback
获得令牌(POST)
http://localhost:8080/oauth2/access_token?client_id=fbed1d1b4b1449daa4bc49397cbe2350&client_secret=fbed1d1b4b1449daa4bc49397cbe2350&grant_type=authorization_code&redirect_uri=http://localhost:8080/oauth_callback&code={code}

客户端
也可以使用如下客户端测试代码,访问 http://localhost:8080/client 测试

/**

  • Created by Irving on 2014/11/24.

  • OAuth2 客户端实现

*/

@Controller

@RequestMapping("/client")

public class ClientController {

private static Logger logger = LoggerFactory.getLogger(ClientController.class);

/*

    response_type:表示授权类型,必选项,此处的值固定为"code"

    client_id:表示客户端的ID,必选项

    redirect_uri:表示重定向URI,可选项

    scope:表示申请的权限范围,可选项

    state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值

*/

/**

 * 获得授权码

 * @return

 */

@RequestMapping(method = RequestMethod.GET)

public String client() {

    try {

        OAuthClientRequest oauthResponse = OAuthClientRequest

                                           .authorizationLocation(ConstantKey.OAUTH_CLIENT_AUTHORIZE)

                                           .setResponseType(OAuth.OAUTH_CODE)

                                           .setClientId(ConstantKey.OAUTH_CLIENT_ID)

                                           .setRedirectURI(ConstantKey.OAUTH_CLIENT_CALLBACK)

                                           .setScope(ConstantKey.OAUTH_CLIENT_SCOPE)

                                           .buildQueryMessage();

        return "redirect:"+oauthResponse.getLocationUri();

    } catch (OAuthSystemException e) {

        e.printStackTrace();

    }

    return "redirect:/home";

}



/*

    grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"

    code:表示上一步获得的授权码,必选项。

    redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致

    client_id:表示客户端ID,必选项

*/

/**

 * 获得令牌

 * @return oauth_callback?code=1234

 */

@RequestMapping(value = "/oauth_callback" ,method = RequestMethod.GET)

public String getToken(HttpServletRequest request,Model model) throws OAuthProblemException {

    OAuthAuthzResponse oauthAuthzResponse = null;

    try {

        oauthAuthzResponse = OAuthAuthzResponse.oauthCodeAuthzResponse(request);

        String code = oauthAuthzResponse.getCode();

        OAuthClientRequest oauthClientRequest = OAuthClientRequest

                                                .tokenLocation(ConstantKey.OAUTH_CLIENT_ACCESS_TOKEN)

                                                .setGrantType(GrantType.AUTHORIZATION_CODE)

                                                .setClientId(ConstantKey.OAUTH_CLIENT_ID)

                                                .setClientSecret(ConstantKey.OAUTH_CLIENT_SECRET)

                                                .setRedirectURI(ConstantKey.OAUTH_CLIENT_CALLBACK)

                                                .setCode(code)

                                                .buildQueryMessage();

        OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());



        //Facebook is not fully compatible with OAuth 2.0 draft 10, access token response is

        //application/x-www-form-urlencoded, not json encoded so we use dedicated response class for that

        //Custom response classes are an easy way to deal with oauth providers that introduce modifications to

        //OAuth 2.0 specification



        //获取access token

        OAuthJSONAccessTokenResponse oAuthResponse = oAuthClient.accessToken(oauthClientRequest, OAuth.HttpMethod.POST);

        String accessToken = oAuthResponse.getAccessToken();

        String refreshToken= oAuthResponse.getRefreshToken();

        Long expiresIn = oAuthResponse.getExpiresIn();

        //获得资源服务

        OAuthClientRequest bearerClientRequest = new OAuthBearerClientRequest(ConstantKey.OAUTH_CLIENT_GET_RESOURCE)

                                                 .setAccessToken(accessToken).buildQueryMessage();

        OAuthResourceResponse resourceResponse = oAuthClient.resource(bearerClientRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class);

        String resBody = resourceResponse.getBody();

        logger.info("accessToken: "+accessToken +" refreshToken: "+refreshToken +" expiresIn: "+expiresIn +" resBody: "+resBody);

        model.addAttribute("accessToken",  "accessToken: "+accessToken + " resBody: "+resBody);

        return "oauth2/token";

    } catch (OAuthSystemException ex) {

        logger.error("getToken OAuthSystemException : " + ex.getMessage());

        model.addAttribute("errorMsg",  ex.getMessage());

        return  "/oauth2/error";

    }

}

}

你可能感兴趣的:(java后端)