分析xxljob登入功能集成OIDC的统一认证

网友投稿 892 2022-10-29

分析xxljob登入功能集成OIDC的统一认证

分析xxljob登入功能集成OIDC的统一认证

目录前言XXL-JOB自身认证功能分析OIDC的认证流程XXL-JOB集成OIDC后的认证流程XXL-JOB登录模块重新设计编码环节

前言nsprZ

xxl-job 是一款 java 开发的、开源的分布式任务调度系统,自带了登录认证功能,不支持对接、扩展 LDAP 、OIDC 等标准认证系统,考虑到单独维护 xxl-job 自有的用户系统不方便,以及存在人员离职、调岗、权限变动等需要及时调整用户权限的情况,需要接入公司统一的 OIDC 认证系统

相关链接

xxl-job :https://github.com/xuxueli/xxl-joboidc :https://openid-/connect/

XXL-JOB 自身认证功能分析

xxl-job 自带的登录认证用户信息维护在 mysql 的 user 表中,用户从登录页提交用户名和密码,后端查询用户信息、校验密码,验证成功后设置登录信息到 cookie 中,采用 cookie 保持登录状态,大致的流程如下:

OIDC 的认证流程

OIDC(OpenID Connect) 是一种融合了 OpenID 、Oauth2 的身份认证协议。认证流程上和 Oauth2 基本一致,但是,OIDC 在 Oauth2 的 access\_token 基础上新增了一个使用 jwt 生成的 idToken,idToken 中携带了用户基本信息,使用私钥验签成功后,可直接使用,省略了通过 access\_token 获取用户信息的步骤。所以 OIDC 的认证流程既和 Oauth2 类似又有区别,基本流程如下:

客户端准备包含所需请求参数的身份验证请求。客户端将请求发送到授权服务器。授权服务器对终端用户进行身份验证。授权服务器获得终端用户同意/授权。授权服务器将 code 发送回客户端 。客户端将 code 发送到令牌端点获取 access_token 和 idToken。客户端使用私钥验证 idToken 拿到用户标识 or 将 access_token 发送到授权服务器获取用户标识。

这里注意最后第 6、7 点操作,这里开始 OIDC 和 Oauth2 不一样了

XXL-JOB 集成 OIDC 后的认证流程

从 OIDC 的认证流程得知,终端用户通过授权服务器授权认证后,授权服务器会携带 code 重定向到客户端服务,客户端通过 code 可以拿到用户唯一标识,通过这个唯一标识,可以继续完成客户端原本的认证流程。集成 OIDC 后,xxl-job 登录的大致流程如下:

集成 OIDC 后,系统认证保持用户登录状态的机制没有变化,依然使用  Cookie ,需要特殊处理以及关注地方有:

用户首次登录系统,由于不存在系统中,需要先创建用户如果系统首次投产使用,记得设计一个可以从配置指定管理账户的功能,不然你得手动改数据库了如果系统运行很久了,需要考虑好原系统用户和 OIDC 授权用户的映射关系退出操作时,除了清除自身的用户登录状态,是否退出 OIDC 服务(实现 sso)的登录状态也需要考虑

XXL-JOB 登录模块重新设计

考虑开发环境使用 OIDC 服务不方便以及解耦对第三方认证授权服务的依赖,决定在集成 OIDC 时,兼容本地登录功能,登录流程由登录模式来控制区分,登录模式使用配置驱动,设计集成 OIDC 后 ,xxl-job 支持的登录模式如下:

onlyLocal :只支持 xxl-job 自身用户系统登录认证onlyOidc : 只支持 Oidc 授权服务器授权登录认证mix :混合模式,同时支持自身用户系统登录认证、Oidc 授权服务器授权登录认证

onlyLocal 模式登录界面:

mix 模式登录界面:

olnyOidc 模式登录界面:

olnyOidc 模式特殊,从设计上来说,如果需要保留用户使用习惯,可以保留一个跳转到 OIDC 授权服务器的链接按钮给用户点击。如果做的干净利落,在 olnyOidc 模式下,访问登录页可以直接 302 到 OIDC 授权服务器。

保留登录按钮的界面(实际这个页面取消了)

编码环节

配置属性类,省略了get、set

/**

* @author kl (http://kailing.pub)

* @since 2021/6/21

*/

@ConfigurationProperties(prefix = "oidc")

@Configuration

public class OidcProperties {

private static final LoginMod DEFAULT_LOGIN_MOD = LoginMod.onlyLocal;

private LoginMod loginMod = DEFAULT_LOGIN_MOD;

private String clientId;

privhttp://ate String clientSecret;

private String accessTokenUrl;

private String profileUrl;

private String redirectUri;

private String logoutUrl;

private String loginUrl;

private ListadminLists = new ArrayList<>();

public enum LoginMod {

mix,

onlyOidc,

onlyLocal

}

}

对应了如下的配置, 除了 login-mod  、redirect-uri 、admin-Lists 是 xxl-job 自身登录功能需要,其他的配置均由 OIDC 授权服务器提供

oidc.login-mod=onlyOidc

oidc.client-id = xxl-job-dev

oidc.client-secret = xx

oidc.base-url =https://sso.security.oidc.com oidc.access-token-url = ${oidc.base-url}/cas/oidc/accessToken

oidc.login-url = ${oidc.base-url}/cas/oidc/authorize?response_type=code&client_id=${oidc.client-id}&redirect_uri=${oidc.redirect-uri}&scope=openid

