박민혁 박민혁 07-23
240723 박민혁 로그인 시큐리티 설정
@614d1ef09110af89f0bd5eca1cd74a0eb83017d6
build.gradle
--- build.gradle
+++ build.gradle
@@ -57,6 +57,18 @@
     // log4jdbc
     implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4:1.16'
 
+    //JWT
+    implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
+    implementation 'io.jsonwebtoken:jjwt-impl:0.12.5'
+    implementation 'io.jsonwebtoken:jjwt-jackson:0.12.5'
+
+    // redis
+    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
+    implementation 'org.springframework.boot:spring-boot-starter-cache'
+
+    // gson
+    implementation 'com.google.code.gson:gson:2.10.1'
+
 }
 
 tasks.named('test') {
src/main/java/com/takensoft/sj_wmp/common/confing/SecurityConfig.java
--- src/main/java/com/takensoft/sj_wmp/common/confing/SecurityConfig.java
+++ src/main/java/com/takensoft/sj_wmp/common/confing/SecurityConfig.java
@@ -1,22 +1,96 @@
 package com.takensoft.sj_wmp.common.confing;
 
+import com.takensoft.sj_wmp.common.filter.JwtFilter;
+import com.takensoft.sj_wmp.common.filter.LoginFilter;
+import com.takensoft.sj_wmp.common.util.JwtUtil;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import java.util.Collections;
+import java.util.List;
 
 @Configuration
 @EnableWebSecurity
 public class SecurityConfig {
 
+    private final AuthenticationConfiguration authenticationConfiguration;
+    private final JwtUtil jwtUtil;
+    private final CommonConfig commonConfig;
+
+    private static String FRONT_URL; // 프론트 경로
+    private static long JWT_ACCESSTIME; // access 토큰 유지 시간
+    private static int COOKIE_TIME; // 쿠키 유지 시간
+
+    public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JwtUtil jwtUtil, CommonConfig commonConfig,
+                          @Value("${frontUrl}") String fUrl, @Value("${spring.jwt.accessTime}") long aTime, @Value("${spring.jwt.refreshTime}") long rTime, @Value("${cookie.time}") int ctime
+    ) {
+        this.authenticationConfiguration = authenticationConfiguration;
+        this.jwtUtil = jwtUtil;
+        this.commonConfig = commonConfig;
+
+        this.FRONT_URL = fUrl;
+        this.JWT_ACCESSTIME = aTime;
+        this.COOKIE_TIME = ctime;
+    }
+
+    // AuthenticationManager Bean 등록
+    @Bean
+    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
+        return configuration.getAuthenticationManager();
+    }
+
     @Bean
     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
-        http.csrf((auth) -> auth.disable());
+        http.csrf((auth) -> auth.disable()); // CSRF 비활성화
+        http.formLogin((auth) -> auth.disable()); // 폼 로그인 비활성화
+        http.httpBasic((auth) -> auth.disable()); // HTTP Basic 비활성화
+        http.sessionManagement((auth) -> auth.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // 세션 비활성화
+        http.cors((auth) -> auth.configurationSource(corsConfigurationSource())); // CORS 설정
+        http.authorizeHttpRequests((auth) -> auth
+                .requestMatchers( "/auth/**", "/auth/login.json").permitAll() // /user/** 경로는 모두 허용
+                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() // swagger 진입 허용
+                .anyRequest().authenticated()); // 나머지 경로는 인증 필요
+
+        // jwt 필터 처리 적용
+        http.addFilterBefore(new JwtFilter(jwtUtil, commonConfig), LoginFilter.class); // 토큰 검증 필터
+        http.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, commonConfig, JWT_ACCESSTIME), UsernamePasswordAuthenticationFilter.class);
 
         return http.build();
     }
 
+    // 패스워드 암호화
+    @Bean
+    public BCryptPasswordEncoder bCryptPasswordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    // CORS 설정
+    @Bean
+    public CorsConfigurationSource corsConfigurationSource() {
+        CorsConfiguration configuration = new CorsConfiguration();
+        configuration.setAllowedOrigins(Collections.singletonList(FRONT_URL));
+        configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
+        configuration.setAllowedHeaders(List.of("*"));
+        configuration.setExposedHeaders(Collections.singletonList("Authorization"));
+
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", configuration);
+
+        return source;
+    }
+
     // inmemory방식 유저 설정
 //    @Bean
 //    public InMemoryUserDetailsManager userDetailsService() {
