| @@ -27,6 +27,16 @@ export default [ | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| path: '/authorize', | |||
| layout: false, | |||
| component: './Authorize/index', | |||
| }, | |||
| { | |||
| path: '/gitlink', | |||
| layout: true, | |||
| component: './GitLink/index', | |||
| }, | |||
| { | |||
| path: '/user', | |||
| layout: false, | |||
| @@ -7,7 +7,6 @@ import defaultSettings from '../config/defaultSettings'; | |||
| import '../public/fonts/font.css'; | |||
| import { getAccessToken } from './access'; | |||
| import './dayjsConfig'; | |||
| import { PageEnum } from './enums/pagesEnums'; | |||
| import './global.less'; | |||
| import { removeAllPageCacheState } from './hooks/pageCacheState'; | |||
| import { | |||
| @@ -23,6 +22,7 @@ export { requestConfig as request } from './requestConfig'; | |||
| import { type GlobalInitialState } from '@/types'; | |||
| import { menuItemRender } from '@/utils/menuRender'; | |||
| import ErrorBoundary from './components/ErrorBoundary'; | |||
| import { needAuth } from './utils'; | |||
| import { gotoLoginPage } from './utils/ui'; | |||
| /** | |||
| @@ -40,14 +40,17 @@ export async function getInitialState(): Promise<GlobalInitialState> { | |||
| roleNames: response.user.roles, | |||
| } as API.CurrentUser; | |||
| } catch (error) { | |||
| console.error(error); | |||
| console.error('1111', error); | |||
| gotoLoginPage(); | |||
| } | |||
| return undefined; | |||
| }; | |||
| // 如果不是登录页面,执行 | |||
| const { location } = history; | |||
| if (location.pathname !== PageEnum.LOGIN) { | |||
| console.log('getInitialState', needAuth(location.pathname)); | |||
| if (needAuth(location.pathname)) { | |||
| const currentUser = await fetchUserInfo(); | |||
| return { | |||
| fetchUserInfo, | |||
| @@ -94,7 +97,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| onPageChange: () => { | |||
| const { location } = history; | |||
| // 如果没有登录,重定向到 login | |||
| if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) { | |||
| if (!initialState?.currentUser && needAuth(location.pathname)) { | |||
| gotoLoginPage(); | |||
| } | |||
| }, | |||
| @@ -159,8 +162,8 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { | |||
| const { location } = e; | |||
| const menus = getRemoteMenu(); | |||
| // console.log('onRouteChange', e); | |||
| if (menus === null && location.pathname !== PageEnum.LOGIN) { | |||
| console.log('onRouteChange', menus); | |||
| if (menus === null && needAuth(location.pathname)) { | |||
| history.go(0); | |||
| } | |||
| }; | |||
| @@ -170,12 +173,12 @@ export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { | |||
| }; | |||
| export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { | |||
| //console.log('patchClientRoutes', e); | |||
| console.log('patchClientRoutes', e); | |||
| patchRouteWithRemoteMenus(e.routes); | |||
| }; | |||
| export function render(oldRender: () => void) { | |||
| // console.log('render'); | |||
| console.log('render'); | |||
| const token = getAccessToken(); | |||
| if (!token || token?.length === 0) { | |||
| oldRender(); | |||
| @@ -12,6 +12,7 @@ export enum IframePageType { | |||
| DatasetAnnotation = 'DatasetAnnotation', // 数据标注 | |||
| AppDevelopment = 'AppDevelopment', // 应用开发 | |||
| DevEnv = 'DevEnv', // 开发环境 | |||
| GitLink = 'GitLink', | |||
| } | |||
| const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||
| @@ -26,6 +27,8 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||
| code: 200, | |||
| data: SessionStorage.getItem(SessionStorage.editorUrlKey) || '', | |||
| }); | |||
| case IframePageType.GitLink: | |||
| return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' }); | |||
| } | |||
| }; | |||
| @@ -1,6 +1,8 @@ | |||
| import { clearSessionToken } from '@/access'; | |||
| import { setRemoteMenu } from '@/services/session'; | |||
| import { logout } from '@/services/system/auth'; | |||
| import { ClientInfo } from '@/types'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import { gotoLoginPage } from '@/utils/ui'; | |||
| import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; | |||
| import { setAlpha } from '@ant-design/pro-components'; | |||
| @@ -64,6 +66,11 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => { | |||
| clearSessionToken(); | |||
| setRemoteMenu(null); | |||
| gotoLoginPage(); | |||
| const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true); | |||
| if (clientInfo) { | |||
| const { logoutUri } = clientInfo; | |||
| location.replace(logoutUri); | |||
| } | |||
| }; | |||
| const actionClassName = useEmotionCss(({ token }) => { | |||
| return { | |||
| @@ -0,0 +1,50 @@ | |||
| import { setSessionToken } from '@/access'; | |||
| import { loginByOauth2Req } from '@/services/auth'; | |||
| import { to } from '@/utils/promise'; | |||
| import { history, useModel, useSearchParams } from '@umijs/max'; | |||
| import { message } from 'antd'; | |||
| import { useEffect } from 'react'; | |||
| import { flushSync } from 'react-dom'; | |||
| import styles from './index.less'; | |||
| function Authorize() { | |||
| const { initialState, setInitialState } = useModel('@@initialState'); | |||
| const [searchParams] = useSearchParams(); | |||
| const code = searchParams.get('code'); | |||
| const redirect = searchParams.get('redirect'); | |||
| useEffect(() => { | |||
| loginByOauth2(); | |||
| }, []); | |||
| // 登录 | |||
| const loginByOauth2 = async () => { | |||
| const params = { | |||
| code, | |||
| }; | |||
| const [res] = await to(loginByOauth2Req(params)); | |||
| debugger; | |||
| if (res && res.data) { | |||
| const { access_token, expires_in } = res.data; | |||
| setSessionToken(access_token, access_token, expires_in); | |||
| message.success('登录成功!'); | |||
| await fetchUserInfo(); | |||
| history.push(redirect || '/'); | |||
| } | |||
| }; | |||
| const fetchUserInfo = async () => { | |||
| const userInfo = await initialState?.fetchUserInfo?.(); | |||
| if (userInfo) { | |||
| flushSync(() => { | |||
| setInitialState((s) => ({ | |||
| ...s, | |||
| currentUser: userInfo, | |||
| })); | |||
| }); | |||
| } | |||
| }; | |||
| return <div className={styles.container}></div>; | |||
| } | |||
| export default Authorize; | |||
| @@ -0,0 +1,7 @@ | |||
| import IframePage, { IframePageType } from '@/components/IFramePage'; | |||
| function GitLink() { | |||
| return <IframePage type={IframePageType.GitLink}></IframePage>; | |||
| } | |||
| export default GitLink; | |||
| @@ -1,11 +1,12 @@ | |||
| import { clearSessionToken, setSessionToken } from '@/access'; | |||
| import { getClientInfoReq } from '@/services/auth'; | |||
| import { getCaptchaImg, login } from '@/services/system/auth'; | |||
| import { parseJsonText } from '@/utils'; | |||
| import { safeInvoke } from '@/utils/functional'; | |||
| import LocalStorage from '@/utils/localStorage'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import { gotoOAuth2 } from '@/utils/ui'; | |||
| import { history, useModel } from '@umijs/max'; | |||
| import { Button, Checkbox, Flex, Form, Image, Input, message, type InputRef } from 'antd'; | |||
| import { Form, message, type InputRef } from 'antd'; | |||
| import CryptoJS from 'crypto-js'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { flushSync } from 'react-dom'; | |||
| @@ -31,25 +32,35 @@ const Login = () => { | |||
| const captchaInputRef = useRef<InputRef>(null); | |||
| useEffect(() => { | |||
| getCaptchaCode(); | |||
| const autoLogin = LocalStorage.getItem(LocalStorage.rememberPasswordKey) ?? 'false'; | |||
| if (autoLogin === 'true') { | |||
| const userStorage = LocalStorage.getItem(LocalStorage.loginUserKey); | |||
| const userJson = safeInvoke((text: string) => | |||
| CryptoJS.AES.decrypt(text, AESKEY).toString(CryptoJS.enc.Utf8), | |||
| )(userStorage); | |||
| const user = safeInvoke(parseJsonText)(userJson); | |||
| if (user && typeof user === 'object' && user.version === VERSION) { | |||
| const { username, password } = user; | |||
| form.setFieldsValue({ username: username, password: password, autoLogin: true }); | |||
| } else { | |||
| form.setFieldsValue({ username: '', password: '', autoLogin: true }); | |||
| LocalStorage.removeItem(LocalStorage.loginUserKey); | |||
| } | |||
| } else { | |||
| form.setFieldsValue({ username: '', password: '', autoLogin: false }); | |||
| } | |||
| // getCaptchaCode(); | |||
| // const autoLogin = LocalStorage.getItem(LocalStorage.rememberPasswordKey) ?? 'false'; | |||
| // if (autoLogin === 'true') { | |||
| // const userStorage = LocalStorage.getItem(LocalStorage.loginUserKey); | |||
| // const userJson = safeInvoke((text: string) => | |||
| // CryptoJS.AES.decrypt(text, AESKEY).toString(CryptoJS.enc.Utf8), | |||
| // )(userStorage); | |||
| // const user = safeInvoke(parseJsonText)(userJson); | |||
| // if (user && typeof user === 'object' && user.version === VERSION) { | |||
| // const { username, password } = user; | |||
| // form.setFieldsValue({ username: username, password: password, autoLogin: true }); | |||
| // } else { | |||
| // form.setFieldsValue({ username: '', password: '', autoLogin: true }); | |||
| // LocalStorage.removeItem(LocalStorage.loginUserKey); | |||
| // } | |||
| // } else { | |||
| // form.setFieldsValue({ username: '', password: '', autoLogin: false }); | |||
| // } | |||
| getClientInfo(); | |||
| }, []); | |||
| const getClientInfo = async () => { | |||
| const [res] = await to(getClientInfoReq()); | |||
| if (res && res.data) { | |||
| const clientInfo = res.data; | |||
| SessionStorage.setItem(SessionStorage.clientInfoKey, clientInfo, true); | |||
| gotoOAuth2(); | |||
| } | |||
| }; | |||
| const getCaptchaCode = async () => { | |||
| const [res] = await to(getCaptchaImg()); | |||
| if (res) { | |||
| @@ -71,6 +82,12 @@ const Login = () => { | |||
| } | |||
| }; | |||
| const handleSubmit2 = async (values: API.LoginParams) => { | |||
| const url = | |||
| 'http://172.20.32.106:8080/oauth/authorize?client_id=ci4s&response_type=code&grant_type=authorization_code'; | |||
| window.location.href = url; | |||
| }; | |||
| // 登录 | |||
| const handleSubmit = async (values: API.LoginParams) => { | |||
| const [res, error] = await to(login({ ...values, uuid })); | |||
| @@ -109,113 +126,115 @@ const Login = () => { | |||
| } | |||
| }; | |||
| return ( | |||
| <div className={styles['user-login']}> | |||
| <div className={styles['user-login__left']}> | |||
| <div className={styles['user-login__left__top']}> | |||
| <img | |||
| src={require('@/assets/img/logo.png')} | |||
| style={{ width: '32px', marginRight: '12px' }} | |||
| draggable={false} | |||
| alt="" | |||
| /> | |||
| 智能材料科研平台 | |||
| </div> | |||
| <div className={styles['user-login__left__title']}> | |||
| <span>智能材料科研平台</span> | |||
| <img | |||
| src={require('@/assets/img/login-ai-logo.png')} | |||
| className={styles['user-login__left__title__img']} | |||
| draggable={false} | |||
| alt="" | |||
| /> | |||
| </div> | |||
| <div className={styles['user-login__left__message']}> | |||
| <span>大语言模型运维 统一管理平台</span> | |||
| </div> | |||
| <img | |||
| className={styles['user-login__left__bottom-img']} | |||
| src={require('@/assets/img/login-left-image.png')} | |||
| draggable={false} | |||
| alt="" | |||
| /> | |||
| </div> | |||
| <div className={styles['user-login__right']}> | |||
| <div> | |||
| <div className={styles['user-login__right__title']}> | |||
| <span style={{ color: '#111111' }}>欢迎登录</span> | |||
| <span>智能材料科研平台</span> | |||
| </div> | |||
| <div className={styles['user-login__right__content']}> | |||
| <div className={styles['user-login__right__content__title']}>账号登录</div> | |||
| <div className={styles['user-login__right__content__form']}> | |||
| <Form | |||
| labelCol={{ span: 0 }} | |||
| wrapperCol={{ span: 24 }} | |||
| initialValues={{ autoLogin: true }} | |||
| onFinish={handleSubmit} | |||
| autoComplete="off" | |||
| form={form} | |||
| > | |||
| <Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}> | |||
| <Input | |||
| placeholder="请输入用户名" | |||
| prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />} | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}> | |||
| <Input.Password | |||
| placeholder="请输入密码" | |||
| prefix={<LoginInputPrefix icon={require('@/assets/img/login-password.png')} />} | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Flex align="start" style={{ height: '98px' }}> | |||
| <div style={{ flex: 1 }}> | |||
| <Form.Item name="code" rules={[{ required: true, message: '请输入验证码' }]}> | |||
| <Input | |||
| placeholder="请输入验证码" | |||
| prefix={ | |||
| <LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} /> | |||
| } | |||
| ref={captchaInputRef} | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </div> | |||
| <Image | |||
| className={styles['user-login__right__content__form__captcha']} | |||
| src={captchaCode} | |||
| alt="验证码" | |||
| preview={false} | |||
| onClick={() => getCaptchaCode()} | |||
| /> | |||
| </Flex> | |||
| <Form.Item | |||
| name="autoLogin" | |||
| valuePropName="checked" | |||
| labelCol={{ span: 0 }} | |||
| wrapperCol={{ span: 16 }} | |||
| > | |||
| <Checkbox>记住密码</Checkbox> | |||
| </Form.Item> | |||
| <Form.Item labelCol={{ span: 0 }} wrapperCol={{ span: 24 }}> | |||
| <Button type="primary" htmlType="submit"> | |||
| 登录 | |||
| </Button> | |||
| </Form.Item> | |||
| </Form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| return <div className={styles['user-login']}></div>; | |||
| // return ( | |||
| // <div className={styles['user-login']}> | |||
| // <div className={styles['user-login__left']}> | |||
| // <div className={styles['user-login__left__top']}> | |||
| // <img | |||
| // src={require('@/assets/img/logo.png')} | |||
| // style={{ width: '32px', marginRight: '12px' }} | |||
| // draggable={false} | |||
| // alt="" | |||
| // /> | |||
| // 智能材料科研平台 | |||
| // </div> | |||
| // <div className={styles['user-login__left__title']}> | |||
| // <span>智能材料科研平台</span> | |||
| // <img | |||
| // src={require('@/assets/img/login-ai-logo.png')} | |||
| // className={styles['user-login__left__title__img']} | |||
| // draggable={false} | |||
| // alt="" | |||
| // /> | |||
| // </div> | |||
| // <div className={styles['user-login__left__message']}> | |||
| // <span>大语言模型运维 统一管理平台</span> | |||
| // </div> | |||
| // <img | |||
| // className={styles['user-login__left__bottom-img']} | |||
| // src={require('@/assets/img/login-left-image.png')} | |||
| // draggable={false} | |||
| // alt="" | |||
| // /> | |||
| // </div> | |||
| // <div className={styles['user-login__right']}> | |||
| // <div> | |||
| // <div className={styles['user-login__right__title']}> | |||
| // <span style={{ color: '#111111' }}>欢迎登录</span> | |||
| // <span>智能材料科研平台</span> | |||
| // </div> | |||
| // <div className={styles['user-login__right__content']}> | |||
| // <div className={styles['user-login__right__content__title']}>账号登录</div> | |||
| // <div className={styles['user-login__right__content__form']}> | |||
| // <Form | |||
| // labelCol={{ span: 0 }} | |||
| // wrapperCol={{ span: 24 }} | |||
| // initialValues={{ autoLogin: true }} | |||
| // onFinish={handleSubmit2} | |||
| // autoComplete="off" | |||
| // form={form} | |||
| // > | |||
| // <Form.Item name="username" rules={[{ required: false, message: '请输入用户名' }]}> | |||
| // <Input | |||
| // placeholder="请输入用户名" | |||
| // prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />} | |||
| // allowClear | |||
| // /> | |||
| // </Form.Item> | |||
| // <Form.Item name="password" rules={[{ required: false, message: '请输入密码' }]}> | |||
| // <Input.Password | |||
| // placeholder="请输入密码" | |||
| // prefix={<LoginInputPrefix icon={require('@/assets/img/login-password.png')} />} | |||
| // allowClear | |||
| // /> | |||
| // </Form.Item> | |||
| // <Flex align="start" style={{ height: '98px' }}> | |||
| // <div style={{ flex: 1 }}> | |||
| // <Form.Item name="code" rules={[{ required: false, message: '请输入验证码' }]}> | |||
| // <Input | |||
| // placeholder="请输入验证码" | |||
| // prefix={ | |||
| // <LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} /> | |||
| // } | |||
| // ref={captchaInputRef} | |||
| // allowClear | |||
| // /> | |||
| // </Form.Item> | |||
| // </div> | |||
| // <Image | |||
| // className={styles['user-login__right__content__form__captcha']} | |||
| // src={captchaCode} | |||
| // alt="验证码" | |||
| // preview={false} | |||
| // onClick={() => getCaptchaCode()} | |||
| // /> | |||
| // </Flex> | |||
| // <Form.Item | |||
| // name="autoLogin" | |||
| // valuePropName="checked" | |||
| // labelCol={{ span: 0 }} | |||
| // wrapperCol={{ span: 16 }} | |||
| // > | |||
| // <Checkbox>记住密码</Checkbox> | |||
| // </Form.Item> | |||
| // <Form.Item labelCol={{ span: 0 }} wrapperCol={{ span: 24 }}> | |||
| // <Button type="primary" htmlType="submit"> | |||
| // 登录 | |||
| // </Button> | |||
| // </Form.Item> | |||
| // </Form> | |||
| // </div> | |||
| // </div> | |||
| // </div> | |||
| // </div> | |||
| // </div> | |||
| // ); | |||
| }; | |||
| export default Login; | |||
| @@ -0,0 +1,16 @@ | |||
| import { request } from '@umijs/max'; | |||
| // 单点登录 | |||
| export function loginByOauth2Req(data) { | |||
| return request(`/api/auth/loginByOauth2`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 登录获取客户端信息 | |||
| export function getClientInfoReq() { | |||
| return request(`/api/auth/oauth2ClientInfo`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| @@ -7,6 +7,17 @@ | |||
| import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | |||
| import type { Settings as LayoutSettings } from '@ant-design/pro-components'; | |||
| export type ClientInfo = { | |||
| accessTokenUri: string; | |||
| checkTokenUri: string; | |||
| clientId: string; | |||
| clientSecret: string; | |||
| loginPage: string; | |||
| logoutUri: string; | |||
| redirectUri: string; | |||
| userAuthorizationUri: string; | |||
| }; | |||
| // 全局初始状态类型 | |||
| export type GlobalInitialState = { | |||
| settings?: Partial<LayoutSettings>; | |||
| @@ -14,6 +25,7 @@ export type GlobalInitialState = { | |||
| fetchUserInfo?: () => Promise<API.CurrentUser | undefined>; | |||
| loading?: boolean; | |||
| collapsed?: boolean; | |||
| clientInfo?: ClientInfo; | |||
| }; | |||
| // 流水线全局参数 | |||
| @@ -9,6 +9,8 @@ export default class SessionStorage { | |||
| static readonly serviceVersionInfoKey = 'service-version-info'; | |||
| // 编辑器 url | |||
| static readonly editorUrlKey = 'editor-url'; | |||
| // 客户端信息 | |||
| static readonly clientInfoKey = 'client-info'; | |||
| static getItem(key: string, isObject: boolean = false) { | |||
| const jsonStr = sessionStorage.getItem(key); | |||
| @@ -6,9 +6,11 @@ | |||
| import { PageEnum } from '@/enums/pagesEnums'; | |||
| import { removeAllPageCacheState } from '@/hooks/pageCacheState'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { type ClientInfo } from '@/types'; | |||
| import { history } from '@umijs/max'; | |||
| import { Modal, message, type ModalFuncProps, type UploadFile } from 'antd'; | |||
| import { closeAllModals } from './modal'; | |||
| import SessionStorage from './sessionStorage'; | |||
| type ModalConfirmProps = ModalFuncProps & { | |||
| isDelete?: boolean; | |||
| @@ -75,7 +77,7 @@ export const gotoLoginPage = (toHome: boolean = true) => { | |||
| const { pathname, search } = location; | |||
| const urlParams = new URLSearchParams(); | |||
| urlParams.append('redirect', pathname + search); | |||
| const newSearch = toHome && pathname !== '/' ? '' : urlParams.toString(); | |||
| const newSearch = toHome || pathname === '/' ? '' : urlParams.toString(); | |||
| // console.log('pathname', pathname); | |||
| // console.log('search', search); | |||
| if (pathname !== PageEnum.LOGIN) { | |||
| @@ -88,6 +90,15 @@ export const gotoLoginPage = (toHome: boolean = true) => { | |||
| } | |||
| }; | |||
| export const gotoOAuth2 = () => { | |||
| const clientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true) as ClientInfo; | |||
| if (clientInfo) { | |||
| const { clientId, userAuthorizationUri } = clientInfo; | |||
| const url = `${userAuthorizationUri}?client_id=${clientId}&response_type=code&grant_type=authorization_code`; | |||
| location.replace(url); | |||
| } | |||
| }; | |||
| /** | |||
| * 验证文件上传 | |||
| * | |||
| @@ -0,0 +1,25 @@ | |||
| package com.ruoyi.auth.config; | |||
| import lombok.Getter; | |||
| import lombok.Setter; | |||
| import org.springframework.boot.context.properties.ConfigurationProperties; | |||
| import org.springframework.cloud.context.config.annotation.RefreshScope; | |||
| import org.springframework.context.annotation.Configuration; | |||
| @Configuration | |||
| @RefreshScope | |||
| @ConfigurationProperties(prefix = "oauth2") | |||
| @Getter | |||
| @Setter | |||
| public class Oauth2ClientProperties { | |||
| private String clientId; | |||
| private String clientSecret; | |||
| private String scope; | |||
| private String userAuthorizationUri; | |||
| private String accessTokenUri; | |||
| private String redirectUri; | |||
| private String logoutUri; | |||
| private String checkTokenUri; | |||
| private String loginPage; | |||
| } | |||
| @@ -2,12 +2,19 @@ package com.ruoyi.auth.controller; | |||
| import javax.servlet.http.HttpServletRequest; | |||
| import com.alibaba.fastjson2.JSON; | |||
| import com.ruoyi.auth.config.Oauth2ClientProperties; | |||
| import com.ruoyi.auth.form.AccessTokenVo; | |||
| import com.ruoyi.auth.form.LoginKeyBody; | |||
| import com.ruoyi.common.core.exception.ServiceException; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.web.bind.annotation.DeleteMapping; | |||
| import org.springframework.web.bind.annotation.PostMapping; | |||
| import org.springframework.web.bind.annotation.RequestBody; | |||
| import org.springframework.web.bind.annotation.RestController; | |||
| import org.springframework.http.HttpEntity; | |||
| import org.springframework.http.HttpHeaders; | |||
| import org.springframework.http.MediaType; | |||
| import org.springframework.http.ResponseEntity; | |||
| import org.springframework.util.LinkedMultiValueMap; | |||
| import org.springframework.util.MultiValueMap; | |||
| import org.springframework.web.bind.annotation.*; | |||
| import com.ruoyi.auth.form.LoginBody; | |||
| import com.ruoyi.auth.form.RegisterBody; | |||
| import com.ruoyi.auth.service.SysLoginService; | |||
| @@ -18,6 +25,9 @@ import com.ruoyi.common.security.auth.AuthUtil; | |||
| import com.ruoyi.common.security.service.TokenService; | |||
| import com.ruoyi.common.security.utils.SecurityUtils; | |||
| import com.ruoyi.system.api.model.LoginUser; | |||
| import org.springframework.web.client.RestTemplate; | |||
| import java.util.Map; | |||
| /** | |||
| * token 控制 | |||
| @@ -33,6 +43,9 @@ public class TokenController | |||
| @Autowired | |||
| private SysLoginService sysLoginService; | |||
| @Autowired | |||
| private Oauth2ClientProperties oauth2ClientProperties; | |||
| @PostMapping("login") | |||
| public R<?> login(@RequestBody LoginBody form) | |||
| { | |||
| @@ -51,6 +64,64 @@ public class TokenController | |||
| return R.ok(tokenService.createToken(userInfo)); | |||
| } | |||
| @PostMapping("loginByOauth2") | |||
| public R<?> loginByOauth2(@RequestBody Map<String,String> params) | |||
| /** | |||
| * { | |||
| * "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZGVtby1hcHAiXSwiZXhwIjoxNzI5MTU0MTExLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp0aSI6IjQzNGVkNmI0LWIzN2MtNDliMS04NTczLWZhNmU4YTg5YTUxYSIsImNsaWVudF9pZCI6IkFCQyIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSJdfQ.U591q4fUaUBtBt5Ex-S2daM7DIl9-Ov0MsveymNfHxI", | |||
| * "token_type": "bearer", | |||
| * "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZGVtby1hcHAiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXRpIjoiNDM0ZWQ2YjQtYjM3Yy00OWIxLTg1NzMtZmE2ZThhODlhNTFhIiwiZXhwIjoxNzMxNjQ5OTY4LCJqdGkiOiJiODFhNmJkMC02ZDQzLTQ4M2QtOThmMy1hZWIxNDcyZDUwNzciLCJjbGllbnRfaWQiOiJBQkMifQ.0uMYVqW1G7j0chwxIdWGwpjDr12ogZPcD1iQfPsAs5k", | |||
| * "expires_in": 7198, | |||
| * "scope": "read write", | |||
| * "account_info": { | |||
| * "id": 1, | |||
| * "clientId": "ABC", | |||
| * "username": "admin", | |||
| * "mobile": "1232378743", | |||
| * "email": "abc@123.com" | |||
| * } | |||
| * } | |||
| */ | |||
| { | |||
| if (params.containsKey("code") && StringUtils.isNotBlank(params.get("code"))){ | |||
| RestTemplate restTemplate = new RestTemplate(); | |||
| String url = oauth2ClientProperties.getAccessTokenUri(); | |||
| HttpHeaders headers = new HttpHeaders(); | |||
| headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | |||
| MultiValueMap<String, String> map= new LinkedMultiValueMap<>(); | |||
| map.add("grant_type", "authorization_code"); | |||
| map.add("code", params.get("code")); | |||
| map.add("redirect_uri", oauth2ClientProperties.getRedirectUri()); | |||
| map.add("client_id", oauth2ClientProperties.getClientId()); | |||
| map.add("client_secret", oauth2ClientProperties.getClientSecret()); | |||
| HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers); | |||
| ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class); | |||
| String body = response.getBody(); | |||
| AccessTokenVo accessTokenVo = JSON.parseObject(body, AccessTokenVo.class); | |||
| String accessToken = accessTokenVo.getAccess_token(); | |||
| AccessTokenVo.Account_info accountInfo = accessTokenVo.getAccount_info(); | |||
| // 用户登录 | |||
| LoginUser userInfo = sysLoginService.login(accountInfo); | |||
| // 获取登录token | |||
| Map<String, Object> token = tokenService.createToken(userInfo); | |||
| token.put("checkTokenUri",oauth2ClientProperties.getCheckTokenUri()); | |||
| token.put("logoutUri",oauth2ClientProperties.getLogoutUri()); | |||
| token.put("oauth2AccessToken",accessToken); | |||
| return R.ok(token); | |||
| } | |||
| throw new ServiceException("用户信息获取失败"); | |||
| } | |||
| @GetMapping("oauth2ClientInfo") | |||
| public R<?> getClientInfo() { | |||
| return R.ok(JSON.toJSON(oauth2ClientProperties)); | |||
| } | |||
| @DeleteMapping("logout") | |||
| public R<?> logout(HttpServletRequest request) | |||
| { | |||
| @@ -0,0 +1,119 @@ | |||
| package com.ruoyi.auth.form; | |||
| import java.io.Serializable; | |||
| import java.lang.Integer; | |||
| import java.lang.String; | |||
| public class AccessTokenVo implements Serializable { | |||
| private String access_token; | |||
| private String refresh_token; | |||
| private String scope; | |||
| private Account_info account_info; | |||
| private String token_type; | |||
| private Integer expires_in; | |||
| public String getAccess_token() { | |||
| return this.access_token; | |||
| } | |||
| public void setAccess_token(String access_token) { | |||
| this.access_token = access_token; | |||
| } | |||
| public String getRefresh_token() { | |||
| return this.refresh_token; | |||
| } | |||
| public void setRefresh_token(String refresh_token) { | |||
| this.refresh_token = refresh_token; | |||
| } | |||
| public String getScope() { | |||
| return this.scope; | |||
| } | |||
| public void setScope(String scope) { | |||
| this.scope = scope; | |||
| } | |||
| public Account_info getAccount_info() { | |||
| return this.account_info; | |||
| } | |||
| public void setAccount_info(Account_info account_info) { | |||
| this.account_info = account_info; | |||
| } | |||
| public String getToken_type() { | |||
| return this.token_type; | |||
| } | |||
| public void setToken_type(String token_type) { | |||
| this.token_type = token_type; | |||
| } | |||
| public Integer getExpires_in() { | |||
| return this.expires_in; | |||
| } | |||
| public void setExpires_in(Integer expires_in) { | |||
| this.expires_in = expires_in; | |||
| } | |||
| public static class Account_info implements Serializable { | |||
| private String clientId; | |||
| private String mobile; | |||
| private Integer id; | |||
| private String email; | |||
| private String username; | |||
| public String getClientId() { | |||
| return this.clientId; | |||
| } | |||
| public void setClientId(String clientId) { | |||
| this.clientId = clientId; | |||
| } | |||
| public String getMobile() { | |||
| return this.mobile; | |||
| } | |||
| public void setMobile(String mobile) { | |||
| this.mobile = mobile; | |||
| } | |||
| public Integer getId() { | |||
| return this.id; | |||
| } | |||
| public void setId(Integer id) { | |||
| this.id = id; | |||
| } | |||
| public String getEmail() { | |||
| return this.email; | |||
| } | |||
| public void setEmail(String email) { | |||
| this.email = email; | |||
| } | |||
| public String getUsername() { | |||
| return this.username; | |||
| } | |||
| public void setUsername(String username) { | |||
| this.username = username; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package com.ruoyi.auth.service; | |||
| import com.ruoyi.auth.form.AccessTokenVo; | |||
| import org.springframework.beans.factory.annotation.Autowired; | |||
| import org.springframework.stereotype.Component; | |||
| import com.ruoyi.common.core.constant.CacheConstants; | |||
| @@ -193,4 +194,62 @@ public class SysLoginService | |||
| } | |||
| return userInfo; | |||
| } | |||
| public LoginUser login(AccessTokenVo.Account_info accountInfo) { | |||
| String username = accountInfo.getUsername(); | |||
| // 用户名或密码为空 错误 | |||
| if (StringUtils.isAnyBlank(username)) | |||
| { | |||
| recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户/密码必须填写"); | |||
| throw new ServiceException("用户/密码必须填写"); | |||
| } | |||
| // 用户名不在指定范围内 错误 | |||
| if (username.length() < UserConstants.USERNAME_MIN_LENGTH | |||
| || username.length() > UserConstants.USERNAME_MAX_LENGTH) | |||
| { | |||
| recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围"); | |||
| throw new ServiceException("用户名不在指定范围"); | |||
| } | |||
| // IP黑名单校验 | |||
| String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST)); | |||
| if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) | |||
| { | |||
| recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾,访问IP已被列入系统黑名单"); | |||
| throw new ServiceException("很遗憾,访问IP已被列入系统黑名单"); | |||
| } | |||
| // 查询用户信息 | |||
| R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER); | |||
| if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) | |||
| { | |||
| recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在"); | |||
| throw new ServiceException("登录用户:" + username + " 不存在"); | |||
| // register(username, "123456"); | |||
| // userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER); | |||
| } | |||
| if (R.FAIL == userResult.getCode()) | |||
| { | |||
| throw new ServiceException(userResult.getMsg()); | |||
| } | |||
| LoginUser userInfo = userResult.getData(); | |||
| SysUser user = userResult.getData().getSysUser(); | |||
| if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) | |||
| { | |||
| recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除"); | |||
| throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); | |||
| } | |||
| if (UserStatus.DISABLE.getCode().equals(user.getStatus())) | |||
| { | |||
| recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员"); | |||
| throw new ServiceException("对不起,您的账号:" + username + " 已停用"); | |||
| } | |||
| recordLogService.recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功"); | |||
| return userInfo; | |||
| } | |||
| } | |||
| @@ -3,7 +3,7 @@ server: | |||
| port: 8082 | |||
| # Spring | |||
| spring: | |||
| spring: | |||
| application: | |||
| # 应用名称 | |||
| name: ruoyi-gateway | |||