# 用户身份认证源使用说明

# 登录介绍

# 登录方式

登录方式,目前分为三种方式:主登录、辅助登录、直接跳转

主登录

主登录,是使用账号密码认证一种方式,其中密码至少由8位组成,传输过程也会进行加密。包括平台账号登录、LDAP登录等账号密码方式

image-20221205133431699image-20221205133938068

辅助登录

辅助登录,是主登录的辅助方式,通常由第三方提供认证过程,点击登录会跳转认证页,比如OpenID方式

image-20221205133718791image-20221205133828363

直接跳转

直接跳转登录,是一种快捷认证方式,在认证过程中,平台登录框不会出现,login请求会直接跳转认证页。比如微信扫码登录、数帆登录等

# 快速开始

# 使用平台账号登录

image-20221205102102096

平台提供账号密码方式登录,且默认开启,如果应用需要快速集成用户身份认证,则可使用平台账号密码方式

# 使用社会化认证源登录

目前支持仅微信H5方式,QQ、支付宝、Sina、GitHub、百度、Google、Twitter、Facebook、Gitee、LinkedIn、Instagram在支持计划中

image-20221205135749006

此场景适用于网页公众号授权登录,具体开发文档详见以下地址

https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#1

# 使用企业认证源登录

目前支持网易OpenID,钉钉、飞书、企业微信、WindowAD、AzureAD、SAML、OAuth2.0、CAS、LCAP、OIDC、WeLink在支持计划中

image-20221205135704708

需要申请OpenID合法站点信息,配置即可

# 使用私有化认证

如果上述方式不能满足认证需求,用户可以通过开发自定义认证插件完成认证流程,详见插件开发流程

# 登录配置开启

参数配置后,可以在账号认证和权限中心 -> 系统管理 -> 登录配置->平台登录页选项开启

主登录和辅助登录方式

image-20221205120726525

直接跳转方式

image-20221205120757476

# 插件认证开发

# 基本介绍

# 1.插件结构

认证插件,分为前端和后端两个部分

前端插件,负责浏览器参数传递,跳转等逻辑,支持JavaScript语言,产出物为js文件

后端插件,处理用户认证信息的获取流程,支持Java语言,产出物为jar文件

如果为主登录方式,前端js插件不需要开发

# 2. 使用对象

有语言基础的开发者,目前支持开发语言

  • 服务端:Java
  • 客户端:js

# 3. 环境要求

  • 推荐jdk1.8及以上、maven

# 4. 插件开发流程

image-20221205191236107

# 5. 插件工作原理

下图为插件交互流程,

如果主登录方式,过程1可以省去,用户输入账号密码后,提交给前端插件走后续流程,获取UserInfo

image-20221205193048326

# 插件框架能力

# 参数配置

针对不同环境,认证过程需要配置的参数,在插件开发中,可以借助@ConfigKey注解,来完成注入

  /**
   * AppKey
   */
    @ConfigKey(ConfigKeyContants.APP_KEY)
    protected String appKey;
    /**
     * AppSecret
     */
    @ConfigKey(ConfigKeyContants.APP_SECRET)
    protected String appSecret;

1
2
3
4
5
6
7
8
9
10
11

声明的ConfigKey参数,可以在平台系统配置-登录配置中看到,如下图所示

image-20221206100433288

image-20221206100612606

# 异常处理

getAuthInfo 认证过程中,可以抛出以下异常,框架会自行处理,并在前端登录页提示给用户

异常名称 描述
AuthException 基础鉴权异常,鉴权相关的子异常应该继承此异常
AuthIncorrectCredentialsException 认证异常,密钥错误
AuthUnknownAccountException 认证异常,不存在此账户
AuthDisabledAccountException 认证异常,账户禁用
# SDK

框架提供上下文能力PluginContext

  • PropertiesContext 配置文件能力
  • EncryptContext 加解密能力
    • 常见加密工具类(Base64、MD5、AES等)
  • RepositoryContext 持久化能力
  • SerializeContext/DeserializeContext 序列化/反序列化能力
    • 常见序列化工具类(json、xml)
  • ClientContext 网络请求能力
    • httpClient (okhttp、httpclient) 目前默认为OKhttp,后续扩展httpClient能力
# 退出登录

如果需要退出登录操作,则需要实现logout方法

    /**
     * 退出登录入口,子类可以实现
     * @return
     */
    default boolean logout(HttpServletRequest servletRequest, final Map<String, String> parameterMap){return true;}