src/main/java/com/takensoft/sj_wmp/common/confing/SwaggerConfig.java
--- src/main/java/com/takensoft/sj_wmp/common/confing/SwaggerConfig.java
+++ src/main/java/com/takensoft/sj_wmp/common/confing/SwaggerConfig.java
@@ -3,15 +3,31 @@
 import io.swagger.v3.oas.models.Components;
 import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
 public class SwaggerConfig {
     @Bean
-    public OpenAPI openAPI() {
+    public OpenAPI api() {
+        // SecurityScheme 설정
+        SecurityScheme apiKey = new SecurityScheme()
+                .type(SecurityScheme.Type.HTTP)
+                .in(SecurityScheme.In.HEADER)
+                .name("Authorization")
+                .scheme("bearer")
+                .bearerFormat("JWT");
+
+        // SecurityRequirement 설정
+        SecurityRequirement securityRequirement = new SecurityRequirement()
+                .addList("Bearer Token");
+
+        // OpenAPI 설정
         return new OpenAPI()
-                .components(new Components())
+                .components(new Components().addSecuritySchemes("Bearer Token", apiKey))
+                .addSecurityItem(securityRequirement)
                 .info(apiInfo());
     }
     private Info apiInfo() {
 
src/main/java/com/takensoft/sj_wmp/common/filter/JwtFilter.java (added)
+++ src/main/java/com/takensoft/sj_wmp/common/filter/JwtFilter.java
@@ -0,0 +1,85 @@
+package com.takensoft.sj_wmp.common.filter;
+
+import com.takensoft.sj_wmp.common.confing.CommonConfig;
+import com.takensoft.sj_wmp.common.util.ErrorResponse;
+import com.takensoft.sj_wmp.common.util.JwtUtil;
+import com.takensoft.sj_wmp.wmp.auth.vo.UserAuthorVO;
+import com.takensoft.sj_wmp.wmp.auth.vo.UserVO;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.JwtException;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Component
+@RequiredArgsConstructor
+public class JwtFilter extends OncePerRequestFilter  {
+
+    private final JwtUtil jwtUtil;
+    private final CommonConfig commonConfig;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws jakarta.servlet.ServletException, IOException {
+        // 헤더에서 access에 대한 토큰을 꺼냄
+        String accessToken = request.getHeader("Authorization");
+        // 토큰이 없다면 다음 필터로 넘김
+        if (accessToken == null) {
+            filterChain.doFilter(request, response);
+            return;
+        }
+        try {
+            // 토큰 만료 여부 검증
+            if (jwtUtil.isExpired(accessToken)) {
+                throw new JwtException("Token expired");
+            }
+            // 토큰에서 페이로드 확인[ 발급시 명시 ]
+            String category = jwtUtil.getCategory(accessToken);
+            if (!category.equals("Authorization")) {
+                throw new JwtException("Wrong Token");
+            }
+            // 토큰에서 사용자 정보 추출
+            UserVO user = new UserVO();
+            List<UserAuthorVO> author = jwtUtil.getRoles(accessToken);
+            user.setLoginId(jwtUtil.getLoginId(accessToken));
+            user.setUsid(jwtUtil.getUsid(accessToken));
+            user.setAuthor(author);
+
+            // 시큐리티 컨텍스트에 사용자 정보 설정
+            Authentication authToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
+            SecurityContextHolder.getContext().setAuthentication(authToken);
+            // 다음 필터로 이동
+            filterChain.doFilter(request, response);
+        } catch (JwtException | InsufficientAuthenticationException e) {
+            // 토큰 검증 실패 시 클라이언트에게 에러 응답을 위한 객체 생성
+            ErrorResponse errorResponse = new ErrorResponse();
+            // 에러 응답 메시지 설정
+            if (e instanceof ExpiredJwtException) {
+                errorResponse.setMessage("Token expired");
+            } else {
+                errorResponse.setMessage(e.getMessage());
+            }
+            errorResponse.setPath(request.getRequestURI()); // 오류 경로 설정
+            errorResponse.setError(HttpStatus.UNAUTHORIZED.getReasonPhrase()); // 오류 원인 설정
+            errorResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); // 상태코드 설정
+            errorResponse.setTimestamp(LocalDateTime.now()); // 응답 시간 설정
+
+            // 응답 헤더 설정 및 json 응답 전송
+            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            response.getOutputStream().write(commonConfig.getObjectMapper().writeValueAsBytes(errorResponse));
+        }
+    }
+}
 
src/main/java/com/takensoft/sj_wmp/common/filter/LoginFilter.java (added)
+++ src/main/java/com/takensoft/sj_wmp/common/filter/LoginFilter.java
@@ -0,0 +1,97 @@
+package com.takensoft.sj_wmp.common.filter;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.takensoft.sj_wmp.common.confing.CommonConfig;
+import com.takensoft.sj_wmp.common.util.JwtUtil;
+import com.takensoft.sj_wmp.wmp.auth.vo.UserVO;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class LoginFilter extends UsernamePasswordAuthenticationFilter{
+
+    private final AuthenticationManager authenticationManager;
+    private final JwtUtil jwtUtil;
+    private final CommonConfig commonConfig;
+
+    private static long JWT_ACCESSTIME; // access 토큰 유지 시간
+
+    public LoginFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil, CommonConfig commonConfig, @Value("${spring.jwt.accessTime}") long jwtAccesstime) {
+        this.authenticationManager = authenticationManager;
+        this.jwtUtil = jwtUtil;
+        this.commonConfig = commonConfig;
+
+        this.JWT_ACCESSTIME = jwtAccesstime;
+
+        setFilterProcessesUrl("/auth/login.json");
+    }
+
+    /**
+     * 로그인 검증
+     */
+    @Override
+    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+        try {
+            Map<String, String> loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class);
+            String loginId = loginData.get("loginId");
+            String password = loginData.get("password");
+
+            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(loginId, password);
+            return authenticationManager.authenticate(authToken);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to parse authentication request body", e);
+        }
+    }
+
+    // 인증 성공 시
+    @Override
+    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setContentType("application/json;charset=UTF-8");
+
+        UserVO user = (UserVO) authResult.getPrincipal();
+        String usid = user.getUsid();
+        String loginId = user.getUsername();
+        String userNm = user.getUserNm();
+        List<String> author = (List) user.getAuthorities();
+
+        String accessToken = jwtUtil.createJwt("Authorization", usid, loginId, userNm, author, JWT_ACCESSTIME);
+
+        Map<String, Object> resData = new HashMap<>();
+        resData.put("result", "success");
+        resData.put("message", "로그인 성공");
+
+        // 헤더 방식
+        response.setHeader("Authorization", accessToken);
+
+        response.getWriter().write(new ObjectMapper().writeValueAsString(resData));
+    }
+
+    // 인증 실패 시
+    @Override
+    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        response.setContentType("application/json;charset=UTF-8");
+
+        Map<String, String> responseData = new HashMap<>();
+        responseData.put("message", "Authentication failed: " + failed.getMessage());
+
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+        response.setStatus(HttpStatus.UNAUTHORIZED.value());
+        response.getWriter().write(commonConfig.getObjectMapper().writeValueAsString(responseData));
+    }
+}
 
