JustAuth, as you can see, is just a third party authorized login tool class library. It takes us away from the tedious third party login SDK and makes login So easy! This column will go through the basics, detailing how to use JustAuth for third-party logins, and how to use JustAuth’s advanced features.

Initialize the project using SpringBoot

Before the tutorial begins, we need to prepare the appropriate software environment. JustAuth

Create projects under IDEA using the following methods: Click file-new-Project in turn and then select Spring Initializr. Perform operations as prompted. When configuring dependencies, select spring-boot-starter-Web and Lombok. I’ve chosen one more spring-boot-devTools here.

After completing the creation as prompted, the POM is as follows

<?xml version="1.0" encoding="UTF-8"? >
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6. RELEASE</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>
    <groupId>me.zhyd.justauth</groupId>
    <artifactId>justauth-tutorial</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>
    <name>justauth-tutorial</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
Copy the code

After the project was compiled, we started the formal JustAuth integration.

Add a JustAuth dependency

Before you start, I suggest you take a look at the JustAuth user documentation: docs.justauth.whnb.wang, and focus on the quick start section.

This chapter contains important information about OAuth and JustAuth, so it is recommended to see this chapter first.

To reiterate, there are three steps to using JustAuth (these three steps also apply to any platform JustAuth supports) :

  1. Apply to register a developer account for a third-party platform
  2. Create third-party applications and obtain configuration information (accessKey.secretKey.redirectUri)
  3. Use this tool to achieve authorized login

Of course, it doesn’t matter which step comes first. You can either implement the code first and apply for a third-party application, or apply for a third-party application and integrate the code later.

Next, we add poM dependencies as instructed in the first step on the documentation.

// ...
        <dependency>
            <groupId>me.zhyd.oauth</groupId>
            <artifactId>JustAuth</artifactId>
            <version>1.15.1</version>
        </dependency>
        // ...
Copy the code

After the dependency is added, we wait for the project to be compiled, and then we start to add JustAuth.

Integrate JustAuth’s API

Note that in the following code, our request link is fetched with the dynamic parameter {source}. This allows us to easily integrate with any platform, such as Gitee, and our request address is: http://localhost:8080/oauth/render/gitee, and callback address is http://localhost:8080/oauth/callback/gitee.

Of course, this is just an example of how you can use it, but if you only want to integrate a single platform, you can simply change {souce} to the platform name, such as gitee

package me.zhyd.justauth;

import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.request.AuthGiteeRequest;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/** * How to use JustAuth to log in **@author yadong.zhang (yadong.zhang0415(a)gmail.com)
 * @version 1.0.0
 * @since1.0.0 * /
@RestController
@RequestMapping("/oauth")
public class JustAuthController {

    /** * Get the authorization link and jump to the third party authorization page **@param response response
     * @throwsIOException Response Possible exception */
    @RequestMapping("/render/{source}")
    public void renderAuth(HttpServletResponse response) throws IOException {
        AuthRequest authRequest = getAuthRequest();
        String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
        response.sendRedirect(authorizeUrl);
    }

    /** * After the user confirms the authorization (login) of the third-party platform, the third-party platform will redirect the user to this address with parameters such as code and state **@paramCallback Specifies the input parameter to a third-party callback@returnThird party platform user information */
    @RequestMapping("/callback/{source}")
    public Object login(AuthCallback callback) {
        AuthRequest authRequest = getAuthRequest();
        return authRequest.login(callback);
    }

    /** * Obtain authorization Request **@return AuthRequest
     */
    private AuthRequest getAuthRequest(a) {
        return new AuthGiteeRequest(AuthConfig.builder()
                .clientId("clientId")
                .clientSecret("clientSecret")
                .redirectUri("redirectUri") .build()); }}Copy the code

Next we need to create our OAuth application on Gitee. After logging in to Gitee, we click the user profile picture in the upper right corner, select Settings, and then click the third-party app to enter the third-party app management page. Click the Create App button in the upper right corner to enter the app creation page

