自学内容网 自学内容网

Auth2.0单点登录(企业开发流程)

实习中接到需求单点登录,记录一下单点登录在企业中的实际应用。其实单点登录在不同的业务中处理细节还是不相同的,我这里仅举例我们这个需求中如何处理的一种情形。

一.概念

简单来说就是一次登录,各处访问。

二.流程

场景一: 用户采取三方登录。

场景一的流程:
  • 用户采用三方登录方式,点击访问三方应用的认证方式
  • 访问认证服务器,用户登录三方的登录界面,登录完成后,点击授权,此时将会根据我们配置的回调地址,携带授权后生成的code.回调访问
  • 随后服务器接收到回调的请求,拿到对应的code,携带这个code再去认证服务器获取token.
  • 获取到token后,再向资源服务器获取用户信息。
  • 解析返回回来的用户信息,完成登录。

背景:三方的数据已同步到本地,这样才有本地的映射。没有也没有关系,根据具体的业务更改逻辑即可。

具体流程

  1. 首先通过redirect_uri注册到资源认证服务器。获取到注册在资源服务器的client_id与client_secret.
  2. 随后访问自己产品的登录界面,配置资源认证服务器的url,对应前端来讲就是一个三方按钮。例如gitee中登录界面中下面的其他软件的登录方式。

  1. 之后我们将携带一系列的参数去访问这个url, 主要参数为
  • client_id: 注册在认证服务器的id
  • client_secret: 注册在认证服务器的secret
  • redirect_uri: 获取code后的回调地址。

  1. 用户在三方完成登录后将会根据回调地址redirect_uri,拼接code参数。打回到本地服务器。
  2. 随后本地服务器将会发送两个http请求。
    1. 一个携带我们获取到的code,去三方服务器获取token
    2. 拿到token后,发送第二个http请求。获取用户信息。
  1. 获取到用户信息后,匹配本地的数据库信息,例如userInfo会返回回来一个字段,能够唯一标识一个用户。根据这个标识我们在本地做一个映射,映射到我们本地服务器数据库存储用户信息的一个字段。查询到用户信息。
  2. 返回用户信息到客户端,完成登录。

我在实现的时候,采取了配置的形式,会在回调地址中拼接一个配置参数。例如sso_code=xxx,利用这个配置我们可以去数据库查询到具体的配置结构。

对应的类

public class Auth2SSOLoginConfig implements Serializable {
    /**
     * sso服务端名称
     */
    private String name;
    /**
     * sso配置唯一id
     */
    private String ssoCode;
    /**
     * 拼接token参数名
     */
    private String tokenKey;
    /**
     * code参数名
     */
    private String codeKey;

    /**
     * token配置
     */
    private HttpContentConfig token;
    /**
     * userinfo配置
     */
    private HttpContentConfig userinfo;


}

HttpContentConfig

@Data
public class HttpContentConfig {
    private HttpRequestConfig requestConfig;
    private SSOResultConfig ssoResultConfig;


    @Data
    public static class HttpRequestConfig{
        private String url;

        private String method;

        private Map<String,String> header;

        private Map<String,Object> params;

        private Map<String,Object> paramsMapping;
    }


    @Data
    public static class SSOResultConfig {

        /**
         * code 解析的path
         */
        private String codePath;

        /**
         * code 成功值,状态码
         */
        private String codeSuccessValue;

        /**
         * 参数映射
         */
        private Map<String,String> valueMapping;

    }
}

数据库表结构为

字段名

类型

描述

id

bigint

自增主键

name

varchar(63)

SSO服务端名

sso_code

varchar(63)

配置唯一id

config

text

配置内容

gmt_create

timestamp

创建时间

gmt_update

timestamp

更新时间

gmt_delete

timestamp

删除时间

create_operator

varchar(255)

创建人

update_operator

varchar(255)

更新人

delete_operator

varchar(255)

删除人

is_delete

boolean

逻辑删除字段

config的结构为一个大Json