src/main/java/com/takensoft/sj_wmp/common/idgen/context/ContextIdgen.java (added)
+++ src/main/java/com/takensoft/sj_wmp/common/idgen/context/ContextIdgen.java
@@ -0,0 +1,57 @@
+package com.takensoft.sj_wmp.common.idgen.context;
+
+
+import com.takensoft.sj_wmp.common.idgen.service.IdgenService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ContextIdgen {
+
+    // 회원정보
+    @Bean(name = "userIdgn")
+    public IdgenService user() {
+        IdgenService idgenServiceImpl = new IdgenService();
+        idgenServiceImpl.setCipers(15);
+        idgenServiceImpl.setFillChar('0');
+        idgenServiceImpl.setPrefix("USID_");
+        idgenServiceImpl.setTblNm("USID");
+        return idgenServiceImpl;
+    }
+
+    // 위원 정보
+    @Bean(name = "mfcmmIdgn")
+    public IdgenService mfcmm() {
+        IdgenService idgenServiceImpl = new IdgenService();
+        idgenServiceImpl.setCipers(15);
+        idgenServiceImpl.setFillChar('0');
+        idgenServiceImpl.setPrefix("MFEMM_");
+        idgenServiceImpl.setTblNm("MFEMM_ID");
+        return idgenServiceImpl;
+    }
+
+    // 위원회 정보
+    @Bean(name = "cmitIdgn")
+    public IdgenService cmit() {
+        IdgenService idgenServiceImpl = new IdgenService();
+        idgenServiceImpl.setCipers(15);
+        idgenServiceImpl.setFillChar('0');
+        idgenServiceImpl.setPrefix("CMIT_");
+        idgenServiceImpl.setTblNm("CMIT_ID");
+        return idgenServiceImpl;
+    }
+
+    // 파일매니저
+    @Bean(name = "fileMngIdgn")
+    public IdgenService fileMng() {
+        IdgenService idgenServiceImpl = new IdgenService();
+        idgenServiceImpl.setCipers(15);
+        idgenServiceImpl.setFillChar('0');
+        idgenServiceImpl.setPrefix("FILE_MNG_");
+        idgenServiceImpl.setTblNm("FILE_MANAGE_ID");
+        return idgenServiceImpl;
+    }
+
+
+
+}(파일 끝에 줄바꿈 문자 없음)
 
