로그인 로그 및 단어사전 수정자 추가

This commit is contained in:
hylee 2024-05-21 18:00:07 +09:00
parent 7388ea931b
commit de080a06d6
13 changed files with 500 additions and 370 deletions

View File

@ -0,0 +1,66 @@
package com.itn.admin.cmn.config;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class CustomUserDetails implements UserDetails {
private final String username;
private final String password;
private final String userId;
private final String id;
private final Collection<? extends GrantedAuthority> authorities;
public CustomUserDetails(String username, String password, String userId, String id, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.userId = userId;
this.id = id;
this.authorities = authorities;
}
public String getUserId() {
return userId;
}
public String getId() {
return id;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@ -3,7 +3,6 @@ package com.itn.admin.cmn.config;
import com.itn.admin.itn.user.mapper.UserMapper; import com.itn.admin.itn.user.mapper.UserMapper;
import com.itn.admin.itn.user.mapper.domain.UserVO; import com.itn.admin.itn.user.mapper.domain.UserVO;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
@ -31,7 +30,7 @@ public class CustomUserDetailsService implements UserDetailsService {
GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole().name()); GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole().name());
Collection<? extends GrantedAuthority> authorities = Collections.singletonList(authority); Collection<? extends GrantedAuthority> authorities = Collections.singletonList(authority);
// User 객체 생성 // CustomUserDetails 객체 생성
return new User(user.getUserId(), user.getPassword(), authorities); return new CustomUserDetails(user.getUserId(), user.getPassword(), user.getUserId(), user.getId(), authorities);
} }
} }

View File

@ -1,6 +1,7 @@
package com.itn.admin.cmn.config; package com.itn.admin.cmn.config;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.itn.admin.itn.user.service.UserService;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -26,9 +27,11 @@ import java.io.PrintWriter;
public class SecurityConfig { public class SecurityConfig {
private final CustomUserDetailsService customUserDetailsService; private final CustomUserDetailsService customUserDetailsService;
private final UserService userService;
public SecurityConfig(CustomUserDetailsService customUserDetailsService) { public SecurityConfig(CustomUserDetailsService customUserDetailsService, UserService userService) {
this.customUserDetailsService = customUserDetailsService; this.customUserDetailsService = customUserDetailsService;
this.userService = userService;
} }
@Bean @Bean
@ -89,8 +92,10 @@ public class SecurityConfig {
public AuthenticationSuccessHandler customAuthenticationSuccessHandler() { public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
return (request, response, authentication) -> { return (request, response, authentication) -> {
// 디버깅 정보를 콘솔에 출력 // 디버깅 정보를 콘솔에 출력
System.out.println("Authentication successful. Username: " + authentication.getName()); CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
System.out.println("Authorities: " + authentication.getAuthorities()); String userId = userDetails.getUserId();
String id = userDetails.getId();
userService.loginLog(id);
response.setStatus(HttpStatus.OK.value()); response.setStatus(HttpStatus.OK.value());
response.sendRedirect("/"); response.sendRedirect("/");
}; };

View File

@ -0,0 +1,19 @@
package com.itn.admin.cmn.config;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
public class SecurityUtil {
public static String getCurrentUserId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
Object principal = authentication.getPrincipal();
if (principal instanceof CustomUserDetails) {
return ((CustomUserDetails) principal).getId();
}
}
return null;
}
}

View File

@ -3,12 +3,14 @@ package com.itn.admin.cmn.config;
import com.itn.admin.itn.user.mapper.domain.UserVO; import com.itn.admin.itn.user.mapper.domain.UserVO;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
@Slf4j
public class UserInterceptor implements HandlerInterceptor { public class UserInterceptor implements HandlerInterceptor {
@Override @Override
@ -33,6 +35,8 @@ public class UserInterceptor implements HandlerInterceptor {
userName = principal.toString(); userName = principal.toString();
} }
log.info("userId : [{}]",userId.toString());
log.info("userName : [{}]",userName.toString());
modelAndView.addObject("userId", userId); modelAndView.addObject("userId", userId);
modelAndView.addObject("userName", userName); modelAndView.addObject("userName", userName);
} }

View File

@ -27,6 +27,7 @@ public class DictionaryVO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Long id; private Long id;
private String userId;
private String koreanWord; private String koreanWord;
private String englishWord; private String englishWord;
private String abbreviation; private String abbreviation;
@ -35,6 +36,7 @@ public class DictionaryVO implements Serializable {
private String frstRegisterId; private String frstRegisterId;
private String frstRegistPnttm; private String frstRegistPnttm;
private String lastUpdusrId; private String lastUpdusrId;
private String lastUpdusrName;
private String lastUpdtPnttm; private String lastUpdtPnttm;
} }

View File