We fill in our app information as prompted.

Application name: generally fill in your own website name

Application description: Generally, fill in your own application description

Application home page: Fill in the home page address of your website

Application callback address: Key, this address is the address of the website to which the user needs to redirect after authorization, and carries a code parameter by default

Permission: operate according to page prompts. By default, select the first one, because we only need to obtain user information

After entering the above information, click ok to create the application. After the application is created, you can click the application details page to view the application key and other information

Copy the following information: Client ID, Client Secret, and application callback address.

Customize HTTP tools

Add the previous step to AuthConfig as follows:

// ...* /private AuthRequest getAuthRequest(a) {
        return new AuthGiteeRequest(AuthConfig.builder()
                .clientId("4c504cd2e1b1dbaba8dc1187d8070adf679acab17b2bc9cf6dfa76b9ae06aadc")
                .clientSecret("fa5857175723475e4675e36af9eafde338545c1a0dfa49d1e0cc78f9c3ce5ebe")
                .redirectUri("http://localhost:8080/oauth/callback/gitee") .build()); }}Copy the code

After completion of the above work, we start the project directly, and then visit http://localhost:8080/oauth/render/gitee in the browser, when in the following pages, said we have integrated the success, and has to jump to the third party authorization page.

Note that if you are not currently logged into your account in your browser, you will see the following page:

After we click “Agree to license”, the third party application (Gitee) will generate a code authorization code and call it back to our configured redirectUri interface along with the state we preloaded.

As shown above, after entering the callback interface, we can break trace to the information returned by the third party platform: state and code.

If you are a new project, there may be a slight problem:

2020-04-21 23:50:02 http-nio-8080-exec-4 me.zhyd.oauth.log.Log(error:45) [ERROR] - Failed to login with request authorization. Com. Xkcoding. HTTP. Exception. SimpleHttpException: HTTP implementation class is not specified! at com.xkcoding.http.HttpUtil.checkHttpNotNull(HttpUtil.java:70)
	at com.xkcoding.http.HttpUtil.post(HttpUtil.java:119)
	at me.zhyd.oauth.request.AuthDefaultRequest.doPostAuthorizationCode(AuthDefaultRequest.java:213)
	at me.zhyd.oauth.request.AuthGiteeRequest.getAccessToken(AuthGiteeRequest.java:31)
	at me.zhyd.oauth.request.AuthDefaultRequest.login(AuthDefaultRequest.java:79)
	at me.zhyd.justauth.JustAuthController.login(JustAuthController.java:47)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHand lerMethod.java:105) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHan dlerAdapter.java:879) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerA dapter.java:793)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Copy the code