src/main/java/com/takensoft/sj_wmp/common/idgen/dao/IdgenMapper.java (added)
+++ src/main/java/com/takensoft/sj_wmp/common/idgen/dao/IdgenMapper.java
@@ -0,0 +1,30 @@
+package com.takensoft.sj_wmp.common.idgen.dao;
+
+
+import com.takensoft.sj_wmp.common.idgen.vo.IdgenVO;
+import org.egovframe.rte.psl.dataaccess.mapper.Mapper;
+
+/**
+ * @author  :takensoft
+ * @since   : 2024.04.01
+ *
+ * Idgen 관련 Mapper
+ */
+@Mapper
+public interface IdgenMapper {
+
+    /**
+     * 테이블명으로 시퀀스 조회
+     * @author takensoft
+     * @since 2024.04.01
+     */
+    IdgenVO selectNextId(String tblNm);
+
+    /**
+     * 시퀀스 등록 및 수정
+     * @author takensoft
+     * @since 2024.04.01
+     */
+    void upsertSeqNmg(IdgenVO idgenVO);
+
+}
 
src/main/java/com/takensoft/sj_wmp/common/idgen/service/IdgenService.java (added)
+++ src/main/java/com/takensoft/sj_wmp/common/idgen/service/IdgenService.java
@@ -0,0 +1,52 @@
+package com.takensoft.sj_wmp.common.idgen.service;
+
+
+import com.takensoft.sj_wmp.common.idgen.dao.IdgenMapper;
+import com.takensoft.sj_wmp.common.idgen.vo.IdgenVO;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@Setter
+@Getter
+public class IdgenService {
+
+    protected Log log = LogFactory.getLog(this.getClass());
+    private String prefix;      // 아이디에 고정적으로 붙일 명칭
+    private int cipers;         // 고정적인 명칭(prefix)를 제외한 아이디의 길이
+    private char fillChar;      // 0
+    private String tblNm;     // 테이블명
+
+    @Autowired
+    private IdgenMapper idgenMapper;
+
+    public String getNextStringId() {
+        IdgenVO idgenVO = idgenMapper.selectNextId(tblNm);		// 다음 아이디값 조회
+        boolean firstFlag = false;		// 신규 생성인지 확인
+        if(idgenVO == null) {			// 신규 생성일 경우
+            idgenVO = new IdgenVO();	// 신규 객체 생성
+            idgenVO.setAftrId(1);		// 신규 아이디값 설정
+            idgenVO.setTblNm(tblNm);	// 신규 테이블명 설정
+            firstFlag = true;			// 신규 등록 여부
+        }
+        // 1. 아이디변수 생성(String)
+        // 2. 아이디 변수 prefix를 넣고 + fillChar를 cipers-prefix.length-id.length 만큼 넣고 + (id + 1)를 넣는다.
+        String nextId = "";				// 아이디값 객체 생성
+        nextId = prefix;				// prefix 설정
+        // 2번째부터는 1씩 업데이트
+        if(!firstFlag) {				// 기존에 데이터가 존재하는 경우
+            idgenVO.setTblNm(tblNm);
+            idgenVO.setAftrId(idgenVO.getAftrId() + 1);
+        }
+        idgenMapper.upsertSeqNmg(idgenVO);	// 아이디값을 1추가하여 데이터 수정
+        int fillCharSize = cipers - (int)(Math.log10(idgenVO.getAftrId()) + 1);		// 아이디에 0이 들어갈 길이 지정
+        for(int i = 0 ; i < fillCharSize; i++) {		// 아이디에 0 설정
+            nextId = nextId + fillChar;		// ex_ TEST_00 + 0 0이 추가되는중
+        }
+        nextId = nextId + String.valueOf(idgenVO.getAftrId());	// 아이디 생성 완료
+        return nextId;
+    }
+
+}
 
src/main/java/com/takensoft/sj_wmp/common/idgen/vo/IdgenVO.java (added)
+++ src/main/java/com/takensoft/sj_wmp/common/idgen/vo/IdgenVO.java
@@ -0,0 +1,18 @@
+package com.takensoft.sj_wmp.common.idgen.vo;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+@SuppressWarnings("serial")
+@NoArgsConstructor
+@Getter
+@Setter
+public class IdgenVO implements Serializable {
+    // 테이블 이름
+    private String tblNm;
+    // 다음 아이디
+    private int aftrId;
+}
 
src/main/java/com/takensoft/sj_wmp/common/util/ErrorResponse.java (added)
+++ src/main/java/com/takensoft/sj_wmp/common/util/ErrorResponse.java
@@ -0,0 +1,24 @@
+package com.takensoft.sj_wmp.common.util;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@Getter
+@Setter
+public class ErrorResponse {
+
+    int status;
+    String error;
+    String message;
+    String path;
+
+    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
+    LocalDateTime timestamp;
+    List<Error> errorList = new ArrayList<Error>();
+}
 
