基於oauth2.0實現應用的第三方登陸

OAuth2

OAuth2所涉及到的對象主要有如下四個:html

  • Client 第三方應用,咱們的應用就是一個Client
  • Resource Owner 資源全部者,即用戶
  • Authorization Server 受權服務器,即提供第三方登陸服務的服務器,如Github
  • Resource Server 擁有資源信息的服務器,一般和受權服務器屬於同一應用

OAuth2的基本流程爲:java

  1. 第三方應用請求用戶受權。
  2. 用戶贊成受權,並返回一個憑證(code)
  3. 第三方應用經過第二步的憑證(code)向受權服務器請求受權
  4. 受權服務器驗證憑證(code)經過後,贊成受權,並返回一個資源訪問的憑證(Access Token)。
  5. 第三方應用經過第四步的憑證(Access Token)向資源服務器請求相關資源。
  6. 資源服務器驗證憑證(Access Token)經過後,將第三方應用請求的資源返回。

Github對應用開放受權

進入github中的Settings/Developer settings中建立一個應用,表示你的應用會使用github受權。git

填寫好相關的信息後,填寫Authorization callback URL爲http://localhost:8080/oauth/github/callback(後面受權會用到),能夠獲得Client IDClient Secret,結果以下:github

github受權第三方應用的過程

  1. 根據 GitHub 登陸連接能夠回調得到 codeweb

  2. 根據Client ID 、Client Secret 和 code 可得到 tokenspring

  3. 根據 token 得到用戶信息sql

必要的URL數據庫

  1. 登陸頁面受權URL:apache

    https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%sjson

  2. 得到Token的URL:

    https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s&redirect_uri=%s&state=%s

  3. 得到用戶信息的URL:

    https://api.github.com/user?access_token=%s

應用得到用戶的信息時,會返回一個惟一的標識,用於惟一標識資源全部者即用戶,因而咱們能夠將此標識與數據庫中咱們本身的本地用戶相關聯。

測試

在進行編碼以前,咱們首先訪問上面的幾種URL,並分析流程及返回結果。

首先訪問https://github.com/login/oauth/authorize?client_id=50d7f61132da7f8574a1&redirect_uri=http://localhost:8080/oauth/github/callback&state=thisisrandomstring

分析:該URL爲引導用戶對應用受權github信息,參數client_id爲該應用建立時的Client ID,redirect_uri爲該應用建立時填寫的Authorization callback URL,state爲隨機字符串,它用於防止跨站點請求僞造攻擊。訪問時結果以下:

響應結果可以理解,而後點擊受權按鈕,就會自動跳轉到http://localhost:8080/oauth/github/callback?code=107b7d2f85201535880c&state=thisisrandomstring,URL爲咱們填寫的回調URL,code參數即爲憑證,

state爲上一步的隨機字符串。

接下來,咱們應該獲取token,根據github官方文檔,咱們須要發起一個POST請求,URL爲https://github.com/login/oauth/access_token

須要攜帶的參數以下:

Name Type Description
client_id string Required. The client ID you received from GitHub for your GitHub App.
client_secret string Required. The client secret you received from GitHub for your GitHub App.
code string Required. The code you received as a response to Step 1.
redirect_uri string The URL in your application where users are sent after authorization.
state string The unguessable random string you provided in Step 1.

接下來,咱們經過Postman模擬這一個過程,結果以下:

您還能夠根據Accept標頭接收不一樣格式的內容:

Accept: application/json
{"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"}

Accept: application/xml
<OAuth>
  <token_type>bearer</token_type>
  <scope>repo,gist</scope>
  <access_token>e72e16c7e42f292c6912e7710c838347ae178b4a</access_token>
</OAuth>

嗯,成功獲取到了Token無誤,接下來該獲取用戶的信息了。發起GET請求,URL爲https://api.github.com/user,攜帶參數access_token=獲取到的token,結果以下,能夠獲取到用戶的基本信息。