This is because JustAuth has integrated simple-HTTP as the HTTP universal interface by default since v1.14.0 (see JustAuth version 1.14.0 for updates! JustAuth will not integrate hutool-HTTP by default. If the developer’s project is new or does not have an integrated HTTP implementation tool, it will need to add the corresponding HTTP implementation class. JustAuth provides three alternative POM dependencies:

hutool-http

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-http</artifactId>
    <version>5.2.5</version>
</dependency>
Copy the code

httpclient

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.12</version>
</dependency>
Copy the code

okhttp

<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
  <version>4.4.1</version>
</dependency>
Copy the code

After adding the HTTP tools rely on, restart the project, visit http://localhost:8080/oauth/render/gitee to link, and then for authorization.

Once the authorization is complete, you will see the following page:

Custom cache

In the OAuth authorization process, there is a very weak but very important parameter state. The state parameter is explained as follows in relevant documents:

RECOMMENDED. An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in Section 10.12.

The preceding content is excerpted from The OAuth 2.0 Authorization Framework 4.1.1

State is an opaque value used to maintain the state between the request and the callback. “Opaque” is better understood as “unpredictable”. When state is not used, websites that have integrated OAuth login are vulnerable to CSRF attack. The details and process of implementing CSRF attack have been jeopardized here, and the interested friends can refer to: CSRF attack on OAuth2

Thus, state runs through the entire OAuth authorization process to ensure continuity and security between processes. However, because it is a non-mandatory parameter, developers often forget to use it, and many third-party platforms have non-mandatory state in the OAuth API.

In the OAuth process, code parameters are time-limited and generally valid for 10 minutes. If they are not used for more than 10 minutes, you need to apply for code information again. For State, there is no explanation of timeliness in OAuth official website documents, that is to say, only the client itself can verify the timeliness and validity of State.

JustAuth takes this into account. In all OAuth processes, on the one hand, the state parameter is passed to ensure the integrity of the process. If the client does not specify it manually, the default unique value is used. On the other hand, state will also be cached to ensure its timeliness and prevent state from being reused.

By default, JustAuth uses a local map to cache state. For details, see the AuthDefaultCache class

package me.zhyd.oauth.cache;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/** * The default cache implementation **@author yadong.zhang (yadong.zhang0415(a)gmail.com)
 * @since1.9.3 * /
public class AuthDefaultCache implements AuthCache {

    /** * state cache */
    private static Map<String, CacheState> stateCache = new ConcurrentHashMap<>();
    private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(true);
    private final Lock writeLock = cacheLock.writeLock();
    private final Lock readLock = cacheLock.readLock();

    public AuthDefaultCache(a) {
        if (AuthCacheConfig.schedulePrune) {
            this.schedulePrune(AuthCacheConfig.timeout); }}/** * set the cache **@paramKey Cache key *@paramValue Cache content */
    @Override
    public void set(String key, String value) {
        set(key, value, AuthCacheConfig.timeout);
    }

    /** * set the cache **@paramKey Cache key *@paramValue Cache content *@paramTimeout Specifies the cache expiration time (ms) */
    @Override
    public void set(String key, String value, long timeout) {
        writeLock.lock();
        try {
            stateCache.put(key, new CacheState(value, timeout));
        } finally{ writeLock.unlock(); }}/** * get cache **@paramKey Cache key *@returnCache content */
    @Override
    public String get(String key) {
        readLock.lock();
        try {
            CacheState cacheState = stateCache.get(key);
            if (null == cacheState || cacheState.isExpired()) {
                return null;
            }
            return cacheState.getState();
        } finally{ readLock.unlock(); }}/** * If there is a key, return false ** if the value of the corresponding key is expired@paramKey Cache key *@returnTrue: the key exists and the value does not expire. False: The key does not exist or has expired */
    @Override
    public boolean containsKey(String key) {
        readLock.lock();
        try {
            CacheState cacheState = stateCache.get(key);
            return null! = cacheState && ! cacheState.isExpired(); }finally{ readLock.unlock(); }}/** * Clear expired cache */
    @Override
    public void pruneCache(a) {
        Iterator<CacheState> values = stateCache.values().iterator();
        CacheState cacheState;
        while (values.hasNext()) {
            cacheState = values.next();
            if(cacheState.isExpired()) { values.remove(); }}}/** ** Periodically clean **@paramDelay Interval, in milliseconds */
    public void schedulePrune(long delay) {
        AuthCacheScheduler.INSTANCE.schedule(this::pruneCache, delay);
    }

    @Getter
    @Setter
    private class CacheState implements Serializable {
        private String state;
        private long expire;

        CacheState(String state, long expire) {
            this.state = state;
            // The actual expiration time is equal to the current time plus the expiration date
            this.expire = System.currentTimeMillis() + expire;
        }

        boolean isExpired(a) {
            return System.currentTimeMillis() > this.expire; }}}Copy the code

prompt

About JustAuth check the state of the process, you can refer to: segmentfault.com/a/119000002…

In addition, JustAuth also supports a developer’s custom cache implementation because the developer may not want to use map as a cache tool, or the developer may already use other cache components in their existing projects, such as Redis, to implement state cache directly.

This paper takes Redis as an example to implement a custom cache.

Start by adding Redis dependencies to your project

