# 认证相关原理及集成说明
# 1 AFCenter认证
# 1.1 登录原理
源码路径
登录接口位于AFCenter源码下的com.primeton.gocom.afcenter.oauth模块儿下,具体路径为:
com.primeton.gocom.afcenter.oauth.controller.LoginManagerController#login
时序图
说明
登陆时,需要用户输入用户名与密码,后端服务会先获取用户的设备类型,方便后续登陆策略中使用。然后会去校验用户信息,包含用户校验、密码校验、安全策略校验等,校验通过后会去根据用户信息创建用户会话信息,创建会话信息时会初始化用户的UserObject对象,UserObject对象包含了用户的基本型信息以及权限信息,在整个后续使用过程中作为登陆凭证来使用。同时记录下用户的登陆操作日志,然后将登录成功信息返回给用户,同时将用户会话绑定UserObject。
# 1.2 认证原理
源码路径:
在AFCenter中基础的认证类源码在com.primeton.gocom.afcenter.common模块儿下,具体路径为:
com.primeton.gocom.afcenter.common.intercepter.UserLoginInterceptor
活动图
描述
认证时,如果是白名单接口,会设置匿名对象进行上下文的初始化。如果不是白名单接口,会从请求中获取所需要的认证凭证信息,如果开启了CAS认证,则根据请求的url判断是否需要校验,如果需要校验则调用CAS的认证接口进行认证,如果不需要校验,根据请求参数判断是否需要登出或者是正常的接口调用,是正常接口调用就根据凭证和会话信息进行eos上下文初始化,然后执行调用。如果凭证和会话信息无效,并且也不是白名单或者CAS的认证方式,则判断是否是OAuth2认证模式(访问授权码模式),如果不是,则返回401未授权信息,否则调用OAuth2的认证服务进行认证,认证成功后携带凭证信息重定向回配置的回调地址。
自定义认证说明
如果项目上有自定义认证需求,可以修改该拦截器及该拦截器的子类(取决于集成模式,starter集成还是sdk集成),子类分别位于如下位置:
starter集成com.primeton.gocom.afcenter.oauth.intercepter.AFCUserLoginInterceptor
sdk集成com.primeton.gocom.afcenter.sdk.SDKLoginInterceptor
# 1.3拦截器伪登录说明
在某些特殊场景下,我们需要页面访问低开资源,此时用户信息可能通过url或者请求头来进行传递,那么我们就可以在拦截器中进行获取,然后通过凭证信息获取用户信息进行伪登录逻辑,完成功能调用,下面举一个例子来配合AFCenter中的拦截器进行伪登录的模拟行为(这里不考虑提供认证的服务的实现方案,只说明在AFCenter中何时、何地调用认证服务)。
例子:通过url来传递
url:http://10.15.15.151:13082/index.html#/module/formDesigner/page/render?resourceType=form&authCode=123456
这个url中携带了authCode来传递用户认证信息,那么我们可以在拦截器中获取该参数,然后调用认证服务提供的接口进行认证,成功后初始化UserObject,将用户信息放入EOS上下文中在进行后续调用,具体demo如下:
package com.primeton.gocom.afcenter.common.intercepter;
import com.eos.access.http.IWebInterceptor;
import com.eos.access.http.IWebInterceptorChain;
import com.eos.access.http.OnlineUserManager;
import com.eos.access.http.UserLoginCheckedFilter;
import com.eos.common.cache.CacheFactory;
import com.eos.common.cache.CacheProperty;
import com.eos.common.cache.ICache;
import com.eos.data.datacontext.DataContextManager;
import com.eos.data.datacontext.IMUODataContext;
import com.eos.data.datacontext.IUserObject;
import com.eos.data.datacontext.UserObject;
import com.eos.runtime.core.TraceLoggerFactory;
import com.eos.spring.BeanFactory;
import com.eos.system.logging.Logger;
import com.eos.system.utility.JsonUtil;
import com.primeton.eos.api.springboot.EOS8ApplicationContext;
import com.primeton.eos.springboot.exception.ResponseErrorModel;
import com.primeton.ext.access.http.HttpMapContextFactory;
import com.primeton.ext.access.http.HttpUrlHelper;
import com.primeton.ext.common.muo.MUODataContextHelper;
import com.primeton.gocom.afcenter.api.oauth.ILoginService;
import com.primeton.gocom.afcenter.api.oauth.IOauthClientService;
import com.primeton.gocom.afcenter.api.resource.MenuUsageRecordService;
import com.primeton.gocom.afcenter.common.config.CrosConfig;
import com.primeton.gocom.afcenter.common.constants.CommonConstants;
import com.primeton.gocom.afcenter.common.context.DataPrivilegeHolder;
import com.primeton.gocom.afcenter.common.exception.AFCExceptionCode;
import com.primeton.gocom.afcenter.common.util.CasUtil;
import com.primeton.gocom.afcenter.common.util.CasUtil.ProxyTicketValidator;
import com.primeton.gocom.afcenter.common.util.UserUtil;
import com.primeton.gocom.afcenter.model.org.Employee;
import com.primeton.gocom.afcenter.model.org.User;
import com.primeton.system.exception.impl.ExceptionStatus;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.cors.CorsUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import static com.primeton.gocom.afcenter.common.constants.CommonConstants.API_PATH_PREFIX;
import static com.primeton.gocom.afcenter.common.constants.CommonConstants.SDK_URL_PREFIX;
/**
* 登录拦截器
*
* @author hanjz
* @date 2022年04月12日-15:16
*/
public class UserLoginInterceptor implements IWebInterceptor {
private static final Logger logger = TraceLoggerFactory.getLogger(UserLoginInterceptor.class);
public static final String AUTHORIZATION = "Authorization";
private static final String ORIGINAL_URL = "original_url";
public static final String TENANTID = "afc-tenantId";
//API接口的认证请求头
public static final String AFC_TOKEN = "afc-token";
protected static boolean isSdk = true;
public static final String ANONYMOUS = "anonymous";
public static final String GATEWAY_VALIDATE_TOKEN_URL = API_PATH_PREFIX + "/login/validation-token";
public static final String AFC_SSO_CAS_VALIDATE_URL = API_PATH_PREFIX + "/login/validation-sso-cas";
private static ICache<String, String> casSsoServiceTicketMapCache;
private static CrosConfig config;
private static IOauthClientService oauthClientService;
private static MenuUsageRecordService menuUsageRecordService;
static {
CacheProperty cacheConfig = new CacheProperty();
cacheConfig.setCacheName("casSsoServiceTicketMapCache");
casSsoServiceTicketMapCache = (ICache<String, String>)CacheFactory.getInstance().createCache(cacheConfig);
BeanFactory instance = BeanFactory.getInstance();
config = instance.getBean(CrosConfig.class);
oauthClientService = instance.getBean(IOauthClientService.class);
menuUsageRecordService = instance.getBean(MenuUsageRecordService.class);
}
@Override
public void doIntercept(HttpServletRequest request, HttpServletResponse response, IWebInterceptorChain chain) throws IOException, ServletException {
//添加跨域请求配置
String originHeader = request.getHeader("Origin") != null
? request.getHeader("Origin").replace(" ", "")
: "*";
//如果配置了可信域,则只能可信域访问,否则默认所有跨域请求都不拦截
boolean enableAllowedOrigins =
config.getAllowedOrigins() != null && config.getAllowedOrigins().length > 0;
if (CorsUtils.isCorsRequest(request) && enableAllowedOrigins &&
!Arrays.asList(config.getAllowedOrigins()).contains(originHeader)) {
ServletServerHttpResponse serverHttpResponse = new ServletServerHttpResponse(response);
serverHttpResponse.setStatusCode(HttpStatus.FORBIDDEN);
serverHttpResponse.getBody().write("Invalid CORS request".getBytes(StandardCharsets.UTF_8));
serverHttpResponse.flush();
return;
}
if (CorsUtils.isCorsRequest(request)) {
response.setHeader("Access-Control-Allow-Origin", originHeader);
response.setHeader("Access-Control-Allow-Methods", request.getMethod());
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setHeader("Access-Control-Max-Age", "3600");
response.setStatus(204);
return;
}
}
DataContextManager.current().setMapContextFactory(new HttpMapContextFactory(request, response));
String servletPath = HttpUrlHelper.getRequestUrl(request, response);
HttpSession session = null;
//logger.info("请求入口:uniqueId:" + request.getHeader(AUTHORIZATION) + ", url:" + servletPath);
//未登录的处理
//排除设置的url
if (HttpUrlHelper.isIn(servletPath, UserLoginCheckedFilter.getExcludeUrls()) || !HttpUrlHelper.isIn(servletPath, UserLoginCheckedFilter.getIncludeUrls())) {
session = request.getSession();
UserObject userObjectAnonymous = new UserObject();
userObjectAnonymous.setUserId(ANONYMOUS);
userObjectAnonymous.setUserName(ANONYMOUS);
userObjectAnonymous.setUserRealName(ANONYMOUS);
userObjectAnonymous.setUserRemoteIP(ANONYMOUS);
String tenantId = request.getHeader(TENANTID);
if (StringUtils.isNotBlank(tenantId)) {
userObjectAnonymous.put(CommonConstants.TENANT, tenantId);
}
if (servletPath.startsWith(SDK_URL_PREFIX)) {
//SDK接口,每次都会创建会话,单独设置10秒超时时间
session.setMaxInactiveInterval(10);
}
//logger.error("白名单:session" + session);
initContext(request, userObjectAnonymous, session);
try {
chain.doIntercept(request, response);
return;
} finally {
DataPrivilegeHolder.clear();
}
}
//这里是伪登录逻辑实现的地方,在上述sdk白名单代码之后紧接着去实现就行
String authCode = request.getParameter("authCode");
//如果没有这个参数既不进行伪登录,按照AFCenter原本逻辑继续执行
if (StringUtils.isNotBlank(authCode)) {
//调用认证服务器接口,完成用户信息的获取
//.......
//这里是获取到的用户id,当然也能是其他唯一标识员工的属性,这个根据具体项目而定
String userId = "123";
Employee emp = getEmployeeByUserId(userId);
session = request.getSession();
UserObject userObjectAnonymous = new UserObject();
userObjectAnonymous.setUserId(emp.getId());
userObjectAnonymous.setUserName(emp.getName());
//租户id
String tenantId = request.getHeader(TENANTID);
if (StringUtils.isNotBlank(tenantId)) {
userObjectAnonymous.put(CommonConstants.TENANT, tenantId);
}
//上述逻辑根据需要可以变更,后续逻辑可以直接照抄
//初始化上下文可以直接调用该方法
initContext(request, userObjectAnonymous, session);
try {
//过滤器链调用标准方法
chain.doIntercept(request, response);
return;
} finally {
DataPrivilegeHolder.clear();
}
//这是结尾,后面没有逻辑了,到这里自定义伪登录就结束了
}
//省略开源源码中的其他逻辑..........
}
}
这是URL携带参数的示例demo,拦截器的源码路径为com.primeton.gocom.afcenter.common.intercepter.UserLoginInterceptor,其中去认证服务器获取参数的方法可以在这个拦截器中定义一个空方法(模板方法),然后分别在oauth模块儿的com.primeton.gocom.afcenter.oauth.intercepter.AFCUserLoginInterceptor类和sdk模块儿的com.primeton.gocom.afcenter.sdk.SDKLoginInterceptor类分别去重写这个方法,这样可以保证不会出现依赖冲突,这里再说明下两个类的区别oauth模块的拦截器是后端starter集成方式使用的,sdk模块的拦截器是后端sdk集成模式使用的。
请求头传递参数的格式跟url唯一的区别就是认证参数从请求头中获取,例如request.getHeader("authCode"),其他没有区别
# 2 作为客户端集成第三方统一认证
# 2.1 OAuth2集成方案
AFCenter提供了默认的基于(授权码模式)OAuth2-Client实现,只需要稍加配置即可使用,如果认证服务器有特殊需求可在此基础上进行修改或者参照去实现就行。
默认实现
后端配置:
#开启oauth2-sso开关,默认为false
afc.sso.oauth2.enable=true
#授权地址
afc.sso.oauth2.authorize-url=http://localhost:1111/oauth/authorize
#获取token地址
afc.sso.oauth2.token-url=http://localhost:1111/oauth/token
#clientid
afc.sso.oauth2.client-id=xxx
#clientSecret
afc.sso.oauth2.client-secret=$2a$10$is2AxEqDRjwUNM2sxcxxxxx
#认证中心,认证成功之后携带code参数跳转afcenter的地址,这里的ip和端口是afcenter的后端地址,路由是一个无用的虚拟路由保证不是白名单路由即可,只做跳转使用
afc.sso.oauth2.redirect-url=http://localhost:28083/token
#afcenter的登录地址
afc.sso.oauth2.login-url=http://localhost:28083/#/login
#获取用户信息的地址
afc.sso.oauth2.userinfo-url=http://localhost:1111/user
#如果获取的用户信息是json对象,映射关系是某个属性,则填写属性名称,否则默认返回的数据就是映射关系值,例如返回的userId
#如果json为多层,如,{"Map":{"id":"1","name","admin"}},配置afc.sso.oauth2.user-key=id即可,不要自行解析json
afc.sso.oauth2.user-key=username
#使用code获取access_token时,如果直接返回值,则无需配置,返回的是json对象,需要配置access_token属性名称
afc.sso.oauth2.access-token-key=access_token
注意:afcenter和微应用使用oauth2-sso时都需要配置这个属性,其中微应用的redirect-url和login-url都是afcenter的地址
自定义实现
如果默认实现不满足需求,比如认证服务器的所有接口都是post请求(默认的获取token的接口是post请求,获取用户信息的是get请求),则可以继承
com.primeton.gocom.afcenter.common.oauth2.DefaultOauth2ClientServiceImpl,
重写对应的方法,或者参考该类,实现
com.primeton.gocom.afcenter.api.oauth.IOauthClientService
接口,重新实现自己的认证逻辑。实现完成后,确保该实现可以被spring的ioc容器管理即可。
**注意:**afc.sso.oauth2.enable配置是必须的,如果重新实现认证逻辑并且定义配置,则默认配置其他属性不需要配置。重新定义参数后,参考
com.primeton.gocom.afcenter.sdk.SdkDefaultOauth2ClientServiceImpl
实现,完成微应用的认证重定向逻辑。
前端配置:
如果获取到token,也可以直接访问前端地址后添加Authorization参数 http://localhost:8000?Authorization={你的token}
# 2.2 CAS集成方案
后端集成说明
文件位置:config/application-afc.properties
# cas sso
# 是否启用cas
afc.sso.cas.enable=true
# ticket校验url
afc.sso.cas.server-validate-url=https://127.0.0.1:8443/sso-server/serviceValidate
# 代理回调URL(可以没有)
afc.sso.cas.server-proxy-callback-url=
前端说明
1、config/app-config.json配置文件
# 是否启用sso登陆
"loginType": 'sso',
# sso登录url
"ssoLoginUrl": "https://127.0.0.1:8443/sso-server/login",
# sso登出url
"ssoLogoutUrl": "https://127.0.0.1:8443/default/loginout",
2、nginx配置
# 部署到nginx 服务器,修改nginx.conf添加配置,告诉afc已经登出
location / {
if ($request_method ~ ^(POST)$ ) {
proxy_pass http://127.0.0.1:13083;
}
}
# 3 作为服务端被第三方系统集成统一认证
# 3.1 OAuth2集成方案
AFCenter可作为OAuth2授权码模式的认证服务进行使用,基本的流程如下:
AFCenter中提供了授权码模式的实现,以RESTFull的方式提供对应接口,方便业务系统灵活的使用各种技术方案去集成。 (提供的后端demo中使用了Spring security sso模式)
示例中的AFCenter前端端口:8001,后端端口:28083
# 认证中心配置说明
AFCenter的properties中需要配置认证中心的登录地址
afc.oauth2.login-url=http://127.0.0.1:8001/#/auth //更改前端页面所在ip和端口
# 认证中心接口说明
后续接口没有特殊说明一律使用GET请求方式,同时建议除了获取授权码接口需要客户端调用外,其他接口都是后端进行调用。
获取授权码
根据以下格式构建url,引导用户访问(注意复制时删减掉多余空格或者换行符)
http://127.0.0.1:28083/api/afc/oauth2/authorize
?response_type=code
&client_id={value}
&redirect_uri={value}
&scope={value}
$state={value}
参数详解:
参数 | 是否必填 | 说明 |
---|---|---|
response_type | 是 | 返回类型,这里请填写:code |
client_id | 是 | 应用id(应用code/租户Id,eg:AFCENTER/123) |
redirect_uri | 是 | 用户确认授权后,重定向的url地址 |
scope | 否 | 固定是all |
state | 否 | 随机值,此参数会在重定向时追加到url末尾,不填不追加 |
1. 如果用户在AFCenter Server端(认证中心)尚未登录,会被转发到登录视图
2. 有没有scope参数默认都是静默授权all
用户登陆成功之后,会重新自动携带之前的参数请求授权接口,授权成功后,会被重定向到redirect_url,并追加code参数与state参数,如下:
redirect_uri?code={code}$state={state}
Code授权码具有以下特点:
- 每次授权产生的Code码都不一样
- Code码用完即废,不能二次使用
- 一个Code码有效期为5分钟,超时自动作废
- 每次授权产生的新Code码,会导致旧Code码立即作废,即使旧Code码未使用
根据授权码获取Access-Token
获得Code码后,可以通过以下接口,获取用户的Access-Token、Refresh_Token等关键信息
http://127.0.0.1:28083/api/afc/oauth2/token
?grant_type=authorization_code
&code={value}
参数详解:
参数 | 是否必填 | 说明 |
---|---|---|
grant_type | 是 | 授权类型,这里请填写:authorization_code |
code | 是 | 步骤1.1中获取到的授权码 |
接口返回示例:
{
"access_token": "7Ngo1Igg6rieWwAmWMe4cxT7j8o46mjyuabuwLETuAoN6JpPzPO2i3PVpEVJ", // Access-Token值
"refresh_token": "ZMG7QbuCVtCIn1FAJuDbgEjsoXt5Kqzii9zsPeyahAmoir893ARA4rbmeR66", // Refresh-Token值
"expires_in": 7199, // Access-Token剩余有效期,单位毫秒
"refresh_expires_in": 2591999, // Refresh-Token剩余有效期,单位毫秒
"client_id": "1001", // 应用id
"scope": "all", // 此令牌包含的权限
"token_type": "bearer" //令牌类型
}
根据Refresh-Token刷新Access-Token(需要客户端自己实现本地刷新Access-Token的逻辑)
Access-Token的有效期较短,如果每次过期都需要重新认证授权的话,会比较影响用户体验,因此可以通过在后台通过Refresh-Token刷新Access-Token
http://127.0.0.1:28083/api/afc/oauth2/token
?grant_type=refresh_token
&client_id={value}
&client_secret={value}
&refresh_token={value}
参数详解:
参数 | 是否必填 | 说明 |
---|---|---|
grant_type | 是 | 授权类型,这里请填写:refresh_token |
client_id | 是 | 应用id(应用code/租户Id,eg:AFCENTER/123) |
client_secret | 是 | 应用秘钥 |
refresh_token | 是 | 步骤2中获取到的Refresh-Token 值 |
接口返回同第2步。
注销Access-Token
SSO客户端在单点退出的同时,需要在认证服务器端也进行单点退出
http://127.0.0.1:28083/api/afc/oauth2/revoke
?client_id={value}
&client_secret={value}
&access_token={value}
参数详解:
参数 | 是否必填 | 说明 |
---|---|---|
client_id | 是 | 应用id(应用code/租户Id,eg:AFCENTER/123) |
client_secret | 是 | 应用秘钥 |
access_token | 是 | 步骤2中获取到的Access-Token 值 |
返回值说明:
{
"code":200,
"msg":"ok"
}
根据Access-Token获取相应用户的账号信息
http://127.0.0.1:28083/api/afc/oauth2/user-info
请求头设置Authorization:accessToken(认证成功后获取到的accessToken)
返回值样例:
{
"username":"sss", //登录账号
"tenantId":"xxx", //所属租户Id
"employeeId":"aaa" //登录账号绑定的员工Id
}
# 应用配置说明
在afcenter的应用管理-应用信息一栏中可以获取密钥以及配置回调地址和访问凭证时间等信息。回调地址只支持配置一个。
# 基于Spring security sso 的客户端简单认证demo示例说明
- pom依赖说明
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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>com.primeton</groupId>
<artifactId>client2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>client2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- application.yml或者application.properties配置参数
server.port=3048
# 应用密钥
security.oauth2.client.client-secret=fbe04a07d39e4fxxxxxxxxx
# 应用id(应用code/租户Id,eg:AFCENTER/123)
security.oauth2.client.client-id=AFCENTER/sys_tenant
# security需要设置query模式,否则调用接口时不去添加secret参数
security.oauth2.client.client-authentication-scheme=query
# 代表授权码模式
security.oauth2.client.grant-type=authorization_code
# 获取授权码接口地址
security.oauth2.client.user-authorization-uri=http://127.0.0.1:28083/api/afc/oauth2/authorize
# 使用授权码Code获取Access-Token接口地址
security.oauth2.client.access-token-uri=http://127.0.0.1:28083/api/afc/oauth2/token
# 使用Access-Token获取用户信息接口地址
security.oauth2.resource.user-info-uri=http://127.0.0.1:28083/api/afc/oauth2/user-info
# 设置cookie名称,可以任意设置,也可不设置
server.servlet.session.cookie.name=s2
- 主要配置类说明
主要涉及3个类的配置,Security配置类,应用启动类,测试接口类
//Security配置类
@Configuration
@EnableOAuth2Sso //启用SSO客户端模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().csrf().disable();
//设置所有接口都需要拦截
}
}
//应用启动类
@SpringBootApplication
public class Client2Application {
public static void main(String[] args) {
SpringApplication.run(Client2Application.class, args);
}
}
//测试接口类
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication.getName() + Arrays.toString(authentication.getAuthorities().toArray());
//从凭证中获取username
}
}
注意
因为AFCenter提供的是RESTFull接口,所以不论后端是什么架构,都可以很方便的实现集成认证中心的功能,基于授权码协议,按照步骤去调用相关接口即可。这里不提供其他方式的Demo,实现时可根据需要确定技术选型,不是只能跟Security集成!
# 4 集成企业微信、钉钉、飞书或者申石
在AFCenter中第三方认证可以配置多个,同时需要在AFCenter的组织权限中心-组织管理-机构管理-员工管理中给员工的详细信息中配置员工的电话(非工作电话),这个电话需要与员工的第三方系统中使用的电话号码相同。
**注意:**当只配置一种第三方认证,并且默认只使用第三方进行登录AFCenter时可以配置afc.thrid-party.global-redirect.enable=true(默认是false),以此开启未认证时访问AFCenter直接跳转第三方登录。
# 4.1 企业微信
AFCenter集成企业微信配置(application.properties中配置)
afc.third-party.wechat.enable=true afc.third-party.wechat.crop-id= afc.third-party.wechat.agent-id= afc.third-party.wechat.secret= afc.third-party.wechat.redirect-uri=http://PC端域名端口/#/login afc.third-party.wechat.mobile-redirect-uri=http://移动端域名端口/#/login # 可选配置 afc.third-party.wechat.compatible-2-solutions=false
afc.third-party.wechat.enable配置为true即开启企业微信集成
afc.third-party.wechat.crop-id是企业的CorpID、afc.third-party.wechat.agent-id是应用的id、afc.third-party.wechat.secret是应用的密钥,在企业微信的后台管理功能中可以查询到
afc.third-party.wechat.redirect-uri是PC端前端的域名和端口(需要在企业微信的应用管理中开启企业微信授权登录,并配置Web网页授权回调域)
afc.third-party.wechat.mobile-redirect-uri是移动端前端的域名和端口(此域名需要在企业微信中配置可信回调域)
afc.third-party.wechat.compatible-2-solutions是一个可选配置,默认是false,此配置如果设置为true则不使用手机号进行用户身份认证,改用企业微信中的userId进行身份认证,注意,userId需要跟AFCenter中的员工编码一致,需要手动提前配置。
企业微信中配置afc移动端首页时需要增加type=weixin参数,例如:http://localhost:81/#/login?type=weixin
# 4.2 钉钉
AFCenter集成钉钉配置(application.properties中配置)
afc.third-party.ding.enable=false afc.third-party.ding.app-key= afc.third-party.ding.secret= afc.third-party.ding.agent-id= afc.third-party.ding.redirect-uri=http://域名和端口/#/login
afc.third-party.ding.enable配置为true即开启钉钉集成
afc.third-party.ding.app-key是钉钉中应用的唯一标识、afc.third-party.ding.secret是钉钉中应用的密钥、afc.third-party.ding.agent-id是微应用的agent_id
afc.third-party.ding.redirect-uri是PC端集成钉钉时的前端域名和端口,移动端的配置在钉钉中进行配置
集成钉钉时还需要给该应用下的组织机构人员在钉钉中配置权限,需要个人手机号信息、通讯录个人信息读权限、邮箱等个人信息、企业员工手机号信息、成员信息读权限、根据手机号姓名获取成员信息的接口访问权限、企业微应用后台免登接口的访问权限、调用OpenApp专有API时需要具备的权限。具备以上这些权限才能保证AFCenter中钉钉集成相关功能可用。
钉钉中配置afc移动端首页时需要增加type=dingding参数,例如:http://localhost:81/#/login?type=dingding
# 4.3 飞书
AFCenter集成飞书配置(application.properties中配置)
afc.third-party.feishu.enable=true afc.third-party.feishu.appId=xxx afc.third-party.feishu.appSecret=xxx afc.third-party.feishu.redirectUri=http://127.0.0.1:13382/#/login afc.third-party.feishu.mobile-redirect-uri=http://移动端域名端口/#/login # 飞书集成权限信息包含用户信息权限及消息发送权限,如果有其他改造需求只需在此添加相应权限按空格隔开 afc.third-party.feishu.scope=contact:contact.base:readonly contact:user.base:readonly contact:user.phone:readonly contact:user.id:readonly contact:contact:readonly_as_app contact:user.employee_id:readonly im:message im:message:send_as_bot im:message:send_multi_users
afc.third-party.feishu.enable配置为true即开启飞书集成
afc.third-party.feishu.appId飞书appId配置
afc.third-party.feishu.appSecret飞书secret配置
afc.third-party.feishu.redirectUri飞书认证后的回调地址只需要修改端口和域名
afc.third-party.feishu.mobile-redirect-uri移动端飞书认证后的回调地址只需要修改端口和域名
afc.third-party.feishu.scope飞书集成权限信息包含用户信息权限及消息发送权限,如果有其他改造需求只需在此添加相应权限按空格隔开
飞书如果要使用发消息的功能,还需要在对应的飞书应用中添加机器人功能,并搭配上述scope中的所有权限
飞书中配置afc移动端首页时需要增加type=feishu参数,例如:http://localhost:81/#/login?type=feishu
# 4.4 申石(IDMesh)
AFCenter集成申石配置(application.properties种配置)
afc.third-party.idmesh.enable=true afc.third-party.idmesh.appId=xxx afc.third-party.idmesh.appSecret=xxx # 申石应用配置地址 afc.third-party.idmesh.host=https://afcenter1.idmesh.top afc.third-party.idmesh.redirectUri=http://localhost:14382/#/login
afc.third-party.idmesh.enable配置为true即为开启申石集成
afc.third-party.idmesh.appId申石appId
afc.third-party.idmesh.appSecret申石appSecret
afc.third-party.idmesh.host申石应用配置地址从申石应用管理页面获取
afc.third-party.idmesh.redirectUri申石认证后的回调地址只需要修改端口和域名
在申石的应用管理-认证配置种需要开启OAuth2.0认证方式,同时配置授权范围如下图,否则无法集成成功