The FizzGate management backend supports OAuth docking with custom third-party platforms since version 2.7.0. This document describes how to dock.

Prerequisite: Only the commercial authorized version supports docking with OAuth

# Instructions for docking with OAuth

  1. FizzGate implements third-party login based on JustAuth (open source address: https://github.com/justauth/JustAuth), and customizes the OAuth document of the third-party platform: https://justauth.wiki/features/customize-the- oauth/

  2. This article uses Gitlab’s third-party login as an example. For other third parties, please refer to JustAuth’s documentation and related platform documentation.

# Docking preparation

Create an application and get the Key

fizz_oauth_gitlab_application

fizz_oauth_gitlab_app_key

# Rear end

# Configuration

To enable third-party login, add the following configuration to the application-prod.xml configuration file:

social:
   enabled: true

fizz_oauth_config_social_enabled

Database executes SQL:

UPDATE blade_client SET access_token_validity = 2592000, refresh_token_validity = 2592000 WHERE id = 1123598811738675201;

This SQL will update the validity time (in seconds) of FizzGate's own token to 1 month. This time needs to be greater than the validity time of the third-party platform token to avoid the need to log in again after FizzGate's own token fails.

# Implement the AuthRequestService interface

Implement the org.springblade.modules.social.service.AuthRequestService interface and provide the AuthRequest corresponding to the third-party platform through the interface.

The implementation reference is as follows:


package org.springblade.modules.social.service.impl;

import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.request.AuthRequest;
import com.alibaba.fastjson.JSONObject;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.enums.AuthUserGender;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthDefaultRequest;
import me.zhyd.oauth.utils.UrlBuilder;
import org.springblade.modules.social.service.AuthRequestService;
import org.springframework.stereotype.Service;

/**
  * AuthRequest service interface implementation class
  * Reference: https://justauth.wiki/features/customize-the-oauth/
  *
  * @author zhongjie
  * @since 2.7.0
  */
@Service
public class AuthRequestServiceImpl implements AuthRequestService {

@Override
public AuthRequest getAuthRequest(String source) {
if ("mygitlab".equals(source)) {
return new AuthMyGitlabRequest(AuthConfig.builder()
.clientId("1898f99a4e0440c9acd3bcb6883f197bb0437f780be4dc8870193446d8fe131e")
.clientSecret("384f9395536c47b13c934b52c192e37508f813e5b04bf2ca56d00acbf1fcc792")
.redirectUri("http://127.0.0.1:8000?source=mygitlab")
.build());
}

return null;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler, String source, String accessToken) {
// Verify logic before each interface call
return true;
}

@Override
public void logout(String source, String accessToken) {
//Logout logic
}

enum AuthCustomSource implements AuthSource {

/**
* Gitlab private server built by myself
*/
MYGITLAB {
/**
* Authorized API
*
* @return url
*/
@Override
public String authorize() {
return "http://gitlab.xxx.com/oauth/authorize";
}

/**
* Get accessToken API
*
* @return url
*/
@Override
public String accessToken() {
return "http://gitlab.xxx.com/oauth/token";
}

/**
* API to obtain user information
*
* @return url
*/
@Override
public String userInfo() {
return "http://gitlab.xxx.com/api/v4/user";
}
}
}

static class AuthMyGitlabRequest extends AuthDefaultRequest {


public AuthMyGitlabRequest(AuthConfig config) {
super(config, AuthCustomSource.MYGITLAB);
}

public AuthMyGitlabRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthCustomSource.MYGITLAB, authStateCache);
}

@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
String response = doPostAuthorizationCode(authCallback.getCode());
JSONObject object = JSONObject.parseObject(response);

this.checkResponse(object);

return AuthToken.builder()
.accessToken(object.getString("access_token"))
.refreshToken(object.getString("refresh_token"))
.idToken(object.getString("id_token"))
.tokenType(object.getString("token_type"))
.scope(object.getString("scope"))
.build();
}

@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String response = doGetUserInfo(authToken);
JSONObject object = JSONObject.parseObject(response);