src/main/java/com/takensoft/sj_wmp/common/util/JwtUtil.java (added)
+++ src/main/java/com/takensoft/sj_wmp/common/util/JwtUtil.java
@@ -0,0 +1,92 @@
+package com.takensoft.sj_wmp.common.util;
+
+import com.takensoft.sj_wmp.wmp.auth.vo.AuthVO;
+import com.takensoft.sj_wmp.wmp.auth.vo.UserAuthorVO;
+import com.takensoft.sj_wmp.wmp.auth.vo.UserVO;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import jakarta.servlet.http.Cookie;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+@Component
+public class JwtUtil {
+
+    private SecretKey secretKey;
+
+    public JwtUtil(@Value("${spring.jwt.secret}") String secret) {
+        this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
+    }
+
+    // 토큰 생성 메서드
+    public String createJwt(String category, String usid, String loginId, String userNm, List<String> author, long expiredMs) {
+
+        return Jwts.builder()
+                .claim("category", category)
+                .claim("usid", usid)
+                .claim("loginId", loginId)
+                .claim("userNm", userNm)
+                .claim("author", author)
+                .issuedAt(new Date(System.currentTimeMillis()))
+                .expiration(new Date(System.currentTimeMillis() + expiredMs))
+                .signWith(secretKey)
+                .compact();
+    }
+    public String getCategory(String token) {
+        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("category", String.class);
+    }
+
+    // 아이디 반환 메서드
+    public String getUsid(String token) {
+        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("usid", String.class);
+    }
+    // 사용자 아이디 반환 메서드
+    public String getLoginId(String token) {
+        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("loginId", String.class);
+    }
+    // 사용자 이름 반환 메서드
+    public String getUserNm(String token) {
+        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("userNm", String.class);
+    }
+    // 접속자 토큰 기반 권한정보 추출
+    public List<UserAuthorVO> getRoles(String token) {
+        // 토큰에서 권한 정보를 가져옴
+        Claims claims = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload();
+        List<HashMap> roles = claims.get("author", List.class);
+        List<UserAuthorVO> authorList = new ArrayList<>();
+        if (roles != null && !roles.isEmpty()) {
+            for(Map role : roles) {
+                UserAuthorVO mberAuthor = new UserAuthorVO();
+                mberAuthor.setAuthorCode(role.get("authority").toString());
+                authorList.add(mberAuthor);
+            }
+        }
+        System.out.println("authorList : " + authorList);
+        return authorList;
+    }
+
+    // 토큰 소멸 여부
+    public Boolean isExpired(String token) {
+        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
+    }
+
+    // 로그인 사용자 아이디 조회
+    public String getWriter() {
+        String loginId = null;
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if(authentication != null && authentication.isAuthenticated()) {
+            Object principal = authentication.getPrincipal();
+            if(principal instanceof UserDetails) loginId = ((UserVO) authentication.getPrincipal()).getLoginId();
+        }
+        return loginId;
+    }
+
+}
 
src/main/java/com/takensoft/sj_wmp/wmp/auth/dao/AuthDAO.java (added)
+++ src/main/java/com/takensoft/sj_wmp/wmp/auth/dao/AuthDAO.java
@@ -0,0 +1,24 @@
+package com.takensoft.sj_wmp.wmp.auth.dao;
+
+import com.takensoft.sj_wmp.wmp.auth.vo.UserVO;
+import org.egovframe.rte.psl.dataaccess.mapper.Mapper;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+import java.util.Map;
+
+/**
+ * @author  : 방선주
+ * @since   : 2024.07.12
+ *
+ * 사용자 관련 Mapper
+ */
+@Mapper("authDAO")
+public interface AuthDAO {
+    int insertUser(UserVO userVO) throws Exception;
+
+    UserDetails findByUserIdSecurity(String loginId) throws UsernameNotFoundException;
+
+    int insertAuth(Map<String, Object> authMap) throws Exception;
+}
+
 
src/main/java/com/takensoft/sj_wmp/wmp/auth/dto/LoginDTO.java (added)
+++ src/main/java/com/takensoft/sj_wmp/wmp/auth/dto/LoginDTO.java
@@ -0,0 +1,35 @@
+package com.takensoft.sj_wmp.wmp.auth.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author  : takensoft
+ * @since   : 2024.04.01
+ *
+ * 로그인 관련 DTO
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class LoginDTO {
+    /**
+     * 로그인 아이디
+     */
+    @NotNull
+    private String loginId;
+    /**
+     * 비밀번호
+     */
+    @NotNull
+    private String password;
+    /**
+     * refreshToken 정보
+     */
+    private String refreshToken;
+}
 
src/main/java/com/takensoft/sj_wmp/wmp/auth/service/AuthService.java (added)
+++ src/main/java/com/takensoft/sj_wmp/wmp/auth/service/AuthService.java
@@ -0,0 +1,13 @@
+package com.takensoft.sj_wmp.wmp.auth.service;
+
+import com.takensoft.sj_wmp.wmp.auth.vo.UserVO;
+
+/**
+ * @author  : 방선주
+ * since   : 2024.07.12
+ *
+ * 인증 관련 인터페이스
+ */
+public interface AuthService {
+    int insertUser(UserVO userVO) throws Exception;
+}
 