// ...
        <! Custom cache implementation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    // ...
Copy the code

Then add the basic redis configuration to the configuration file

 // ...
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
Copy the code

Next, we implement the cache interface AuthStateCache provided by JustAuth. We use RedisTemplate to cache state.

package me.zhyd.justauth.cache;

import me.zhyd.oauth.cache.AuthCacheConfig;
import me.zhyd.oauth.cache.AuthStateCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;

/** * Extend Redis state cache **@author yadong.zhang (yadong.zhang0415(a)gmail.com)
 * @version 1.0.0
 * @date 2020/4/25 14:21
 * @since1.0.0 * /
@Component
public class AuthStateRedisCache implements AuthStateCache {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private ValueOperations<String, String> valueOperations;

    @PostConstruct
    public void init(a) {
        valueOperations = redisTemplate.opsForValue();
    }

    /** * Save to cache, default 3 minutes **@paramKey Cache key *@paramValue Cache content */
    @Override
    public void cache(String key, String value) {
        valueOperations.set(key, value, AuthCacheConfig.timeout, TimeUnit.MILLISECONDS);
    }

    /** * store in cache **@paramKey Cache key *@paramValue Cache content *@paramTimeout Specifies the cache expiration time (ms) */
    @Override
    public void cache(String key, String value, long timeout) {
        valueOperations.set(key, value, timeout, TimeUnit.MILLISECONDS);
    }

    /** * get the cache contents **@paramKey Cache key *@returnCache content */
    @Override
    public String get(String key) {
        return valueOperations.get(key);
    }

    /** * If there is a key, return false ** if the value of the corresponding key is expired@paramKey Cache key *@returnTrue: the key exists and the value does not expire. False: The key does not exist or has expired */
    @Override
    public boolean containsKey(String key) {
        returnredisTemplate.hasKey(key); }}Copy the code

Pay attention to

AuthCacheConfig is the default JustAuth cache configuration class, and AuthCacheConfig. Timeout is the built-in cache expiration time, which is 3 minutes by default

The Request class provided by JustAuth supports two construction parameters by default. One is to pass in AuthConfig, and JustAuth uses built-in cache by default. The other option is to pass in AuthConfig and AuthStateCache

Next we need to inject the stateCache implementation class created in the previous step into JustAuth’s Request.

// ...
    /** * Inject a custom cache implementation class */
    @Autowired
    private AuthStateRedisCache stateRedisCache;
 // ...
    private AuthRequest getAuthRequest(a) {
        return new AuthGiteeRequest(AuthConfig.builder()
                .clientId("4c504cd2e1b1dbaba8dc1187d8070adf679acab17b2bc9cf6dfa76b9ae06aadc")
                .clientSecret("fa5857175723475e4675e36af9eafde338545c1a0dfa49d1e0cc78f9c3ce5ebe")
                .redirectUri("http://localhost:8080/oauth/callback/gitee")
                // ...
                .build(), stateRedisCache);
    }
 // ...
Copy the code

Let’s use breakpoints to see if our custom cache implementation is enabled.

As you can see, the cache implementation currently in use is our custom cache.

Pay attention to

AuthCacheConfig. Timeout Specifies the built-in cache expiration time. The default validity period is 3 minutes. Considering the normal process, the time of an authorization process will not be long. Therefore, in order to ensure the normal completion of OAuth process and the validity of state, the system has a validity period of 3 minutes by default. You can customize the cache time by reassigning AuthCacheConfig. Timeout or by customizing the cache expiration time when implementing the public void Cache (String Key, String Value) method.

Integrate your own Gitlab private server login

Up to now, JustAuth has basically covered most well-known websites at home and abroad. JustAuth has always been loved and supported by developers for its integrity and simplicity.

However, as OAuth technology becomes more and more mature, more and more individual webmasters or enterprises start to build their own OAuth authorization platforms. In this case, JustAuth cannot cover all aspects, nor can it integrate all OAuth supporting websites (which is also unrealistic).