1
2
3
4
5

# API

API列表如下

API 描述
IAuthService 认证基类
AbstractPluginAuthService 插件类型认证抽象类 (插件继承实现)
AuthInfo 认证信息

# IAuthService

# getAuthInfo

getAuthInfo为认证核心逻辑,开发者可以在实现与认证源的交互

HttpServletRequest servletRequest,

可以获取访问浏览器header、cookie等信息

Map<String, String> parameterMap

获取认证参数,其中参数名要保持一致

# logout

注销方法,如果认证源如果同步注销,则需要实现该方法

# getAuthPageUrl

获取认证页地址,如果认证源有落地认证页,则需要实现该方法,一般适用于OAuth协议场景

# AbstractPluginAuthService

默认注入了框架能力PluginConext,LoggerContext,插件类可直接使用

# doHandle

认证核心逻辑

# preHandle

认证前处理,可选择实现

# postHandle

认证后处理,可选择实现

# AuthInfo

字段 描述 是否必填
userId 唯一用户ID,类似OpenId等
userName 登录系统使用的用户账号,类似手机号、邮箱等字段,UserName不可重复
displayName 显示名称,一般为用户姓名、昵称等具备可读性的字段(例如微信昵称、QQ昵称、微博昵称等)
authParam 校验参数 (若参数不为空,则每次用户请求,则会再次发起校验)

# 最佳实践

# 准备插件开发环境

# 安装JDK

根据本机环境下载对应安装包

http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

下载安装好并配置环境变量JAVA_HOME,在命令行窗口执行Java命令

 $ java -version
 
java version "1.8.0_341"
Java(TM) SE Runtime Environment (build 1.8.0_341-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.341-b10, mixed mode)
1
2
3
4
5
# 安装maven

下载安装maven包管理工具,并配置环境变量

https://maven.apache.org/download.cgi

 $ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: D:\workspace\tools\apache-maven-3.6.3\bin\..
Java version: 1.8.0_333, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk1.8.0_333\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
1
2
3
4
5
6
# 安装开发工具

推荐安装Idea,下面案例以IntelliJ Idea为例

https://www.jetbrains.com/idea/download/#section=windows

# 创建插件工程

模板工程: auth-plugin-archetype https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/devtools/simple-auth-plugin-archetype.zip)

可以基于demo工程(示例工程:https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/devtools/sub-plugin.zip)来创建

下载模板工程代码,并解压至某个文件夹,如下图所示:

image-20220907201109829

在根目录开启命令行窗口,执行以下命令,把模板工程安装到maven仓库

mvn install
1

创建插件应用,以Test插件为例(插件名称要求首字母大写,比如:Test1、Test2、Test3等),生成好的目录结构如下入所示

mvn archetype:generate                                  
  -DarchetypeGroupId=com.netease.cloud.nuims              
  -DarchetypeArtifactId=auth-plugin-archetype         
  -DarchetypeVersion=1.0.1                
  -DgroupId=com.netease.cloud.nuims    
  -DartifactId=test-plugin
1
2
3
4
5
6

image-20220907202124854

依赖包处理

  • nuims-auth-api-1.0.1.jar(https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/devtools/nuims-auth-api-1.0.1.jar)

下载依赖包到本地,完成后对文件进行重命名, nuims-auth-api-1.0.1.jar

目前有两种方式可以依赖

方案一

mvn install 到本地仓库,pom.xml依赖

mvn install:install-file -Dfile=nuims-auth-api-1.0.1.jar -DgroupId=com.netease.cloud.nuims -DartifactId=nuims-auth-api -Dversion=1.0.1 -Dpackaging=jar -DgeneratePom=true
1
<dependency>
    <groupId>com.netease.cloud.nuims</groupId>
    <artifactId>nuims-auth-api</artifactId>
    <version>1.0.1</version>
</dependency>

1
2
3
4
5
6

方案二:

本地依赖

新建libs目录,拷贝jar到libs目录下,进行pom.xml依赖

<dependency>
    <groupId>com.netease.cloud.nuims</groupId>
    <artifactId>nuims-auth-api</artifactId>
    <version>1.0.1</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/libs/nuims-auth-api-1.0.1.jar</systemPath>
</dependency>
1
2
3
4
5
6
7

应用目录介绍:

  • 核心认证类

TestPluginAuthService.java

  • 类声明文件

resources\META-INF\services\com.netease.cloud.nuims.auth.api.AbstractPluginAuthService

