회원가입 기능 추가.

로그인 작업중.
cks
강석 최 2023-11-22 17:59:38 +09:00
parent 5c5307b9b3
commit 0c4aca0239
11 changed files with 238 additions and 133 deletions

View File

@ -20,7 +20,7 @@
.Plogin .list li + li {margin-top: 5px;}
.Pjoin h1 {color: #222; font-size: 48px; font-weight: 500; letter-spacing: -2px; line-height: 48px; text-align: center;}
.Pjoin .join_box {position: relative; width: 690px; margin: 54px auto 0; padding: 70px 95px 120px 95px ; border: 1px solid #dde2e5; border-radius: 25px; box-shadow: 3px 4px 5px #ccc;}
.Pjoin .join_box {position: relative; width: 690px; margin: 54px auto 0; padding: 70px 95px; border: 1px solid #dde2e5; border-radius: 25px; box-shadow: 3px 4px 5px #ccc;}
.Pjoin .join_box input[type=text],
.Pjoin .join_box input[type=password] {width: 100%; height: 46px; padding: 0 20px; border: 0; border-radius: 8px; color: #666; font-size: 16px; background: #f5f5f5;}
.Pjoin .join_box .group input + input {margin-top: 18px;}
@ -29,6 +29,7 @@
.Pjoin .join_box .chk {margin-top: 20px;}
.Pjoin .join_box .chk em {display: inline-block; height: 30px; margin-left: 40px; color: #666; font-size: 16px;}
.Pjoin .join_box button {width: 500px;height: 50px;border-radius: 8px;color: #fff;font-size: 20px;font-weight: 500;text-align: center;line-height: 50px;background: #169bd5;}
.Pjoin .join_box button:disabled {background: rgba(22, 155, 213, 0.4); cursor: auto;}
.Pjoin .join_box button span {display: block; position: relative; height: 100%;}
.Pjoin .join_box .list li {position: relative; padding-left: 15px; color: #666; font-size: 16px; line-height: 26px;}
.Pjoin .join_box .list li + li {margin-top: 5px;}

View File

@ -3,12 +3,11 @@ import {Link, useLocation, useNavigate} from 'react-router-dom';
import * as EgovNet from 'api/egovFetch';
import URL from 'constants/url';
import CODE from 'constants/code';
import CODE from "constants/code";
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import { getLocalItem, setLocalItem, setSessionItem } from 'utils/storage';
import EgovLoginContent from "../EgovLoginContent";
import InfoShareChk from "./InfoShareChk";
function Join(props) {
@ -20,31 +19,46 @@ function Join(props) {
const location = useLocation();
console.log("JoinContent [location] : ", location);
const [userInfo, setUserInfo] = useState({ id: '', email: '', password: '' });
const [userInfo, setUserInfo] = useState({ id: '', email: '', password: '', passwordChk: '' });
const [infoShareChk, setInfoShareChk] = useState(false);
const [submitFlag, setSubmitFlag] = useState(true);
const submitFormHandler = (e) => {
console.log("JoinContent submitFormHandler()");
const loginUrl = "/auth/join"
const requestOptions = {
method: "POST",
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(userInfo)
let valueChk = true;
for(let value in userInfo){
if(!userInfo[value]){
valueChk = false;
}
}
if(valueChk){
const loginUrl = "/auth/join"
const requestOptions = {
method: "POST",
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(userInfo)
}
EgovNet.requestFetch(loginUrl,
requestOptions,
(resp) => {
let resultVO = resp.resultVO;
let jToken = resp?.jToken || null;
EgovNet.requestFetch(loginUrl,
requestOptions,
(resp) => {
let resultVO = resp.resultVO;
let jToken = resp?.jToken || null;
setSessionItem('jToken', jToken);
setSessionItem('jToken', jToken);
if (Number(resp.resultCode) === Number(CODE.RCV_SUCCESS)) {
alert(resp.resultMessage);
navigate('/login');
}else{
alert(resp.resultMessage);
}
})
})
}else{
window.alert("입력되지 않은 값이 있습니다.")
}
}
console.log("------------------------------JoinContent [End]");
@ -80,10 +94,10 @@ function Join(props) {
onChange={e => setUserInfo({ ...userInfo, id: e.target.value })} />
<input type="text" name="" title="이메일" placeholder="이메일" value={userInfo?.email}
onChange={e => setUserInfo({ ...userInfo, email: e.target.value })} />
<input type="password" name="" title="비밀번호" placeholder="비밀번호"
onChange={e => setUserInfo({ ...userInfo, password: e.target.value })} />
<input type="password" name="" title="비밀번호 확인" placeholder="비밀번호 확인"
onChange={e => setUserInfo({ ...userInfo, passwordConfirm: e.target.value })} />
<input type="password" name="" id="passwordInput" title="비밀번호" placeholder="비밀번호"
onChange={e => setUserInfo({ ...userInfo, password: e.target.value })}/>
<input type="password" name="" id="passwordChkInput" title="비밀번호 확인" placeholder="비밀번호 확인"
onChange={e => setUserInfo({ ...userInfo, passwordChk: e.target.value })}/>
</span>
<ul className="list">
<li>비밀번호는 6~12자의 영문 /소문자, 숫자, 특수문자를 혼합해서 사용하실 있습니다.</li>

View File

@ -45,6 +45,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'

View File

@ -17,12 +17,16 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.ui.ModelMap;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
/**
*
@ -44,12 +48,12 @@ import java.util.HashMap;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
@Tag(name="EgovLoginApiController",description = "로그인 관련")
public class EgovLoginApiController extends BaseController {
/** EgovLoginService */
@Resource(name = "loginService")
private EgovLoginService loginService;
/** EgovMessageSource */
@ -67,98 +71,34 @@ public class EgovLoginApiController extends BaseController {
tags = {"EgovLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "로그인 성공"),
@ApiResponse(responseCode = "300", description = "로그인 실패")
@ApiResponse(responseCode = "200", description = "가입 성공"),
@ApiResponse(responseCode = "300", description = "가입 실패")
})
@PostMapping(value = "/join")
public HashMap<String, Object> actionJoin(@RequestBody LoginVO loginVO, HttpServletRequest request) throws Exception {
public HashMap<String, Object> actionJoin(@RequestBody @Valid LoginVO loginVO, Errors errors, HttpServletRequest request) throws Exception {
HashMap<String, Object> resultMap = new HashMap<String, Object>();
return resultMap;
}
/**
*
* @param loginVO - , LoginVO
* @param request - HttpServletRequest
* @return result - ()
* @exception Exception
*/
@Operation(
summary = "일반 로그인",
description = "일반 로그인 처리",
tags = {"EgovLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "로그인 성공"),
@ApiResponse(responseCode = "300", description = "로그인 실패")
})
@PostMapping(value = "/login", consumes = {MediaType.APPLICATION_JSON_VALUE , MediaType.TEXT_HTML_VALUE})
public HashMap<String, Object> actionLogin(@RequestBody LoginVO loginVO, HttpServletRequest request) throws Exception {
HashMap<String,Object> resultMap = new HashMap<String,Object>();
// 1. 일반 로그인 처리
LoginVO loginResultVO = loginService.actionLogin(loginVO);
if (loginResultVO != null && loginResultVO.getId() != null && !loginResultVO.getId().equals("")) {
request.getSession().setAttribute("LoginVO", loginResultVO);
resultMap.put("resultVO", loginResultVO);
resultMap.put("resultCode", "200");
resultMap.put("resultMessage", "성공 !!!");
} else {
resultMap.put("resultVO", loginResultVO);
if(errors.hasErrors()){
StringBuilder msg = new StringBuilder();
for(FieldError error: errors.getFieldErrors()){
msg.append(error.getDefaultMessage());
msg.append("\n");
}
resultMap.put("resultCode", "300");
resultMap.put("resultMessage", egovMessageSource.getMessage("fail.common.login"));
}
return resultMap;
}
@Operation(
summary = "JWT 로그인",
description = "JWT 로그인 처리",
tags = {"EgovLoginApiController"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "로그인 성공"),
@ApiResponse(responseCode = "300", description = "로그인 실패")
})
@PostMapping(value = "/login-jwt")
public HashMap<String, Object> actionLoginJWT(@RequestBody LoginVO loginVO, HttpServletRequest request, ModelMap model) throws Exception {
HashMap<String, Object> resultMap = new HashMap<String, Object>();
// 1. 일반 로그인 처리
LoginVO loginResultVO = loginService.actionLogin(loginVO);
if (loginResultVO != null && loginResultVO.getId() != null && !loginResultVO.getId().equals("")) {
log.debug("===>>> loginVO.getUserSe() = "+loginVO.getUserSe());
log.debug("===>>> loginVO.getId() = "+loginVO.getId());
log.debug("===>>> loginVO.getPassword() = "+loginVO.getPassword());
String jwtToken = jwtTokenUtil.generateToken(loginResultVO);
String username = jwtTokenUtil.getUserSeFromToken(jwtToken);
log.debug("Dec jwtToken username = "+username);
//서버사이드 권한 체크 통과를 위해 삽입
//EgovUserDetailsHelper.isAuthenticated() 가 그 역할 수행. DB에 정보가 없으면 403을 돌려 줌. 로그인으로 튕기는 건 프론트 쪽에서 처리
request.getSession().setAttribute("LoginVO", loginResultVO);
resultMap.put("resultVO", loginResultVO);
resultMap.put("jToken", jwtToken);
resultMap.put("resultCode", "200");
resultMap.put("resultMessage", "성공 !!!");
} else {
resultMap.put("resultVO", loginResultVO);
resultMap.put("resultMessage", msg.toString());
}else if(!loginVO.getPassword().equals(loginVO.getPasswordChk())){
resultMap.put("resultCode", "300");
resultMap.put("resultMessage", egovMessageSource.getMessage("fail.common.login"));
resultMap.put("resultMessage", "비밀번호 확인이 잘못 입력되었습니다.");
}else{
Integer insertResult = loginService.insertUser(loginVO);
if(insertResult!=null){
resultMap.put("resultCode", "200");
resultMap.put("resultMessage", "저장 되었습니다.");
}else{
resultMap.put("resultCode", "300");
resultMap.put("resultMessage", "저장에 실패하였습니다.");
}
}
return resultMap;
}

View File

@ -6,6 +6,8 @@ import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
/**
@ -34,30 +36,40 @@ public class LoginVO implements Serializable{
private static final long serialVersionUID = -8274004534207618049L;
@Schema(description = "아이디")
@Pattern(regexp = "^[a-zA-Z]{1}[a-zA-Z0-9_]{4,11}$")
@NotBlank(message = "아이디를 입력해주세요.")
private String id;
@Email(message = "이메일 형식에 맞지 않습니다.")
@Schema(description = "이메일주소")
@NotBlank(message = "이메일을 입력해주세요.")
private String email;
@Pattern(regexp = "^(?=.*[a-z])(?=.*\\d)(?=.*[@$#!%*?&])[A-Za-z\\d@$#!%*?&]{8,}$",
message = "비밀번호는 영문 대,소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 8자 이상의 문자 조합이여야 합니다.")
@Schema(description = "비밀번호")
@NotBlank(message = "비밀번호를 입력해주세요.")
private String password;
@Schema(description = "비밀번호확인")
@NotBlank(message = "비밀번호확인을 입력해주세요.")
private String passwordChk;
@Schema(description = "사용자 구분", allowableValues = {"GNR", "ENT", "USR"}, defaultValue = "USR")
private String userSe;
@Schema(description = "이름")
private String name;
@Schema(description = "주민등록번호")
private String ihidNum;
@Email(regexp = "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,3}")
@Schema(description = "이메일주소")
private String email;
@Schema(description = "비밀번호")
private String password;
@Schema(description = "비밀번호 힌트")
private String passwordHint;
@Schema(description = "비밀번호 정답")
private String passwordCnsr;
@Schema(description = "사용자 구분", allowableValues = {"GNR", "ENT", "USR"}, defaultValue = "USR")
private String userSe;
@Schema(description = "조직(부서)ID")
private String orgnztId;

View File

@ -0,0 +1,72 @@
package com.dbnt.kcscbackend.auth.entity;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@Getter
@Setter
@Entity
@NoArgsConstructor
@DynamicInsert
@DynamicUpdate
@Table(name = "user_info")
public class UserInfo implements UserDetails{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_seq")
private Integer userSeq;
@Column(name = "user_id")
private String userId;
@Column(name = "password")
private String password;
@Column(name = "email")
private String email;
@Column(name = "user_se")
private String userSe;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> roles = new HashSet<>();
for (String role : userSe.split(",")) {
roles.add(new SimpleGrantedAuthority(role));
}
return roles;
}
@Override
public String getUsername() {
return userId;
}
@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

@ -0,0 +1,12 @@
package com.dbnt.kcscbackend.auth.repository;
import com.dbnt.kcscbackend.auth.entity.UserInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserInfoRepository extends JpaRepository<UserInfo, Integer> {
Optional<UserInfo> findByUserId(String userId);
}

View File

@ -49,4 +49,5 @@ public interface EgovLoginService {
*/
public boolean searchPassword(LoginVO vo) throws Exception;
public Integer insertUser(LoginVO loginVO);
}

View File

@ -1,6 +1,8 @@
package com.dbnt.kcscbackend.auth.service.impl;
import com.dbnt.kcscbackend.auth.entity.UserInfo;
import com.dbnt.kcscbackend.auth.mapper.LoginUserMapper;
import com.dbnt.kcscbackend.auth.repository.UserInfoRepository;
import com.dbnt.kcscbackend.auth.service.EgovLoginService;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
import com.dbnt.kcscbackend.config.egov.EgovFileScrty;
@ -8,6 +10,10 @@ import com.dbnt.kcscbackend.config.util.EgovNumberUtil;
import com.dbnt.kcscbackend.config.util.EgovStringUtil;
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.password.Pbkdf2PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -28,12 +34,31 @@ import org.springframework.transaction.annotation.Transactional;
*
* </pre>
*/
@Service
@Transactional
@Service("loginService")
@RequiredArgsConstructor
public class EgovLoginServiceImpl extends EgovAbstractServiceImpl implements EgovLoginService {
public class EgovLoginServiceImpl extends EgovAbstractServiceImpl implements EgovLoginService, UserDetailsService {
private final LoginUserMapper loginMapper;
private final UserInfoRepository userInfoRepository;
@Transactional
public Integer insertUser(LoginVO loginVO){
UserInfo info = new UserInfo();
info.setUserId(loginVO.getId());
info.setPassword(convertPassword(loginVO.getPassword()));
info.setEmail(loginVO.getEmail());
info.setUserSe("USR");
userInfoRepository.save(info);
return info.getUserSeq();
}
@Override
public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
return userInfoRepository.findByUserId(userId).orElseThrow(() -> new UsernameNotFoundException(userId));
}
/**
*
@ -122,4 +147,10 @@ public class EgovLoginServiceImpl extends EgovAbstractServiceImpl implements Ego
return result;
}
private String convertPassword(String password){
Pbkdf2PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder();
return passwordEncoder.encode(password);
}
}

View File

@ -1,6 +1,7 @@
package com.dbnt.kcscbackend.config.jwt;
import com.dbnt.kcscbackend.auth.entity.UserInfo;
import com.dbnt.kcscbackend.config.egov.EgovProperties;
import com.dbnt.kcscbackend.auth.entity.LoginVO;
import io.jsonwebtoken.Claims;
@ -70,7 +71,7 @@ public class EgovJwtTokenUtil implements Serializable{
}
//generate token for user
public String generateToken(LoginVO loginVO) {
public String generateToken(UserInfo loginVO) {
return doGenerateToken(loginVO, "Authorization");
}
@ -79,14 +80,12 @@ public class EgovJwtTokenUtil implements Serializable{
//2. Sign the JWT using the HS512 algorithm and secret key.
//3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
// compaction of the JWT to a URL-safe string
private String doGenerateToken(LoginVO loginVO, String subject) {
private String doGenerateToken(UserInfo loginVO, String subject) {
Map<String, Object> claims = new HashMap<>();
claims.put("id", loginVO.getId() );
claims.put("name", loginVO.getName() );
claims.put("id", loginVO.getUserId() );
claims.put("userSe", loginVO.getUserSe() );
claims.put("orgnztId", loginVO.getOrgnztId() );
claims.put("uniqId", loginVO.getUniqId() );
claims.put("uniqId", loginVO.getUserSeq() );
claims.put("type", subject);
log.debug("===>>> secret = "+SECRET_KEY);

View File

@ -1,7 +1,10 @@
package com.dbnt.kcscbackend.config.security;
import com.dbnt.kcscbackend.auth.entity.UserInfo;
import com.dbnt.kcscbackend.config.jwt.EgovJwtTokenUtil;
import com.dbnt.kcscbackend.config.jwt.JwtAuthenticationEntryPoint;
import com.dbnt.kcscbackend.config.jwt.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
@ -9,12 +12,17 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
/**
@ -31,6 +39,9 @@ import java.util.Arrays;
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private EgovJwtTokenUtil jwtTokenUtil;
//Http Methpd : Get 인증예외 List
private String[] AUTH_GET_WHITELIST = {
"/schedule/daily", //일별 일정 조회
@ -90,8 +101,7 @@ public class SecurityConfig {
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
return http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers(HttpMethod.GET,AUTH_GET_WHITELIST).permitAll()
@ -108,4 +118,16 @@ public class SecurityConfig {
.build();
}
@Bean
public AuthenticationSuccessHandler loginSuccessHandler() {
return (request, response, authentication) -> {
UserInfo info = (UserInfo)authentication.getPrincipal();
if (info != null && info.getUserId() != null && !info.getUserId().equals("")){
String jwtToken = jwtTokenUtil.generateToken(info);
String userName = jwtTokenUtil.getUserSeFromToken(jwtToken);
}
new DefaultRedirectStrategy().sendRedirect(request,response,"/");
};
}
}