优化代码

pull/42/head
smallbun 2023-09-05 19:56:06 +08:00
parent 9727f14278
commit 8a97cd7355
92 changed files with 1421 additions and 396 deletions

View File

@ -28,6 +28,7 @@ import cn.topiam.employee.common.enums.app.FormSubmitType;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
/** /**
* Form * Form
@ -38,6 +39,7 @@ import lombok.experimental.SuperBuilder;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@SuperBuilder @SuperBuilder
@Jacksonized
public class FormProtocolConfig extends AbstractProtocolConfig { public class FormProtocolConfig extends AbstractProtocolConfig {
@Serial @Serial

View File

@ -26,6 +26,7 @@ import cn.topiam.employee.common.enums.app.JwtIdTokenSubjectType;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
/** /**
* Form * Form
@ -36,6 +37,7 @@ import lombok.experimental.SuperBuilder;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@SuperBuilder @SuperBuilder
@Jacksonized
public class JwtProtocolConfig extends AbstractProtocolConfig { public class JwtProtocolConfig extends AbstractProtocolConfig {
@Serial @Serial

View File

@ -28,6 +28,7 @@ import cn.topiam.employee.application.AbstractProtocolConfig;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
/** /**
* Oidc * Oidc
@ -38,6 +39,7 @@ import lombok.experimental.SuperBuilder;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@SuperBuilder @SuperBuilder
@Jacksonized
public class OidcProtocolConfig extends AbstractProtocolConfig { public class OidcProtocolConfig extends AbstractProtocolConfig {
@Serial @Serial

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { SortOrder } from 'antd/es/table/interface'; import { SortOrder } from 'antd/es/table/interface';
import { ReactText } from 'react';
import { RequestData } from '@ant-design/pro-components'; import { RequestData } from '@ant-design/pro-components';
import { request } from '@@/exports'; import { request } from '@@/exports';
import { filterParamConverter, sortParamConverter } from '@/utils/utils'; import { filterParamConverter, sortParamConverter } from '@/utils/utils';
@ -92,7 +91,7 @@ export async function identitySourceConfigValidator(data: {
export async function getIdentitySourceSyncHistoryList( export async function getIdentitySourceSyncHistoryList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, ReactText[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<AccountAPI.ListIdentitySourceSyncHistory>> { ): Promise<RequestData<AccountAPI.ListIdentitySourceSyncHistory>> {
return request(`/api/v1/identity_source/sync/history_list`, { return request(`/api/v1/identity_source/sync/history_list`, {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
@ -112,7 +111,7 @@ export async function getIdentitySourceSyncHistoryList(
export async function getIdentitySourceSyncRecordList( export async function getIdentitySourceSyncRecordList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, ReactText[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<AccountAPI.ListIdentitySourceSyncRecord>> { ): Promise<RequestData<AccountAPI.ListIdentitySourceSyncRecord>> {
return request(`/api/v1/identity_source/sync/record_list`, { return request(`/api/v1/identity_source/sync/record_list`, {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
@ -132,7 +131,7 @@ export async function getIdentitySourceSyncRecordList(
export async function getIdentitySourceEventRecordList( export async function getIdentitySourceEventRecordList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, ReactText[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<AccountAPI.ListIdentitySourceEventRecord>> { ): Promise<RequestData<AccountAPI.ListIdentitySourceEventRecord>> {
return request(`/api/v1/identity_source/event/record_list`, { return request(`/api/v1/identity_source/event/record_list`, {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { SortOrder } from 'antd/es/table/interface'; import { SortOrder } from 'antd/es/table/interface';
import { ReactText } from 'react';
import { RequestData } from '@ant-design/pro-components'; import { RequestData } from '@ant-design/pro-components';
import { request } from '@@/exports'; import { request } from '@@/exports';
import { filterParamConverter, sortParamConverter } from '@/utils/utils'; import { filterParamConverter, sortParamConverter } from '@/utils/utils';
@ -27,7 +26,7 @@ import { filterParamConverter, sortParamConverter } from '@/utils/utils';
export async function getIdentityProviderList( export async function getIdentityProviderList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, ReactText[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<AccountAPI.ListIdentitySource>> { ): Promise<RequestData<AccountAPI.ListIdentitySource>> {
return request<API.ApiResult<AccountAPI.ListIdentitySource>>('/api/v1/identity_source/list', { return request<API.ApiResult<AccountAPI.ListIdentitySource>>('/api/v1/identity_source/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },

View File

@ -21,7 +21,7 @@ import { QuestionCircleOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns } from '@ant-design/pro-components'; import type { ActionType, ProColumns } from '@ant-design/pro-components';
import { ProTable } from '@ant-design/pro-components'; import { ProTable } from '@ant-design/pro-components';
import { Badge, App, Popconfirm, Table } from 'antd'; import { App, Badge, Popconfirm, Table } from 'antd';
import { useRef } from 'react'; import { useRef } from 'react';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Avatar, Image, App, Popconfirm, Skeleton } from 'antd'; import { App, Avatar, Image, Popconfirm, Skeleton } from 'antd';
import { import {
ActionType, ActionType,
ProCard, ProCard,

View File

@ -26,8 +26,8 @@ import MemberList from './components/MemberList';
import { UserGroupDetailTabs } from './constant'; import { UserGroupDetailTabs } from './constant';
import queryString from 'query-string'; import queryString from 'query-string';
import { useIntl, useLocation } from '@umijs/max'; import { useIntl, useLocation } from '@umijs/max';
import useStyles from "./style"; import useStyles from './style';
import AccessStrategy from "@/pages/account/UserGroupDetail/components/AccessStrategy"; import AccessStrategy from '@/pages/account/UserGroupDetail/components/AccessStrategy';
/** /**
* *

View File

@ -48,7 +48,9 @@ export default (props: { id: string }) => {
*/ */
const columns: ProColumns<AccountAPI.ListUser>[] = [ const columns: ProColumns<AccountAPI.ListUser>[] = [
{ {
title: intl.formatMessage({ id: 'pages.account.user_group_detail.add_member.columns.full_name' }), title: intl.formatMessage({
id: 'pages.account.user_group_detail.add_member.columns.full_name',
}),
dataIndex: 'fullName', dataIndex: 'fullName',
fixed: 'left', fixed: 'left',
ellipsis: true, ellipsis: true,

View File

@ -16,4 +16,5 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import CreateUser from './CreateUser'; import CreateUser from './CreateUser';
export default CreateUser; export default CreateUser;

View File

@ -16,4 +16,5 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import UpdateUser from './UpdateUser'; import UpdateUser from './UpdateUser';
export default UpdateUser; export default UpdateUser;

View File

@ -20,7 +20,7 @@ import { DesktopOutlined, ProfileOutlined } from '@ant-design/icons';
import { GridContent, PageContainer } from '@ant-design/pro-components'; import { GridContent, PageContainer } from '@ant-design/pro-components';
import { useAsyncEffect } from 'ahooks'; import { useAsyncEffect } from 'ahooks';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { Menu, App } from 'antd'; import { App, Menu } from 'antd';
import React, { useLayoutEffect, useRef, useState } from 'react'; import React, { useLayoutEffect, useRef, useState } from 'react';
import AccessPolicy from './components/AccessPolicy'; import AccessPolicy from './components/AccessPolicy';
import AppAccount from './components/AppAccount'; import AppAccount from './components/AppAccount';

View File

@ -34,10 +34,11 @@ import {
ProTable, ProTable,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { Button, Form, App, Popconfirm, Table } from 'antd'; import { App, Button, Form, Popconfirm, Table } from 'antd';
import { useRef, useState } from 'react';
import * as React from 'react'; import * as React from 'react';
import { useRef, useState } from 'react';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
/** /**
* *
* *

View File

@ -21,7 +21,7 @@ import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns } from '@ant-design/pro-components'; import type { ActionType, ProColumns } from '@ant-design/pro-components';
import { ModalForm, ProFormText, ProTable } from '@ant-design/pro-components'; import { ModalForm, ProFormText, ProTable } from '@ant-design/pro-components';
import { Alert, Button, Form, App, Popconfirm, Table } from 'antd'; import { Alert, App, Button, Form, Popconfirm, Table } from 'antd';
import { useRef } from 'react'; import { useRef } from 'react';
import { AppProtocolType } from '@/constant'; import { AppProtocolType } from '@/constant';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';

View File

@ -18,6 +18,7 @@
import { SsoScope } from '@/pages/app/AppConfig/constant'; import { SsoScope } from '@/pages/app/AppConfig/constant';
import { ProFormSelect } from '@ant-design/pro-components'; import { ProFormSelect } from '@ant-design/pro-components';
import { useIntl } from '@@/exports'; import { useIntl } from '@@/exports';
/** /**
* *
* *

View File

@ -17,7 +17,7 @@
*/ */
import { getAppConfig, saveAppConfig } from '@/services/app'; import { getAppConfig, saveAppConfig } from '@/services/app';
import { useAsyncEffect } from 'ahooks'; import { useAsyncEffect } from 'ahooks';
import { Alert, Divider, Form, App, Spin } from 'antd'; import { Alert, App, Divider, Form, Spin } from 'antd';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { import {
EditableProTable, EditableProTable,
@ -36,6 +36,7 @@ import { useIntl } from '@umijs/max';
import { AuthorizationType } from '../CommonConfig'; import { AuthorizationType } from '../CommonConfig';
import { GetApp } from '../../../data.d'; import { GetApp } from '../../../data.d';
import { generateUUID } from '@/utils/utils'; import { generateUUID } from '@/utils/utils';
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
span: 6, span: 6,

View File

@ -17,7 +17,7 @@
*/ */
import { getAppConfig, saveAppConfig } from '@/services/app'; import { getAppConfig, saveAppConfig } from '@/services/app';
import { useAsyncEffect } from 'ahooks'; import { useAsyncEffect } from 'ahooks';
import { Divider, Form, App, Spin, Alert } from 'antd'; import { Alert, App, Divider, Form, Spin } from 'antd';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { import {
FooterToolbar, FooterToolbar,
@ -32,6 +32,7 @@ import { omit } from 'lodash';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { AuthorizationType } from '../CommonConfig'; import { AuthorizationType } from '../CommonConfig';
import { GetApp } from '../../../data.d'; import { GetApp } from '../../../data.d';
const layout = { const layout = {
labelCol: { labelCol: {
xs: { span: 24 }, xs: { span: 24 },

View File

@ -81,11 +81,6 @@ export default (props: { app: GetApp | Record<string, any> }) => {
form.setFieldsValue({ form.setFieldsValue({
appId: id, appId: id,
...result, ...result,
//重定向URI
redirectUris: result.redirectUris?.length > 0 ? result.redirectUris : [undefined],
//登出重定向URI
postLogoutRedirectUris:
result.postLogoutRedirectUris?.length > 0 ? result.postLogoutRedirectUris : [undefined],
}); });
//设置Endpoint相关 //设置Endpoint相关
setProtocolEndpoint(result.protocolEndpoint); setProtocolEndpoint(result.protocolEndpoint);
@ -224,7 +219,7 @@ export default (props: { app: GetApp | Record<string, any> }) => {
</span> </span>
</> </>
), ),
} },
]} ]}
/> />
<ProFormDependency name={['authGrantTypes']}> <ProFormDependency name={['authGrantTypes']}>
@ -250,18 +245,20 @@ export default (props: { app: GetApp | Record<string, any> }) => {
{ {
validator: async (_, value) => { validator: async (_, value) => {
if (value && value.length > 0) { if (value && value.length > 0) {
return; return null;
} }
throw new Error( return Promise.reject(
new Error(
intl.formatMessage({ intl.formatMessage({
id: 'pages.app.config.items.login_access.protocol_config.oidc.redirect_uris.rule.0.message', id: 'pages.app.config.items.login_access.protocol_config.oidc.redirect_uris.rule.0.message',
}), }),
),
); );
}, },
}, },
]} ]}
> >
{(fields, { add, remove }, {}) => ( {(fields, { add, remove }, { errors }) => (
<> <>
{fields.map((field, index) => ( {fields.map((field, index) => (
<Form.Item <Form.Item
@ -275,12 +272,6 @@ export default (props: { app: GetApp | Record<string, any> }) => {
}) })
: '' : ''
} }
extra={
index === fields.length - 1 &&
intl.formatMessage({
id: 'pages.app.config.items.login_access.protocol_config.oidc.redirect_uris.extra',
})
}
> >
<div <div
style={{ style={{
@ -314,13 +305,24 @@ export default (props: { app: GetApp | Record<string, any> }) => {
})} })}
/> />
</Form.Item> </Form.Item>
{fields.length > 1 ? (
<DeleteOutlined onClick={() => remove(field.name)} /> <DeleteOutlined onClick={() => remove(field.name)} />
) : null}
</div> </div>
</Form.Item> </Form.Item>
))} ))}
<Form.Item {...formItemLayoutWithOutLabel}> <Form.Item
{...(fields.length === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
required={true}
label={
fields.length === 0
? intl.formatMessage({
id: 'pages.app.config.items.login_access.protocol_config.oidc.redirect_uris',
})
: ''
}
extra={intl.formatMessage({
id: 'pages.app.config.items.login_access.protocol_config.oidc.redirect_uris.extra',
})}
>
<Button <Button
type="dashed" type="dashed"
onClick={() => add()} onClick={() => add()}
@ -329,27 +331,12 @@ export default (props: { app: GetApp | Record<string, any> }) => {
> >
{intl.formatMessage({ id: 'app.add' })} {intl.formatMessage({ id: 'app.add' })}
</Button> </Button>
<Form.ErrorList errors={errors} />
</Form.Item> </Form.Item>
</> </>
)} )}
</Form.List> </Form.List>
<Form.List <Form.List name="postLogoutRedirectUris">
name="postLogoutRedirectUris"
rules={[
{
validator: async (_, value) => {
if (value && value.length > 0) {
return;
}
throw new Error(
intl.formatMessage({
id: 'pages.app.config.items.login_access.protocol_config.oidc.post_logout_redirect_uris.rule.0.message',
}),
);
},
},
]}
>
{(fields, { add, remove }, {}) => ( {(fields, { add, remove }, {}) => (
<> <>
{fields.map((field, index) => { {fields.map((field, index) => {
@ -357,7 +344,6 @@ export default (props: { app: GetApp | Record<string, any> }) => {
<Form.Item <Form.Item
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)} {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
key={field.key} key={field.key}
required={true}
label={ label={
index === 0 index === 0
? intl.formatMessage({ ? intl.formatMessage({
@ -365,12 +351,6 @@ export default (props: { app: GetApp | Record<string, any> }) => {
}) })
: '' : ''
} }
extra={
index === fields.length - 1 &&
intl.formatMessage({
id: 'pages.app.config.items.login_access.protocol_config.oidc.post_logout_redirect_uris.extra',
})
}
> >
<div <div
style={{ style={{
@ -404,14 +384,24 @@ export default (props: { app: GetApp | Record<string, any> }) => {
})} })}
/> />
</Form.Item> </Form.Item>
{fields.length > 1 ? (
<DeleteOutlined onClick={() => remove(field.name)} /> <DeleteOutlined onClick={() => remove(field.name)} />
) : null}
</div> </div>
</Form.Item> </Form.Item>
); );
})} })}
<Form.Item {...formItemLayoutWithOutLabel}> <Form.Item
{...(fields.length === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
label={
fields.length === 0
? intl.formatMessage({
id: 'pages.app.config.items.login_access.protocol_config.oidc.post_logout_redirect_uris',
})
: ''
}
extra={intl.formatMessage({
id: 'pages.app.config.items.login_access.protocol_config.oidc.post_logout_redirect_uris.extra',
})}
>
<Button <Button
type="dashed" type="dashed"
onClick={() => add()} onClick={() => add()}

View File

@ -15,7 +15,6 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { createApp, getAppTemplateList } from '@/services/app';
import { history } from '@@/core/history'; import { history } from '@@/core/history';
import { import {
ActionType, ActionType,
@ -36,8 +35,9 @@ import { useRef, useState } from 'react';
import useStyle from './style'; import useStyle from './style';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import classnames from 'classnames'; import classnames from 'classnames';
import { ListTemplate } from '@/pages/app/AppCreate/data.d'; import { ListTemplate } from './data.d';
import { AppType } from '@/constant'; import { AppType } from '@/constant';
import { createApp, getAppTemplateList } from './service';
const { Paragraph } = Typography; const { Paragraph } = Typography;
const prefixCls = 'topiam-create-app'; const prefixCls = 'topiam-create-app';

View File

@ -0,0 +1,54 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { request } from '@umijs/max';
import { ListTemplate } from './data.d';
import { RequestData } from '@ant-design/pro-components';
import { filterParamConverter, sortParamConverter } from '@/utils/utils';
import { SortOrder } from 'antd/es/table/interface';
/**
*
*/
export async function getAppTemplateList(
params: Record<string, any>,
sort: Record<string, SortOrder>,
filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<ListTemplate>> {
return request<API.ApiResult<ListTemplate>>('/api/v1/app/template/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
}).then((result: API.ApiResult<ListTemplate>) => {
const data: RequestData<ListTemplate> = {
data: result ? result?.result : [],
success: result?.success,
};
return Promise.resolve(data);
});
}
/**
* Create Application
*/
export async function createApp(
params: Record<string, string>,
): Promise<API.ApiResult<Record<string, string>>> {
return request<API.ApiResult<Record<string, string>>>(`/api/v1/app/create`, {
method: 'POST',
data: params,
requestType: 'json',
});
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { disableApp, enableApp, getAppList, removeApp } from '@/services/app'; import { getAppList } from '@/services/app';
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import type { ActionType } from '@ant-design/pro-components'; import type { ActionType } from '@ant-design/pro-components';
import { PageContainer, ProList } from '@ant-design/pro-components'; import { PageContainer, ProList } from '@ant-design/pro-components';
@ -25,6 +25,7 @@ import { history, useIntl } from '@umijs/max';
import useStyle from './style'; import useStyle from './style';
import classnames from 'classnames'; import classnames from 'classnames';
import { AppList } from './data.d'; import { AppList } from './data.d';
import { disableApp, enableApp, removeApp } from './service';
const prefixCls = 'app-list'; const prefixCls = 'app-list';

View File

@ -0,0 +1,41 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { request } from '@umijs/max';
/**
*
*/
export async function enableApp(id: string): Promise<API.ApiResult<boolean>> {
return request(`/api/v1/app/enable/${id}`, { method: 'PUT' });
}
/**
*
*/
export async function disableApp(id: string): Promise<API.ApiResult<boolean>> {
return request(`/api/v1/app/disable/${id}`, { method: 'PUT' });
}
/**
* Remove Application
*/
export async function removeApp(id: string): Promise<API.ApiResult<boolean>> {
return request<API.ApiResult<boolean>>(`/api/v1/app/delete/${id}`, {
method: 'DELETE',
});
}

View File

@ -25,11 +25,11 @@ import { Badge, DatePicker, Select, Space, Tag } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import ExpandedCard from './components/ExpandedCard'; import ExpandedCard from './components/ExpandedCard';
import { EventStatus, UserType } from './data.d'; import { AuditList, AuditTypeGroup, EventStatus, UserType } from './data.d';
import useStyles from './style'; import useStyles from './style';
import classNames from 'classnames'; import classNames from 'classnames';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { AuditList, AuditTypeGroup } from './data.d';
const { OptGroup } = Select; const { OptGroup } = Select;
const { Option } = Select; const { Option } = Select;
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;

View File

@ -19,9 +19,10 @@ import { Collapse, Typography } from 'antd';
import Paragraph from 'antd/es/typography/Paragraph'; import Paragraph from 'antd/es/typography/Paragraph';
import moment from 'moment'; import moment from 'moment';
import type { UserType } from '../../data.d'; import type { UserType } from '../../data.d';
import { EventStatus, AuditList } from '../../data.d'; import { AuditList, EventStatus } from '../../data.d';
import useStyles from './style'; import useStyles from './style';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
const { Text } = Typography; const { Text } = Typography;
interface ExpandedCardProps { interface ExpandedCardProps {

View File

@ -18,7 +18,6 @@
import { filterParamConverter, sortParamConverter } from '@/utils/utils'; import { filterParamConverter, sortParamConverter } from '@/utils/utils';
import type { RequestData } from '@ant-design/pro-components'; import type { RequestData } from '@ant-design/pro-components';
import type { SortOrder } from 'antd/es/table/interface'; import type { SortOrder } from 'antd/es/table/interface';
import type { ReactText } from 'react';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { AuditList, AuditTypeGroup } from './data.d'; import { AuditList, AuditTypeGroup } from './data.d';
@ -28,7 +27,7 @@ import { AuditList, AuditTypeGroup } from './data.d';
export async function getAuditList( export async function getAuditList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, ReactText[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<AuditList>> { ): Promise<RequestData<AuditList>> {
return request<API.ApiResult<AuditList>>(`/api/v1/audit/list`, { return request<API.ApiResult<AuditList>>(`/api/v1/audit/list`, {
params: { params: {

View File

@ -18,7 +18,6 @@
import { filterParamConverter, sortParamConverter } from '@/utils/utils'; import { filterParamConverter, sortParamConverter } from '@/utils/utils';
import type { RequestData } from '@ant-design/pro-components'; import type { RequestData } from '@ant-design/pro-components';
import type { SortOrder } from 'antd/es/table/interface'; import type { SortOrder } from 'antd/es/table/interface';
import type { ReactText } from 'react';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { GetIdentityProvider, ListIdentityProvider } from './data.d'; import { GetIdentityProvider, ListIdentityProvider } from './data.d';
@ -28,7 +27,7 @@ import { GetIdentityProvider, ListIdentityProvider } from './data.d';
export async function getIdpList( export async function getIdpList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, ReactText[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<ListIdentityProvider>> { ): Promise<RequestData<ListIdentityProvider>> {
return request<API.ApiResult<ListIdentityProvider>>('/api/v1/authn/idp/list', { return request<API.ApiResult<ListIdentityProvider>>('/api/v1/authn/idp/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },

View File

@ -20,7 +20,7 @@ import { QuestionCircleOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns } from '@ant-design/pro-components'; import type { ActionType, ProColumns } from '@ant-design/pro-components';
import { PageContainer, ProTable } from '@ant-design/pro-components'; import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Badge, App, Popconfirm, Space, Table } from 'antd'; import { App, Badge, Popconfirm, Space, Table } from 'antd';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { SessionList } from './data.d'; import { SessionList } from './data.d';

View File

@ -16,4 +16,5 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import CreateAdministrator from './CreateAdministrator'; import CreateAdministrator from './CreateAdministrator';
export default CreateAdministrator; export default CreateAdministrator;

View File

@ -16,4 +16,5 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import ResetAdministratorPassword from './ResetAdministratorPassword'; import ResetAdministratorPassword from './ResetAdministratorPassword';
export default ResetAdministratorPassword; export default ResetAdministratorPassword;

View File

@ -16,4 +16,5 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import UpdateAdministrator from './UpdateAdministrator'; import UpdateAdministrator from './UpdateAdministrator';
export default UpdateAdministrator; export default UpdateAdministrator;

View File

@ -15,4 +15,21 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/// /**
*
*/
export interface AdministratorList {
id: string;
username: string;
fullName: string;
avatar: string;
email: string;
phone: string;
status: string;
emailVerified: boolean;
phoneVerified: boolean;
authTotal: number;
lastAuthIp: string;
lastAuthTime: Date;
initialized: boolean;
}

View File

@ -15,11 +15,11 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { SortOrder } from 'antd/es/table/interface';
import { ReactText } from 'react';
import { RequestData } from '@ant-design/pro-components'; import { RequestData } from '@ant-design/pro-components';
import { request } from '@@/exports'; import { request } from '@@/exports';
import { filterParamConverter, sortParamConverter } from '@/utils/utils'; import { filterParamConverter, sortParamConverter } from '@/utils/utils';
import { SortOrder } from 'antd/es/table/interface';
import { AdministratorList } from './data';
/** /**
* *
@ -27,15 +27,12 @@ import { filterParamConverter, sortParamConverter } from '@/utils/utils';
export async function getAdministratorList( export async function getAdministratorList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, ReactText[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<SettingAPI.AdministratorList>> { ): Promise<RequestData<AdministratorList>> {
return request<API.ApiResult<SettingAPI.AdministratorList>>( return request<API.ApiResult<AdministratorList>>('/api/v1/setting/administrator/list', {
'/api/v1/setting/administrator/list',
{
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
}, }).then((result: API.ApiResult<AdministratorList>) => {
).then((result: API.ApiResult<SettingAPI.AdministratorList>) => { const data: RequestData<AdministratorList> = {
const data: RequestData<SettingAPI.AdministratorList> = {
data: result?.result?.list ? result?.result?.list : [], data: result?.result?.list ? result?.result?.list : [],
success: result?.success, success: result?.success,
total: result?.result?.pagination ? result?.result?.pagination.total : 0, total: result?.result?.pagination ? result?.result?.pagination.total : 0,

View File

@ -16,4 +16,5 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import Basic from './Basic'; import Basic from './Basic';
export default Basic; export default Basic;

View File

@ -16,4 +16,5 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import DefensePolicy from './DefensePolicy'; import DefensePolicy from './DefensePolicy';
export default DefensePolicy; export default DefensePolicy;

View File

@ -28,7 +28,7 @@ import {
ProFormSwitch, ProFormSwitch,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { useAsyncEffect } from 'ahooks'; import { useAsyncEffect } from 'ahooks';
import { Form, App, Space, Spin } from 'antd'; import { App, Form, Space, Spin } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import MaxMind from './components/MaxMind'; import MaxMind from './components/MaxMind';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';

View File

@ -30,7 +30,7 @@ import {
ProFormText, ProFormText,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { useAsyncEffect } from 'ahooks'; import { useAsyncEffect } from 'ahooks';
import { Form, App, Space, Spin } from 'antd'; import { App, Form, Space, Spin } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';

View File

@ -17,7 +17,7 @@
*/ */
import { disableCustomTemplate, getMailTemplate, saveMailTemplate } from '../../service'; import { disableCustomTemplate, getMailTemplate, saveMailTemplate } from '../../service';
import { useAsyncEffect } from 'ahooks'; import { useAsyncEffect } from 'ahooks';
import { Button, Drawer, Form, Input, App, Spin, Switch } from 'antd'; import { App, Button, Drawer, Form, Input, Spin, Switch } from 'antd';
import 'codemirror/lib/codemirror.css'; import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/clike/clike'; import 'codemirror/mode/clike/clike';
import 'codemirror/mode/cmake/cmake'; import 'codemirror/mode/cmake/cmake';

View File

@ -38,7 +38,7 @@ import {
ProFormText, ProFormText,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { useAsyncEffect } from 'ahooks'; import { useAsyncEffect } from 'ahooks';
import { Form, App, Segmented, Spin } from 'antd'; import { App, Form, Segmented, Spin } from 'antd';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import AliCloud from './AliCloud'; import AliCloud from './AliCloud';
import QiNiu from './QiNiu'; import QiNiu from './QiNiu';

View File

@ -15,25 +15,6 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/**
*
*/
export interface AdministratorList {
id: string;
username: string;
fullName: string;
avatar: string;
email: string;
phone: string;
status: string;
emailVerified: boolean;
phoneVerified: boolean;
authTotal: number;
lastAuthIp: string;
lastAuthTime: Date;
initialized: boolean;
}
/** /**
* *
*/ */

View File

@ -26,7 +26,7 @@ import {
ProFormSwitch, ProFormSwitch,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { useAsyncEffect } from 'ahooks'; import { useAsyncEffect } from 'ahooks';
import { Form, App, Space, Spin } from 'antd'; import { App, Form, Space, Spin } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import AliCloudOss from './components/AliCloud'; import AliCloudOss from './components/AliCloud';
import MinIO from './components/MinIo'; import MinIO from './components/MinIo';

View File

@ -17,11 +17,10 @@
*/ */
import { LOGIN_PATH } from '@/utils/utils'; import { LOGIN_PATH } from '@/utils/utils';
import { history } from '@@/core/history'; import { history } from '@@/core/history';
import { useModel } from '@umijs/max'; import { useIntl, useLocation, useModel } from '@umijs/max';
import { useMount } from 'ahooks'; import { useMount } from 'ahooks';
import { App } from 'antd'; import { App } from 'antd';
import queryString from 'query-string'; import queryString from 'query-string';
import { useIntl, useLocation } from '@umijs/max';
export default () => { export default () => {
const { setInitialState } = useModel('@@initialState'); const { setInitialState } = useModel('@@initialState');

View File

@ -19,7 +19,7 @@ import { filterParamConverter, sortParamConverter } from '@/utils/utils';
import type { RequestData } from '@ant-design/pro-components'; import type { RequestData } from '@ant-design/pro-components';
import type { SortOrder } from 'antd/es/table/interface'; import type { SortOrder } from 'antd/es/table/interface';
import qs from 'qs'; import qs from 'qs';
import type { Key, ReactText } from 'react'; import type { Key } from 'react';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { ParamCheckType } from '@/constant'; import { ParamCheckType } from '@/constant';
@ -129,7 +129,7 @@ export async function moveOrganization(
export async function getUserList( export async function getUserList(
params: Record<string, any>, params: Record<string, any>,
sort?: Record<string, SortOrder>, sort?: Record<string, SortOrder>,
filter?: Record<string, ReactText[] | null>, filter?: Record<string, (string | number)[] | null>,
): Promise<RequestData<AccountAPI.ListUser>> { ): Promise<RequestData<AccountAPI.ListUser>> {
return request<API.ApiResult<AccountAPI.ListUser>>('/api/v1/user/list', { return request<API.ApiResult<AccountAPI.ListUser>>('/api/v1/user/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
@ -171,7 +171,7 @@ export async function batchGetUser(ids: string[]): Promise<API.ApiResult<Account
export async function getLoginAuditList( export async function getLoginAuditList(
params: Record<string, any>, params: Record<string, any>,
sort?: Record<string, SortOrder>, sort?: Record<string, SortOrder>,
filter?: Record<string, ReactText[] | null>, filter?: Record<string, (string | number)[] | null>,
): Promise<RequestData<AccountAPI.UserLoginAuditList>> { ): Promise<RequestData<AccountAPI.UserLoginAuditList>> {
return request<API.ApiResult<AccountAPI.UserLoginAuditList>>('/api/v1/user/login_audit/list', { return request<API.ApiResult<AccountAPI.UserLoginAuditList>>('/api/v1/user/login_audit/list', {
method: 'GET', method: 'GET',
@ -334,7 +334,7 @@ export async function disableUser(id: string): Promise<API.ApiResult<boolean>> {
export async function getUserListNotInGroup( export async function getUserListNotInGroup(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, ReactText[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<AccountAPI.ListUser>> { ): Promise<RequestData<AccountAPI.ListUser>> {
return request<API.ApiResult<AccountAPI.ListUser>>(`/api/v1/user/notin_group_list`, { return request<API.ApiResult<AccountAPI.ListUser>>(`/api/v1/user/notin_group_list`, {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
@ -354,7 +354,7 @@ export async function getUserListNotInGroup(
export async function getUserGroupList( export async function getUserGroupList(
params: Record<string, any>, params: Record<string, any>,
sort?: Record<string, SortOrder>, sort?: Record<string, SortOrder>,
filter?: Record<string, ReactText[] | null>, filter?: Record<string, (string | number)[] | null>,
): Promise<RequestData<AccountAPI.ListUserGroup>> { ): Promise<RequestData<AccountAPI.ListUserGroup>> {
return request<API.ApiResult<AccountAPI.ListUserGroup>>('/api/v1/user_group/list', { return request<API.ApiResult<AccountAPI.ListUserGroup>>('/api/v1/user_group/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
@ -434,7 +434,7 @@ export async function removeUserGroupMember(
export async function getUserGroupMemberList( export async function getUserGroupMemberList(
params: Record<string, any>, params: Record<string, any>,
sort?: Record<string, SortOrder>, sort?: Record<string, SortOrder>,
filter?: Record<string, ReactText[] | null>, filter?: Record<string, (string | number)[] | null>,
): Promise<RequestData<AccountAPI.ListUser>> { ): Promise<RequestData<AccountAPI.ListUser>> {
return request<API.ApiResult<AccountAPI.ListUser>>( return request<API.ApiResult<AccountAPI.ListUser>>(
`/api/v1/user_group/${params.id}/member_list`, `/api/v1/user_group/${params.id}/member_list`,

View File

@ -21,25 +21,6 @@ import type { SortOrder } from 'antd/es/table/interface';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import type { UploadFile } from 'antd/es/upload/interface'; import type { UploadFile } from 'antd/es/upload/interface';
/**
*
*/
export async function getAppTemplateList(
params: Record<string, any>,
sort: Record<string, SortOrder>,
filter: Record<string, string[] | null>,
): Promise<RequestData<AppAPI.ListTemplate>> {
return request<API.ApiResult<AppAPI.ListTemplate>>('/api/v1/app/template/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
}).then((result: API.ApiResult<AppAPI.ListTemplate>) => {
const data: RequestData<AppAPI.ListTemplate> = {
data: result ? result?.result : [],
success: result?.success,
};
return Promise.resolve(data);
});
}
/** /**
* Get Application Template FormSchema * Get Application Template FormSchema
*/ */
@ -58,7 +39,7 @@ export async function getAppTemplateFormSchema(
export async function getAppList( export async function getAppList(
params?: Record<string, any>, params?: Record<string, any>,
sort?: Record<string, SortOrder>, sort?: Record<string, SortOrder>,
filter?: Record<string, string[] | null>, filter?: Record<string, (string | number)[] | null>,
): Promise<RequestData<AppAPI.AppList>> { ): Promise<RequestData<AppAPI.AppList>> {
return request<API.ApiResult<AppAPI.AppList>>('/api/v1/app/list', { return request<API.ApiResult<AppAPI.AppList>>('/api/v1/app/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
@ -73,19 +54,6 @@ export async function getAppList(
}); });
} }
/**
* Create Application
*/
export async function createApp(
params: Record<string, string>,
): Promise<API.ApiResult<Record<string, string>>> {
return request<API.ApiResult<Record<string, string>>>(`/api/v1/app/create`, {
method: 'POST',
data: params,
requestType: 'json',
});
}
/** /**
* Update Application * Update Application
*/ */
@ -97,15 +65,6 @@ export async function updateApp(params: Record<string, string>): Promise<API.Api
}); });
} }
/**
* Remove Application
*/
export async function removeApp(id: string): Promise<API.ApiResult<boolean>> {
return request<API.ApiResult<boolean>>(`/api/v1/app/delete/${id}`, {
method: 'DELETE',
});
}
/** /**
* Get Application * Get Application
*/ */
@ -115,20 +74,6 @@ export async function getApp(id: string): Promise<API.ApiResult<AppAPI.GetApp>>
}); });
} }
/**
*
*/
export async function enableApp(id: string): Promise<API.ApiResult<boolean>> {
return request(`/api/v1/app/enable/${id}`, { method: 'PUT' });
}
/**
*
*/
export async function disableApp(id: string): Promise<API.ApiResult<boolean>> {
return request(`/api/v1/app/disable/${id}`, { method: 'PUT' });
}
/** /**
* Get Config * Get Config
*/ */
@ -189,7 +134,7 @@ export async function getCertList(
export async function getAppAccountList( export async function getAppAccountList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, string[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<AppAPI.AppAccountList>> { ): Promise<RequestData<AppAPI.AppAccountList>> {
return request<API.ApiResult<AppAPI.AppAccountList>>('/api/v1/app/account/list', { return request<API.ApiResult<AppAPI.AppAccountList>>('/api/v1/app/account/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },
@ -231,7 +176,7 @@ export async function removeAccount(id: string): Promise<API.ApiResult<boolean>>
export async function getAppAccessPolicyList( export async function getAppAccessPolicyList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, string[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<AppAPI.AppAccessPolicyList>> { ): Promise<RequestData<AppAPI.AppAccessPolicyList>> {
return request<API.ApiResult<AppAPI.AppAccessPolicyList>>('/api/v1/app/access_policy/list', { return request<API.ApiResult<AppAPI.AppAccessPolicyList>>('/api/v1/app/access_policy/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },

View File

@ -423,18 +423,6 @@ declare namespace AppAPI {
remark: string; remark: string;
}; };
/**
*
*/
export type ListTemplate = {
protocol: string;
type: string;
code: string;
icon: string;
name: string;
desc: string;
};
/** /**
* *
*/ */

View File

@ -20,6 +20,10 @@ package cn.topiam.employee.portal.configuration.security;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -44,6 +48,7 @@ import cn.topiam.employee.portal.handler.PortalAccessDeniedHandler;
import cn.topiam.employee.portal.handler.PortalAuthenticationEntryPoint; import cn.topiam.employee.portal.handler.PortalAuthenticationEntryPoint;
import cn.topiam.employee.portal.handler.PortalLogoutSuccessHandler; import cn.topiam.employee.portal.handler.PortalLogoutSuccessHandler;
import cn.topiam.employee.portal.listener.PortalSessionInformationExpiredStrategy; import cn.topiam.employee.portal.listener.PortalSessionInformationExpiredStrategy;
import cn.topiam.employee.support.redis.KeyStringRedisSerializer;
import cn.topiam.employee.support.security.csrf.SpaCsrfTokenRequestHandler; import cn.topiam.employee.support.security.csrf.SpaCsrfTokenRequestHandler;
import static org.springframework.security.web.header.writers.XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK; import static org.springframework.security.web.header.writers.XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK;
import static org.springframework.web.cors.CorsConfiguration.ALL; import static org.springframework.web.cors.CorsConfiguration.ALL;
@ -202,6 +207,18 @@ public class AbstractSecurityConfiguration {
return configurer -> configurer.requireExplicitSave(false); return configurer -> configurer.requireExplicitSave(false);
} }
public RedisTemplate<String, String> getRedisTemplate(RedisConnectionFactory redisConnectionFactory,
CacheProperties cacheProperties) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
KeyStringRedisSerializer keyStringRedisSerializer = new KeyStringRedisSerializer(
cacheProperties.getRedis().getKeyPrefix());
redisTemplate.setKeySerializer(keyStringRedisSerializer);
redisTemplate.setValueSerializer(StringRedisSerializer.UTF_8);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
private final SettingRepository settingRepository; private final SettingRepository settingRepository;
public AbstractSecurityConfiguration(SettingRepository settingRepository) { public AbstractSecurityConfiguration(SettingRepository settingRepository) {

View File

@ -17,11 +17,14 @@
*/ */
package cn.topiam.employee.portal.configuration.security; package cn.topiam.employee.portal.configuration.security;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
@ -33,6 +36,7 @@ import cn.topiam.employee.audit.event.AuditEventPublish;
import cn.topiam.employee.common.repository.setting.SettingRepository; import cn.topiam.employee.common.repository.setting.SettingRepository;
import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationFailureEventListener; import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationFailureEventListener;
import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationSuccessEventListener; import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationSuccessEventListener;
import cn.topiam.employee.protocol.jwt.authorization.RedisJwtAuthorizationService;
import cn.topiam.employee.protocol.jwt.configurers.JwtAuthorizationServerConfigurer; import cn.topiam.employee.protocol.jwt.configurers.JwtAuthorizationServerConfigurer;
import static cn.topiam.employee.common.constant.ConfigBeanNameConstants.JWT_PROTOCOL_SECURITY_FILTER_CHAIN; import static cn.topiam.employee.common.constant.ConfigBeanNameConstants.JWT_PROTOCOL_SECURITY_FILTER_CHAIN;
@ -103,4 +107,12 @@ public class JwtProtocolSecurityConfiguration extends AbstractSecurityConfigurat
return new JwtAuthenticationFailureEventListener(auditEventPublish); return new JwtAuthenticationFailureEventListener(auditEventPublish);
} }
@Bean
public RedisJwtAuthorizationService redisJwtAuthorizationService(RedisConnectionFactory redisConnectionFactory,
CacheProperties cacheProperties,
AutowireCapableBeanFactory beanFactory) {
return new RedisJwtAuthorizationService(
getRedisTemplate(redisConnectionFactory, cacheProperties), beanFactory);
}
} }

View File

@ -146,14 +146,8 @@ public class OidcProtocolSecurityConfiguration extends AbstractSecurityConfigura
CacheProperties cacheProperties, CacheProperties cacheProperties,
RegisteredClientRepository clientRepository, RegisteredClientRepository clientRepository,
AutowireCapableBeanFactory beanFactory) { AutowireCapableBeanFactory beanFactory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); return new RedisOAuth2AuthorizationServiceWrapper(
redisTemplate.setConnectionFactory(redisConnectionFactory); getRedisTemplate(redisConnectionFactory, cacheProperties), clientRepository,
KeyStringRedisSerializer keyStringRedisSerializer = new KeyStringRedisSerializer(
cacheProperties.getRedis().getKeyPrefix());
redisTemplate.setKeySerializer(keyStringRedisSerializer);
redisTemplate.setValueSerializer(StringRedisSerializer.UTF_8);
redisTemplate.afterPropertiesSet();
return new RedisOAuth2AuthorizationServiceWrapper(redisTemplate, clientRepository,
beanFactory); beanFactory);
} }

File diff suppressed because one or more lines are too long

View File

@ -59,7 +59,7 @@ const GlobalHeaderRight: React.FC = () => {
return ( return (
<div className={styles.main}> <div className={styles.main}>
<Helmet> <Helmet>
<link rel="icon" href={initialState?.globalConfig?.appearance?.favicon} /> <link rel="icon" href={'/favicon.ico'} />
</Helmet> </Helmet>
<About /> <About />
<SelectLang className={styles.action} /> <SelectLang className={styles.action} />

View File

@ -28,8 +28,7 @@ import { AccountSettingsStateKey } from './data.d';
import classnames from 'classnames'; import classnames from 'classnames';
import useStyle from './style'; import useStyle from './style';
import queryString from 'query-string'; import queryString from 'query-string';
import { useLocation } from '@umijs/max'; import { useIntl, useLocation } from '@umijs/max';
import { useIntl } from '@umijs/max';
const prefixCls = 'account'; const prefixCls = 'account';

View File

@ -16,8 +16,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import { ProForm, ProFormText, ProFormTextArea } from '@ant-design/pro-components'; import {
import { Avatar, Button, Form, App, Skeleton, Upload } from 'antd'; ProForm,
ProFormText,
ProFormTextArea,
useStyle as useAntdStyle,
} from '@ant-design/pro-components';
import { App, Avatar, Button, Form, Skeleton, Upload } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import { changeBaseInfo } from '@/pages/Account/service'; import { changeBaseInfo } from '@/pages/Account/service';
@ -28,7 +33,6 @@ import ImgCrop from 'antd-img-crop';
import { uploadFile } from '@/services/upload'; import { uploadFile } from '@/services/upload';
import { useModel } from '@umijs/max'; import { useModel } from '@umijs/max';
import classnames from 'classnames'; import classnames from 'classnames';
import { useStyle as useAntdStyle } from '@ant-design/pro-components';
import { useIntl } from '@@/exports'; import { useIntl } from '@@/exports';
const prefixCls = 'account-base'; const prefixCls = 'account-base';

View File

@ -22,13 +22,17 @@ import { aesEcbEncrypt } from '@/utils/aes';
import { onGetEncryptSecret, phoneIsValidNumber } from '@/utils/utils'; import { onGetEncryptSecret, phoneIsValidNumber } from '@/utils/utils';
import { FormattedMessage } from '@@/plugin-locale/localeExports'; import { FormattedMessage } from '@@/plugin-locale/localeExports';
import type { CaptFieldRef, ProFormInstance } from '@ant-design/pro-components'; import type { CaptFieldRef, ProFormInstance } from '@ant-design/pro-components';
import { ModalForm, ProFormCaptcha, ProFormText } from '@ant-design/pro-components'; import {
import { ConfigProvider, App, Spin } from 'antd'; ModalForm,
ProFormCaptcha,
ProFormText,
useStyle as useAntdStyle,
} from '@ant-design/pro-components';
import { App, ConfigProvider, Spin } from 'antd';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { useContext, useEffect, useRef, useState } from 'react'; import { useContext, useEffect, useRef, useState } from 'react';
import { FormLayout } from './constant'; import { FormLayout } from './constant';
import classnames from 'classnames'; import classnames from 'classnames';
import { useStyle as useAntdStyle } from '@ant-design/pro-components';
import { ConfigContext } from 'antd/es/config-provider'; import { ConfigContext } from 'antd/es/config-provider';
import { useIntl } from '@@/exports'; import { useIntl } from '@@/exports';
import PhoneAreaCodeSelect from '@/components/PhoneAreaCodeSelect'; import PhoneAreaCodeSelect from '@/components/PhoneAreaCodeSelect';

View File

@ -16,4 +16,5 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import Account from './Account'; import Account from './Account';
export default Account; export default Account;

View File

@ -16,22 +16,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import type { ActionType } from '@ant-design/pro-components'; import type { ActionType } from '@ant-design/pro-components';
import { PageContainer, ProList } from '@ant-design/pro-components'; import { PageContainer, ProCard, ProList } from '@ant-design/pro-components';
import { Alert, Avatar, Card, Input, App, Typography } from 'antd'; import { Alert, App, Avatar, Badge, Card, Input, Typography } from 'antd';
import { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import type { AppList } from './data.d'; import type { AppList } from './data.d';
import { InitLoginType } from './data.d'; import { InitLoginType } from './data.d';
import { queryAppList } from './service'; import { queryAppList } from './service';
import { useIntl } from '@@/exports'; import { useIntl } from '@@/exports';
import useStyle from './style';
import classnames from 'classnames';
const { Paragraph } = Typography; const { Paragraph } = Typography;
const prefixCls = 'topiam-app-list';
const CardList = () => { const CardList = () => {
const intl = useIntl(); const intl = useIntl();
const { styles } = useStyle(prefixCls);
const [activeKey, setActiveKey] = useState<React.Key | undefined>('tab1');
const { message } = App.useApp(); const { message } = App.useApp();
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const [searchParams, setSearchParams] = useState<{ name: string }>(); const [searchParams, setSearchParams] = useState<{ name: string }>();
const content = ( const content = (
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<Input.Search <Input.Search
@ -60,13 +64,45 @@ const CardList = () => {
document.body.removeChild(div); document.body.removeChild(div);
}; };
const renderBadge = (count: number, active = false) => {
return ( return (
<PageContainer content={content}> <Badge
<Alert message={intl.formatMessage({ id: 'pages.application.alert' })} showIcon /> count={count}
style={{
marginBlockStart: -2,
marginInlineStart: 4,
color: active ? '#1890FF' : '#999',
backgroundColor: active ? '#E6F7FF' : '#eee',
}}
/>
);
};
return (
<div className={styles}>
<PageContainer
className={classnames(`${prefixCls}`)}
tabList={[
{
tab: '应用列表',
key: 'list',
},
{
tab: '应用账号',
key: 'account',
},
]}
>
<Alert
banner
type={'info'}
message={intl.formatMessage({ id: 'pages.application.alert' })}
showIcon
/>
<br /> <br />
<ProList<AppList> <ProList<AppList>
rowKey="id" rowKey="id"
ghost split
grid={{ grid={{
xs: 1, xs: 1,
sm: 2, sm: 2,
@ -77,15 +113,39 @@ const CardList = () => {
}} }}
request={queryAppList} request={queryAppList}
pagination={{}} pagination={{}}
toolbar={{
menu: {
type: 'tab',
activeKey,
items: [
{
key: 'tab1',
label: <span>{renderBadge(99, activeKey === 'tab1')}</span>,
},
{
key: 'tab2',
label: <span>{renderBadge(32, activeKey === 'tab2')}</span>,
},
],
onChange(key) {
setActiveKey(key);
},
},
}}
params={searchParams} params={searchParams}
actionRef={actionRef} actionRef={actionRef}
tableExtraRender={() => {
return <ProCard>{content}</ProCard>;
}}
renderItem={(item: AppList) => { renderItem={(item: AppList) => {
return ( return (
item && item &&
item.id && ( item.id && (
<Card <Card
style={{ margin: 5 }} style={{ margin: 8 }}
className={`${prefixCls}-item-card`}
hoverable hoverable
bordered={false}
onClick={async () => { onClick={async () => {
if (item.initLoginType === InitLoginType.PORTAL_OR_APP_INIT_SSO) { if (item.initLoginType === InitLoginType.PORTAL_OR_APP_INIT_SSO) {
initSso(item.initLoginUrl); initSso(item.initLoginUrl);
@ -96,19 +156,28 @@ const CardList = () => {
); );
}} }}
> >
<Card.Meta <div className={`${prefixCls}-item-content-wrapper`} key={item.id}>
avatar={<Avatar key={item.id} shape="square" size={50} src={item.icon} />} <div className={`${prefixCls}-item-avatar`}>
title={item.name} <Avatar key={item.icon} shape="square" src={item.icon} size={45} />
description={ </div>
<Paragraph ellipsis={{ rows: 2, tooltip: true }}>{item.description}</Paragraph> <div className={`${prefixCls}-item-content`}>
} <span className={`${prefixCls}-item-content-title`}>{item.name}</span>
/> <Paragraph
className={`${prefixCls}-item-content-desc`}
ellipsis={{ tooltip: item.description, rows: 2 }}
title={item.description}
>
{item.description ? item.description : <>&nbsp;</>}
</Paragraph>
</div>
</div>
</Card> </Card>
) )
); );
}} }}
/> />
</PageContainer> </PageContainer>
</div>
); );
}; };

View File

@ -18,7 +18,6 @@
import { filterParamConverter, sortParamConverter } from '@/utils/utils'; import { filterParamConverter, sortParamConverter } from '@/utils/utils';
import type { RequestData } from '@ant-design/pro-components'; import type { RequestData } from '@ant-design/pro-components';
import type { SortOrder } from 'antd/es/table/interface'; import type { SortOrder } from 'antd/es/table/interface';
import type { ReactText } from 'react';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import type { AppList } from './data.d'; import type { AppList } from './data.d';
@ -28,7 +27,7 @@ import type { AppList } from './data.d';
export async function queryAppList( export async function queryAppList(
params?: Record<string, any>, params?: Record<string, any>,
sort?: Record<string, SortOrder>, sort?: Record<string, SortOrder>,
filter?: Record<string, ReactText[] | null>, filter?: Record<string, (string | number)[] | null>,
): Promise<RequestData<AppList>> { ): Promise<RequestData<AppList>> {
const { result, success } = await request<API.ApiResult<AppList>>('/api/v1/app/list', { const { result, success } = await request<API.ApiResult<AppList>>('/api/v1/app/list', {
params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) }, params: { ...params, ...sortParamConverter(sort), ...filterParamConverter(filter) },

View File

@ -0,0 +1,87 @@
/*
* eiam-portal - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { createStyles } from 'antd-style';
const useStyles = createStyles(({ token, css, prefixCls }, prop) => {
const prefixClassName = prop as string;
return css`
.${prefixClassName} {
border: none;
border-radius: ${token.borderRadius};
.${prefixCls}-pro-table-list-toolbar-container {
margin-bottom: 10px;
}
&-item-card {
.${prefixCls}-card-body {
padding: 16px !important;
background: #f7f8fa;
border-radius: ${token.borderRadius}px;
}
}
&-item-content-wrapper {
display: flex;
}
&-item-avatar {
width: 54px;
height: 54px;
border-radius: 4px;
margin-right: 5px;
& .${prefixCls}-avatar {
width: 54px !important;
height: 54px !important;
display: flex;
align-items: center;
border-radius: 4px;
> img {
height: auto;
}
}
}
&-item-content {
display: flex;
flex-direction: column;
font-size: 16px;
min-width: 0;
&-title {
font-weight: 500;
color: #293350;
margin-top: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&-type {
color: #9fabcb;
font-size: 13px;
margin-top: 4px;
}
&-desc {
margin-top: 12px;
}
}
}
`;
});
export default useStyles;

View File

@ -18,9 +18,10 @@
import { Collapse, Typography } from 'antd'; import { Collapse, Typography } from 'antd';
import Paragraph from 'antd/es/typography/Paragraph'; import Paragraph from 'antd/es/typography/Paragraph';
import moment from 'moment'; import moment from 'moment';
import { EventStatus, AuditList } from '../../data.d'; import { AuditList, EventStatus } from '../../data.d';
import useStyles from './style'; import useStyles from './style';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
const { Text } = Typography; const { Text } = Typography;
interface ExpandedCardProps { interface ExpandedCardProps {

View File

@ -18,7 +18,6 @@
import { filterParamConverter, sortParamConverter } from '@/utils/utils'; import { filterParamConverter, sortParamConverter } from '@/utils/utils';
import type { RequestData } from '@ant-design/pro-components'; import type { RequestData } from '@ant-design/pro-components';
import type { SortOrder } from 'antd/es/table/interface'; import type { SortOrder } from 'antd/es/table/interface';
import type { ReactText } from 'react';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import type { AuditList, AuditTypeGroup } from './data.d'; import type { AuditList, AuditTypeGroup } from './data.d';
@ -28,7 +27,7 @@ import type { AuditList, AuditTypeGroup } from './data.d';
export async function getAuditList( export async function getAuditList(
params: Record<string, any>, params: Record<string, any>,
sort: Record<string, SortOrder>, sort: Record<string, SortOrder>,
filter: Record<string, ReactText[] | null>, filter: Record<string, (string | number)[] | null>,
): Promise<RequestData<AuditList>> { ): Promise<RequestData<AuditList>> {
return request<API.ApiResult<AuditList>>(`/api/v1/audit/list`, { return request<API.ApiResult<AuditList>>(`/api/v1/audit/list`, {
params: { params: {

View File

@ -22,7 +22,7 @@ import { getCurrentStatus, getLoginEncryptSecret } from '@/services';
import { aesEcbEncrypt } from '@/utils/aes'; import { aesEcbEncrypt } from '@/utils/aes';
import { ProCard, ProForm, ProFormCheckbox, ProFormInstance } from '@ant-design/pro-components'; import { ProCard, ProForm, ProFormCheckbox, ProFormInstance } from '@ant-design/pro-components';
import { useAsyncEffect, useRequest, useSafeState } from 'ahooks'; import { useAsyncEffect, useRequest, useSafeState } from 'ahooks';
import { Alert, Avatar, App, Skeleton, Space, Spin, Tabs, Tooltip } from 'antd'; import { Alert, App, Avatar, Skeleton, Space, Spin, Tabs, Tooltip } from 'antd';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { FormattedMessage, Helmet, history, useIntl } from '@umijs/max'; import { FormattedMessage, Helmet, history, useIntl } from '@umijs/max';

View File

@ -24,7 +24,7 @@ import { history } from '@@/core/history';
import { LockTwoTone, UserOutlined } from '@ant-design/icons'; import { LockTwoTone, UserOutlined } from '@ant-design/icons';
import { ProForm, ProFormText } from '@ant-design/pro-components'; import { ProForm, ProFormText } from '@ant-design/pro-components';
import { useMount, useSafeState } from 'ahooks'; import { useMount, useSafeState } from 'ahooks';
import { Col, Form, App, Row, Spin, Typography, Alert } from 'antd'; import { Alert, App, Col, Form, Row, Spin, Typography } from 'antd';
import { idpBindUser } from '../../service'; import { idpBindUser } from '../../service';
import { useIntl } from '@@/plugin-locale'; import { useIntl } from '@@/plugin-locale';
import { createStyles } from 'antd-style'; import { createStyles } from 'antd-style';

View File

@ -22,7 +22,7 @@ import { ProFormCaptcha, ProFormText } from '@ant-design/pro-components';
import { App } from 'antd'; import { App } from 'antd';
import { useImperativeHandle, useRef } from 'react'; import { useImperativeHandle, useRef } from 'react';
import { sendLoginCaptchaOpt } from '../service'; import { sendLoginCaptchaOpt } from '../service';
import { phoneIsValidNumber, phoneParseNumber, emailValidator } from '@/utils/utils'; import { emailValidator, phoneIsValidNumber, phoneParseNumber } from '@/utils/utils';
import { createStyles } from 'antd-style'; import { createStyles } from 'antd-style';
const useStyle = createStyles(({ token }) => { const useStyle = createStyles(({ token }) => {

View File

@ -30,6 +30,7 @@ import { forgetPasswordCode } from '@/pages/Login/service';
import Title from '@/components/Title'; import Title from '@/components/Title';
import { useRef } from 'react'; import { useRef } from 'react';
import { phoneIsValidNumber, phoneParseNumber } from '@/utils/utils'; import { phoneIsValidNumber, phoneParseNumber } from '@/utils/utils';
const { Paragraph } = Typography; const { Paragraph } = Typography;
const Code = (props: ProFormProps) => { const Code = (props: ProFormProps) => {
const intl = useIntl(); const intl = useIntl();

View File

@ -21,6 +21,7 @@ import { FormattedMessage, useIntl } from '@@/exports';
import useStyle from './style'; import useStyle from './style';
import Title from '@/components/Title'; import Title from '@/components/Title';
import { Typography } from 'antd'; import { Typography } from 'antd';
const prefixCls = 'topiam-forget-password'; const prefixCls = 'topiam-forget-password';
const { Paragraph } = Typography; const { Paragraph } = Typography;

View File

@ -18,6 +18,7 @@
import { Button } from 'antd'; import { Button } from 'antd';
import useStyle from './style'; import useStyle from './style';
import { useIntl } from '@@/exports'; import { useIntl } from '@@/exports';
const prefixCls = 'topiam-forget-password'; const prefixCls = 'topiam-forget-password';
const Success = ({ close }: { close: () => void }) => { const Success = ({ close }: { close: () => void }) => {

View File

@ -18,7 +18,7 @@
import { QuestionCircleOutlined } from '@ant-design/icons'; import { QuestionCircleOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns } from '@ant-design/pro-components'; import type { ActionType, ProColumns } from '@ant-design/pro-components';
import { PageContainer, ProTable } from '@ant-design/pro-components'; import { PageContainer, ProTable } from '@ant-design/pro-components';
import { Badge, App, Popconfirm, Space, Table } from 'antd'; import { App, Badge, Popconfirm, Space, Table } from 'antd';
import { useRef } from 'react'; import { useRef } from 'react';
import type { SessionList } from './data.d'; import type { SessionList } from './data.d';
import { getSessions, removeSessions } from './service'; import { getSessions, removeSessions } from './service';

View File

@ -26,6 +26,8 @@ import org.springframework.util.Assert;
import cn.topiam.employee.application.form.model.FormProtocolConfig; import cn.topiam.employee.application.form.model.FormProtocolConfig;
import cn.topiam.employee.application.jwt.model.JwtProtocolConfig; import cn.topiam.employee.application.jwt.model.JwtProtocolConfig;
import lombok.Getter;
/** /**
* *
* @author TopIAM * @author TopIAM
@ -41,16 +43,19 @@ public class FormAuthenticationToken extends AbstractAuthenticationToken {
/** /**
* *
*/ */
@Getter
private final String accountUsername; private final String accountUsername;
/** /**
* *
*/ */
@Getter
private final String accountCredential; private final String accountCredential;
/** /**
* *
*/ */
@Getter
private final FormProtocolConfig config; private final FormProtocolConfig config;
/** /**
@ -105,15 +110,4 @@ public class FormAuthenticationToken extends AbstractAuthenticationToken {
return this.principal; return this.principal;
} }
public String getAccountUsername() {
return accountUsername;
}
public String getAccountCredential() {
return accountCredential;
}
public FormProtocolConfig getConfig() {
return config;
}
} }

View File

@ -25,6 +25,8 @@ import org.springframework.util.Assert;
import cn.topiam.employee.application.form.model.FormProtocolConfig; import cn.topiam.employee.application.form.model.FormProtocolConfig;
import lombok.Getter;
/** /**
* *
* @author TopIAM * @author TopIAM
@ -33,6 +35,7 @@ import cn.topiam.employee.application.form.model.FormProtocolConfig;
public class FormRequestAuthenticationToken extends AbstractAuthenticationToken { public class FormRequestAuthenticationToken extends AbstractAuthenticationToken {
private final Authentication principal; private final Authentication principal;
@Getter
private final FormProtocolConfig config; private final FormProtocolConfig config;
/** /**
@ -79,7 +82,4 @@ public class FormRequestAuthenticationToken extends AbstractAuthenticationToken
return this.principal; return this.principal;
} }
public FormProtocolConfig getConfig() {
return config;
}
} }

View File

@ -46,8 +46,6 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.GRANT_TYPE;
import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE; import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE;
import static cn.topiam.employee.support.util.HttpRequestUtils.getRequestHeaders; import static cn.topiam.employee.support.util.HttpRequestUtils.getRequestHeaders;
@ -85,7 +83,6 @@ public final class FormAuthorizationServerContextFilter extends OncePerRequestFi
return; return;
} }
try { try {
request.getParameterValues(GRANT_TYPE);
//@formatter:off //@formatter:off
Map<String, String> variables = matcher.getVariables(); Map<String, String> variables = matcher.getVariables();
String appCode = variables.get(APP_CODE); String appCode = variables.get(APP_CODE);

View File

@ -195,6 +195,13 @@ public final class FormAuthenticationEndpointFilter extends OncePerRequestFilter
LogMessage.format("Authorization request failed: %s", ex.getError()), ex); LogMessage.format("Authorization request failed: %s", ex.getError()), ex);
} }
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
} catch (Exception ex) {
FormError error = new FormError(SERVER_ERROR,ex.getMessage(),FORM_ERROR_URI);
if (this.logger.isTraceEnabled()) {
this.logger.trace(error, ex);
}
this.authenticationFailureHandler.onAuthenticationFailure(request, response,
new FormAuthenticationException(error));
} }
} }

View File

@ -0,0 +1,56 @@
package cn.topiam.employee.protocol.jwt.authentication;
import java.io.IOException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import cn.topiam.employee.protocol.jwt.exception.JwtAuthenticationException;
import cn.topiam.employee.protocol.jwt.exception.JwtError;
import cn.topiam.employee.protocol.jwt.exception.JwtErrorCodes;
import cn.topiam.employee.protocol.jwt.http.converter.JwtErrorHttpMessageConverter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/9/4 13:03
*/
public class JwtAuthenticationFailureHandler implements AuthenticationFailureHandler {
/**
* Called when an authentication attempt fails.
*
* @param request the request during which the authentication attempt occurred.
* @param response the response.
* @param exception the exception which was thrown to reject the authentication
* request.
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException,
ServletException {
if (exception instanceof JwtAuthenticationException) {
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
JwtError error = ((JwtAuthenticationException) exception).getError();
if (error.getErrorCode().equals(JwtErrorCodes.SERVER_ERROR)) {
httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
}
if (error.getErrorCode().equals(JwtErrorCodes.INVALID_REQUEST)) {
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
}
errorHttpResponseConverter.write(error, null, httpResponse);
}
}
/**
*
*/
private final HttpMessageConverter<JwtError> errorHttpResponseConverter = new JwtErrorHttpMessageConverter();
}

View File

@ -18,6 +18,7 @@
package cn.topiam.employee.protocol.jwt.authentication; package cn.topiam.employee.protocol.jwt.authentication;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.UUID;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -25,6 +26,8 @@ import org.springframework.security.core.Authentication;
import cn.topiam.employee.application.jwt.model.JwtProtocolConfig; import cn.topiam.employee.application.jwt.model.JwtProtocolConfig;
import cn.topiam.employee.protocol.jwt.token.IdToken; import cn.topiam.employee.protocol.jwt.token.IdToken;
import lombok.Getter;
/** /**
* *
* @author TopIAM * @author TopIAM
@ -32,6 +35,9 @@ import cn.topiam.employee.protocol.jwt.token.IdToken;
*/ */
public class JwtAuthenticationToken extends AbstractAuthenticationToken { public class JwtAuthenticationToken extends AbstractAuthenticationToken {
@Getter
private final String id;
/** /**
* principal * principal
*/ */
@ -40,11 +46,13 @@ public class JwtAuthenticationToken extends AbstractAuthenticationToken {
/** /**
* idToken * idToken
*/ */
@Getter
private final IdToken idToken; private final IdToken idToken;
/** /**
* *
*/ */
@Getter
private final JwtProtocolConfig config; private final JwtProtocolConfig config;
/** /**
@ -60,6 +68,7 @@ public class JwtAuthenticationToken extends AbstractAuthenticationToken {
this.principal = principal; this.principal = principal;
this.idToken = idToken; this.idToken = idToken;
this.config = config; this.config = config;
this.id = UUID.randomUUID().toString();
setAuthenticated(true); setAuthenticated(true);
} }
@ -93,11 +102,4 @@ public class JwtAuthenticationToken extends AbstractAuthenticationToken {
return this.principal; return this.principal;
} }
public IdToken getIdToken() {
return idToken;
}
public JwtProtocolConfig getConfig() {
return config;
}
} }

View File

@ -0,0 +1,68 @@
package cn.topiam.employee.protocol.jwt.authentication;
import java.util.ArrayList;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import lombok.Getter;
/**
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/9/4 13:43
*/
public class JwtLogoutAuthenticationToken extends AbstractAuthenticationToken {
private final Authentication principal;
@Getter
private final String sessionId;
public JwtLogoutAuthenticationToken(Authentication principal, String sessionId) {
super(new ArrayList<>());
this.principal = principal;
this.sessionId = sessionId;
}
/**
* The credentials that prove the principal is correct. This is usually a password,
* but could be anything relevant to the <code>AuthenticationManager</code>. Callers
* are expected to populate the credentials.
*
* @return the credentials that prove the identity of the <code>Principal</code>
*/
@Override
public Object getCredentials() {
return "";
}
/**
* The identity of the principal being authenticated. In the case of an authentication
* request with username and password, this would be the username. Callers are
* expected to populate the principal for an authentication request.
* <p>
* The <tt>AuthenticationManager</tt> implementation will often return an
* <tt>Authentication</tt> containing richer information as the principal for use by
* the application. Many of the authentication providers will create a
* {@code UserDetails} object as the principal.
*
* @return the <code>Principal</code> being authenticated or the authenticated
* principal after authentication.
*/
@Override
public Object getPrincipal() {
return principal;
}
/**
* Returns {@code true} if {@link #getPrincipal()} is authenticated, {@code false} otherwise.
*
* @return {@code true} if {@link #getPrincipal()} is authenticated, {@code false} otherwise
*/
public boolean isPrincipalAuthenticated() {
return !AnonymousAuthenticationToken.class.isAssignableFrom(this.principal.getClass()) &&
this.principal.isAuthenticated();
}
}

View File

@ -25,6 +25,8 @@ import org.springframework.security.core.Authentication;
import cn.topiam.employee.application.jwt.model.JwtProtocolConfig; import cn.topiam.employee.application.jwt.model.JwtProtocolConfig;
import lombok.Getter;
/** /**
* *
* @author TopIAM * @author TopIAM
@ -39,25 +41,21 @@ public class JwtRequestAuthenticationToken extends AbstractAuthenticationToken {
/** /**
* URL * URL
*/ */
@Getter
private String targetUrl; private String targetUrl;
/** /**
* *
*/ */
@Getter
private final JwtProtocolConfig config; private final JwtProtocolConfig config;
/** /**
* *
*/ */
@Getter
private final Map<String, Object> additionalParameters; private final Map<String, Object> additionalParameters;
public JwtRequestAuthenticationToken(Authentication principal, JwtProtocolConfig config,
Map<String, Object> additionalParameters) {
super(new ArrayList<>());
this.principal = principal;
this.config = config;
this.additionalParameters = additionalParameters;
}
public JwtRequestAuthenticationToken(Authentication principal, String targetUrl, public JwtRequestAuthenticationToken(Authentication principal, String targetUrl,
JwtProtocolConfig config, JwtProtocolConfig config,
@ -99,19 +97,8 @@ public class JwtRequestAuthenticationToken extends AbstractAuthenticationToken {
return principal; return principal;
} }
public JwtProtocolConfig getConfig() {
return config;
}
public void setTargetUrl(String targetUrl) { public void setTargetUrl(String targetUrl) {
this.targetUrl = targetUrl; this.targetUrl = targetUrl;
} }
public String getTargetUrl() {
return targetUrl;
}
public Map<String, Object> getAdditionalParameters() {
return additionalParameters;
}
} }

View File

@ -37,6 +37,7 @@ import cn.topiam.employee.protocol.jwt.token.IdToken;
import cn.topiam.employee.protocol.jwt.token.IdTokenContext; import cn.topiam.employee.protocol.jwt.token.IdTokenContext;
import cn.topiam.employee.protocol.jwt.token.IdTokenGenerator; import cn.topiam.employee.protocol.jwt.token.IdTokenGenerator;
import cn.topiam.employee.protocol.jwt.token.JwtIdTokenGenerator; import cn.topiam.employee.protocol.jwt.token.JwtIdTokenGenerator;
import cn.topiam.employee.support.security.authentication.WebAuthenticationDetails;
import cn.topiam.employee.support.security.userdetails.UserDetails; import cn.topiam.employee.support.security.userdetails.UserDetails;
import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE_VARIABLE; import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE_VARIABLE;
import static cn.topiam.employee.common.constant.ProtocolConstants.JwtEndpointConstants.JWT_SSO_PATH; import static cn.topiam.employee.common.constant.ProtocolConstants.JwtEndpointConstants.JWT_SSO_PATH;
@ -80,11 +81,12 @@ public final class JwtRequestAuthenticationTokenProvider implements Authenticati
JwtProtocolConfig config = requestAuthenticationToken.getConfig(); JwtProtocolConfig config = requestAuthenticationToken.getConfig();
String issuer = ServerHelp.getPortalPublicBaseUrl() + JWT_SSO_PATH.replace(APP_CODE_VARIABLE, config.getAppCode()); String issuer = ServerHelp.getPortalPublicBaseUrl() + JWT_SSO_PATH.replace(APP_CODE_VARIABLE, config.getAppCode());
String subject = getSubject(config,(UserDetails) principal.getPrincipal()); String subject = getSubject(config,(UserDetails) principal.getPrincipal());
WebAuthenticationDetails details = (WebAuthenticationDetails) requestAuthenticationToken.getDetails();
IdTokenContext tokenContext = IdTokenContext.builder() IdTokenContext tokenContext = IdTokenContext.builder()
.issuer(issuer) .issuer(issuer)
.subject(subject) .subject(subject)
.audience(config.getAppCode()) .audience(config.getAppCode())
.sessionId(details.getSessionId())
.idTokenTimeToLive(config.getIdTokenTimeToLive()) .idTokenTimeToLive(config.getIdTokenTimeToLive())
.privateKey(config.getJwtPrivateKey()) .privateKey(config.getJwtPrivateKey())
.build(); .build();

View File

@ -0,0 +1,55 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.topiam.employee.protocol.jwt.authentication;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
/**
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/9/4 16:11
*/
public final class OidcLogoutAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
JwtLogoutAuthenticationToken logoutAuthenticationToken= (JwtLogoutAuthenticationToken) authentication;
SessionInformation sessionInformation=sessionRegistry.getSessionInformation(logoutAuthenticationToken.getSessionId());
if (sessionInformation.isExpired()){
}
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return JwtLogoutAuthenticationToken.class.isAssignableFrom(authentication);
}
private final SessionRegistry sessionRegistry;
public OidcLogoutAuthenticationProvider(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
}

View File

@ -17,5 +17,37 @@
*/ */
package cn.topiam.employee.protocol.jwt.authorization; package cn.topiam.employee.protocol.jwt.authorization;
import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationToken;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn
*/
public class InMemoryJwtAuthorizationService implements JwtAuthorizationService { public class InMemoryJwtAuthorizationService implements JwtAuthorizationService {
/**
* save
*
* @param token {@link JwtAuthenticationToken}
*/
@Override
public void save(JwtAuthenticationToken token) {
}
@Override
public void remove(JwtAuthenticationToken authorization) {
}
@Override
public JwtAuthenticationToken findById(String id) {
return null;
}
@Override
public JwtAuthenticationToken findByToken(String token) {
return null;
}
} }

View File

@ -17,9 +17,25 @@
*/ */
package cn.topiam.employee.protocol.jwt.authorization; package cn.topiam.employee.protocol.jwt.authorization;
import org.springframework.lang.Nullable;
import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationToken;
/** /**
*
* @author TopIAM * @author TopIAM
* Created by support@topiam.cn on 2023/7/8 00:23 * Created by support@topiam.cn on 2023/7/8 00:23
*/ */
public interface JwtAuthorizationService { public interface JwtAuthorizationService {
void save(JwtAuthenticationToken token);
void remove(JwtAuthenticationToken authorization);
@Nullable
JwtAuthenticationToken findById(String id);
@Nullable
JwtAuthenticationToken findByToken(String token);
} }

View File

@ -0,0 +1,184 @@
/*
* eiam-protocol-jwt - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.protocol.jwt.authorization;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.*;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.http.converter.json.SpringHandlerInstantiator;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationToken;
import cn.topiam.employee.protocol.jwt.jackson.JwtAuthorizationModule;
import cn.topiam.employee.support.jackjson.SupportJackson2Module;
import lombok.Setter;
import static cn.topiam.employee.protocol.jwt.constant.JwtProtocolConstants.JWT_PROTOCOL_CACHE_PREFIX;
/**
* redis
*
* @author TopIAM
*
* Created by support@topiam.cn / 2689170096@qq.com on 2023/9/1 12:51
*/
public class RedisJwtAuthorizationService implements JwtAuthorizationService {
private static final String CID_TO_AUTHORIZATIONS = "cid_to_authorizations:";
private static final String UID_TO_AUTHORIZATIONS = "uid_to_authorizations:";
private static final String ID_TO_AUTHORIZATION = "id_to_authorization:";
private static final String ID_TO_CORRELATIONS = "id_to_correlations:";
public RedisJwtAuthorizationService(RedisOperations<String, String> redisOperations,
AutowireCapableBeanFactory beanFactory) {
Assert.notNull(redisOperations, "redisOperations mut not be null");
this.redisOperations = redisOperations;
ClassLoader classLoader = this.getClass().getClassLoader();
objectMapper.registerModules(SupportJackson2Module.getModules(classLoader));
objectMapper.registerModule(new JwtAuthorizationModule());
objectMapper.setHandlerInstantiator(new SpringHandlerInstantiator(beanFactory));
}
/**
* save
*
* @param token {@link JwtAuthenticationToken}
*/
@Override
public void save(JwtAuthenticationToken token) {
String authorizationId = token.getId();
String uidToAuthorizationsKey = getIdTokenToAuthorization(authorizationId);
String idToAuthorizationKey = getIdToAuthorizationKey(authorizationId);
String idToCorrelationsKey = getIdToCorrelations(authorizationId);
String cidToAuthorizationsKey = getCidToAuthorizations(token.getConfig().getClientId());
//过期时间
Duration timeToLive = Duration.of(token.getConfig().getIdTokenTimeToLive(),
ChronoUnit.SECONDS);
Set<String> correlationValues = new HashSet<>();
//add client authorizations
correlationValues.add(cidToAuthorizationsKey);
redisOperations.opsForSet().add(cidToAuthorizationsKey, authorizationId);
redisOperations.expire(cidToAuthorizationsKey, timeToLive);
//save authorization
correlationValues.add(idToAuthorizationKey);
redisOperations.opsForValue().set(idToAuthorizationKey, write(token));
redisOperations.expire(idToAuthorizationKey, timeToLive);
//save id_token
correlationValues.add(uidToAuthorizationsKey);
redisOperations.opsForValue().set(uidToAuthorizationsKey,
token.getIdToken().getTokenValue());
redisOperations.expire(uidToAuthorizationsKey, timeToLive);
//save correlations
correlationValues.add(idToCorrelationsKey);
redisOperations.opsForSet().add(idToCorrelationsKey,
correlationValues.toArray(String[]::new));
redisOperations.expire(idToCorrelationsKey, timeToLive);
}
@Override
public void remove(JwtAuthenticationToken authorization) {
String authorizationId = authorization.getId();
String uidToAuthorizationsKey = getIdTokenToAuthorization(authorizationId);
String idToAuthorizationKey = getIdToAuthorizationKey(authorizationId);
String idToCorrelationsKey = getIdToCorrelations(authorizationId);
String cidToAuthorizationsKey = getCidToAuthorizations(
authorization.getConfig().getClientId());
redisOperations.delete(idToAuthorizationKey);
redisOperations.delete(idToCorrelationsKey);
redisOperations.delete(uidToAuthorizationsKey);
redisOperations.opsForSet().remove(cidToAuthorizationsKey, authorization.getId());
}
@Override
public JwtAuthenticationToken findById(String id) {
return Optional.ofNullable(redisOperations.opsForValue().get(getIdToAuthorizationKey(id)))
.map(this::parse).orElse(null);
}
@Override
public JwtAuthenticationToken findByToken(String token) {
return Optional
.ofNullable(redisOperations.opsForValue().get(getIdTokenToAuthorization(token)))
.map(this::parse).orElse(null);
}
private String getIdToCorrelations(String authorizationId) {
return prefix + ID_TO_CORRELATIONS + authorizationId;
}
private String write(Object data) {
try {
return this.objectMapper.writeValueAsString(data);
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private JwtAuthenticationToken parse(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<>() {
});
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
public String getCidToAuthorizations(String clientId) {
return prefix + CID_TO_AUTHORIZATIONS + clientId;
}
private String getIdToAuthorizationKey(String authorizationId) {
return prefix + ID_TO_AUTHORIZATION + authorizationId;
}
private String getIdTokenToAuthorization(String idToken) {
return prefix + UID_TO_AUTHORIZATIONS + generateKey(idToken);
}
protected static String generateKey(String rawKey) {
byte[] bytes = DIGEST.digest(rawKey.getBytes(StandardCharsets.UTF_8));
return String.format("%032x", new BigInteger(1, bytes));
}
private final RedisOperations<String, String> redisOperations;
@Setter
private String prefix = JWT_PROTOCOL_CACHE_PREFIX;
@Setter
private ObjectMapper objectMapper = new ObjectMapper();
private static final MessageDigest DIGEST;
static {
try {
DIGEST = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -27,6 +27,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import cn.topiam.employee.protocol.code.configurer.AbstractConfigurer; import cn.topiam.employee.protocol.code.configurer.AbstractConfigurer;
import cn.topiam.employee.protocol.code.util.ProtocolConfigUtils;
import cn.topiam.employee.protocol.jwt.authentication.JwtRequestAuthenticationTokenProvider; import cn.topiam.employee.protocol.jwt.authentication.JwtRequestAuthenticationTokenProvider;
import cn.topiam.employee.protocol.jwt.authorization.JwtAuthorizationService; import cn.topiam.employee.protocol.jwt.authorization.JwtAuthorizationService;
import cn.topiam.employee.protocol.jwt.endpoint.JwtAuthenticationEndpointFilter; import cn.topiam.employee.protocol.jwt.endpoint.JwtAuthenticationEndpointFilter;
@ -73,9 +74,11 @@ public class JwtAuthorizationEndpointConfigurer extends AbstractConfigurer {
.getSharedObject(AuthenticationManager.class); .getSharedObject(AuthenticationManager.class);
JwtAuthorizationService authorizationService = JwtAuthenticationUtils JwtAuthorizationService authorizationService = JwtAuthenticationUtils
.getAuthorizationService(httpSecurity); .getAuthorizationService(httpSecurity);
JwtAuthenticationEndpointFilter initSingleSignOnEndpointFilter = new JwtAuthenticationEndpointFilter( JwtAuthenticationEndpointFilter jwtAuthenticationEndpointFilter = new JwtAuthenticationEndpointFilter(
requestMatcher, authenticationManager, authorizationService); requestMatcher, authenticationManager, authorizationService);
httpSecurity.addFilterBefore(postProcess(initSingleSignOnEndpointFilter), jwtAuthenticationEndpointFilter.setAuthenticationDetailsSource(
ProtocolConfigUtils.getAuthenticationDetailsSource(httpSecurity));
httpSecurity.addFilterBefore(postProcess(jwtAuthenticationEndpointFilter),
AuthorizationFilter.class); AuthorizationFilter.class);
} }

View File

@ -17,8 +17,8 @@
*/ */
package cn.topiam.employee.protocol.jwt.constant; package cn.topiam.employee.protocol.jwt.constant;
import static cn.topiam.employee.common.constant.AuthorizeConstants.AUTHORIZE_PATH; import static cn.topiam.employee.protocol.code.constant.ProtocolConstants.PROTOCOL_CACHE_PREFIX;
import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE_VARIABLE; import static cn.topiam.employee.support.constant.EiamConstants.COLON;
/** /**
* *
@ -29,10 +29,9 @@ import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE_VARI
public class JwtProtocolConstants { public class JwtProtocolConstants {
/** /**
* JWT IDP SSO *
*/ */
public static final String IDP_JWT_SSO_INITIATOR = AUTHORIZE_PATH + "/jwt/" + APP_CODE_VARIABLE public static final String JWT_PROTOCOL_CACHE_PREFIX = PROTOCOL_CACHE_PREFIX + "jwt" + COLON;
+ "/initiator";
public static final String TARGET_URL = "target_url"; public static final String TARGET_URL = "target_url";
public static final String ID_TOKEN = "id_token"; public static final String ID_TOKEN = "id_token";
@ -43,4 +42,6 @@ public class JwtProtocolConstants {
public static final String JWT_ERROR_URI = "https://eiam.topiam.cn"; public static final String JWT_ERROR_URI = "https://eiam.topiam.cn";
public static final String S_ID = "sid";
} }

View File

@ -46,8 +46,6 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.GRANT_TYPE;
import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE; import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE;
import static cn.topiam.employee.support.util.HttpRequestUtils.getRequestHeaders; import static cn.topiam.employee.support.util.HttpRequestUtils.getRequestHeaders;
@ -85,7 +83,6 @@ public final class JwtAuthorizationServerContextFilter extends OncePerRequestFil
return; return;
} }
try { try {
request.getParameterValues(GRANT_TYPE);
//@formatter:off //@formatter:off
Map<String, String> variables = matcher.getVariables(); Map<String, String> variables = matcher.getVariables();
String appCode = variables.get(APP_CODE); String appCode = variables.get(APP_CODE);

View File

@ -24,15 +24,11 @@ import org.apache.commons.compress.utils.CharsetNames;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@ -44,6 +40,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
import cn.topiam.employee.application.jwt.model.JwtProtocolConfig; import cn.topiam.employee.application.jwt.model.JwtProtocolConfig;
import cn.topiam.employee.protocol.code.exception.TemplateNotExistException; import cn.topiam.employee.protocol.code.exception.TemplateNotExistException;
import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationFailureHandler;
import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationToken; import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationToken;
import cn.topiam.employee.protocol.jwt.authentication.JwtRequestAuthenticationToken; import cn.topiam.employee.protocol.jwt.authentication.JwtRequestAuthenticationToken;
import cn.topiam.employee.protocol.jwt.authorization.JwtAuthorizationService; import cn.topiam.employee.protocol.jwt.authorization.JwtAuthorizationService;
@ -51,7 +48,6 @@ import cn.topiam.employee.protocol.jwt.endpoint.authentication.JwtRequestAuthent
import cn.topiam.employee.protocol.jwt.exception.JwtAuthenticationException; import cn.topiam.employee.protocol.jwt.exception.JwtAuthenticationException;
import cn.topiam.employee.protocol.jwt.exception.JwtError; import cn.topiam.employee.protocol.jwt.exception.JwtError;
import cn.topiam.employee.protocol.jwt.exception.JwtErrorCodes; import cn.topiam.employee.protocol.jwt.exception.JwtErrorCodes;
import cn.topiam.employee.protocol.jwt.http.converter.JwtErrorHttpMessageConverter;
import cn.topiam.employee.protocol.jwt.token.IdToken; import cn.topiam.employee.protocol.jwt.token.IdToken;
import freemarker.cache.ClassTemplateLoader; import freemarker.cache.ClassTemplateLoader;
@ -62,6 +58,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import static cn.topiam.employee.protocol.jwt.constant.JwtProtocolConstants.*; import static cn.topiam.employee.protocol.jwt.constant.JwtProtocolConstants.*;
import static cn.topiam.employee.protocol.jwt.endpoint.JwtAuthenticationEndpointUtils.throwError;
/** /**
* @author TopIAM * @author TopIAM
@ -89,11 +86,6 @@ public final class JwtAuthenticationEndpointFilter extends OncePerRequestFilter
*/ */
private final JwtAuthorizationService authorizationService; private final JwtAuthorizationService authorizationService;
/**
*
*/
private final HttpMessageConverter<JwtError> errorHttpResponseConverter = new JwtErrorHttpMessageConverter();
/** /**
* *
*/ */
@ -107,7 +99,7 @@ public final class JwtAuthenticationEndpointFilter extends OncePerRequestFilter
/** /**
* *
*/ */
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse; private AuthenticationFailureHandler authenticationFailureHandler = new JwtAuthenticationFailureHandler();
/** /**
* *
@ -198,6 +190,13 @@ public final class JwtAuthenticationEndpointFilter extends OncePerRequestFilter
LogMessage.format("Authorization request failed: %s", ex.getError()), ex); LogMessage.format("Authorization request failed: %s", ex.getError()), ex);
} }
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
} catch (Exception ex) {
JwtError error = new JwtError(JwtErrorCodes.SERVER_ERROR,ex.getMessage(),JWT_ERROR_URI);
if (this.logger.isTraceEnabled()) {
this.logger.trace(error, ex);
}
this.authenticationFailureHandler.onAuthenticationFailure(request, response,
new JwtAuthenticationException(error));
} }
} }
@ -229,36 +228,15 @@ public final class JwtAuthenticationEndpointFilter extends OncePerRequestFilter
data.put(ID_TOKEN, idToken.getTokenValue()); data.put(ID_TOKEN, idToken.getTokenValue());
data.put(TARGET_URL, targetUri); data.put(TARGET_URL, targetUri);
template.process(data, response.getWriter()); template.process(data, response.getWriter());
//save
authorizationService.save(authenticationToken);
} catch (Exception e) { } catch (Exception e) {
JwtError error = new JwtError(JwtErrorCodes.SERVER_ERROR,e.getMessage(),JWT_ERROR_URI); JwtError error = new JwtError(JwtErrorCodes.SERVER_ERROR,e.getMessage(),JWT_ERROR_URI);
throw new JwtAuthenticationException(error); throwError(error);
} }
//@formatter:on //@formatter:on
} }
/**
*
*
* @param request {@link HttpServletRequest}
* @param response {@link HttpServletResponse}
* @param exception {@link AuthenticationException}
*/
private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException {
if (exception instanceof JwtAuthenticationException) {
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
JwtError error = ((JwtAuthenticationException) exception).getError();
if (error.getErrorCode().equals(JwtErrorCodes.SERVER_ERROR)) {
httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
}
if (error.getErrorCode().equals(JwtErrorCodes.INVALID_REQUEST)) {
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
}
errorHttpResponseConverter.write(error, null, httpResponse);
}
}
private void configFreemarkerTemplate() { private void configFreemarkerTemplate() {
try { try {
//模板存放路径 //模板存放路径

View File

@ -0,0 +1,16 @@
package cn.topiam.employee.protocol.jwt.endpoint;
import cn.topiam.employee.protocol.jwt.exception.JwtAuthenticationException;
import cn.topiam.employee.protocol.jwt.exception.JwtError;
/**
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/9/4 13:05
*/
public class JwtAuthenticationEndpointUtils {
public static void throwError(JwtError jwtError) {
throw new JwtAuthenticationException(jwtError);
}
}

View File

@ -0,0 +1,196 @@
/*
* eiam-protocol-jwt - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.protocol.jwt.endpoint;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationFailureHandler;
import cn.topiam.employee.protocol.jwt.authentication.JwtLogoutAuthenticationToken;
import cn.topiam.employee.protocol.jwt.endpoint.authentication.JwtLogoutAuthenticationConverter;
import cn.topiam.employee.protocol.jwt.exception.JwtAuthenticationException;
import cn.topiam.employee.protocol.jwt.exception.JwtError;
import cn.topiam.employee.protocol.jwt.exception.JwtErrorCodes;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import static cn.topiam.employee.protocol.jwt.constant.JwtProtocolConstants.JWT_ERROR_URI;
/**
*
* @author TopIAM
* Created by support@topiam.cn on 2023/9/4 20:14
*/
public final class JwtLogoutAuthenticationEndpointFilter extends OncePerRequestFilter {
/**
*
*/
private final RequestMatcher requestMatcher;
/**
*
*/
private AuthenticationFailureHandler authenticationFailureHandler = new JwtAuthenticationFailureHandler();
/**
* AuthenticationSuccessHandler
*/
private AuthenticationSuccessHandler authenticationSuccessHandler=this::sendAuthorizationResponse;
/**
* LogoutHandler
*/
private final LogoutHandler logoutHandler;
/**
*
*/
private AuthenticationConverter authenticationConverter;
/**
* AuthenticationDetailsSource
*/
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
/**
*
*/
private final AuthenticationManager authenticationManager;
public JwtLogoutAuthenticationEndpointFilter(RequestMatcher requestMatcher,
SessionRegistry sessionRegistry, AuthenticationManager authenticationManager) {
Assert.notNull(requestMatcher, "requestMatcher cannot be empty");
Assert.notNull(sessionRegistry, "sessionRegistry cannot be empty");
Assert.notNull(sessionRegistry, "authenticationManager cannot be empty");
this.authenticationManager = authenticationManager;
this.logoutHandler = new SecurityContextLogoutHandler();
this.requestMatcher = requestMatcher;
authenticationConverter=new JwtLogoutAuthenticationConverter();
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
this.authenticationFailureHandler = authenticationFailureHandler;
}
public void setAuthenticationFailureHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
Assert.notNull(authenticationFailureHandler, "authenticationSuccessHandler cannot be null");
this.authenticationSuccessHandler = authenticationSuccessHandler;
}
public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}
/**
* Sets the {@link AuthenticationDetailsSource} used for building an authentication details instance from {@link HttpServletRequest}.
*
* @param authenticationDetailsSource the {@link AuthenticationDetailsSource} used for building an authentication details instance from {@link HttpServletRequest}
*/
public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null");
this.authenticationDetailsSource = authenticationDetailsSource;
}
/**
* Same contract as for {@code doFilter}, but guaranteed to be
* just invoked once per request within a single request thread.
* See {@link #shouldNotFilterAsyncDispatch()} for details.
* <p>Provides HttpServletRequest and HttpServletResponse arguments instead of the
* default ServletRequest and ServletResponse ones.
*
* @param request {@link HttpServletRequest}
* @param response {@link HttpServletResponse}
* @param filterChain {@link FilterChain}
*/
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull FilterChain filterChain) throws ServletException,
IOException {
if (!requestMatcher.matches(request)) {
doFilter(request, response, filterChain);
return;
}
try {
Authentication authentication= authenticationConverter.convert(request);
if (authentication instanceof AbstractAuthenticationToken) {
((AbstractAuthenticationToken) authentication)
.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
Authentication authenticationResult= authenticationManager.authenticate(authentication);
authenticationSuccessHandler.onAuthenticationSuccess(request,response,authenticationResult);
} catch (JwtAuthenticationException ex) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("JWT logout request failed: %s", ex.getError()),
ex);
}
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
} catch (Exception ex) {
JwtError error = new JwtError(JwtErrorCodes.SERVER_ERROR,ex.getMessage(),JWT_ERROR_URI);
if (this.logger.isTraceEnabled()) {
this.logger.trace(error, ex);
}
this.authenticationFailureHandler.onAuthenticationFailure(request, response,
new JwtAuthenticationException(error));
}
}
/**
*
*
* @param request {@link HttpServletRequest}
* @param response {@link HttpServletResponse}
* @param authentication {@link Authentication}
*/
private void sendAuthorizationResponse(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
JwtLogoutAuthenticationToken jwtLogoutAuthentication= (JwtLogoutAuthenticationToken) authentication;
// Check for active user session
if (jwtLogoutAuthentication.isPrincipalAuthenticated() &&
StringUtils.hasText(jwtLogoutAuthentication.getSessionId())) {
// Perform logout
this.logoutHandler.logout(request, response,
(Authentication) jwtLogoutAuthentication.getPrincipal());
}
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2020-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.topiam.employee.protocol.jwt.endpoint.authentication;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.StringUtils;
import cn.topiam.employee.protocol.jwt.authentication.JwtLogoutAuthenticationToken;
import cn.topiam.employee.protocol.jwt.exception.JwtError;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import static cn.topiam.employee.protocol.jwt.constant.JwtProtocolConstants.S_ID;
import static cn.topiam.employee.protocol.jwt.endpoint.JwtAuthenticationEndpointUtils.throwError;
/**
*
* @author SanLi
* Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2023/9/4 16:01
*/
public final class JwtLogoutAuthenticationConverter implements AuthenticationConverter {
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
@Override
public Authentication convert(HttpServletRequest request) {
if (request.getParameterValues(S_ID).length != 1) {
throwError(new JwtError(OAuth2ErrorCodes.INVALID_REQUEST,
"JWT Logout Request Parameter: " + S_ID));
}
String sessionId = request.getParameter(S_ID);
if (!StringUtils.hasText(sessionId)) {
HttpSession session = request.getSession(false);
if (session != null) {
sessionId = session.getId();
}
}
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
if (principal == null) {
principal = ANONYMOUS_AUTHENTICATION;
}
return new JwtLogoutAuthenticationToken(principal,sessionId);
}
}

View File

@ -0,0 +1,33 @@
/*
* eiam-protocol-jwt - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.protocol.jwt.jackson;
import com.fasterxml.jackson.annotation.*;
/**
* JwtAuthenticationTokenMixin
*
* @author TopIAM
* Created by support@topiam.cn on 2023/6/30 21:07
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class JwtAuthenticationTokenMixin {
}

View File

@ -0,0 +1,46 @@
/*
* eiam-protocol-jwt - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.protocol.jwt.jackson;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.module.SimpleModule;
import cn.topiam.employee.protocol.jwt.authentication.JwtAuthenticationToken;
/**
* JwtAuthorizationModule
*
* @author TopIAM
* Created by support@topiam.cn on 2023/6/30 21:07
*/
public class JwtAuthorizationModule extends SimpleModule {
public JwtAuthorizationModule() {
super(JwtAuthorizationModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void setupModule(SetupContext context) {
SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
context.setMixInAnnotations(JwtAuthenticationToken.class,
JwtAuthenticationTokenMixin.class);
}
}

View File

@ -23,6 +23,7 @@ import java.util.Map;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.jackson.Jacksonized;
/** /**
* *
@ -31,6 +32,7 @@ import lombok.NonNull;
*/ */
@Data @Data
@Builder @Builder
@Jacksonized
public class IdToken { public class IdToken {
@NonNull @NonNull

View File

@ -38,6 +38,9 @@ public class IdTokenContext {
@NonNull @NonNull
private String audience; private String audience;
@NonNull
private String sessionId;
/** /**
* Token * Token
*/ */

View File

@ -27,6 +27,7 @@ import cn.topiam.employee.protocol.jwt.exception.IdTokenGenerateException;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import static cn.topiam.employee.protocol.jwt.constant.JwtProtocolConstants.S_ID;
/** /**
* *
@ -50,6 +51,7 @@ public class JwtIdTokenGenerator implements IdTokenGenerator {
.setAudience(context.getAudience()) .setAudience(context.getAudience())
.setExpiration(new Date(expiresAt.toEpochMilli())) .setExpiration(new Date(expiresAt.toEpochMilli()))
.signWith(rsaPrivateKey, SignatureAlgorithm.RS256) .signWith(rsaPrivateKey, SignatureAlgorithm.RS256)
.claim(S_ID,context.getSessionId())
.compact(); .compact();
return IdToken.builder().tokenValue(tokenValue) return IdToken.builder().tokenValue(tokenValue)
.issuedAt(issuedAt) .issuedAt(issuedAt)

View File

@ -50,7 +50,6 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.GRANT_TYPE;
import static org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT; import static org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT;
import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE; import static cn.topiam.employee.common.constant.ProtocolConstants.APP_CODE;
@ -91,7 +90,6 @@ public final class OidcAuthorizationServerContextFilter extends OncePerRequestFi
return; return;
} }
try { try {
request.getParameterValues(GRANT_TYPE);
//@formatter:off //@formatter:off
Map<String, String> variables = matcher.getVariables(); Map<String, String> variables = matcher.getVariables();
String appCode = variables.get(APP_CODE); String appCode = variables.get(APP_CODE);