src/main/java/com/takensoft/sj_wmp/wmp/auth/service/Impl/AuthServiceImpl.java (added)
+++ src/main/java/com/takensoft/sj_wmp/wmp/auth/service/Impl/AuthServiceImpl.java
@@ -0,0 +1,79 @@
+package com.takensoft.sj_wmp.wmp.auth.service.Impl;
+
+import com.takensoft.sj_wmp.common.idgen.service.IdgenService;
+import com.takensoft.sj_wmp.common.util.JwtUtil;
+import com.takensoft.sj_wmp.wmp.auth.dao.AuthDAO;
+import com.takensoft.sj_wmp.wmp.auth.service.AuthService;
+import com.takensoft.sj_wmp.wmp.auth.vo.AuthVO;
+import com.takensoft.sj_wmp.wmp.auth.vo.UserVO;
+import lombok.RequiredArgsConstructor;
+import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author  : 방선주
+ * @since   : 2024.07.12
+ * 사용자 관련 구현체
+ * EgovAbstractServiceImpl : 전자정부 상속
+ * AuthService : 인중 인터페이스 상속
+ */
+@Service("authService")
+@RequiredArgsConstructor
+public class AuthServiceImpl extends EgovAbstractServiceImpl implements UserDetailsService, AuthService {
+
+    private final AuthDAO authDAO;
+    private final BCryptPasswordEncoder bCryptPasswordEncoder;
+    private final JwtUtil jwtUtil;
+
+    private final IdgenService userIdgn;
+
+    @Override
+    @Transactional(readOnly = true)
+    public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
+        return authDAO.findByUserIdSecurity(loginId);
+    }
+
+    /**
+     * 사용자 등록
+     */
+    @Override
+    @Transactional
+    public int insertUser(UserVO userVO) throws Exception {
+        // TODO 동일한 계정이 있는지 확인 하는 로직 추가
+        UserVO checkLoginId = (UserVO) authDAO.findByUserIdSecurity(userVO.getLoginId());
+        if(checkLoginId != null) {
+            return -1;
+        }
+        String usid = userIdgn.getNextStringId();
+        userVO.setUsid(usid);
+
+        // 작성자 조회 및 등록
+        if(jwtUtil.getWriter() != null && !jwtUtil.getWriter().equals("")) {
+            userVO.setReg(jwtUtil.getWriter());
+        }
+        // 패스워드 암호화
+        userVO.setPassword(bCryptPasswordEncoder.encode(userVO.getPassword()));
+        // 사용자 등록
+        int result = authDAO.insertUser(userVO);
+
+        // 권한 등록
+        Map<String, Object> authMap = new HashMap<>();
+        authMap.put("usid", usid);
+        authMap.put("author_code", "USER");
+        authDAO.insertAuth(authMap);
+
+        return result;
+    }
+
+
+}
+
+
 
src/main/java/com/takensoft/sj_wmp/wmp/auth/vo/AuthVO.java (added)
+++ src/main/java/com/takensoft/sj_wmp/wmp/auth/vo/AuthVO.java
@@ -0,0 +1,32 @@
+package com.takensoft.sj_wmp.wmp.auth.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Setter
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public class AuthVO {
+    // 권한 코드
+    private String authorCode;
+    // 권한 명
+    private String authorNm;
+    // 권한 설명
+    private String authorDc;
+    // 사용 여부
+    private String useAt;
+    // 등록자 명
+    private String reg;
+    // 등록일자
+    private LocalDateTime regDt;
+    // 수정자 명
+    private String updusr;
+    // 수정일자
+    private LocalDateTime updtDt;
+
+}
 
src/main/java/com/takensoft/sj_wmp/wmp/auth/vo/UserAuthorVO.java (added)
+++ src/main/java/com/takensoft/sj_wmp/wmp/auth/vo/UserAuthorVO.java
@@ -0,0 +1,17 @@
+package com.takensoft.sj_wmp.wmp.auth.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserAuthorVO {
+    // 회원아이디
+    private String usid;
+    // 권한 코드
+    private String authorCode;
+}
 
