mirror of
https://github.com/certd/certd.git
synced 2026-04-24 04:17:25 +08:00
chore: 优化oidc登录
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { addonRegistry, BaseController, Constants, SysInstallInfo, SysSettingsService } from "@certd/lib-server";
|
||||
import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core";
|
||||
import { ALL, Body, Controller, Get, Inject, Param, Post, Provide, Query } from "@midwayjs/core";
|
||||
import { AddonGetterService } from "../../../modules/pipeline/service/addon-getter-service.js";
|
||||
import { IOauthProvider } from "../../../plugins/plugin-oauth/api.js";
|
||||
import { LoginService } from "../../../modules/login/service/login-service.js";
|
||||
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
||||
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
||||
import { UserEntity } from "../../../modules/sys/authority/entity/user.js";
|
||||
import { simpleNanoId } from "@certd/basic";
|
||||
import { logger, simpleNanoId } from "@certd/basic";
|
||||
import { OauthBoundService } from "../../../modules/login/service/oauth-bound-service.js";
|
||||
import { OauthBoundEntity } from "../../../modules/login/entity/oauth-bound.js";
|
||||
|
||||
@@ -56,33 +56,83 @@ export class ConnectController extends BaseController {
|
||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||
const bindUrl = installInfo?.bindUrl || "";
|
||||
//构造登录url
|
||||
const redirectUrl = `${bindUrl}#/oauth/callback/${body.type}`;
|
||||
const loginUrl = await addon.buildLoginUrl({ redirectUri: redirectUrl });
|
||||
return this.ok({loginUrl});
|
||||
const redirectUrl = `${bindUrl}api/oauth/callback/${body.type}`;
|
||||
const { loginUrl, ticketValue } = await addon.buildLoginUrl({ redirectUri: redirectUrl });
|
||||
const ticket = this.codeService.setValidationValue(ticketValue)
|
||||
this.ctx.cookies.set("oauth_ticket", ticket, {
|
||||
httpOnly: true,
|
||||
// secure: true,
|
||||
// sameSite: "strict",
|
||||
})
|
||||
return this.ok({ loginUrl, ticket });
|
||||
}
|
||||
@Post('/callback', { summary: Constants.per.guest })
|
||||
public async callback(@Body(ALL) body: any) {
|
||||
@Get('/callback/:type', { summary: Constants.per.guest })
|
||||
public async callback(@Param('type') type: string, @Query() query: Record<string, string>) {
|
||||
//处理登录回调
|
||||
const addon = await this.getOauthProvider(body.type);
|
||||
const tokenRes = await addon.onCallback({
|
||||
code: body.code,
|
||||
state: body.state,
|
||||
});
|
||||
const addon = await this.getOauthProvider(type);
|
||||
const request = this.ctx.request;
|
||||
// const ticketValue = this.codeService.getValidationValue(ticket);
|
||||
// if (!ticketValue) {
|
||||
// throw new Error("登录ticket已过期");
|
||||
// }
|
||||
|
||||
const userInfo = tokenRes.userInfo;
|
||||
const ticket = this.ctx.cookies.get("oauth_ticket");
|
||||
if (!ticket) {
|
||||
throw new Error("ticket已过期");
|
||||
}
|
||||
const ticketValue = this.codeService.getValidationValue(ticket);
|
||||
if (!ticketValue) {
|
||||
throw new Error("ticketValue已过期");
|
||||
}
|
||||
|
||||
const openId = userInfo.openId;
|
||||
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
||||
const bindUrl = installInfo?.bindUrl || "";
|
||||
const currentUrl = `${bindUrl}api/oauth/callback/${type}?${request.querystring}`
|
||||
try {
|
||||
const tokenRes = await addon.onCallback({
|
||||
code: query.code,
|
||||
state: query.state,
|
||||
ticketValue,
|
||||
currentURL: new URL(currentUrl)
|
||||
});
|
||||
|
||||
const userInfo = tokenRes.userInfo;
|
||||
|
||||
const loginRes = await this.loginService.loginByOpenId({ openId, type: body.type });
|
||||
if (loginRes == null) {
|
||||
// 用户还未绑定,让用户选择绑定已有账号还是自动注册新账号
|
||||
const validationCode = await this.codeService.setValidationValue({
|
||||
type: body.type,
|
||||
type,
|
||||
userInfo,
|
||||
});
|
||||
|
||||
const redirectUrl = `${bindUrl}#/oauth/callback/${type}?validationCode=${validationCode}`;
|
||||
this.ctx.redirect(redirectUrl);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
this.ctx.redirect(`${bindUrl}#/oauth/callback/${type}?error=${err.error_description || err.message}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Post('/token', { summary: Constants.per.guest })
|
||||
public async token(@Body(ALL) body: { validationCode: string, type: string }) {
|
||||
const validationValue = await this.codeService.getValidationValue(body.validationCode);
|
||||
if (!validationValue) {
|
||||
throw new Error("校验码错误");
|
||||
}
|
||||
|
||||
const type = validationValue.type;
|
||||
if (type !== body.type) {
|
||||
throw new Error("校验码错误");
|
||||
}
|
||||
const userInfo = validationValue.userInfo;
|
||||
const openId = userInfo.openId;
|
||||
|
||||
const loginRes = await this.loginService.loginByOpenId({ openId, type });
|
||||
if (loginRes == null) {
|
||||
|
||||
return this.ok({
|
||||
bindRequired: true,
|
||||
validationCode,
|
||||
validationCode: body.validationCode,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -98,11 +148,13 @@ export class ConnectController extends BaseController {
|
||||
if (!validationValue) {
|
||||
throw new Error("校验码错误");
|
||||
}
|
||||
|
||||
const type = validationValue.type;
|
||||
const userInfo = validationValue.userInfo;
|
||||
const openId = userInfo.openId;
|
||||
await this.oauthBoundService.bind({
|
||||
userId,
|
||||
type: body.type,
|
||||
openId: validationValue.openId,
|
||||
type,
|
||||
openId,
|
||||
});
|
||||
return this.ok(1);
|
||||
}
|
||||
@@ -117,12 +169,12 @@ export class ConnectController extends BaseController {
|
||||
const userInfo = validationValue.userInfo;
|
||||
const oauthType = validationValue.type;
|
||||
let newUser = new UserEntity()
|
||||
newUser.username = `${oauthType}:_${userInfo.nickName}_${simpleNanoId(6)}`;
|
||||
newUser.username = `${oauthType}_${userInfo.nickName}_${simpleNanoId(6)}`;
|
||||
newUser.avatar = userInfo.avatar;
|
||||
newUser.nickName = userInfo.nickName;
|
||||
newUser.nickName = userInfo.nickName || simpleNanoId(6);
|
||||
|
||||
newUser = await this.userService.register("username", newUser, async (txManager) => {
|
||||
const oauthBound : OauthBoundEntity = new OauthBoundEntity()
|
||||
const oauthBound: OauthBoundEntity = new OauthBoundEntity()
|
||||
oauthBound.userId = newUser.id;
|
||||
oauthBound.type = oauthType;
|
||||
oauthBound.openId = userInfo.openId;
|
||||
|
||||
@@ -225,6 +225,7 @@ export class UserService extends BaseService<UserEntity> {
|
||||
|
||||
await this.transaction(async txManager => {
|
||||
newUser = await txManager.save(newUser);
|
||||
user.id = newUser.id;
|
||||
const userRole: UserRoleEntity = UserRoleEntity.of(newUser.id, Constants.role.defaultUser);
|
||||
await txManager.save(userRole);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export type OnCallbackReq = {
|
||||
code: string;
|
||||
state: string;
|
||||
currentURL: URL;
|
||||
ticketValue: any;
|
||||
}
|
||||
|
||||
export type OauthToken = {
|
||||
@@ -30,8 +32,12 @@ export type OnBindReply = {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type LoginUrlReply = {
|
||||
loginUrl: string;
|
||||
ticketValue: any;
|
||||
}
|
||||
|
||||
export interface IOauthProvider {
|
||||
buildLoginUrl: (params: { redirectUri: string }) => Promise<string>;
|
||||
buildLoginUrl: (params: { redirectUri: string }) => Promise<LoginUrlReply>;
|
||||
onCallback: (params: OnCallbackReq) => Promise<OauthToken>;
|
||||
onBind: (params: OnBindReq) => Promise<OnBindReply>;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
|
||||
import { IOauthProvider, OnBindReq, OnCallbackReq } from "../api.js";
|
||||
import { IOauthProvider, OnCallbackReq } from "../api.js";
|
||||
|
||||
@IsAddon({
|
||||
addonType: "oauth",
|
||||
name: 'oidc',
|
||||
title: 'OpenId connect 认证',
|
||||
desc: '',
|
||||
title: 'OIDC认证',
|
||||
desc: 'OpenID Connect 认证,统一认证服务',
|
||||
icon:"simple-icons:fusionauth",
|
||||
showTest: false,
|
||||
})
|
||||
export class OidcOauthProvider extends BaseAddon implements IOauthProvider {
|
||||
@@ -54,41 +55,7 @@ export class OidcOauthProvider extends BaseAddon implements IOauthProvider {
|
||||
client
|
||||
}
|
||||
}
|
||||
|
||||
async onCallback(req: OnCallbackReq) {
|
||||
const { config, client } = await this.getClient()
|
||||
|
||||
const currentUrl = new URL("")
|
||||
let tokens: any = await client.authorizationCodeGrant(
|
||||
config,
|
||||
currentUrl,
|
||||
{
|
||||
pkceCodeVerifier: req.code,
|
||||
expectedState: req.state,
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Token Endpoint Response', tokens)
|
||||
const claims = tokens.claims()
|
||||
return {
|
||||
token:{
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
expiresIn: tokens.expires_in,
|
||||
},
|
||||
userInfo: {
|
||||
openId: claims.sub,
|
||||
nickName: claims.nickname,
|
||||
avatar: claims.picture,
|
||||
},
|
||||
}
|
||||
};
|
||||
async onBind(req: OnBindReq) {
|
||||
return {
|
||||
success: false,
|
||||
message: '绑定失败',
|
||||
}
|
||||
}
|
||||
|
||||
async buildLoginUrl(params: { redirectUri: string }) {
|
||||
const { config, client } = await this.getClient()
|
||||
|
||||
@@ -126,6 +93,40 @@ export class OidcOauthProvider extends BaseAddon implements IOauthProvider {
|
||||
|
||||
// now redirect the user to redirectTo.href
|
||||
console.log('redirecting to', redirectTo.href)
|
||||
return redirectTo.href;
|
||||
return {
|
||||
loginUrl: redirectTo.href,
|
||||
ticketValue: {
|
||||
codeVerifier: code_verifier,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async onCallback(req: OnCallbackReq) {
|
||||
const { config, client } = await this.getClient()
|
||||
|
||||
|
||||
let tokens: any = await client.authorizationCodeGrant(
|
||||
config,
|
||||
req.currentURL,
|
||||
{
|
||||
expectedState: client.skipStateCheck ,
|
||||
pkceCodeVerifier: req.ticketValue.codeVerifier,
|
||||
}
|
||||
)
|
||||
|
||||
console.log('Token Endpoint Response', tokens)
|
||||
const claims = tokens.claims()
|
||||
return {
|
||||
token:{
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
expiresIn: tokens.expires_in,
|
||||
},
|
||||
userInfo: {
|
||||
openId: claims.sub,
|
||||
nickName: claims.nickname || claims.preferred_username || "",
|
||||
avatar: claims.picture,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user