@ -1,5 +1,6 @@
package com.itn.admin.itn.dictionary.service.impl; package com.itn.admin.itn.dictionary.service.impl;
import com.itn.admin.cmn.config.SecurityUtil;
import com.itn.admin.itn.dictionary.mapper.DictionaryMapper; import com.itn.admin.itn.dictionary.mapper.DictionaryMapper;
import com.itn.admin.itn.dictionary.mapper.domain.DictionaryVO; import com.itn.admin.itn.dictionary.mapper.domain.DictionaryVO;
import com.itn.admin.itn.dictionary.service.DictionaryService; import com.itn.admin.itn.dictionary.service.DictionaryService;
@ -30,6 +31,15 @@ public class DictionaryServiceImpl implements DictionaryService {
log.info("Received data for update: [{}]", dictionaryMap); log.info("Received data for update: [{}]", dictionaryMap);
// 현재 인증된 사용자 정보 가져오기
String userId = SecurityUtil.getCurrentUserId();
if (userId == null) {
log.warn("Failed to retrieve current user ID.");
throw new IllegalStateException("Current user ID is not available");
}
log.info("Updating by user: [{}]", userId);
Map<String, Object> data = (Map<String, Object>) dictionaryMap.get("data"); Map<String, Object> data = (Map<String, Object>) dictionaryMap.get("data");
log.info("data: [{}]", data); log.info("data: [{}]", data);
String action = (String) dictionaryMap.get("action"); String action = (String) dictionaryMap.get("action");
@ -46,6 +56,7 @@ public class DictionaryServiceImpl implements DictionaryService {
} }
} else { } else {
DictionaryVO dictionaryVO = getSingleDate(dictionaryMap); DictionaryVO dictionaryVO = getSingleDate(dictionaryMap);
dictionaryVO.setUserId(userId);
if("edit".equals(action)) { if("edit".equals(action)) {
log.info("edit :: data for update: [{}]", dictionaryVO); log.info("edit :: data for update: [{}]", dictionaryVO);
dictionaryMapper.save(dictionaryVO); dictionaryMapper.save(dictionaryVO);

View File

@ -6,6 +6,7 @@ import com.itn.admin.itn.user.mapper.domain.UserVO;
import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -14,7 +15,7 @@ import java.util.stream.Collectors;
public interface UserMapper { public interface UserMapper {
// UserVO save(UserVO userVO); // UserVO save(UserVO userVO);
@Select("SELECT user_id AS userId, user_pw AS password, user_name AS username, role FROM users WHERE user_id = #{userId}") @Select("SELECT id, user_id AS userId, user_pw AS password, user_name AS username, role FROM users WHERE user_id = #{userId}")
UserVO getUserById(String userId); UserVO getUserById(String userId);
@Select("SELECT id, user_id AS userId, user_pw AS password, user_name AS username, role FROM users WHERE id = #{id}") @Select("SELECT id, user_id AS userId, user_pw AS password, user_name AS username, role FROM users WHERE id = #{id}")
@ -37,4 +38,7 @@ public interface UserMapper {
List<UserVO> findAll(UserVO userVO); List<UserVO> findAll(UserVO userVO);
void updateRole(UserVO user); void updateRole(UserVO user);
@Insert("INSERT INTO login_logs (id, FRST_REGIST_PNTTM) VALUES (#{id}, now())")
void loginLog(String id);
} }

View File

@ -14,4 +14,6 @@ public interface UserService {
Map<String, Object> getList(UserVO userVO); Map<String, Object> getList(UserVO userVO);
RestResponse updateRole(String id, Role role); RestResponse updateRole(String id, Role role);
void loginLog(String id);
} }

View File

@ -72,5 +72,11 @@ public class UserServiceImpl implements UserService {
.build(); .build();
} }
@Override
public void loginLog(String id) {
userMapper.loginLog(id);
}
} }

View File

@ -8,17 +8,20 @@
<select id="findAll" resultType="dictionaryVO"> <select id="findAll" resultType="dictionaryVO">
<!-- <select id="findAll" resultType="com.itn.admin.itn.dictionary.mapper.domain.DictionaryVO" parameterType="com.itn.admin.itn.dictionary.mapper.domain.DictionaryVO">--> <!-- <select id="findAll" resultType="com.itn.admin.itn.dictionary.mapper.domain.DictionaryVO" parameterType="com.itn.admin.itn.dictionary.mapper.domain.DictionaryVO">-->
SELECT SELECT
id a.id
, KOREAN_WORD , a.KOREAN_WORD
, ENGLISH_WORD , a.ENGLISH_WORD
, ABBREVIATION , a.ABBREVIATION
, IS_CONFIRMED , a.IS_CONFIRMED
, IS_ACTIVE , a.IS_ACTIVE
, FRST_REGISTER_ID , a.FRST_REGISTER_ID
, FRST_REGIST_PNTTM , a.FRST_REGIST_PNTTM
, LAST_UPDUSR_ID , a.LAST_UPDUSR_ID
, LAST_UPDT_PNTTM , a.LAST_UPDT_PNTTM
FROM dictionary , b.USER_NAME as lastUpdusrName
FROM dictionary a
LEFT JOIN users b
ON a.LAST_UPDUSR_ID = b.ID
</select> </select>
@ -41,6 +44,9 @@
<if test="isActive != null"> <if test="isActive != null">
IS_ACTIVE = #{isActive}, IS_ACTIVE = #{isActive},
</if> </if>
<if test="userId != null">
LAST_UPDUSR_ID = #{userId},
</if>
LAST_UPDT_PNTTM = NOW() LAST_UPDT_PNTTM = NOW()
WHERE id = #{id} WHERE id = #{id}
</update> </update>