oidc.redirect-uri =http://172.26.203.103:8071/oidc/tokenLogin oidc.logout-url =${oidc.base-url}/cas/logout?service=${oidc.redirect-uri}

oidc.admin-Lists = chenkailing

Oidc 服务类,使用这个类里的方法和 OIDC 授权服务器交互

/**

* @author kl (http://kailing.pub)

* @since 2021/6/21

*/

@Service

public class OidcService {

private final OidcProperties oidcProperties;

private final RestTemplate restTemplate;

public OidcService(OidcProperties oidcProperties, RestTemplate restTemplate) {

this.oidcProperties = oidcProperties;

this.restTemplate = restTemplate;

}

/**

* 请求 OIDC 授权服务器,获取 idToken

* idToken 中包含的信息 (非标准)

* {

* "sub": "248289761001",

* "name": "Jane Doe",

* "given_name": "Jane",

* "family_name": "Doe",

* "preferred_username": "j.doe",

* "email": "janedoe@example.com",

* "picture": "http://example.com/janedoe/me.jpg"

* }

*/

public String getUsernameByCode(String code) {

URI uri = UriComponentsBuilder.fromUriString(oidcProperties.getAccessTokenUrl())

.queryParam("client_id", oidcProperties.getClientId())

.queryParam("client_secret", oidcProperties.getClientSecret())

.queryParam("redirect_uri", oidcProperties.getRedirectUri())

.queryParam("code", code)

.queryParam("grant_type", "authorization_code")

.build()

.toUri();

AuthorizationEntity auth = restTemplate.getForObject(uri, AuthorizationEntity.class);

Assert.notNull(auth, "AccessToken is null");

String idToken = auth.getIdToken();

int i = idToken.lastIndexOf('.');

String withoutSignatureToken = idToken.substring(0, i+1);

return Jwts.parserBuilder()

.build()

.parseClaimsJwt(withoutSignatureToken)

.getBody()

.get("sub", String.class);

}

/**

* @return 1 : 管理员 、0 : 普通用户

*/

public int getUserRole(XxlJobUser user) {

ListadminLists = oidcProperties.getAdminLists();

if (adminLists.contains(user.getUsername())) {

return 1;

}

return 0;

}

public String getOidcLoginUrl() {

return oidcProperties.getLoginUrl();

}

public OidcProperties.LoginMod getLoginMod() {

return oidcProperties.getLoginMod();

}

public boolean isRedirectOidcLoginUrl() {

return oidcProperties.getLoginMod().equals(OidcProperties.LoginMod.onlyOidc);

}

public String getLogoutUrl() {

return oidcProperties.getLogoutUrl();

}

static class AuthorizationEntity {

@jsonProperty("access_token")

private String accessToken;

@JsonProperty("id_token")

private String idToken;

@JsonProperty("refresh_token")

private String refreshToken;

@JsonProperty("expires_in")

private String expiresIn;

@JsonProperty("token_type")

private String tokenType;

private String scope;

}

}

OIDC 登录接口,也就是提供给 OIDC 授权服务器回调的接口

/**

* OIDC登录

*/

@RequestMapping(value = "/oidc/tokenLogin", method = {RequestMethod.POST, RequestMethod.GET})

@PermissionLimit(limit = false)

public ModelAndView loginByOidc(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) {

if (loginService.ifLogin(request, response) != null) {

modelAndView.setView(new RedirectView("/", true, false));

return modelAndView;

}

String code = request.getParameter("code");

if (Objects.isNull(code)) {

return this.loginPageView();

}

String username = oidcService.getUsernameByCode(code);

loginService.oidcLogin(username, response);

modelAndView.setView(new RedirectView("/", true, false));

return modelAndView;

}

这个接口对应了 xxl-job 集成 OIDC 后的认证流程:

判断是否登录,已经登录则跳转到登录成功的页面获取 code ,不存在则调整到登录页面通过 code 请求 OIDC 授权服务器获取 UserInfo处理内部登录逻辑(用户是否存在,存在则设置 Cookie,不存在则先创建用户在设置 Cookie)跳转到登录成功的页面

跳转登录页逻辑做了封装,因为,根据登录模式的不同,有不同的处理逻辑:

private ModelAndView loginPageView() {

ModelAndView modelAndView = new ModelAndView(LOGIN_PAGE);

if (oidcService.isRedirectOidcLoginUrl()) {

modelAndView.setView(new RedirectView(oidcService.getOidcLoginUrl(), true, false));

} else {

modelAndView.addObject("loginMod", oidcService.getLoginMod().name());

modelAndView.addObject("oidcLoginUrl", oidcService.getOidcLoginUrl());

}

return modelAndView;

}

目前的策略,如果配置了登录模式为 onlyOidc ,则跳转登录页时,直接 302 到 OIDC 授权页,否则,将登录模式,和 OIDC 授权页传递给前端,由前端控制展示的 UI

以上就是分析xxljob登入功能集成OIDC的统一认证的详细内容,更多关于xxljob登入集成OIDC统一认证的资料请关注我们其它相关文章!

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:W-Blog是一个基于vue和node的小小小博客 前端用vue,后端用egg.js
下一篇:鼠标按下或鼠标松开做不同的处理
相关文章

 发表评论

暂时没有评论,来抢沙发吧~