src/main/java/com/takensoft/sj_wmp/wmp/auth/vo/UserVO.java (added)
+++ src/main/java/com/takensoft/sj_wmp/wmp/auth/vo/UserVO.java
@@ -0,0 +1,101 @@
+package com.takensoft.sj_wmp.wmp.auth.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.*;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author  : 방선주
+ * since   : 2024.05.09
+ *
+ * 사용자 관련 VO
+ */
+
+@Setter
+@Getter
+@ToString
+@AllArgsConstructor
+@NoArgsConstructor
+public class UserVO implements UserDetails {
+
+    // 사용자 아이디
+    private String usid;
+    // 로그인 아이디
+    private String loginId;
+    // 비밀번호
+    private String password;
+    // 사용자명
+    private String userNm;
+    // 이메일
+    private String email;
+    // 전화번호
+    private String telno;
+    // 계정 사용 여부
+    private boolean useAt;
+    // 등록자 명
+    private String reg;
+    // 등록일자
+    private LocalDateTime registDt;
+    // 수정자 명
+    private String updusr;
+    // 수정일자
+    private LocalDateTime updtDt;
+    // 권한
+    @JsonIgnore
+    private List<UserAuthorVO> author;
+
+    // 계정이 가지곤 있는 권한 목록 리턴
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        if (this.author != null) {
+            if (this.author.size() > 0) {
+                return this.author.stream().map(auth -> new SimpleGrantedAuthority(
+                        auth.getAuthorCode()
+                )).collect(Collectors.toList());
+            } else {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    // 계정의 id를 리턴
+    @Override
+    public String getUsername() {
+        return loginId;
+    }
+    // 계정의 패스워드 리턴
+    @Override
+    public String getPassword() {
+        return password;
+    }
+    // 계정이 만료되지 않았는지를 리턴
+    @Override
+    public boolean isAccountNonExpired() {
+        return this.useAt;
+    }
+    // 계정이 잠겨있지 않은지를 리턴
+    @Override
+    public boolean isAccountNonLocked() {
+        return this.useAt;
+    }
+    // 계정의 패스워드가 만료되지 않았는지 리턴
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return this.useAt;
+    }
+    //  계정이 사용가능한 계정인지를 리턴
+    @Override
+    public boolean isEnabled() {
+        return this.useAt;
+    }
+}
 
src/main/java/com/takensoft/sj_wmp/wmp/auth/web/AuthController.java (added)
+++ src/main/java/com/takensoft/sj_wmp/wmp/auth/web/AuthController.java
@@ -0,0 +1,68 @@
+package com.takensoft.sj_wmp.wmp.auth.web;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.takensoft.sj_wmp.wmp.auth.service.AuthService;
+import com.takensoft.sj_wmp.wmp.auth.vo.UserVO;
+import io.swagger.v3.oas.annotations.Operation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author  : 방선주
+ * since   : 2024.05.09
+ * 사용자 관련 Controller
+ */
+@RestController
+@RequiredArgsConstructor
+@Slf4j
+@RequestMapping(value="/auth")
+public class AuthController {
+
+    private final AuthService authService;
+
+    /**
+     * @author  방선주
+     * @since   2024.07.12
+     * param   UserVO
+     * @return
+     * @throws  Exception
+     *
+     * 사용자 등록
+     */
+    @PostMapping("/insertUser.json")
+    @Operation(summary = "사용자 등록")
+    public String insertUser(@RequestBody UserVO userVO) throws Exception {
+        Gson gson = new Gson();
+        JsonObject response = new JsonObject();
+
+        try {
+            int result = authService.insertUser(userVO);
+
+
+            if (result == -1) {
+                response.addProperty("status", "fail");
+                response.addProperty("message", "동일한 계정이 존재합니다.");
+                return gson.toJson(response);
+            }
+
+            response.addProperty("status", "success");
+            response.addProperty("message", "사용자가 등록되었습니다.");
+            return gson.toJson(response);
+        } catch (Exception e) {
+            response.addProperty("status", "error");
+            response.addProperty("message", e.getMessage());
+            return gson.toJson(response);
+        }
+
+
+    }
+
+
+}
src/main/resources/application.yml
--- src/main/resources/application.yml
+++ src/main/resources/application.yml
@@ -2,6 +2,13 @@
 # port setting
 server:
   port: 9080
+  servlet:
+    session:
+      cookie:
+        name: JSESSIONID
+
+# front url
+frontUrl: http://localhost:80
 
 # spring docs setting
 springdoc:
@@ -19,18 +26,29 @@
   application:
     name: sj_wmp
   #  Datasource configuration - postgresql
+  datasource:
+    driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
+    username: takensoft
+    password: tts96314728!@
+    url: jdbc:log4jdbc:postgresql://210.180.118.83:5432/ai_lms?currentSchema=ai_lms
+  #  datasource:
+#    driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
+#    username: postgres
+#    password: 1234
+#    url: jdbc:log4jdbc:postgresql://localhost/postgres?currentSchema=public
+
+  #  Datasource configuration - mariaDB
 #  datasource:
 #    driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
 #    username: root
 #    password: 1234
-#    url: jdbc:log4jdbc:postgresql://localhost/test?currentSchema=public
-
-  #  Datasource configuration - mariaDB
-  datasource:
-    driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
-    username: root
-    password: 1234
-    url: jdbc:log4jdbc:mariadb://localhost/test
+#    url: jdbc:log4jdbc:mariadb://localhost/test
+  jwt:
+    secret: 42b08045ac84dbcdf50e946bf8ad34d35af707979b3230cfb84fb8fe34236d2f
+    accessTime: 600000      # 10분 600000
+    refreshTime: 86400000   # 24시간 86400000
+cookie:
+  time: 86400
 
 mybatis:
   type-aliases-package: com.takensoft.**.**.vo, com.takensoft.**.**.dto, com.takensoft.common
 
src/main/resources/mybatis/mapper/auth/auth-SQL.xml (added)
+++ src/main/resources/mybatis/mapper/auth/auth-SQL.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.takensoft.sj_wmp.wmp.auth.dao.AuthDAO">
+
+    <resultMap id="userMap" type="UserVO">
+        <result property="usid" column="usid"/>
+        <result property="loginId" column="login_id"/>
+        <result property="password" column="password"/>
+        <result property="userNm" column="user_nm"/>
+        <result property="email" column="email"/>
+        <result property="telno" column="telno"/>
+        <result property="useAt" column="use_at"/>
+        <result property="reg" column="reg"/>
+        <result property="registDt" column="regist_dt"/>
+        <result property="updusr" column="updusr"/>
+        <result property="updtDt" column="updt_dt"/>
+        <collection property="author" javaType="java.util.ArrayList" ofType="UserAuthorVO" select="findByUserAuthor" column="usid" />
+    </resultMap>
+
+    <resultMap id="authMap" type="UserAuthorVO">
+        <result property="authorCode" column="author_code" />
+    </resultMap>
+    <!--
+         작성자 : 방선주
+         작성일 : 2024.07.12
+         내 용 : 로그인 시 계정 검색
+     -->
+    <select id="findByUserIdSecurity" parameterType="String" resultMap="userMap">
+        SELECT usid
+             , login_id
+             , "password"
+             , user_nm
+             , email
+             , telno
+             , use_at
+             , reg
+             , regist_dt
+             , updusr
+             , updt_dt
+        FROM users
+        WHERE login_id = #{loginId}
+          AND use_at = 'Y'
+    </select>
+    <!--
+         작성자 : 방선주
+         작성일 : 2024.07.12
+         내 용 : 로그인 시 계정 권한 검색
+     -->
+    <select id="findByUserAuthor" parameterType="String" resultMap="authMap">
+        SELECT author_code
+        FROM users_author
+        WHERE usid = #{usid}
+    </select>
+    <!--
+        작성자 : 방선주
+        작성일 : 2024.07.12
+        내 용 : 사용자 등록 관련
+    -->
+    <insert id="insertUser" parameterType="AuthVO">
+        INSERT INTO users( usid
+                         , login_id
+                         , "password"
+                         , user_nm
+                         , email
+                         , telno
+                         , use_at
+                         , reg
+                         , regist_dt
+                  )VALUES( #{usid}
+                         , #{loginId}
+                         , #{password}
+                         , #{userNm}
+                         , #{email}
+                         , #{telno}
+                         , 'Y'
+                         , #{reg}
+                         , now()
+        );
+    </insert>
+    <!--
+       작성자 : 방선주
+       작성일 : 2024.07.18
+       내 용 : 사용자 권한 등록
+   -->
+    <insert id="insertAuth" parameterType="HashMap">
+        INSERT INTO users_author(
+                   usid
+                 , author_code
+        ) VALUES (
+                   #{usid}
+                 , #{author_code}
+        );
+    </insert>
+
+
+</mapper>(파일 끝에 줄바꿈 문자 없음)
 
src/main/resources/mybatis/mapper/common/idgen-SQL.xml (added)
+++ src/main/resources/mybatis/mapper/common/idgen-SQL.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.takensoft.sj_wmp.common.idgen.dao.IdgenMapper">
+    <!--
+        작 성 자 : takensoft
+        작 성 일 : 2024.01.01
+        내    용 : 테이블명으로 시퀀스 조회
+    -->
+    <select id="selectNextId" parameterType="String" resultType="IdgenVO">
+        SELECT aftr_id
+        FROM cmmn_idgn
+        WHERE tbl_nm = #{tblNm}
+    </select>
+
+    <!--
+        작 성 자 : takensoft
+        작 성 일 : 2024.04.01
+        내    용 : 시퀀스 등록 및 수정
+    -->
+    <insert id="upsertSeqNmg" parameterType="IdgenVO">
+        INSERT INTO cmmn_idgn (
+                    tbl_nm
+                  , aftr_id
+        ) VALUES (
+                   #{tblNm}
+                 , #{aftrId}
+                 )
+        ON CONFLICT (tbl_nm)
+        DO UPDATE
+               SET aftr_id = #{aftrId}
+    </insert>
+</mapper>(파일 끝에 줄바꿈 문자 없음)
Add a comment
List