

![Logo](https://user-images.githubusercontent.com/71620349/185368586-aed82d30-1534-453d-86ff-ecfa9d0f35bd.png)

## 登录系统对接

[KnowStreaming](https://github.com/didi/KnowStreaming)（以下简称KS） 除了实现基于本地MySQL的用户登录认证方式外，还已经实现了基于Ldap的登录认证。

但是，登录认证系统并非仅此两种。因此，为了具有更好的拓展性，KS具有自定义登陆认证逻辑，快速对接已有系统的特性。

在KS中，我们将登陆认证相关的一些文件放在[km-extends](https://github.com/didi/KnowStreaming/tree/master/km-extends)模块下的[km-account](https://github.com/didi/KnowStreaming/tree/master/km-extends/km-account)模块里。

本文将介绍KS如何快速对接自有的用户登录认证系统。

### 对接步骤

- 创建一个登陆认证类，实现[LogiCommon](https://github.com/didi/LogiCommon)的LoginExtend接口；
- 将[application.yml](https://github.com/didi/KnowStreaming/blob/master/km-rest/src/main/resources/application.yml)中的spring.logi-security.login-extend-bean-name字段改为登陆认证类的bean名称；

```Java
//LoginExtend 接口
public interface LoginExtend {
  
    /**
     * 验证登录信息，同时记住登录状态
     */
    UserBriefVO verifyLogin(AccountLoginDTO var1, HttpServletRequest var2, HttpServletResponse var3) throws LogiSecurityException;
  
    /**
     * 登出接口，清楚登录状态
     */
    Result<Boolean> logout(HttpServletRequest var1, HttpServletResponse var2);
  
    /**
     * 检查是否已经登录
     */
    boolean interceptorCheck(HttpServletRequest var1, HttpServletResponse var2, String var3, List<String> var4) throws IOException;
  
}

```



### 对接例子

我们以Ldap对接为例，说明KS如何对接登录认证系统。

+ 编写[LdapLoginServiceImpl](https://github.com/didi/KnowStreaming/blob/master/km-extends/km-account/src/main/java/com/xiaojukeji/know/streaming/km/account/login/ldap/LdapLoginServiceImpl.java)类，实现LoginExtend接口。
+ 设置[application.yml](https://github.com/didi/KnowStreaming/blob/master/km-rest/src/main/resources/application.yml)中的spring.logi-security.login-extend-bean-name=ksLdapLoginService。

完成上述两步即可实现KS对接Ldap认证登陆。

```Java
@Service("ksLdapLoginService")
public class LdapLoginServiceImpl implements LoginExtend  {
  

    @Override
    public UserBriefVO verifyLogin(AccountLoginDTO loginDTO,
                                   HttpServletRequest request,
                                   HttpServletResponse response) throws LogiSecurityException {
        String decodePasswd = AESUtils.decrypt(loginDTO.getPw());

        // 去LDAP验证账密
        LdapPrincipal ldapAttrsInfo = ldapAuthentication.authenticate(loginDTO.getUserName(), decodePasswd);
        if (ldapAttrsInfo == null) {
            // 用户不存在，正常来说上如果有问题，上一步会直接抛出异常
            throw new LogiSecurityException(ResultCode.USER_NOT_EXISTS);
        }

        // 进行业务相关操作
        
        // 记录登录状态，Ldap因为无法记录登录状态，因此有KnowStreaming进行记录
        initLoginContext(request, response, loginDTO.getUserName(), user.getId());
        return CopyBeanUtil.copy(user, UserBriefVO.class);
    }

    @Override
    public Result<Boolean> logout(HttpServletRequest request, HttpServletResponse response) {
        
        //清理cookie和session
      
        return Result.buildSucc(Boolean.TRUE);
    }

    @Override
    public boolean interceptorCheck(HttpServletRequest request, HttpServletResponse response, String requestMappingValue, List<String> whiteMappingValues) throws IOException {
       
        // 检查是否已经登录
        String userName = HttpRequestUtil.getOperator(request);
        if (StringUtils.isEmpty(userName)) {
            // 未登录，则进行登出
            logout(request, response);
            return Boolean.FALSE;
        }

        return Boolean.TRUE;
    }
}

```



### 实现原理

因为登陆和登出整体实现逻辑是一致的，所以我们以登陆逻辑为例进行介绍。

+ 登陆原理

登陆走的是[LogiCommon](https://github.com/didi/LogiCommon)自带的LoginController。

```java
@RestController
public class LoginController {
  

    //登陆接口
    @PostMapping({"/login"})
    public Result<UserBriefVO> login(HttpServletRequest request, HttpServletResponse response, @RequestBody AccountLoginDTO loginDTO) {
        try {
             //登陆认证
            UserBriefVO userBriefVO = this.loginService.verifyLogin(loginDTO, request, response);
            return Result.success(userBriefVO);
          
        } catch (LogiSecurityException var5) {
            return Result.fail(var5);
        }
    }

}
```

而登陆操作是调用LoginServiceImpl类来实现，但是具体由哪个登陆认证类来执行登陆操作却由loginExtendBeanTool来指定。

```java
//LoginServiceImpl类
@Service
public class LoginServiceImpl implements LoginService {

    //实现登陆操作，但是具体哪个登陆类由loginExtendBeanTool来管理
    public UserBriefVO verifyLogin(AccountLoginDTO loginDTO, HttpServletRequest request, HttpServletResponse response) throws LogiSecurityException {
      
        return this.loginExtendBeanTool.getLoginExtendImpl().verifyLogin(loginDTO, request, response);
    }


}
```

而loginExtendBeanTool类会优先去查找用户指定的登陆认证类，如果失败则调用默认的登陆认证函数。

```java
//LoginExtendBeanTool类 
@Component("logiSecurityLoginExtendBeanTool")
public class LoginExtendBeanTool {

    public LoginExtend getLoginExtendImpl() {
        LoginExtend loginExtend;
      	//先调用用户指定登陆类，如果失败则调用系统默认登陆认证
        try {
          //调用的类由spring.logi-security.login-extend-bean-name指定
            loginExtend = this.getCustomLoginExtendImplBean();
        } catch (UnsupportedOperationException var3) {
            loginExtend = this.getDefaultLoginExtendImplBean();
        }

        return loginExtend;
    }
}
```

+ 认证原理

认证的实现则比较简单，向Spring中注册我们的拦截器PermissionInterceptor。

拦截器会调用LoginServiceImpl类的拦截方法，LoginServiceImpl后续处理逻辑就和前面登陆是一致的。

```java
public class PermissionInterceptor implements HandlerInterceptor {


    /**
     * 拦截预处理
     * @return boolean false:拦截, 不向下执行, true:放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       
      	//免登录相关校验，如果验证通过，提前返回
				
      	//走拦截函数，进行普通用户验证
        return loginService.interceptorCheck(request, response, classRequestMappingValue, whiteMappingValues);
    }

}
```