{
  "token": {
    "requestConfig": {
      "url": "http://localhost:8080/demo/code",
      "method": "POST",
      "header": [
        {
          "Content-Type": "application/x-www-form-urlencoded",
          "Accept": "text/html,application/xhtml+xml,application/xml"
        }
      ],
      "params": {
        "grant_type": "authorization_code",
        "client_id": "",
        "client_secret": "",
        "redirect_uri": "",
        "endpoint_uri": ""
      }
    },
    "ssoResultConfig": {
      "codeSuccessValue": "",
      "codePath": "",
      "valueMapping": {
        "access_token": "access_token",
        "refresh_token": "refresh_token",
        "scope": "scope",
        "token_type": "token_type",
        "expires_in": "expires_in"
      },
      "paramsMapping": {
        "code": "code"
      }
    }
  },
  "userInfo": {
    "requestConfig": {
      "url": "http://localhost:8080/demo/token",
      "method": "POST",
      "header": [
        {
          "Content-Type": "application/x-www-form-urlencoded",
          "Accept": "text/html,application/xhtml+xml,application/xml"
        }
      ],
      "params": {
        "client_id": "",
        "client_secret": "",
        "endpoint_uri": ""
      },
      "paramsMapping": {
        "token": "token"
      }
    },
    "ssoResultConfig": {
      "codeSuccessValue": "",
      "codePath": "",
      "valueMapping": {
        "scope": "scope",
        "active": "active",
        "token_type": "token_type",
        "exp": "exp",
        "iat": "iat",
        "client_id": "client_id",
        "account": "username"
      }
    }
  },
  "codeKey": "code",
  "tokenKey": "token"
}

这里解释一下codeKey,tokenKey与paramsMapping中的code与token的差别与作用。

codeKey是我们去动态解析响应的结果的参数名。这样配置以后获取是不叫code,只需要改配置解析即可。

而tokenKey是场景二的使用,待会再解释。

而paramsMapping中的code与token是我们拼接参数值的参数名。作用也是可以动态配置解析,以后参数名变更我们只需要改配置即可。

场景二的流程:

场景二:

用户已在三方登录完成,访问本地产品页面

  • 检验本地session中用户信息是否存在
  • 存在直接登录。不存在查询配置
  • 获取所有的配置,拿到对应的Map,key为tokenKey,value为token对应的配置
  • 查询cookie当中是否存在tokenKey
  • 存在直接携带此token与配置获取用户信息
  • 不存在直接重定向回登录页面。

这是我们需要去拦截器去完成的一个过程。

这里需要了解一个知识,我们可以通过设置cookiedomain,来保证双方的cookie互通,

比如

A网址 org.example.com

B网址 www.example.com

我们设置domain为example.com这样会把这样设置的cookie共同传递到两个网址。我们再根据我们配置的tokenKey就能够正常拿到对应的tokenValue。然后剩下的流程其实就是我们场景一有了token之后的过程。

然后我们在拦截器大致就是这样一个处理逻辑,如下。

private boolean checkSSOLogin(HttpServletRequest request, HttpServletResponse response) {
    Map<String, Auth2SSOLoginConfig> allConfig = ssoAuth2LoginManager.getAllConfig();
    if(MapUtils.isEmpty(allConfig)){
        return false;
    }
    Set<String> keySet = allConfig.keySet();
    String tokenValue = null;
    String tokenKey = null;
    for (Cookie cookie : request.getCookies()) {
        if (keySet.contains(cookie.getName())) {
            tokenKey = cookie.getName();
            tokenValue = cookie.getValue();
            break;
        }
    }
    if (StringUtils.isNotBlank(tokenValue)) {
        UserDTO userDTO = userManager.getUserDTOForTokenAndUserInfo(allConfig.get(tokenKey), tokenValue);
        HttpSession session = request.getSession();
        //在session域中记录信息,并在SessionId中记录信息
        userHelper.writeUserIntoToSession(userDTO, session);
        return true;
    }
    return false;
}

原文地址:https://blog.csdn.net/m0_65013257/article/details/142771560

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!