Home

0

Spring Security - SessionManagementFilter & ConcurrentSessionFilter

목차 Spring Security 권한 계층 사용하기 - @RoleHierarcy Spring Security - DelegateFilterProxy Spring Security - Remember Me와 관련된 인증 API - RememberMeConfigurer Spring Security - RembmerMeAuthenticationFilter Spring Security - SessionManagementFilter & ConcurrentSessionFilter Spring Security - 인가 API ExpressionUrlAuthorizationConfigurer Spring Security - Security 설정을 위한 WebSecurityConfigurerAdatper Spring Security - AuthenticationProvider Spring Security - AuthenticationManager Spring Security - UsernamePasswordAuthenticationFilter & AbstractAuthenticationProcessingFilter Spring Security - SecurityContextHolder 와 SecurityContext Spring Security - Authentication 객체 SessionManagementFilter SessionManagementFilter 는 인증된 사용자에 대한 Session 관리 와 Session 공격으로 부터의 보호 를 위한 Filter Session 관리 인증시 사용자의 Session 등록, 조회, 삭제 등의 이력을 관리한다. 동시성 Session 제어 동일 계정으로 접속하는 최대 Session 수를 설정한다. Session 고정 보호 인증할때 마다 새로운 Session 쿠키를 새로 발급해 쿠키 조작을 방지한다. Session 생성 정책 SessionManagementFilter.java private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { if (request.getAttribute(FILTER_APPLIED) != null) { chain.doFilter(request, response); return; } request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (!this.securityContextRepository.containsContext(request)) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && !this.trustResolver.isAnonymous(authentication)) { try { // 최대 생성 개수를 넘어섰는지?, Session 고정 공격이 들어왔는지? 등 // Session에 대한 인증을 진행한다. this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response); } catch (SessionAuthenticationException ex) { this.logger.debug("SessionAuthenticationStrategy rejected the authentication object", ex); SecurityContextHolder.clearContext(); this.failureHandler.onAuthenticationFailure(request, response, ex); return; } this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response); } else { if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) { if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Request requested invalid session id %s", request.getRequestedSessionId())); } if (this.invalidSessionStrategy != null) { this.invalidSessionStrategy.onInvalidSessionDetected(request, response); return; } } } } chain.doFilter(request, response);} Session 관리를 위한 SessionAuthenticationStrategy SessionAuthenticationStrategy 는 Session 이 존재하는지 확인하거나 Session 고정 공격에 대한 보호를 위해 Session Id를 변경한다.

0

Spring Security - RembmerMeAuthenticationFilter

목차 Spring Security 권한 계층 사용하기 - @RoleHierarcy Spring Security - DelegateFilterProxy Spring Security - Remember Me와 관련된 인증 API - RememberMeConfigurer Spring Security - RembmerMeAuthenticationFilter Spring Security - SessionManagementFilter & ConcurrentSessionFilter Spring Security - 인가 API ExpressionUrlAuthorizationConfigurer Spring Security - Security 설정을 위한 WebSecurityConfigurerAdatper Spring Security - AuthenticationProvider Spring Security - AuthenticationManager Spring Security - UsernamePasswordAuthenticationFilter & AbstractAuthenticationProcessingFilter Spring Security - SecurityContextHolder 와 SecurityContext Spring Security - Authentication 객체 참고 https://docs.spring.io/spring-security/reference/servlet/authentication/rememberme.html RembmerMeAuthenticationFilter Client 로부터 Request(요청) 이 오게 되면 Request 객체 Cookie 에 Remember Me Token 이 있는지 확인한다. Token 을 이용해 인증을 진행한 후 SecurityContext 에 Authentication 객체를 저장한다. RememberMeAuthenticationFilter.java private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // SecurityContext 내 Authentication 객체가 있는지 확인 한다. if (SecurityContextHolder.getContext().getAuthentication() != null) { this.logger.debug(LogMessage .of(() -> "SecurityContextHolder not populated with remember-me token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'")); chain.doFilter(request, response); return; } // Remember Me 인증을 진행한 후 Authentication 객체를 반환받는다. Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response); if (rememberMeAuth != null) { // Attempt authenticaton via AuthenticationManager try { // RememberMeAuthenticationToken 객체내 Key 값에 대한 Hash 비교를 통해 유효성 인증을 진행 후 객체를 그대로 반환받는다. rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth); // SecurityContextHolder 에 인증 받은 Authentication 객체를 저장한다. SecurityContextHolder.getContext().setAuthentication(rememberMeAuth); // 인증 성공 후 후작업을 진행한다. onSuccessfulAuthentication(request, response, rememberMeAuth); this.logger.debug(LogMessage.of(() -> "SecurityContextHolder populated with remember-me token: '" + SecurityContextHolder.getContext().getAuthentication() + "'")); if (this.eventPublisher != null) { this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( SecurityContextHolder.getContext().getAuthentication(), this.getClass())); } if (this.successHandler != null) { this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth); return; } } catch (AuthenticationException ex) { this.logger.debug(LogMessage .format("SecurityContextHolder not populated with remember-me token, as AuthenticationManager " + "rejected Authentication returned by RememberMeServices: '%s'; " + "invalidating remember-me token", rememberMeAuth), ex); this.rememberMeServices.loginFail(request, response); onUnsuccessfulAuthentication(request, response, ex); } } chain.doFilter(request, response);} AbstractRememberMeServices

0

React 로그인 Modal 만들기 8 - Redux 사용하기

React 로그인 Modal 만들기 9 - Redux Saga 적용 React 로그인 Modal 만들기 8 - Redux 사용하기 React 로그인 Modal 만들기 7 - JWT 적용하기 React 로그인 Modal 만들기 6 - Login 구현 React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현 React 로그인 Modal 만들기 4 - 비동기 처리 React 로그인 Modal 만들기 3 - 회원 가입 React 로그인 Modal 만들기 2 - styled-component 사용하기 React 로그인 Modal 만들기 React 로그인 Modal 만들기 8 - Redux 사용하기모듈 설치하기yarn add reduxyarn add react-redux Action 정의export const LOGIN_REQUEST = "login/LOGIN_REQUEST"; Action 생성 함수 및 Reducer 생성하기import axios from 'axios';import { LOGIN_REQUEST } from "../type"const initialState = { token: localStorage.getItem('token'),};export const loginRequest = (loginData) => { const response = axios.post("/api/login", loginData) .then((response) => { localStorage.setItem("token", response.headers.authorization); console.log(localStorage.getItem("token")); return response.data; }) .catch((err) => { console.log(err); }); return { type: LOGIN_REQUEST, data: response };};const LoginReducer = (state = initialState, action) => { switch (action.type) { case LOGIN_REQUEST: return { ...state, token: localStorage.getItem('token'), }; default: return state; }}export default LoginReducer; Store 생성하기

0

React 로그인 Modal 만들기 7 - JWT 적용하기

React 로그인 Modal 만들기 9 - Redux Saga 적용 React 로그인 Modal 만들기 8 - Redux 사용하기 React 로그인 Modal 만들기 7 - JWT 적용하기 React 로그인 Modal 만들기 6 - Login 구현 React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현 React 로그인 Modal 만들기 4 - 비동기 처리 React 로그인 Modal 만들기 3 - 회원 가입 React 로그인 Modal 만들기 2 - styled-component 사용하기 React 로그인 Modal 만들기 React 로그인 Modal 만들기 7 - JWT 적용하기Properties에 JWT 설정 값 추가JWT 를 암호화 하기 위한 key 값, token의 유효시간을 정의하기 위한 값, 그리고 토큰을 Http Header에 저장하기 위핸 Header Key를 정의해준다. application.yml jwt: header: Authorization secret: c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK token-validity-in-seconds: 86400000 JWT를 다루기 위한 JwtUtils 생성 JwtUtils에서는 createToken 메소드를 이용해 새로운 JWT를 만들고 validateToken 메소드를 이용해 전달 받은 JWT의 유효성을 검증하고 getAuthentication 메소드를 이용해 전달 받은 JWT로부터 Authentication 객체를 가져오도록 한다. Jwts.builder 를 이용해 JWT를 생성하고 Jwts.parser 를 이용해 JWT로부터 정보를 가져온다. Value 어노테이션을 이용해 Properties에 저장한 token을 암호화 하기 위한 Key값과 token 유효시간 정보를 가져온다.

0

React 로그인 Modal 만들기 6 - Login 구현

React 로그인 Modal 만들기 9 - Redux Saga 적용 React 로그인 Modal 만들기 8 - Redux 사용하기 React 로그인 Modal 만들기 7 - JWT 적용하기 React 로그인 Modal 만들기 6 - Login 구현 React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현 React 로그인 Modal 만들기 4 - 비동기 처리 React 로그인 Modal 만들기 3 - 회원 가입 React 로그인 Modal 만들기 2 - styled-component 사용하기 React 로그인 Modal 만들기 React 로그인 Modal 만들기 6 - Login 구현Json 인증 Filter 구현하기UsernamePasswordAuthenticationFilter 의 경우 Form Login을 위해 구현된 Filter라 Request Body로 요청이 들어오는 Login 요청을 적용하기는 어렵기에 JSON 을 이용해 Login을 진행할 수 있도록 JsonAuthenticationFilter 를 새로 만들어줄 필요가 있다. UsernamePasswordAuthenticationFilter 와 인증을 진행하는 과정은 비슷하므로 AbstractAuthenticationProcessingFilter 를 상속해 새로운 Filter를 생성해준다. JsonAuthenticationFilter.java public class JsonAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private static final String LOGIN_PROCESSING_URL = "/api/login"; @Autowired private ObjectMapper objectMapper; public JsonAuthenticationFilter() { super(new AntPathRequestMatcher(LOGIN_PROCESSING_URL)); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { if(!isApplicationJSON(request)){ throw new IllegalStateException("Content Type is not Application/json"); } LoginDto loginDto = objectMapper.readValue(request.getReader(), LoginDto.class); if(ObjectUtils.isEmpty(loginDto.getUsername()) || ObjectUtils.isEmpty(loginDto.getPassword())){ throw new IllegalArgumentException("Username or Password is empty"); } JsonAuthenticationToken jsonAuthenticationToken = new JsonAuthenticationToken(loginDto.getUsername(), loginDto.getPassword()); getAuthenticationManager().authenticate(jsonAuthenticationToken); return null; } private boolean isApplicationJSON(HttpServletRequest httpServletRequest){ if(httpServletRequest.getHeader("Content-type").equals(MediaType.APPLICATION_JSON_VALUE)){ return true; } return false; }} 새로운 Authentication Token 만들기 JsonAuthenticationFilter 에서 사용하기 위한 새로운 Authentication 생성

0

React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현

React 로그인 Modal 만들기 9 - Redux Saga 적용 React 로그인 Modal 만들기 8 - Redux 사용하기 React 로그인 Modal 만들기 7 - JWT 적용하기 React 로그인 Modal 만들기 6 - Login 구현 React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현 React 로그인 Modal 만들기 4 - 비동기 처리 React 로그인 Modal 만들기 3 - 회원 가입 React 로그인 Modal 만들기 2 - styled-component 사용하기 React 로그인 Modal 만들기 React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현회원 데이터를 저장할 UserInfo 정의회원 가입을 위해 사용자 id, username, email, password와 사용자 별로 권한을 관리하기 위한 UserInfo 객체를 관리하도록 정의 @Entity@NoArgsConstructor@AllArgsConstructor@Getter@Builderpublic class UserInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String username; @Column(nullable = false) private String email; @Column(nullable = false) private String password; @Enumerated(EnumType.STRING) private UserRole userRole;} User 권한 목록 권한은 3가지로 일반사용자를 위한 USER 권한, 수정/변경 권한이 있는 MANAGER 권한과 관리자인 ADMIN 3가지 권한으로 분류 한다 public enum UserRole { USER("ROLE_USER"), MANAGER("ROLE_MANAGER"), ADMIN("ROLE_ADMIN"); private String value; UserRole(String value){ this.value = value; } public String getValue(){ return this.value; }} 사용자 이름과 Email을 이용해 사용자 정보를 가져올 수 있도록 메소드를 추가한다.

0

React 로그인 Modal 만들기 4 - 비동기 처리

React 로그인 Modal 만들기 9 - Redux Saga 적용 React 로그인 Modal 만들기 8 - Redux 사용하기 React 로그인 Modal 만들기 7 - JWT 적용하기 React 로그인 Modal 만들기 6 - Login 구현 React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현 React 로그인 Modal 만들기 4 - 비동기 처리 React 로그인 Modal 만들기 3 - 회원 가입 React 로그인 Modal 만들기 2 - styled-component 사용하기 React 로그인 Modal 만들기 React 로그인 Modal 만들기 4 - 비동기 처리import axios from "axios";import React, { useState, useCallback } from "react";import { LoginBody, LoginFooter, LoginHeader, ModalBackGround, ModalBox,} from "../assets/css/login";const LoginTemplate = ({ open, close }) => { const [form, setForm] = useState({ email: "", password: "", }); const onChange = useCallback( (e) => { setForm({ ...form, [e.target.name]: e.target.value, }); }, [form] ); const onClick = useCallback( (e) => { axios .post("/api/login", form) .then((response) => { console.log(response); }) .catch((err) => { console.log(err); }); }, [form] ); return open ? ( <ModalBackGround> <ModalBox> <LoginHeader> 로그인 <button className="close" onClick={close}> {" "} &times;{" "} </button> </LoginHeader> <LoginBody className="login-body"> <div> <input id="email" name="email" type="email" placeholder="Email" onChange={onChange} /> </div> <div> <input id="password" name="password" type="password" placeholder="Password" onChange={onChange} /> </div> </LoginBody> <LoginFooter> <button onClick={onClick}>로그인</button> </LoginFooter> </ModalBox> </ModalBackGround> ) : null;};export default LoginTemplate; import React, { useCallback, useState } from "react";import { ModalBox } from "../assets/css/login";import axios from "axios";import { ModalBackGround, SignUpBody, SignUpFooter, SignUpHeader,} from "../assets/css/signup";const SignUpTemplate = ({ open, onChangeOpen }) => { const [form, setForm] = useState({ username: "", email: "", password: "", }); const onChange = useCallback( (e) => { setForm({ ...form, [e.target.name]: e.target.value, }); }, [form] ); const onClickSignUp = useCallback(() => { axios.post("/api/signup", form).then((Response) => { console.log(Response); }); }, [form]); return open ? ( <ModalBackGround className="signup-modal-box"> <ModalBox> <SignUpHeader> 회원 가입 <button className="close" onClick={onChangeOpen}> {" "} &times;{" "} </button> </SignUpHeader> <SignUpBody> <div> <input type="username" id="username" name="username" placeholder="이름" onChange={onChange} /> </div> <div> <input type="email" id="email" name="email" placeholder="Email" onChange={onChange} /> </div> <div> <input type="password" id="password" name="password" placeholder="Password" onChange={onChange} /> </div> </SignUpBody> <SignUpFooter> <button onClick={onClickSignUp}>회원가입</button> </SignUpFooter> </ModalBox> </ModalBackGround> ) : null;};export default SignUpTemplate;

0

React 로그인 Modal 만들기 3 - 회원 가입

React 로그인 Modal 만들기 9 - Redux Saga 적용 React 로그인 Modal 만들기 8 - Redux 사용하기 React 로그인 Modal 만들기 7 - JWT 적용하기 React 로그인 Modal 만들기 6 - Login 구현 React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현 React 로그인 Modal 만들기 4 - 비동기 처리 React 로그인 Modal 만들기 3 - 회원 가입 React 로그인 Modal 만들기 2 - styled-component 사용하기 React 로그인 Modal 만들기 React 로그인 Modal 만들기 3 - 회원 가입import styled, { keyframes } from "styled-components";export const ModalFade = keyframes`{ from { opacity: 0; margin-top: -50px; } to { opacity: 1; margin-top: 0; }}`;export const ModalBackGround = styled.div` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; animation: modal-bg-show 0.3s;`;export const ModalBox = styled.div` width: 90%; max-width: 400px; margin: 0 auto; border-radius: 0.3rem; background-color: #fff; animation: ModalFade 0.3s; overflow: hidden;`;export const SignUpHeader = styled.div` position: relative; padding: 16px 64px 16px 16px; background-color: #f1f1f1; font-weight: 700; & > button { position: absolute; top: 15px; right: 15px; width: 30px; font-size: 21px; font-weight: 700; text-align: center; color: #999; border: 0; }`;export const SignUpBody = styled.div` padding: 10px; & > div { padding: 5px; display: flex; align-items: center; justify-content: center; } & > div > input { padding-top: 5px; padding-bottom: 5px; padding-left: 15px; padding-right: 15px; width: 290px; height: 30px; font-size: 14px; border: 1px solid rgb(222, 226, 230); }`;export const SignUpFooter = styled.div` border-top: 1px solid; padding: 7px 16px; text-align: right; & > button { padding: 6px 12px; color: #fff; background-color: #6c757d; border-radius: 5px; font-size: 13px; }`; import React, { useCallback, useState } from "react";import { ModalBox } from "../assets/css/login";import { ModalBackGround, SignUpBody, SignUpFooter, SignUpHeader,} from "../assets/css/signup";const SignUpTemplate = ({ open, onChangeOpen }) => { const [form, setForm] = useState({ username: "", email: "", password: "", }); const onChange = useCallback( (e) => { setForm({ ...form, [e.target.name]: e.target.value, }); }, [form] ); return open ? ( <ModalBackGround className="signup-modal-box"> <ModalBox> <SignUpHeader> 회원 가입 <button className="close" onClick={onChangeOpen}> {" "} &times;{" "} </button> </SignUpHeader> <SignUpBody> <div> <input type="username" id="username" name="username" placeholder="이름" onChange={onChange} /> </div> <div> <input type="email" id="email" name="email" placeholder="Email" onChange={onChange} /> </div> <div> <input type="password" id="password" name="password" placeholder="Password" onChange={onChange} /> </div> </SignUpBody> <SignUpFooter> <button onClick={onChangeOpen}>닫기</button> </SignUpFooter> </ModalBox> </ModalBackGround> ) : null;};export default SignUpTemplate;

0

React 로그인 Modal 만들기 2 - styled-component 사용하기

React 로그인 Modal 만들기 9 - Redux Saga 적용 React 로그인 Modal 만들기 8 - Redux 사용하기 React 로그인 Modal 만들기 7 - JWT 적용하기 React 로그인 Modal 만들기 6 - Login 구현 React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현 React 로그인 Modal 만들기 4 - 비동기 처리 React 로그인 Modal 만들기 3 - 회원 가입 React 로그인 Modal 만들기 2 - styled-component 사용하기 React 로그인 Modal 만들기 React 로그인 Modal 만들기 2 - styled-component 사용하기기존에 style 내용들을 css 파일로 관리해도 문제가 없으나, React에서는 style마저도 js파일로 관리할 수 있도록 지원해주는 다양한 프레임 워크들이 있다. js파일로 관리하게 되면 React의 여러 기능을 사용하기 좋기 때문에 기존에 작성해준 Login 관련 css 내용을 js 파일로 변경하려고 한다. import styled, { keyframes } from "styled-components";export const ModalFade = keyframes`{ from { opacity: 0; margin-top: -50px; } to { opacity: 1; margin-top: 0; }}`;export const ModalBackGround = styled.div` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center;`;export const ModalBox = styled.div` width: 90%; max-width: 400px; margin: 0 auto; border-radius: 0.3rem; background-color: #fff; animation: ${ModalFade} 0.5s; overflow: hidden;`;export const LoginHeader = styled.div` position: relative; padding: 16px 64px 16px 16px; background-color: #f1f1f1; font-weight: 700; & > button { position: absolute; top: 15px; right: 15px; width: 30px; font-size: 21px; font-weight: 700; text-align: center; color: #999; border: 0; }`;export const LoginBody = styled.div` padding: 10px; & > div { padding: 5px; display: flex; align-items: center; justify-content: center; } & > div > input { padding-top: 5px; padding-bottom: 5px; padding-left: 15px; padding-right: 15px; width: 290px; height: 30px; font-size: 14px; border: 1px solid rgb(222, 226, 230); }`;export const LoginFooter = styled.div` border-top: 1px solid rgb(222, 226, 230); padding: 7px 16px; text-align: right; & > button { padding: 6px 12px; color: #fff; background-color: #6c757d; border-radius: 5px; font-size: 13px; }`; Styled-Component 적용하기import React, { useState, useCallback } from "react";import { LoginBody, LoginFooter, LoginHeader, ModalBackGround, ModalBox,} from "../assets/css/login";const LoginTemplate = ({ open, close }) => { const [form, setForm] = useState({ email: "", password: "", }); const onChange = useCallback( (e) => { setForm({ ...form, [e.target.name]: e.target.value, }); }, [form] ); return open ? ( <ModalBackGround> <ModalBox> <LoginHeader> 로그인 <button className="close" onClick={close}> {" "} &times;{" "} </button> </LoginHeader> <LoginBody className="login-body"> <div> <input id="email" name="email" type="email" placeholder="Email" onChange={onChange} /> </div> <div> <input id="password" name="password" type="password" placeholder="Password" onChange={onChange} /> </div> </LoginBody> <LoginFooter> <button onClick={close}>닫기</button> </LoginFooter> </ModalBox> </ModalBackGround> ) : null;};export default LoginTemplate;

0

React 로그인 Modal 만들기

React 로그인 Modal 만들기 9 - Redux Saga 적용 React 로그인 Modal 만들기 8 - Redux 사용하기 React 로그인 Modal 만들기 7 - JWT 적용하기 React 로그인 Modal 만들기 6 - Login 구현 React 로그인 Modal 만들기 5 - 회원 가입 Back end 구현 React 로그인 Modal 만들기 4 - 비동기 처리 React 로그인 Modal 만들기 3 - 회원 가입 React 로그인 Modal 만들기 2 - styled-component 사용하기 React 로그인 Modal 만들기 로그인 Modal 만들기기존에 개인 프로젝트로 로그인 창을 만들게 되면 별도의 웹 페이지로 로그인 화면을 구현을 했다. 하지만 좀 더 깔끔하고 반응적인 방법을 찾다가 Modal에 대해 알게 됐고 Modal을 이용해 Login 화면을 구현해 보고자 한다. 로그인 컴포넌트 만들기가장 바깥 Wrapper는 로그인 버튼을 누른 상태와 누르지 않은 상태를 보여줘야 함으로 조건절을 이용해 classname을 변경하도록 설정한다. // Login 페이지가 열리면 modal-wrapper가 선택된다. <div className={open ? "modal-wrapper" : null}> 내부 컴포넌트도 상태에 따라 보이거나 보이지 않게 상태를 변경해줄 필요가 있어 조건절을 통해 내부 컴포넌트를 가져올 수 있도록 설정한다. {open ? ( <div className="login-modal"> <div className="login-header">로그인</div> <div className="login-input"> <div className="login-id"> <input className="email" type="email" id="email" placeholder="Email" onChange={onChangeEmail} /> </div> <div className="login-password"> <input className="password" type="password" id="password" placeholder="Password" onChange={onChangePassword} /> </div> <div className="close-login"> <button className="close-button" onClick={close}> 닫기 </button> </div> </div> </div>) : null} LoginTemplate.js