com.netease.cloud.nuims.plugin.extension.TestPluginAuthService

通用流程步骤为:

  • 编写框架代码: 实现类TestPluginAuthService ,继承抽象类AbstractPluginAuthService, name方法为认证标识
  • 编码完成后声明: 在META-INF/services/com.netease.cloud.nuims.auth.api.AbstractPluginAuthService文件中声明
com.netease.cloud.nuims.plugin.extension.TestPluginAuthService
1

# 编写插件逻辑

核心方法doHandler,此方法可以完成与认证源的交互

@ConfigKey 为个性化参数声明,可以通过配置文件注入

以下为常见三种类型认证方式示例:

# 账号密码,适用于主登录方式

注意UserName、Password 为页面参数名称,不可自定义命名

public class Test1PluginAuthService extends AbstractPluginAuthService {
    //账号为页面参数名称,不可更改
    private String USERNAME_KEY = "UserName";
    //密码为页面参数名称,不可更改
    private String PASSWORD_KEY = "Password";

    @Override
    protected AuthInfo doHandle(HttpServletRequest servletRequest, Map<String, String> parameterMap) throws AuthException {
        String userNameParams= parameterMap.get(USERNAME_KEY);
        String passwordParams = parameterMap.get(PASSWORD_KEY);
        if(Objects.nonNull(userNameParams) && Objects.nonNull(passwordParams)){
            if("12345678".equalsIgnoreCase(userNameParams) && "12345678".equalsIgnoreCase(passwordParams)){
                return AuthInfo.ofSuccess()
                    .setUserName(userNameParams)
                    .setDisplayName("昵称")
                    .setUserId(jsonObject.getString("userId"));
            }
        }
        pluginContext.getLoggerContext().info("Authenticate Test1 userInfo failed");
        throw new AuthIncorrectCredentialsException("UserName or Password");
    }
    @Override
    public String name() {
        return "Test1";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Oauth方式,适用于辅助登录和直接跳转方式
public class TestPluginAuthService extends AbstractPluginAuthService {
    @ConfigKey(ConfigKeyContants.APP_KEY)
    protected String appKey;
    @ConfigKey(ConfigKeyContants.APP_SECRET)
    protected String appSecret;
    private final String URL_AUTHORIZE = "https://b2hl7rcn.aliyunidaas.com/login/app/app_mklmfzgdvg4v3gsnjn5ywxpiwe/oauth2/authorize?client_id=%s&redirect_uri=%s&response_type=code&scope=openid&state=%s&prompt=login";
    private final String URL_TOKEN = "https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_udovrubrco6sqq7duh6trfhojm/app_mklmfzgdvg4v3gsnjn5ywxpiwe/oauth2/token?grant_type=authorization_code&code=%s&client_id=%s&client_secret=%s&redirect_uri=%s";
    private final String URL_USERINFO = "https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_udovrubrco6sqq7duh6trfhojm/app_mklmfzgdvg4v3gsnjn5ywxpiwe/oauth2/userinfo?access_token=%s";
    private String LOGIN_TYPE = "Test";
    private final String KEY_ACCESS_TOKEN = "access_token";
    private String state;
    private String redirectUrl = "http://127.0.0.1:8080/auth/token?authType=%s";

    @Override
    protected AuthInfo doHandle(HttpServletRequest servletRequest, Map<String, String> parameterMap) throws AuthException {
        AuthInfo authInfo = new AuthInfo();
        //获取access_token
        String code = parameterMap.get("Code");
        String tokenUrl = String.format(URL_TOKEN, code, appKey, appSecret, String.format(redirectUrl, name()));
        String result = pluginContext.getClientContext().doGet(tokenUrl);
        if (result == null || result.length() == 0) {
            throw new AuthException(name() + " tokenUrl get result is null");
        }
        JSONObject jsonObject = pluginContext.getDeserializeContext().fromJson(result);
        if (jsonObject.containsKey(KEY_ACCESS_TOKEN)) {
            String accessToken = jsonObject.getString(KEY_ACCESS_TOKEN);
            //获取UserInfo
            String userInfoUrl = String.format(URL_USERINFO, accessToken);
            result = pluginContext.getClientContext().doGet(userInfoUrl);
            if (result == null || result.length() == 0) {
                throw new AuthException(name() + " userInfoUrl get result is null");
            }
            jsonObject = pluginContext.getDeserializeContext().fromJson(result);
            authInfo.setUserName(jsonObject.getString("email"));
            authInfo.setUserId(jsonObject.getString("sub"));
        }
        return authInfo;
    }
    @Override
    public String getAuthPageUrl() {
        return String.format(URL_AUTHORIZE, appKey, String.format(redirectUrl, name()));
    }
    @Override
    public String name() {
        return LOGIN_TYPE;
    }


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 插件调试

  • 打包

    注意pom.xml的build配置不可更改,否则会出现打包不完整的现象

    执行以下命令

    mvn clean package -DskipTest=true
    
    1
  • 调试

    1. 下载调试工程jar (https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/devtools/container.jar)

    2. 新建配置文件模板:application.yaml

    auth:
      plugin:
        list:
          Test: # 认证插件名称
            #插件文件路径
            path: D:\workspace\netease\nuims\nuims-plugin-extension\test-plugin\target\test-plugin.jar
            #插件网络路径,和path二选一
            #url: https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/Test-plugin.jar
            category: main # 有三种类型 main-主登录 sub-辅助登录 direct-直接跳转登录
            config: #@configKey的参数默认值
              appKey: test
              appSecret: test
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    3.启动命令

     java -jar  -Dspring.config.location=./application.yaml container.jar 
    
    1

    如果无配置参数可直接启动:

    java  -jar -Dauth.plugin.list.Test.path=D:\workspace\netease\nuims\nuims-plugin-extension\Test-plugin\target\Test-plugin.jar  container.jar
    
    1
    1. 启动成功后,打开浏览器访问 http://127.0.0.1:8080/即可

image-20220907203821386

  1. 参数调试

    主登录(账号密码)方式
    Curl -X POST  http://127.0.0.1:8080/auth/token?authType=Test&UserName=test&Password=123456
    
    辅助登录和直接跳转方式,具体参数由认证源规则而定
    Curl -X POST  http://127.0.0.1:8080/auth/token?authType=Test&Code=**********
    
    1
    2
    3
    4
    5

# 插件上传

1、 在账号与权限管理后台,系统管理->插件管理,新增插件

image-20221205141024362

2、更新插件资源,上传调试好的jar文件、js文件(如果不需要可以不传)

image-20221205141048249

3、登录配置,配置相关参数,保存开启

image-20221205160920790

# 插件升级

插件默认版本号为0,如果插件逻辑有改动,则需要复写version()方法,升级版本号,再发布升级

/**
 */
public class TestPluginAuthService extends AbstractPluginAuthService {
    @Override
    protected AuthInfo doHandle(HttpServletRequest servletRequest, Map<String, String> parameterMap) throws AuthException {
        AuthInfo authInfo = new AuthInfo();
        return authInfo;
    }
    @Override
    protected void preHandle(Map<String, String> parameterMap) {
        logger.info("preHandle");
    }
    @Override
    protected void postHandle(AuthInfo authInfo) {
        logger.info("postHandle");
    }
    @Override
    public String getAuthPageUrl() {
        return String.format(URL_AUTHORIZE, appKey, String.format(redirectUrl, name()), generateState());
    }
    @Override
    public String name() {
        return LOGIN_TYPE;
    }
    @Override
    public int version() {
        return 2;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

业务插件,可以更改版本号version,目前不允许向下升级,只能递增版本号升级,比如

1 -> 2

2 -> 3

非法升级

3->2

# 插件使用

应用开启

在IDE登录页组件右侧属性面板开启登录,如果为直接跳转方式,则需要把 “是否开启登录页” 关闭才能选择

image-20221205144237002

如果为用户下沉场景,则需要配置登录方式相关参数,点击发布即可开启成功

image-20221205144259873

# 测试登录

image-20221205161914242

# 常见问题

开发者环境要求

maven、java、node

jdk版本需要指定吗? 使用jdk1.8

# 资源下载

  • 模板工程: auth-plugin-archetype (https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/devtools/simple-auth-plugin-archetype.zip)
  • 依赖jar文件
    • nuims-auth-api-1.0.1.jar(https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/devtools/nuims-auth-api-1.0.1.jar)
  • 测试Jar文件:container.jar (https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/devtools/container.jar)
  • 示例工程:
    • 辅助登录:https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/devtools/sub-plugin.zip
    • 主登录:https://lcpapp-static.nos-eastchina1.126.net/auth-plugin/devtools/main-plugin.zip
上次更新: 2023年06月14日