Since this need is considered, it is necessary to find a way to solve it and fill the hole, not for myself, but also for all the friends who accompany JustAuth along the way.

The JustAuth development team has also added a new feature in v1.12.0, which enables easy OAuth login from any Website that supports OAuth!

This section will demonstrate how to integrate your own Gitlab private server for third-party login.


Prepare the Gitlab private server

First we need to have a private server available for Gitlab. If not, please solve it yourself.

Create the OAuth application

After creation, you should get the following:

Copy the Application Id, Secret, and Callback URL for later use

Implement the AuthSource interface

Authsource.java is a unified interface that provides API addresses for the OAuth platform, providing the following interfaces:

AuthSource#authorize(): Obtain authorization URL. Must be implemented

  • AuthSource#accessToken(): get the url for accessToken. Must be implemented

  • AuthSource#userInfo(): Url to get user information. Must be implemented

  • AuthSource# REVOKE (): Get the URL to revoke authorization. Optional interface implementation (not supported on some platforms)

  • AuthSource#refresh(): gets the url for the refresh authorization. Optional interface implementation (not supported on some platforms)

Note:

Pay attention to

  1. Refer to this when implementing third-party authorization through the JustAuth extensionAuthDefaultSourceCreate your own enumeration class and implement the AuthSource interface
  2. If it is not an enumeration class used, it needs to be handled separately when obtaining user information after successful authorizationsourceField assignment
  3. If the corresponding enumeration class is extended, theme.zhyd.oauth.request.AuthRequest#login(AuthCallback)Can be passed throughxx.toString()Get the corresponding source
package me.zhyd.justauth.ext;

import me.zhyd.oauth.config.AuthSource;

/** * Custom AuthSource for integration with your own OAUTH system **@author yadong.zhang (yadong.zhang0415(a)gmail.com)
 * @version 1.0.0
 * @date2020/4/25 answering *@since1.0.0 * /
public enum AuthCustomSource implements AuthSource {

    /** ** gitLab private server */
    MYGITLAB {
        /** * authorized API **@return url
         */
        @Override
        public String authorize(a) {
            return "http://gitlab.demo.dev/oauth/authorize";
        }

        /** * Get the accessToken API **@return url
         */
        @Override
        public String accessToken(a) {
            return "http://gitlab.demo.dev/oauth/token";
        }

        /** * API to get user information **@return url
         */
        @Override
        public String userInfo(a) {
            return "http://gitlab.demo.dev/api/v4/user"; }}}Copy the code

Note that the URL of gitlab service in the article has been desensitized by me. Please change the user to his/her own gitlab service domain name, for example: Your private domain name is https://gitlab.zhyd.me, then replace all the above http://gitlab.demo.dev with https://gitlab.zhyd.me.

Create a custom Request

AuthGitlabRequest can be directly referenced here, the complete code is as follows:

package me.zhyd.justauth.ext;

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;