this.checkResponse(object);

return AuthUser.builder()
.uuid(object.getString("id"))
.username(object.getString("username"))
.nickname(object.getString("name"))
.avatar(object.getString("avatar_url"))
.blog(object.getString("web_url"))
.company(object.getString("organization"))
.location(object.getString("location"))
.email(object.getString("email"))
.remark(object.getString("bio"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source.toString())
.build();
}

private void checkResponse(JSONObject object) {
//oauth/token verification exception
if (object.containsKey("error")) {
throw new AuthException(object.getString("error_description"));
}
// user verification exception
if (object.containsKey("message")) {
throw new AuthException(object.getString("message"));
}
}

/**
		 * returnAuthorization url with {@code state} parameter, this {@code state} will be included in the authorization callback
*
* @param state state Verify the parameters of the authorization process to prevent csrf
* @return Returns the authorized address
* @since 1.11.0
*/
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(super.authorize(state))
.queryParam("scope", "read_user+openid")
.build();
}
}
}

After the third party logs in, FizzGate uses its own token verification logic and no longer interacts with the third party.

If you need to verify the third-party platform access token every time you request the background, you can implement the preHandle interface in AuthRequestService to implement your own verification logic.

If you need to notify the third-party platform to log out when logging out, you can implement the logout interface in AuthRequestService to implement your own logout logic.

The source code can be viewed in the project fizz-manager-stardard/fizz-manager-bootstrap/src/main/java/org/springblade/modules/social/service/impl/AuthRequestServiceImpl.

# Front end

  1. Third-party login portal (source code: src\page\login\thirdlogin.vue) Use window.location.href to open a new page, the address is: ${window.location.origin}/api/fizz-manager/blade-auth/oauth/render/${source} The source corresponds to the source of the back-end code.
<template>
<div class="social-container">
<div class="box"
@click="handleClick('mygitlab')">
<span class="container"
:style="{backgroundColor:'#6ba2d6'}">
<i class="logo-icon el-icon-s-comment"/>
</span>
<p class="title">mygitlab</p>
</div>
</div>
</template>

<script>
import website from '@/config/website';
export default {
name: "thirdLogin",
methods: {
handleClick(source) {
window.location.href=`${website.authUrl}${source}`;
}
}
};
</script>
  1. After successfully logging in to the third-party platform, the login page will jump. Among them, the address jumped back will carry source, code, and state parameters. By calling the handleLogin() method, parse the source, code, and state parameters and call the /blade-auth/oauth/token interface to obtain user information and cache it on the front end (roughly the same operation logic as logging in through account and password), and then jump to front page. At this point, you have logged in successfully. (Source code: src\page\login\index.vue)
handleLogin() {
const topUrl = getTopUrl();
const redirectUrl = "/oauth/redirect/";
this.socialForm.source = getQueryString("source");
this.socialForm.code = getQueryString("code");
this.socialForm.state = getQueryString("state");
if (validatenull(this.socialForm.source) && topUrl.includes(redirectUrl)) {
let source = topUrl.split("?")[0];
source = source.split(redirectUrl)[1];
this.socialForm.source = source;
}
if (!validatenull(this.socialForm.source) && !validatenull(this.socialForm.code) && !validatenull(this.socialForm.state)) {
const loading = this.$loading({
lock: true,
text: 'Logging in to third-party system, please wait. . . ',
spinner: "el-icon-loading"
});
this.$store.dispatch("LoginBySocial", this.socialForm).then(() => {
console.log(this.$route.query)
const redirectUrl = decodeURIComponent(this.$route.query.redirect) || getQueryString("redirect_uri");
if (this.$route.query.redirect && redirectUrl) {
this.$router.replace(redirectUrl);
} else {
this.$router.push({path: this.tagWel.value});
}
this.clearUrlQuery();
this.$store.dispatch("GetLicense");
this.getEGifInfo();
loading.close();
}).catch(() => {
loading.close();
});
}
}

# Test effect

fizz_oauth_demo_1

fizz_oauth_demo_2

fizz_oauth_demo_3