View File

@ -28,7 +28,7 @@
<body layout:fragment="body"> <body layout:fragment="body">
<div class="wrapper"> <div class="wrapper">
<div th:replace="~{fragments/top_nav :: topFragment}"/> <div th:replace="~{fragments/top_nav :: topFragment}"/>
<!-- Main Sidebar Container --> <!-- Main Sidebar Container -->
@ -77,7 +77,8 @@
<th>영어단어</th> <th>영어단어</th>
<th>약어</th> <th>약어</th>
<th>확정</th> <th>확정</th>
<!-- <th>isActive</th>--> <th>최근수정자</th>
<th>수정시간</th>
</tr> </tr>
</thead> </thead>
</table> </table>
@ -108,37 +109,37 @@
<!-- Control sidebar content goes here --> <!-- Control sidebar content goes here -->
</aside> </aside>
<!-- /.control-sidebar --> <!-- /.control-sidebar -->
</div> </div>
<!-- ./wrapper --> <!-- ./wrapper -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.css"> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/3.0.2/css/buttons.dataTables.css"> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/3.0.2/css/buttons.dataTables.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/select/2.0.2/css/select.dataTables.css"> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/select/2.0.2/css/select.dataTables.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/datetime/1.5.2/css/dataTables.dateTime.min.css"> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/datetime/1.5.2/css/dataTables.dateTime.min.css">
<link rel="stylesheet" th:href="@{/plugins/datatable_editor/css/editor.dataTables.css}"> <link rel="stylesheet" th:href="@{/plugins/datatable_editor/css/editor.dataTables.css}">
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.js"></script> <script src="https://cdn.datatables.net/2.0.7/js/dataTables.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.2/js/dataTables.buttons.js"></script> <script src="https://cdn.datatables.net/buttons/3.0.2/js/dataTables.buttons.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script> <script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
<script src="https://cdn.datatables.net/select/2.0.2/js/dataTables.select.js"></script> <script src="https://cdn.datatables.net/select/2.0.2/js/dataTables.select.js"></script>
<script src="https://cdn.datatables.net/select/2.0.2/js/select.dataTables.js"></script> <script src="https://cdn.datatables.net/select/2.0.2/js/select.dataTables.js"></script>
<script src="https://cdn.datatables.net/datetime/1.5.2/js/dataTables.dateTime.min.js"></script> <script src="https://cdn.datatables.net/datetime/1.5.2/js/dataTables.dateTime.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.6.3/papaparse.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.6.3/papaparse.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.html5.min.js"></script> <script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.html5.min.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.print.min.js"></script> <script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.print.min.js"></script>
<script th:src="@{/plugins/datatable_editor/js/dataTables.editor.js}"></script> <script th:src="@{/plugins/datatable_editor/js/dataTables.editor.js}"></script>
<script th:src="@{/plugins/datatable_editor/js/editor.dataTables.js}"></script> <script th:src="@{/plugins/datatable_editor/js/editor.dataTables.js}"></script>
<script> <script>
// https://editor.datatables.net/examples/simple/simple.html // https://editor.datatables.net/examples/simple/simple.html
var editor = new DataTable.Editor({ var editor = new DataTable.Editor({
ajax: { ajax: {
type: 'POST', type: 'POST',
@ -180,11 +181,17 @@
{ label: 'Yes', value: 'Y' }, { label: 'Yes', value: 'Y' },
{ label: 'No', value: 'N' } { label: 'No', value: 'N' }
] ]
},
{
label: '최근수정자:',
name: 'lastUpdusrName'
},
{
label: '수정시간:',
name: 'lastUpdtPnttm',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss'
} }
// ,{
// label: 'isActive:',
// name: 'isActive'
// }
] ]
}); });
@ -305,8 +312,9 @@
render: function(data, type, row) { render: function(data, type, row) {
return data === 'Y' ? 'Yes' : 'No'; return data === 'Y' ? 'Yes' : 'No';
} }
} },
// ,{ data: 'isActive' } { data: 'lastUpdusrName' },
{ data: 'lastUpdtPnttm' }
], ],
layout: { layout: {
topStart: { topStart: {
@ -400,10 +408,8 @@
} }
} }
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -35,7 +35,7 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1 class="m-0">출퇴근 관리</h1> <h1 class="m-0">사용자 관리</h1>
</div><!-- /.col --> </div><!-- /.col -->
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">