Spring Security, 로그인 작업중

main
semin.baek 6 months ago
parent 05b9dc84db
commit 2fc4fa18cf

@ -7,7 +7,7 @@
<title>Vite App</title> <title>Vite App</title>
</head> </head>
<body> <body>
<div id="app" class="container d-flex"></div> <div id="app" class="container d-flex min-vh-100 flex-column"></div>
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
</body> </body>
</html> </html>

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"axios": "^1.11.0",
"bootstrap": "^5.3.7", "bootstrap": "^5.3.7",
"bootstrap-icons": "^1.13.1", "bootstrap-icons": "^1.13.1",
"chart.js": "^4.5.0", "chart.js": "^4.5.0",

@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"axios": "^1.11.0",
"bootstrap": "^5.3.7", "bootstrap": "^5.3.7",
"bootstrap-icons": "^1.13.1", "bootstrap-icons": "^1.13.1",
"chart.js": "^4.5.0", "chart.js": "^4.5.0",

@ -19,8 +19,8 @@ import Login from './views/LoginView.vue'
</div> </div>
</header> </header>
--> -->
<RouterView /> <RouterView class="flex-fill" />
<footer class="w-100 d-flex"> <footer class="w-100 border-top d-flex">
<div class="bg-info-subtle p-2 d-flex align-items-center w-100 bg-purple">바닥글(저작권, 링크 ) 영역</div> <div class="bg-info-subtle p-2 d-flex align-items-center w-100 bg-purple">바닥글(저작권, 링크 ) 영역</div>
</footer> </footer>
</template> </template>

@ -38,4 +38,5 @@ a,
footer { footer {
min-height: 2rem; min-height: 2rem;
max-height: 2rem;
} }

@ -1,8 +1,48 @@
import { ref } from 'vue' import { reactive } from 'vue'
import axios from 'axios'
const userInfo = ref({ const userInfo = reactive({
isLogin: true, isLogin: false,
userId: '', userId: '',
}) })
export const userApi = {
loginProcess: async function (inputUsername, inputUserPassword) {
const data = { username: inputUsername, password: inputUserPassword }
axios.post('/public-api/loginProcess', data).then((response) => {
if (response.status == 200 && response.data && response.data.success == true) {
userInfo.isLogin = true
userInfo.userId = response.data.username
return true
} else {
return false
}
})
},
logoutProcess: async function () {
axios.post('/public-api/logoutProcess').then((response) => {
if (response.status == 200 && response.data && response.data.success == true) {
userInfo.isLogin = false
userInfo.userId = ''
return true
} else {
return false
}
})
},
loginCheck: async function () {
axios.get('/public-api/sessionCheck').then((response) => {
if (response.status == 200 && response.data && response.data.success == true) {
userInfo.isLogin = true
userInfo.userId = response.data.username
return true
} else {
userInfo.isLogin = false
userInfo.userId = ''
return false
}
})
},
}
window.userApi = userApi
export default userInfo export default userInfo

