generated from gc/template-java-maven
feat: 로그인 프로세스 제거 + 사용자 역할 토글 버튼
- JWT 인증 및 LoginPage 제거, SecurityConfig permitAll 전환 - @PreAuthorize 어노테이션 전체 제거 (@EnableMethodSecurity 비활성화) - ADMIN/MANAGER/USER 역할 토글 버튼 (헤더) + localStorage 연동 - X-User-Id 헤더 기반 사용자 식별 (ApiKeyController, ApiKeyRequestController) - RoleGuard 컴포넌트로 관리자 전용 페이지 접근 제어 - WebViewController 루트 리다이렉트 수정 (이중 context-path 방지) closes #35
This commit is contained in:
부모
bfaf5e9b97
커밋
97e5a24343
@ -1,10 +1,7 @@
|
|||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import ThemeProvider from './store/ThemeContext';
|
import ThemeProvider from './store/ThemeContext';
|
||||||
import AuthProvider from './store/AuthContext';
|
import AuthProvider from './store/AuthContext';
|
||||||
import AuthLayout from './layouts/AuthLayout';
|
|
||||||
import MainLayout from './layouts/MainLayout';
|
import MainLayout from './layouts/MainLayout';
|
||||||
import ProtectedRoute from './components/ProtectedRoute';
|
|
||||||
import LoginPage from './pages/LoginPage';
|
|
||||||
import DashboardPage from './pages/DashboardPage';
|
import DashboardPage from './pages/DashboardPage';
|
||||||
import RequestLogsPage from './pages/monitoring/RequestLogsPage';
|
import RequestLogsPage from './pages/monitoring/RequestLogsPage';
|
||||||
import RequestLogDetailPage from './pages/monitoring/RequestLogDetailPage';
|
import RequestLogDetailPage from './pages/monitoring/RequestLogDetailPage';
|
||||||
@ -22,6 +19,7 @@ import ApiStatsPage from './pages/statistics/ApiStatsPage';
|
|||||||
import TenantStatsPage from './pages/statistics/TenantStatsPage';
|
import TenantStatsPage from './pages/statistics/TenantStatsPage';
|
||||||
import UsageTrendPage from './pages/statistics/UsageTrendPage';
|
import UsageTrendPage from './pages/statistics/UsageTrendPage';
|
||||||
import NotFoundPage from './pages/NotFoundPage';
|
import NotFoundPage from './pages/NotFoundPage';
|
||||||
|
import RoleGuard from './components/RoleGuard';
|
||||||
|
|
||||||
const BASE_PATH = '/snp-connection';
|
const BASE_PATH = '/snp-connection';
|
||||||
|
|
||||||
@ -31,31 +29,25 @@ const App = () => {
|
|||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<AuthLayout />}>
|
<Route element={<MainLayout />}>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
</Route>
|
<Route path="/dashboard" element={<DashboardPage />} />
|
||||||
|
<Route path="/monitoring/request-logs" element={<RequestLogsPage />} />
|
||||||
<Route element={<ProtectedRoute />}>
|
<Route path="/monitoring/request-logs/:id" element={<RequestLogDetailPage />} />
|
||||||
<Route element={<MainLayout />}>
|
<Route path="/monitoring/service-status" element={<ServiceStatusPage />} />
|
||||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
<Route path="/monitoring/service-status/:serviceId" element={<ServiceStatusDetailPage />} />
|
||||||
<Route path="/dashboard" element={<DashboardPage />} />
|
<Route path="/statistics/services" element={<ServiceStatsPage />} />
|
||||||
<Route path="/monitoring/request-logs" element={<RequestLogsPage />} />
|
<Route path="/statistics/users" element={<UserStatsPage />} />
|
||||||
<Route path="/monitoring/request-logs/:id" element={<RequestLogDetailPage />} />
|
<Route path="/statistics/apis" element={<ApiStatsPage />} />
|
||||||
<Route path="/monitoring/service-status" element={<ServiceStatusPage />} />
|
<Route path="/statistics/tenants" element={<TenantStatsPage />} />
|
||||||
<Route path="/monitoring/service-status/:serviceId" element={<ServiceStatusDetailPage />} />
|
<Route path="/statistics/usage-trend" element={<UsageTrendPage />} />
|
||||||
<Route path="/statistics/services" element={<ServiceStatsPage />} />
|
<Route path="/apikeys/my-keys" element={<MyKeysPage />} />
|
||||||
<Route path="/statistics/users" element={<UserStatsPage />} />
|
<Route path="/apikeys/request" element={<KeyRequestPage />} />
|
||||||
<Route path="/statistics/apis" element={<ApiStatsPage />} />
|
<Route path="/apikeys/admin" element={<RoleGuard allowedRoles={['ADMIN', 'MANAGER']}><KeyAdminPage /></RoleGuard>} />
|
||||||
<Route path="/statistics/tenants" element={<TenantStatsPage />} />
|
<Route path="/admin/services" element={<RoleGuard allowedRoles={['ADMIN']}><ServicesPage /></RoleGuard>} />
|
||||||
<Route path="/statistics/usage-trend" element={<UsageTrendPage />} />
|
<Route path="/admin/users" element={<RoleGuard allowedRoles={['ADMIN']}><UsersPage /></RoleGuard>} />
|
||||||
<Route path="/apikeys/my-keys" element={<MyKeysPage />} />
|
<Route path="/admin/tenants" element={<RoleGuard allowedRoles={['ADMIN']}><TenantsPage /></RoleGuard>} />
|
||||||
<Route path="/apikeys/request" element={<KeyRequestPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
<Route path="/apikeys/admin" element={<KeyAdminPage />} />
|
|
||||||
<Route path="/admin/services" element={<ServicesPage />} />
|
|
||||||
<Route path="/admin/users" element={<UsersPage />} />
|
|
||||||
<Route path="/admin/tenants" element={<TenantsPage />} />
|
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
|
||||||
</Route>
|
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|||||||
@ -1,22 +1,5 @@
|
|||||||
import { Navigate, Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
import { useAuth } from '../hooks/useAuth';
|
|
||||||
|
|
||||||
const ProtectedRoute = () => {
|
const ProtectedRoute = () => <Outlet />;
|
||||||
const { isAuthenticated, isLoading } = useAuth();
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="flex min-h-screen items-center justify-center bg-gray-100 dark:bg-gray-900">
|
|
||||||
<div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-500 border-t-transparent dark:border-blue-400 dark:border-t-transparent" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
return <Navigate to="/login" replace />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Outlet />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProtectedRoute;
|
export default ProtectedRoute;
|
||||||
|
|||||||
41
frontend/src/components/RoleGuard.tsx
Normal file
41
frontend/src/components/RoleGuard.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useAuth } from '../hooks/useAuth';
|
||||||
|
|
||||||
|
interface RoleGuardProps {
|
||||||
|
allowedRoles: string[];
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoleGuard = ({ allowedRoles, children }: RoleGuardProps) => {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
if (!user || !allowedRoles.includes(user.role)) {
|
||||||
|
return (
|
||||||
|
<div className="max-w-7xl mx-auto flex flex-col items-center justify-center py-32">
|
||||||
|
<div className="bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 shadow-lg p-10 text-center max-w-md">
|
||||||
|
<div className="w-16 h-16 mx-auto mb-5 rounded-full bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-700/30 flex items-center justify-center">
|
||||||
|
<svg className="w-8 h-8 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.5">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 dark:text-gray-100 mb-2">접근 권한이 없습니다</h2>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||||||
|
이 페이지는 <span className="font-semibold text-gray-700 dark:text-gray-300">{allowedRoles.join(', ')}</span> 권한이 필요합니다.
|
||||||
|
<br />현재 권한: <span className="font-semibold text-gray-700 dark:text-gray-300">{user?.role || '-'}</span>
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/dashboard')}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 text-white px-5 py-2.5 rounded-lg text-sm font-medium transition-colors"
|
||||||
|
>
|
||||||
|
대시보드로 돌아가기
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RoleGuard;
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { Outlet } from 'react-router-dom';
|
|
||||||
|
|
||||||
const AuthLayout = () => {
|
|
||||||
return (
|
|
||||||
<div className="flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-900">
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthLayout;
|
|
||||||
@ -46,8 +46,10 @@ const navGroups: NavGroup[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ROLES = ['ADMIN', 'MANAGER', 'USER'] as const;
|
||||||
|
|
||||||
const MainLayout = () => {
|
const MainLayout = () => {
|
||||||
const { user, logout } = useAuth();
|
const { user, setRole } = useAuth();
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
const [openGroups, setOpenGroups] = useState<Record<string, boolean>>({
|
const [openGroups, setOpenGroups] = useState<Record<string, boolean>>({
|
||||||
Monitoring: true,
|
Monitoring: true,
|
||||||
@ -60,10 +62,6 @@ const MainLayout = () => {
|
|||||||
setOpenGroups((prev) => ({ ...prev, [label]: !prev[label] }));
|
setOpenGroups((prev) => ({ ...prev, [label]: !prev[label] }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = async () => {
|
|
||||||
await logout();
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAdminOrManager = user?.role === 'ADMIN' || user?.role === 'MANAGER';
|
const isAdminOrManager = user?.role === 'ADMIN' || user?.role === 'MANAGER';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -170,16 +168,22 @@ const MainLayout = () => {
|
|||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<span className="text-sm text-gray-700 dark:text-gray-300">{user?.userName}</span>
|
<span className="text-sm text-gray-500 dark:text-gray-400">Role:</span>
|
||||||
<span className="rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800">
|
<div className="flex rounded-lg overflow-hidden border border-gray-300 dark:border-gray-600">
|
||||||
{user?.role}
|
{ROLES.map((role) => (
|
||||||
</span>
|
<button
|
||||||
<button
|
key={role}
|
||||||
onClick={handleLogout}
|
onClick={() => setRole(role)}
|
||||||
className="rounded-lg px-3 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-100 transition-colors"
|
className={`px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
>
|
user?.role === role
|
||||||
Logout
|
? 'bg-blue-600 text-white'
|
||||||
</button>
|
: 'bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{role}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@ -1,93 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import type { FormEvent } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { useAuth } from '../hooks/useAuth';
|
|
||||||
|
|
||||||
const LoginPage = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { login } = useAuth();
|
|
||||||
const [loginId, setLoginId] = useState('');
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
||||||
|
|
||||||
const handleSubmit = async (e: FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setError('');
|
|
||||||
|
|
||||||
if (!loginId.trim() || !password.trim()) {
|
|
||||||
setError('아이디와 비밀번호를 입력해주세요.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSubmitting(true);
|
|
||||||
try {
|
|
||||||
await login(loginId, password);
|
|
||||||
navigate('/dashboard', { replace: true });
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
setError(err.message);
|
|
||||||
} else {
|
|
||||||
setError('로그인에 실패했습니다.');
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setIsSubmitting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full max-w-md">
|
|
||||||
<div className="rounded-xl bg-white dark:bg-gray-800 px-8 py-10 shadow-lg">
|
|
||||||
<h1 className="mb-8 text-center text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
||||||
SNP Connection Monitoring
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-5">
|
|
||||||
<div>
|
|
||||||
<label htmlFor="loginId" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
||||||
아이디
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="loginId"
|
|
||||||
type="text"
|
|
||||||
value={loginId}
|
|
||||||
onChange={(e) => setLoginId(e.target.value)}
|
|
||||||
className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-4 py-2.5 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
||||||
placeholder="아이디를 입력하세요"
|
|
||||||
autoComplete="username"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
||||||
비밀번호
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
className="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-4 py-2.5 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
||||||
placeholder="비밀번호를 입력하세요"
|
|
||||||
autoComplete="current-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<p className="text-sm text-red-600">{error}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
className="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-colors"
|
|
||||||
>
|
|
||||||
{isSubmitting ? '로그인 중...' : '로그인'}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LoginPage;
|
|
||||||
@ -2,93 +2,24 @@ import type { ApiResponse } from '../types/api';
|
|||||||
|
|
||||||
const BASE_URL = '/snp-connection/api';
|
const BASE_URL = '/snp-connection/api';
|
||||||
|
|
||||||
let isRefreshing = false;
|
let currentUserId: number = 1;
|
||||||
let refreshPromise: Promise<boolean> | null = null;
|
|
||||||
|
|
||||||
const getAccessToken = (): string | null => {
|
export const setApiClientUserId = (userId: number) => {
|
||||||
return localStorage.getItem('snp_access_token');
|
currentUserId = userId;
|
||||||
};
|
|
||||||
|
|
||||||
const getRefreshToken = (): string | null => {
|
|
||||||
return localStorage.getItem('snp_refresh_token');
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearStorage = () => {
|
|
||||||
localStorage.removeItem('snp_access_token');
|
|
||||||
localStorage.removeItem('snp_refresh_token');
|
|
||||||
localStorage.removeItem('snp_user');
|
|
||||||
};
|
|
||||||
|
|
||||||
const attemptRefresh = async (): Promise<boolean> => {
|
|
||||||
const refreshToken = getRefreshToken();
|
|
||||||
if (!refreshToken) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${BASE_URL}/auth/refresh`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ refreshToken }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) return false;
|
|
||||||
|
|
||||||
const data = await response.json() as ApiResponse<{ accessToken: string }>;
|
|
||||||
if (data.success && data.data?.accessToken) {
|
|
||||||
localStorage.setItem('snp_access_token', data.data.accessToken);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTokenRefresh = async (): Promise<boolean> => {
|
|
||||||
if (isRefreshing && refreshPromise) {
|
|
||||||
return refreshPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
isRefreshing = true;
|
|
||||||
refreshPromise = attemptRefresh().finally(() => {
|
|
||||||
isRefreshing = false;
|
|
||||||
refreshPromise = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
return refreshPromise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const request = async <T>(url: string, options: RequestInit = {}): Promise<ApiResponse<T>> => {
|
const request = async <T>(url: string, options: RequestInit = {}): Promise<ApiResponse<T>> => {
|
||||||
const token = getAccessToken();
|
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'X-User-Id': String(currentUserId),
|
||||||
...options.headers as Record<string, string>,
|
...options.headers as Record<string, string>,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (token) {
|
const response = await fetch(`${BASE_URL}${url}`, {
|
||||||
headers['Authorization'] = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let response = await fetch(`${BASE_URL}${url}`, {
|
|
||||||
...options,
|
...options,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
const refreshed = await handleTokenRefresh();
|
|
||||||
if (refreshed) {
|
|
||||||
const newToken = getAccessToken();
|
|
||||||
headers['Authorization'] = `Bearer ${newToken}`;
|
|
||||||
response = await fetch(`${BASE_URL}${url}`, {
|
|
||||||
...options,
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
clearStorage();
|
|
||||||
window.location.href = '/snp-connection/login';
|
|
||||||
return { success: false, message: 'Authentication failed' };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json() as ApiResponse<T>;
|
const data = await response.json() as ApiResponse<T>;
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,67 +1,2 @@
|
|||||||
import { post } from './apiClient';
|
// Auth service removed - login/logout no longer needed.
|
||||||
import type { LoginRequest, LoginResponse, User } from '../types/auth';
|
// Role is managed via AuthContext's setRole function.
|
||||||
|
|
||||||
const TOKEN_KEY = 'snp_access_token';
|
|
||||||
const REFRESH_TOKEN_KEY = 'snp_refresh_token';
|
|
||||||
const USER_KEY = 'snp_user';
|
|
||||||
|
|
||||||
export const login = async (req: LoginRequest): Promise<LoginResponse> => {
|
|
||||||
const response = await post<LoginResponse>('/auth/login', req);
|
|
||||||
|
|
||||||
if (!response.success || !response.data) {
|
|
||||||
throw new Error(response.message || '로그인에 실패했습니다.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = response.data;
|
|
||||||
localStorage.setItem(TOKEN_KEY, data.accessToken);
|
|
||||||
localStorage.setItem(REFRESH_TOKEN_KEY, data.refreshToken);
|
|
||||||
localStorage.setItem(USER_KEY, JSON.stringify({
|
|
||||||
loginId: data.loginId,
|
|
||||||
userName: data.userName,
|
|
||||||
role: data.role,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const logout = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
await post('/auth/logout');
|
|
||||||
} finally {
|
|
||||||
localStorage.removeItem(TOKEN_KEY);
|
|
||||||
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
||||||
localStorage.removeItem(USER_KEY);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const refreshToken = async (): Promise<void> => {
|
|
||||||
const storedRefreshToken = localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
||||||
if (!storedRefreshToken) {
|
|
||||||
throw new Error('No refresh token available');
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await post<{ accessToken: string }>('/auth/refresh', {
|
|
||||||
refreshToken: storedRefreshToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.success || !response.data) {
|
|
||||||
throw new Error('Token refresh failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem(TOKEN_KEY, response.data.accessToken);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getStoredUser = (): User | null => {
|
|
||||||
const userStr = localStorage.getItem(USER_KEY);
|
|
||||||
if (!userStr) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(userStr) as User;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAccessToken = (): string | null => {
|
|
||||||
return localStorage.getItem(TOKEN_KEY);
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
import { createContext, useState, useEffect, useCallback } from 'react';
|
import { createContext, useState } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { User } from '../types/auth';
|
import type { User } from '../types/auth';
|
||||||
import * as authService from '../services/authService';
|
import { setApiClientUserId } from '../services/apiClient';
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthContextValue {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}
|
setRole: (role: 'ADMIN' | 'MANAGER' | 'USER') => void;
|
||||||
|
|
||||||
interface AuthContextValue extends AuthState {
|
|
||||||
login: (loginId: string, password: string) => Promise<void>;
|
|
||||||
logout: () => Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthContext = createContext<AuthContextValue | null>(null);
|
export const AuthContext = createContext<AuthContextValue | null>(null);
|
||||||
@ -20,56 +16,32 @@ interface AuthProviderProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ROLE_USERS: Record<string, User> = {
|
||||||
|
ADMIN: { userId: 1, loginId: 'admin', userName: '관리자', role: 'ADMIN' },
|
||||||
|
MANAGER: { userId: 7, loginId: 'manager', userName: '매니저', role: 'MANAGER' },
|
||||||
|
USER: { userId: 2, loginId: 'user', userName: '사용자', role: 'USER' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitialUser = (): User => {
|
||||||
|
const savedRole = localStorage.getItem('snp-role') as 'ADMIN' | 'MANAGER' | 'USER' | null;
|
||||||
|
const u = ROLE_USERS[savedRole || 'ADMIN'];
|
||||||
|
setApiClientUserId(u.userId);
|
||||||
|
return u;
|
||||||
|
};
|
||||||
|
|
||||||
const AuthProvider = ({ children }: AuthProviderProps) => {
|
const AuthProvider = ({ children }: AuthProviderProps) => {
|
||||||
const [state, setState] = useState<AuthState>({
|
const [user, setUser] = useState<User>(getInitialUser);
|
||||||
user: null,
|
|
||||||
isAuthenticated: false,
|
|
||||||
isLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
const setRole = (role: 'ADMIN' | 'MANAGER' | 'USER') => {
|
||||||
const storedUser = authService.getStoredUser();
|
localStorage.setItem('snp-role', role);
|
||||||
const token = authService.getAccessToken();
|
const u = ROLE_USERS[role];
|
||||||
|
setUser(u);
|
||||||
if (storedUser && token) {
|
setApiClientUserId(u.userId);
|
||||||
setState({
|
window.location.reload();
|
||||||
user: storedUser,
|
};
|
||||||
isAuthenticated: true,
|
|
||||||
isLoading: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState({
|
|
||||||
user: null,
|
|
||||||
isAuthenticated: false,
|
|
||||||
isLoading: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const login = useCallback(async (loginId: string, password: string) => {
|
|
||||||
const response = await authService.login({ loginId, password });
|
|
||||||
setState({
|
|
||||||
user: {
|
|
||||||
loginId: response.loginId,
|
|
||||||
userName: response.userName,
|
|
||||||
role: response.role,
|
|
||||||
},
|
|
||||||
isAuthenticated: true,
|
|
||||||
isLoading: false,
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const logout = useCallback(async () => {
|
|
||||||
await authService.logout();
|
|
||||||
setState({
|
|
||||||
user: null,
|
|
||||||
isAuthenticated: false,
|
|
||||||
isLoading: false,
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ ...state, login, logout }}>
|
<AuthContext.Provider value={{ user, isAuthenticated: true, isLoading: false, setRole }}>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,18 +1,5 @@
|
|||||||
export interface LoginRequest {
|
|
||||||
loginId: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginResponse {
|
|
||||||
accessToken: string;
|
|
||||||
refreshToken: string;
|
|
||||||
loginId: string;
|
|
||||||
userName: string;
|
|
||||||
role: string;
|
|
||||||
expiresIn: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
|
userId: number;
|
||||||
loginId: string;
|
loginId: string;
|
||||||
userName: string;
|
userName: string;
|
||||||
role: string;
|
role: string;
|
||||||
|
|||||||
@ -9,10 +9,10 @@ import com.gcsc.connection.apikey.dto.UpdatePermissionsRequest;
|
|||||||
import com.gcsc.connection.apikey.service.ApiKeyPermissionService;
|
import com.gcsc.connection.apikey.service.ApiKeyPermissionService;
|
||||||
import com.gcsc.connection.apikey.service.ApiKeyService;
|
import com.gcsc.connection.apikey.service.ApiKeyService;
|
||||||
import com.gcsc.connection.common.dto.ApiResponse;
|
import com.gcsc.connection.common.dto.ApiResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
@ -34,6 +34,7 @@ public class ApiKeyController {
|
|||||||
|
|
||||||
private final ApiKeyService apiKeyService;
|
private final ApiKeyService apiKeyService;
|
||||||
private final ApiKeyPermissionService apiKeyPermissionService;
|
private final ApiKeyPermissionService apiKeyPermissionService;
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 내 API Key 목록 조회
|
* 내 API Key 목록 조회
|
||||||
@ -49,7 +50,6 @@ public class ApiKeyController {
|
|||||||
* 전체 API Key 목록 조회 (관리자용)
|
* 전체 API Key 목록 조회 (관리자용)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/all")
|
@GetMapping("/all")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<List<ApiKeyResponse>>> getAllKeys() {
|
public ResponseEntity<ApiResponse<List<ApiKeyResponse>>> getAllKeys() {
|
||||||
List<ApiKeyResponse> keys = apiKeyService.getAllKeys();
|
List<ApiKeyResponse> keys = apiKeyService.getAllKeys();
|
||||||
return ResponseEntity.ok(ApiResponse.ok(keys));
|
return ResponseEntity.ok(ApiResponse.ok(keys));
|
||||||
@ -59,7 +59,6 @@ public class ApiKeyController {
|
|||||||
* API Key 상세 조회 (복호화된 키 포함, 관리자 전용)
|
* API Key 상세 조회 (복호화된 키 포함, 관리자 전용)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<ApiKeyDetailResponse>> getKeyDetail(@PathVariable Long id) {
|
public ResponseEntity<ApiResponse<ApiKeyDetailResponse>> getKeyDetail(@PathVariable Long id) {
|
||||||
ApiKeyDetailResponse detail = apiKeyService.getKeyDetail(id);
|
ApiKeyDetailResponse detail = apiKeyService.getKeyDetail(id);
|
||||||
return ResponseEntity.ok(ApiResponse.ok(detail));
|
return ResponseEntity.ok(ApiResponse.ok(detail));
|
||||||
@ -69,7 +68,6 @@ public class ApiKeyController {
|
|||||||
* API Key 생성 (관리자 전용)
|
* API Key 생성 (관리자 전용)
|
||||||
*/
|
*/
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<ApiKeyCreateResponse>> createKey(
|
public ResponseEntity<ApiResponse<ApiKeyCreateResponse>> createKey(
|
||||||
@RequestBody @Valid CreateApiKeyRequest request) {
|
@RequestBody @Valid CreateApiKeyRequest request) {
|
||||||
Long userId = getCurrentUserId();
|
Long userId = getCurrentUserId();
|
||||||
@ -99,7 +97,6 @@ public class ApiKeyController {
|
|||||||
* API Key 권한 수정 (관리자/매니저 전용)
|
* API Key 권한 수정 (관리자/매니저 전용)
|
||||||
*/
|
*/
|
||||||
@PutMapping("/{id}/permissions")
|
@PutMapping("/{id}/permissions")
|
||||||
@PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
|
|
||||||
public ResponseEntity<ApiResponse<List<PermissionResponse>>> updatePermissions(
|
public ResponseEntity<ApiResponse<List<PermissionResponse>>> updatePermissions(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestBody @Valid UpdatePermissionsRequest request) {
|
@RequestBody @Valid UpdatePermissionsRequest request) {
|
||||||
@ -109,6 +106,15 @@ public class ApiKeyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Long getCurrentUserId() {
|
private Long getCurrentUserId() {
|
||||||
return Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
|
String userIdHeader = request.getHeader("X-User-Id");
|
||||||
|
if (userIdHeader != null && !userIdHeader.isBlank()) {
|
||||||
|
return Long.parseLong(userIdHeader);
|
||||||
|
}
|
||||||
|
// fallback: SecurityContext
|
||||||
|
try {
|
||||||
|
return Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 1L; // default admin
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,10 +6,10 @@ import com.gcsc.connection.apikey.dto.ApiKeyRequestResponse;
|
|||||||
import com.gcsc.connection.apikey.dto.ApiKeyRequestReviewDto;
|
import com.gcsc.connection.apikey.dto.ApiKeyRequestReviewDto;
|
||||||
import com.gcsc.connection.apikey.service.ApiKeyRequestService;
|
import com.gcsc.connection.apikey.service.ApiKeyRequestService;
|
||||||
import com.gcsc.connection.common.dto.ApiResponse;
|
import com.gcsc.connection.common.dto.ApiResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
@ -31,6 +31,7 @@ import java.util.List;
|
|||||||
public class ApiKeyRequestController {
|
public class ApiKeyRequestController {
|
||||||
|
|
||||||
private final ApiKeyRequestService apiKeyRequestService;
|
private final ApiKeyRequestService apiKeyRequestService;
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API Key 신청 생성
|
* API Key 신청 생성
|
||||||
@ -57,7 +58,6 @@ public class ApiKeyRequestController {
|
|||||||
* 신청 목록 조회 (관리자/매니저용)
|
* 신청 목록 조회 (관리자/매니저용)
|
||||||
*/
|
*/
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
|
|
||||||
public ResponseEntity<ApiResponse<List<ApiKeyRequestResponse>>> getRequests(
|
public ResponseEntity<ApiResponse<List<ApiKeyRequestResponse>>> getRequests(
|
||||||
@RequestParam(required = false) String status) {
|
@RequestParam(required = false) String status) {
|
||||||
List<ApiKeyRequestResponse> responses;
|
List<ApiKeyRequestResponse> responses;
|
||||||
@ -73,7 +73,6 @@ public class ApiKeyRequestController {
|
|||||||
* 신청 심사 (승인/거절)
|
* 신청 심사 (승인/거절)
|
||||||
*/
|
*/
|
||||||
@PutMapping("/{id}/review")
|
@PutMapping("/{id}/review")
|
||||||
@PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
|
|
||||||
public ResponseEntity<ApiResponse<ApiKeyCreateResponse>> reviewRequest(
|
public ResponseEntity<ApiResponse<ApiKeyCreateResponse>> reviewRequest(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestBody @Valid ApiKeyRequestReviewDto request) {
|
@RequestBody @Valid ApiKeyRequestReviewDto request) {
|
||||||
@ -86,6 +85,14 @@ public class ApiKeyRequestController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Long getCurrentUserId() {
|
private Long getCurrentUserId() {
|
||||||
return Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
|
String userIdHeader = request.getHeader("X-User-Id");
|
||||||
|
if (userIdHeader != null && !userIdHeader.isBlank()) {
|
||||||
|
return Long.parseLong(userIdHeader);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(SecurityContextHolder.getContext().getAuthentication().getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 1L;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
package com.gcsc.connection.config;
|
package com.gcsc.connection.config;
|
||||||
|
|
||||||
import com.gcsc.connection.auth.jwt.JwtAuthenticationFilter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
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.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
@ -13,16 +11,12 @@ import org.springframework.security.config.http.SessionCreationPolicy;
|
|||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableMethodSecurity
|
// @EnableMethodSecurity -- disabled (no login)
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
@ -42,9 +36,8 @@ public class SecurityConfig {
|
|||||||
.requestMatchers("/actuator/**").permitAll()
|
.requestMatchers("/actuator/**").permitAll()
|
||||||
.requestMatchers("/", "/*.html", "/assets/**", "/favicon*", "/site.webmanifest").permitAll()
|
.requestMatchers("/", "/*.html", "/assets/**", "/favicon*", "/site.webmanifest").permitAll()
|
||||||
.requestMatchers("/gateway/**").permitAll()
|
.requestMatchers("/gateway/**").permitAll()
|
||||||
.requestMatchers("/api/**").authenticated()
|
.requestMatchers("/api/**").permitAll()
|
||||||
.anyRequest().permitAll())
|
.anyRequest().permitAll());
|
||||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,14 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||||||
@Controller
|
@Controller
|
||||||
public class WebViewController {
|
public class WebViewController {
|
||||||
|
|
||||||
@GetMapping({"/", "/login", "/dashboard", "/dashboard/**",
|
@GetMapping("/")
|
||||||
"/monitoring/**", "/apikeys", "/apikeys/**",
|
public String root() {
|
||||||
|
return "redirect:/dashboard";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping({"/dashboard", "/dashboard/**",
|
||||||
|
"/monitoring/**", "/statistics/**",
|
||||||
|
"/apikeys", "/apikeys/**",
|
||||||
"/admin/**"})
|
"/admin/**"})
|
||||||
public String forward() {
|
public String forward() {
|
||||||
return "forward:/index.html";
|
return "forward:/index.html";
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import com.gcsc.connection.monitoring.dto.ServiceStatusDetailResponse;
|
|||||||
import com.gcsc.connection.monitoring.service.HeartbeatService;
|
import com.gcsc.connection.monitoring.service.HeartbeatService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@ -68,7 +67,6 @@ public class HeartbeatController {
|
|||||||
* 수동 헬스체크 실행
|
* 수동 헬스체크 실행
|
||||||
*/
|
*/
|
||||||
@PostMapping("/{serviceId}/check")
|
@PostMapping("/{serviceId}/check")
|
||||||
@PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
|
|
||||||
public ResponseEntity<ApiResponse<HeartbeatStatusResponse>> checkService(
|
public ResponseEntity<ApiResponse<HeartbeatStatusResponse>> checkService(
|
||||||
@PathVariable Long serviceId) {
|
@PathVariable Long serviceId) {
|
||||||
HeartbeatStatusResponse result = heartbeatService.checkService(serviceId);
|
HeartbeatStatusResponse result = heartbeatService.checkService(serviceId);
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import com.gcsc.connection.service.service.ServiceManagementService;
|
|||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@ -44,7 +43,6 @@ public class ServiceController {
|
|||||||
* 서비스 생성
|
* 서비스 생성
|
||||||
*/
|
*/
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<ServiceResponse>> createService(
|
public ResponseEntity<ApiResponse<ServiceResponse>> createService(
|
||||||
@RequestBody @Valid CreateServiceRequest request) {
|
@RequestBody @Valid CreateServiceRequest request) {
|
||||||
ServiceResponse service = serviceManagementService.createService(request);
|
ServiceResponse service = serviceManagementService.createService(request);
|
||||||
@ -55,7 +53,6 @@ public class ServiceController {
|
|||||||
* 서비스 수정
|
* 서비스 수정
|
||||||
*/
|
*/
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<ServiceResponse>> updateService(
|
public ResponseEntity<ApiResponse<ServiceResponse>> updateService(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestBody @Valid UpdateServiceRequest request) {
|
@RequestBody @Valid UpdateServiceRequest request) {
|
||||||
@ -77,7 +74,6 @@ public class ServiceController {
|
|||||||
* 서비스 API 생성
|
* 서비스 API 생성
|
||||||
*/
|
*/
|
||||||
@PostMapping("/{id}/apis")
|
@PostMapping("/{id}/apis")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<ServiceApiResponse>> createServiceApi(
|
public ResponseEntity<ApiResponse<ServiceApiResponse>> createServiceApi(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestBody @Valid CreateServiceApiRequest request) {
|
@RequestBody @Valid CreateServiceApiRequest request) {
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import com.gcsc.connection.tenant.service.TenantService;
|
|||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@ -33,7 +32,6 @@ public class TenantController {
|
|||||||
* 전체 테넌트 목록 조회
|
* 전체 테넌트 목록 조회
|
||||||
*/
|
*/
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<List<TenantResponse>>> getTenants() {
|
public ResponseEntity<ApiResponse<List<TenantResponse>>> getTenants() {
|
||||||
List<TenantResponse> tenants = tenantService.getTenants();
|
List<TenantResponse> tenants = tenantService.getTenants();
|
||||||
return ResponseEntity.ok(ApiResponse.ok(tenants));
|
return ResponseEntity.ok(ApiResponse.ok(tenants));
|
||||||
@ -43,7 +41,6 @@ public class TenantController {
|
|||||||
* 테넌트 생성
|
* 테넌트 생성
|
||||||
*/
|
*/
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<TenantResponse>> createTenant(
|
public ResponseEntity<ApiResponse<TenantResponse>> createTenant(
|
||||||
@RequestBody @Valid CreateTenantRequest request) {
|
@RequestBody @Valid CreateTenantRequest request) {
|
||||||
TenantResponse tenant = tenantService.createTenant(request);
|
TenantResponse tenant = tenantService.createTenant(request);
|
||||||
@ -54,7 +51,6 @@ public class TenantController {
|
|||||||
* 테넌트 수정
|
* 테넌트 수정
|
||||||
*/
|
*/
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<TenantResponse>> updateTenant(
|
public ResponseEntity<ApiResponse<TenantResponse>> updateTenant(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestBody @Valid UpdateTenantRequest request) {
|
@RequestBody @Valid UpdateTenantRequest request) {
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import jakarta.validation.Valid;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@ -36,7 +35,6 @@ public class UserController {
|
|||||||
* 전체 사용자 목록 조회
|
* 전체 사용자 목록 조회
|
||||||
*/
|
*/
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
|
|
||||||
public ResponseEntity<ApiResponse<List<UserResponse>>> getUsers() {
|
public ResponseEntity<ApiResponse<List<UserResponse>>> getUsers() {
|
||||||
List<UserResponse> users = userService.getUsers();
|
List<UserResponse> users = userService.getUsers();
|
||||||
return ResponseEntity.ok(ApiResponse.ok(users));
|
return ResponseEntity.ok(ApiResponse.ok(users));
|
||||||
@ -46,7 +44,6 @@ public class UserController {
|
|||||||
* 사용자 생성
|
* 사용자 생성
|
||||||
*/
|
*/
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<UserResponse>> createUser(
|
public ResponseEntity<ApiResponse<UserResponse>> createUser(
|
||||||
@RequestBody @Valid CreateUserRequest request) {
|
@RequestBody @Valid CreateUserRequest request) {
|
||||||
UserResponse user = userService.createUser(request);
|
UserResponse user = userService.createUser(request);
|
||||||
@ -57,7 +54,6 @@ public class UserController {
|
|||||||
* 사용자 단건 조회 (ADMIN/MANAGER 또는 본인)
|
* 사용자 단건 조회 (ADMIN/MANAGER 또는 본인)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
|
|
||||||
public ResponseEntity<ApiResponse<UserResponse>> getUser(@PathVariable Long id) {
|
public ResponseEntity<ApiResponse<UserResponse>> getUser(@PathVariable Long id) {
|
||||||
String currentUserId = SecurityContextHolder.getContext().getAuthentication().getName();
|
String currentUserId = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||||
if (!isAdminOrManager() && !currentUserId.equals(String.valueOf(id))) {
|
if (!isAdminOrManager() && !currentUserId.equals(String.valueOf(id))) {
|
||||||
@ -86,7 +82,6 @@ public class UserController {
|
|||||||
* 사용자 비활성화 (소프트 삭제)
|
* 사용자 비활성화 (소프트 삭제)
|
||||||
*/
|
*/
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
public ResponseEntity<ApiResponse<Void>> deactivateUser(@PathVariable Long id) {
|
public ResponseEntity<ApiResponse<Void>> deactivateUser(@PathVariable Long id) {
|
||||||
userService.deactivateUser(id);
|
userService.deactivateUser(id);
|
||||||
return ResponseEntity.ok(ApiResponse.ok(null, "사용자가 비활성화되었습니다"));
|
return ResponseEntity.ok(ApiResponse.ok(null, "사용자가 비활성화되었습니다"));
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user