/** * Custom OAuth platform Request **@author yadong.zhang (yadong.zhang0415(a)gmail.com)
 * @version 1.0.0
 * @date2020/4/25 "*@since1.0.0 * /
public 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 responseBody = doPostAuthorizationCode(authCallback.getCode());
        JSONObject object = JSONObject.parseObject(responseBody);

        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 responseBody = doGetUserInfo(authToken);
        JSONObject object = JSONObject.parseObject(responseBody);

        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 authentication is abnormal
        if (object.containsKey("error")) {
            throw new AuthException(object.getString("error_description"));
        }
        // User authentication is abnormal
        if (object.containsKey("message")) {
            throw new AuthException(object.getString("message")); }}/** * return string {@codeThe authorization URL for the state} parameter, which will be taken with the authorization callback {@code state}
     *
     * @paramState State Verifies the parameters of the authorization process to prevent CSRF *@returnReturns the authorized address *@since1.11.0 * /
    @Override
    public String authorize(String state) {
        return UrlBuilder.fromBaseUrl(super.authorize(state))
                .queryParam("scope"."read_user+openid") .build(); }}Copy the code

Modify JustAuthController

Here we modify JustAuthController to support multiple platforms. The complete code is as follows:

package me.zhyd.justauth;

import me.zhyd.justauth.cache.AuthStateRedisCache;
import me.zhyd.justauth.ext.AuthMyGitlabRequest;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.request.AuthDingTalkRequest;
import me.zhyd.oauth.request.AuthGiteeRequest;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/** * How to use JustAuth to log in **@author yadong.zhang (yadong.zhang0415(a)gmail.com)
 * @version 1.0.0
 * @since1.0.0 * /
@RestController
@RequestMapping("/oauth")
public class JustAuthController {

    /** * Inject a custom cache implementation class */
    @Autowired
    private AuthStateRedisCache stateRedisCache;

    /** * Get the authorization link and jump to the third party authorization page **@param response response
     * @throwsIOException Response Possible exception */
    @RequestMapping("/render/{source}")
    // ...
    public void renderAuth(@PathVariable("source") String source, HttpServletResponse response) throws IOException {
        AuthRequest authRequest = getAuthRequest(source);
        String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
        response.sendRedirect(authorizeUrl);
    }

    /** * After the user confirms the authorization (login) of the third-party platform, the third-party platform will redirect the user to this address with parameters such as code and state **@paramCallback Specifies the input parameter to a third-party callback@returnThird party platform user information */
    @RequestMapping("/callback/{source}")
    // ...
    public Object login(@PathVariable("source") String source, AuthCallback callback) {
        AuthRequest authRequest = getAuthRequest(source);
        return authRequest.login(callback);
    }

    /** * Obtain authorization Request **@return AuthRequest
     */
    // ...
    private AuthRequest getAuthRequest(String source) {
        AuthRequest authRequest = null;
        switch (source) {
            case "gitee":
                authRequest = new AuthGiteeRequest(AuthConfig.builder()
                        .clientId("4c504cd2e1b1dbaba8dc1187d8070adf679acab17b2bc9cf6dfa76b9ae06aadc")
                        .clientSecret("fa5857175723475e4675e36af9eafde338545c1a0dfa49d1e0cc78f9c3ce5ebe")
                        .redirectUri("http://localhost:8080/oauth/callback/gitee")
                        .build(), stateRedisCache);
                break;
            case "mygitlab":
                authRequest = new AuthMyGitlabRequest(AuthConfig.builder()
                        .clientId("6ff1e2ccc356a4c193b663a2fbd4be34807e97a630e6e225d8d980ee9406d4a1")
                        .clientSecret("d935d24579e689c7cc8b41407a1b7886e45e8da3cd40fb2694bc8a00c0430c4e")
                        .redirectUri("http://localhost:8080/oauth/callback/mygitlab")
                        .build());
                break;
            default:
                break;
        }
        if (null == authRequest) {
            throw new AuthException("No valid Auth configuration obtained");
        }
        returnauthRequest; }}Copy the code

Restart the project, the browser to http://localhost:8080/oauth/render/mygitlab you will see the following page:

Click Authorize to complete the login of gITLab private server

At this point, we have implemented the function of integrating our own Gitlab private server login.

New plans: Support for more language SDKS

Finally, a reminder to recruit more NodeJS, Python, PHP, and Go community developers:

JustAuth: github.com/justauth

  • Java (completed: github.com/justauth/Ju…

  • .net (Completed: github.com/justauth/Co… Developed by JustAuth, now included in JustAuth organization)

JustAuth plans to open source the following versions of the SDK:

  • NodeJS
  • Python
  • PHP
  • Go

The latest version of JustAuth will give priority to the NodeJS version. If you are interested, please sign up and join us. The same thing: doing something you like with a group of like-minded friends is a kind of happiness.

If you are interested, please add WX: RZ04151123 (add note: JustAuth development) to get you into the exclusive developer exchange group.

Want to learn more exciting practical skills tutorial? Come and visit the Tooquine community.