編碼

  1. 首先須要一個service用來定義oauth的一些方法,如獲取token,獲取用戶信息等。

    package com.yunche.novels.service;
    
    import com.yunche.novels.vo.AuthUserVO;
    import org.springframework.util.MultiValueMap;
    
    /**
     * @author yunche
     * @date 2019/04/04
     */
    public interface AuthService {
    
        String getToken(MultiValueMap<String, String> params);
    
        AuthUserVO getUserInfo(String token);
    
        boolean checkIsExistsOpenId(String openId);
    
        boolean storeOpenIdByUser(String openId, Integer userId);
    
        String getUserNameByOpenId(String openId);
    }
  2. 接着,使用GitHub來完成具體的service的實現。

    package com.yunche.novels.service.impl;
    
    import com.yunche.novels.mapper.AuthForGitHubMapper;
    import com.yunche.novels.service.AuthService;
    import com.yunche.novels.util.AuthHelper;
    import com.yunche.novels.vo.AuthTokenVO;
    import com.yunche.novels.vo.AuthUserVO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.util.MultiValueMap;
    
    import java.sql.Timestamp;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author yunche
     * @date 2019/04/04
     */
    @Service
    public class GitHubAuthServiceImpl implements AuthService {
        @Autowired
        private AuthForGitHubMapper gitHubMapper;
    
        private static final String GET_TOKEN_URL = "https://github.com/login/oauth/access_token";
    
        private static final String GET_USER_URL = "https://api.github.com/user";
    
        private static final String CLIENT_ID = "50d7f61132da7f8574a1";
    
        private static final String CLIENT_SECRET = "6779d154cfc44115e1f3607c0000085c5c1cf178";
    
        private static final String REDIRECT_URI = "http://localhost:8080/oauth/github/callback";
    
        @Override
        public String getToken(MultiValueMap<String, String> params) {
            params.add("client_id", CLIENT_ID);
            params.add("client_secret", CLIENT_SECRET);
            params.add("redirect_uri", REDIRECT_URI);
            AuthTokenVO authTokenVO = AuthHelper.sendPostGetToken(GET_TOKEN_URL, params);
            String token = authTokenVO.getAccess_token();
            return token;
        }
    
        @Override
        public AuthUserVO getUserInfo(String token) {
           Map<String, String> map = new HashMap<>();
            map.put("access_token", token);
            return AuthHelper.sendGetToUser(GET_USER_URL, map);
        }
    
        @Override
        public boolean checkIsExistsOpenId(String openId) {
            return gitHubMapper.checkIsExists(openId) > 0;
        }
    
        @Override
        public boolean storeOpenIdByUser(String openId, Integer userId) {
            Date date = new Date();
            Timestamp timeStamp = new Timestamp(date.getTime());
            return gitHubMapper.storeOpenIdByUser(openId, userId, timeStamp) > 0;
        }
    
        @Override
        public String getUserNameByOpenId(String openId) {
            return gitHubMapper.getUserNameByOpenId(openId);
        }
    }
  3. 將須要獲取的token和用戶信息的json封裝成對象。

    package com.yunche.novels.vo;
    
    /**
     * @author yunche
     * @date 2019/04/04
     */
    public class AuthTokenVO {
    
        private String access_token;
    
        private String token_type;
    
        private String scope;
    
        public String getAccess_token() {
            return access_token;
        }
    
        public void setAccess_token(String access_token) {
            this.access_token = access_token;
        }
    
        public String getToken_type() {
            return token_type;
        }
    
        public void setToken_type(String token_type) {
            this.token_type = token_type;
        }
    
        public String getScope() {
            return scope;
        }
    
        public void setScope(String scope) {
            this.scope = scope;
        }
    
        public AuthTokenVO() {
        }
    
        public AuthTokenVO(String access_token, String token_type, String scope) {
            this.access_token = access_token;
            this.token_type = token_type;
            this.scope = scope;
        }
    }
    package com.yunche.novels.vo;
    
    /**
     * @author yunche
     * @date 2019/04/04
     */
    public class AuthUserVO {
    
        /**
         * 用戶第三方應用名
         */
        private String login;
    
        /**
         * 用戶第三方惟一標識
         */
        private String id;
    
        /**
         * 用戶第三方頭像
         */
        private String avatar_url;
    
        public String getLogin() {
            return login;
        }
    
        public void setLogin(String login) {
            this.login = login;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getAvatar_url() {
            return avatar_url;
        }
    
        public void setAvatar_url(String avatar_url) {
            this.avatar_url = avatar_url;
        }
    
    }
  4. mapper類操做數據庫。

    package com.yunche.novels.mapper;
    
    import org.apache.ibatis.annotations.*;
    
    import java.util.Date;
    
    /**
     * @author yunche
     * @date 2019/04/05
     */
    @Mapper
    public interface AuthForGitHubMapper {
    
        /**
         * 檢查該openId是否已經註冊過
         * @param openId
         * @return
         */
        @Select("SELECT COUNT(*) FROM oauth_detail WHERE open_id=#{openId} and app_type='github'")
        Integer checkIsExists(String openId);
    
        /**
         * 存儲該OpenId
         * @param openId
         * @param userId
         * @return
         */
        @Insert("INSERT INTO oauth_detail(open_id, app_type, user_id, status, create_time) VALUES(#{openId},'github',#{userId},1,#{createTime})")
        Integer storeOpenIdByUser(@Param(value = "openId") String openId, @Param(value = "userId") Integer userId, @Param(value = "createTime") Date createTime);
    
        @Select("SELECT user_name FROM user, oauth_detail WHERE user_id=user.id AND open_id = #{openId}")
        String getUserNameByOpenId(String openId);
    }
    package com.yunche.novels.mapper;
    
    import com.yunche.novels.bean.User;
    import org.apache.ibatis.annotations.*;
    
    /**
     * @author yunche
     * @date 2019/04/05
     */
    @Mapper
    public interface UserMapper {
    
        @Insert("INSERT INTO user(user_name, password) VALUES(#{userName}, #{password}) ")
        @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
        Integer storeUser(User user);
    
        @Select("SELECT COUNT(*) FROM user where user_name=#{name}")
        Integer checkUserNameIsExists(String name);
    }
  5. Controller類。

    package com.yunche.novels.controller;
    
    
    import com.yunche.novels.bean.User;
    import com.yunche.novels.service.UserService;
    import com.yunche.novels.service.impl.GitHubAuthServiceImpl;
    import com.yunche.novels.util.MD5Utils;
    import com.yunche.novels.util.StringHelper;
    import com.yunche.novels.vo.AuthUserVO;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.util.MultiValueMap;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.http.HttpSession;
    
    /**
     * @author yunche
     * @date 2019/04/04
     */
    @Controller
    public class AuthController {
    
        @Autowired
        private GitHubAuthServiceImpl authService;
        @Autowired
        private UserService userService;
    
        @GetMapping("/oauth/github/callback")
        public String authorizeForGitHub(@RequestParam("code") String code, @RequestParam("state") String state, HttpSession session) {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("code", code);
            map.add("state", state);
            String token = authService.getToken(map);
            //獲取用戶在第三方的信息
            AuthUserVO userVO = authService.getUserInfo(token);
            String openId = userVO.getId();
            //註冊該openId
            if(!authService.checkIsExistsOpenId(openId)) {
                User u = new User();
                String userName = userVO.getLogin();
                //確保用戶的用戶名惟一
                while (userService.IsExistsName(userName)) {
                    userName += StringHelper.getRandomString(3);
                }
                u.setUserName(userName);
                //生成一個隨機的必定長度的字符串並使用MD5加密,因爲第三方的密碼不可用,故隨機。
                u.setPassword(MD5Utils.getMD5(StringHelper.getRandomString(16)));
    
                //註冊用戶
                if(userService.insertUser(u)) {
                    //將本地用戶與OpenId相關聯
                    if(authService.storeOpenIdByUser(openId, u.getId())) {
                        //存儲用戶session
                        session.setAttribute("user", u.getUserName());
                    }
                }
            }
            else {
                session.setAttribute("user", authService.getUserNameByOpenId(openId));
            }
            // 重定向到以前須要受權的頁面
            return "redirect:" + state;
        }
    }

參考資料

OAuth2.0認證和受權機制講解

SpringBoot網站添加第三方登陸之GitHub登陸

相關文章
相關標籤/搜索