@ -46,9 +46,9 @@ const router = createRouter({
console.log('test') console.log('test')
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (to.meta.isRequiredAuth && !userInfo.value.isLogin) { if (to.meta.isRequiredAuth && !userInfo.isLogin) {
next('/login.html') next('/login.html')
} else if ((to.path === '/login.html' || to.path === '/') && userInfo.value.isLogin) { } else if ((to.path === '/login.html' || to.path === '/') && userInfo.isLogin) {
next('/main.html') next('/main.html')
} else { } else {
next() next()

@ -1,52 +1,61 @@
<script setup></script> <script setup>
import { reactive } from 'vue'
import { userApi } from '@/components/userInfo'
const loginForm = reactive({
username: '',
password: '',
})
async function loginProcess() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(loginForm.username)) {
alert('사용자 아이디를 이메일 형식으로 입력하여 주십시오.')
return
}
if (!loginForm.password) {
alert('비밀번호를 입력하여 주십시오.')
return
}
let loginResult = await userApi.loginProcess(loginForm.username, loginForm.password)
if (!loginResult) {
alert('사용자 아이디 또는 비밀번호가 일치하지 않습니다.')
document.getElementById('floatingPassword').focus()
return
}
}
</script>
<template> <template>
<main class="form-signin"> <main class="form-signin container flex-fill">
<form> <form @submit.prevent="loginProcess">
<h1 class="h3 mb-3 fw-normal">Please sign in</h1> <h1 class="h3 mb-3 fw-normal">Please sign in</h1>
<div class="form-floating"> <div class="form-floating">
<input <input type="email" class="form-control" id="floatingInput" placeholder="name@example.com" :value="loginForm.username" />
type="email"
class="form-control"
id="floatingInput"
placeholder="name@example.com"
/>
<label for="floatingInput">Email address</label> <label for="floatingInput">Email address</label>
</div> </div>
<div class="form-floating"> <div class="form-floating">
<input type="password" class="form-control" id="floatingPassword" placeholder="Password" /> <input type="password" class="form-control" id="floatingPassword" placeholder="Password" :value="loginForm.password" @keydown.enter="loginProcess" />
<label for="floatingPassword">Password</label> <label for="floatingPassword">Password</label>
</div> </div>
<div class="checkbox mb-3"> <div class="checkbox mb-3">
<label> <input type="checkbox" value="remember-me" /> Remember me </label> <label> <input type="checkbox" value="remember-me" /> Remember me </label>
</div> </div>
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button> <button class="w-100 btn btn-lg btn-primary" type="submit" @submit="loginProcess">Sign in</button>
<p class="mt-5 mb-3 text-muted">&copy; 20172021</p> <p class="mt-5 mb-3 text-muted">&copy; 20172021</p>
</form> </form>
</main> </main>
</template> </template>
<style scoped> <style scoped>
html,
body {
height: 100%;
}
body {
display: flex;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin { .form-signin {
width: 100%; width: 100%;
max-width: 330px; max-width: 330px;
padding: 15px; padding: 15px;
margin: auto; margin: auto;
margin-top: 20%;
} }
.form-signin .checkbox { .form-signin .checkbox {
@ -76,10 +85,4 @@ body {
-moz-user-select: none; -moz-user-select: none;
user-select: none; user-select: none;
} }
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style> </style>

@ -0,0 +1,20 @@
package com.bsmlab.dfx.dfxconsole.framework.config;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class ConsoleLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write("{\"success\": true}");
}
}

@ -1,32 +0,0 @@
package com.bsmlab.dfx.dfxconsole.framework.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
@Controller
public class PasswordGeneratorController {
private final PasswordEncoder passwordEncoder;
@RequestMapping(value = "/public-api/passwordGenerator")
public Map<String, String> passwordGenerator(Map<String, String> paramMap) {
Map<String, String> resultMap = new HashMap<>();
String plainPassword = paramMap.get("password");
if(StringUtils.isNotEmpty(plainPassword)) {
String encryptedPassword = passwordEncoder.encode(plainPassword);
resultMap.put("password", encryptedPassword);
}
else {
resultMap.put("password", "");
}
return resultMap;
}
}

@ -0,0 +1,60 @@
package com.bsmlab.dfx.dfxconsole.framework.config;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
@Controller
public class PublicApiController {
private final PasswordEncoder passwordEncoder;
/**
* API
* @param paramMap {"password": "평문 문자열"}
* @return {"password": "암호화 문자열"}
*/
@RequestMapping(value = "/public-api/passwordGenerator")
public ResponseEntity<?> passwordGenerator(Map<String, String> paramMap) {
Map<String, String> resultMap = new HashMap<>();
String plainPassword = paramMap.get("password");
if(StringUtils.isNotEmpty(plainPassword)) {
String encryptedPassword = passwordEncoder.encode(plainPassword);
resultMap.put("password", encryptedPassword);
}
else {
resultMap.put("password", "");
}
return ResponseEntity.ok(resultMap);
}
/**
* API
* @param userDetails DI
* @return {"success": true/false, "username": }
*/
@RequestMapping(value = "/public-api/sessionCheck")
public ResponseEntity<?> sessionCheck(@AuthenticationPrincipal UserDetails userDetails) {
Map<String, Object> resultMap = new HashMap<>();
if(userDetails != null) {
resultMap.put("success", true);
resultMap.put("username", userDetails.getUsername());
}
else {
resultMap.put("success", false);
resultMap.put("username", "");
}
return ResponseEntity.ok(resultMap);
}
}

@ -16,6 +16,7 @@ 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; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
@Slf4j @Slf4j
@Configuration @Configuration
@ -40,7 +41,7 @@ public class SecurityConfig {
) )
.logout(logout -> logout .logout(logout -> logout
.logoutUrl("/public-api/logoutProcess") .logoutUrl("/public-api/logoutProcess")
.logoutSuccessUrl("/login.html") .logoutSuccessHandler(this.logoutSuccessHandler())
.invalidateHttpSession(true) .invalidateHttpSession(true)
.deleteCookies("JSESSIONID") .deleteCookies("JSESSIONID")
) )
@ -79,6 +80,11 @@ public class SecurityConfig {
return httpSecurity.build(); return httpSecurity.build();
} }
@Bean
public LogoutSuccessHandler logoutSuccessHandler() {
return new ConsoleLogoutSuccessHandler();
}
@Bean @Bean
public PasswordEncoder getPasswordEncoder() { public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();

@ -37,7 +37,7 @@ server:
include-exception: true include-exception: true
include-message: always include-message: always
include-stacktrace: always include-stacktrace: always
port: 9443 port: 443
ssl: ssl:
enabled: true enabled: true
certificate: ${user.home}/.vite-plugin-mkcert/bsm-lab.dev.pem certificate: ${user.home}/.vite-plugin-mkcert/bsm-lab.dev.pem

Loading…
Cancel
Save