100일 챌린지/빅데이터기반 인공지능 융합 서비스 개발자

Day 81 - React에서 login 프로그램 만들기 (4) Spring backend 활용하기 + Oauth

ksyke 2024. 11. 22. 17:32

목차

    프로젝트 만들기

    application.properties

    spring.application.name=sts15
    
    spring.h2.console.path=/h2
    spring.h2.console.enabled=true
    
    spring.datasource.url=jdbc:h2:mem:testDB
    spring.datasource.username=sa
    
    spring.jpa.hibernate.ddl-auto=create
    spring.jpa.show-sql=true
    
    logging.level.com.gimhae.sts15.controller=debug

    Dept.class

    package com.gimhae.sts15.model.entity;
    
    import com.gimhae.sts15.model.Deptvo;
    
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Entity
    public class Dept {
    
    	@Id
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	int deptno;
    	String dname;
    	String loc;
    	public Deptvo toVo() {
    		return Deptvo.builder().deptno(deptno).dname(dname).loc(loc).build();
    		
    	}
    }

    DeptVo.class

    package com.gimhae.sts15.model;
    
    import com.gimhae.sts15.model.entity.Dept;
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class Deptvo {
    
    	int deptno;
    	String dname;
    	String loc;
    	
    	public Dept toEntity() {
    		return Dept.builder().deptno(deptno).dname(dname).loc(loc).build();
    		
    	}
    }

    Users.class

    package com.gimhae.sts15.model.entity;
    
    import com.gimhae.sts15.model.UsersVo;
    
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    @Entity
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class Users {
    	
    	@Id
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	int num;
    	String email;
    	String pw;
    	String name;
    	
    	public UsersVo toVo() {
    		return UsersVo.builder().num(num).email(email).pw(pw).name(name).build();
    		
    	}
    }

    UsersVo.class

    package com.gimhae.sts15.model;
    
    import com.gimhae.sts15.model.entity.Users;
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class UsersVo {
    
    	int num;
    	String email,pw,name;
    	
    	public Users toEntity() {
    		return Users.builder().num(num).email(email).pw(pw).name(name).build();
    	}
    }

    DeptRepo.Interface

    package com.gimhae.sts15.model;
    
    import org.springframework.data.repository.CrudRepository;
    
    import com.gimhae.sts15.model.entity.Dept;
    
    public interface DeptRepo extends CrudRepository<Dept, Integer> {
    
    }

    UsersRepo.Class

    package com.gimhae.sts15.model;
    
    import java.util.Optional;
    
    import org.springframework.data.repository.CrudRepository;
    
    import com.gimhae.sts15.model.entity.Users;
    
    public interface UsersRepo extends CrudRepository<Users, Integer> {
    
    	Optional<Users> findByEmailAndPw(String email,String pw);
    }

    ProjectApplication

    package com.gimhae.sts15;
    
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    
    import com.gimhae.sts15.model.DeptRepo;
    import com.gimhae.sts15.model.Deptvo;
    import com.gimhae.sts15.model.UsersRepo;
    import com.gimhae.sts15.model.UsersVo;
    
    @SpringBootApplication
    public class Sts15Application implements CommandLineRunner{
    
    	public static void main(String[] args) {
    		SpringApplication.run(Sts15Application.class, args);
    	}
    	
    	@Autowired
    	UsersRepo usersRepo;
    	@Autowired
    	DeptRepo deptRepo;
    
    	@Override
    	public void run(String... args) throws Exception {
    		List<UsersVo> userList=List.of(
    			UsersVo.builder().email("user1@localhost").pw("1234").name("user1").build(),
    			UsersVo.builder().email("user2@localhost").pw("1234").name("user2").build()
    		);
    		
    		userList.forEach(vo->usersRepo.save(vo.toEntity()));		
    //		usersRepo.saveAll(()->userList.iterator());
    		
    		List.of(
    				Deptvo.builder().dname("test1").loc("test").build(),
    				Deptvo.builder().dname("test2").loc("test").build(),
    				Deptvo.builder().dname("test3").loc("test").build(),
    				Deptvo.builder().dname("test4").loc("test").build()
    				).forEach(vo->deptRepo.save(vo.toEntity()));
    	}
    
    }

    DeptController

    package com.gimhae.sts15.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.gimhae.sts15.model.DeptRepo;
    import com.gimhae.sts15.model.Deptvo;
    
    import jakarta.servlet.http.HttpSession;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestParam;
    
    
    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/dept/")
    @CrossOrigin(origins = "http://localhost:3000",methods = {RequestMethod.GET,RequestMethod.POST})
    @Slf4j
    public class DeptController {
    	final DeptRepo deptRepo;
    	ResponseEntity resp=ResponseEntity.status(401).build();
    
    	@GetMapping("")
    	public ResponseEntity<?> list(HttpSession session) {
    //		log.debug(session.getAttribute("result").toString());
    		if(session.getAttribute("result")==null) return resp;
    		log.debug("login ok");
    		List<Object> list=new ArrayList<>();
    		deptRepo.findAll().forEach(entity->list.add(entity.toVo()));
    		return ResponseEntity.ok().body(list);
    	}
    	
    	@PostMapping("")
    	public ResponseEntity<?> add(@RequestBody Deptvo bean,HttpSession session) {
    		if(session.getAttribute("result")==null) return resp;
    		deptRepo.save(bean.toEntity());
    		return ResponseEntity.ok().build();
    	}
    	
    	@GetMapping("{deptno}")
    	public ResponseEntity<?> detail(@PathVariable int deptno,HttpSession session) {
    		if(session.getAttribute("result")==null) return resp;
    		deptRepo.findById(deptno).ifPresentOrElse(t->{
    			resp=ResponseEntity.ok().body(t.toVo());
    		}, ()->{
    			resp=ResponseEntity.badRequest().build();
    		});
    		return resp;
    	}
    }

    LoginController

    package com.gimhae.sts15.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.gimhae.sts15.model.UsersRepo;
    import com.gimhae.sts15.model.UsersVo;
    
    import jakarta.servlet.http.HttpSession;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    
    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.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    
    
    @RestController
    @RequestMapping("/login/")
    @RequiredArgsConstructor
    @Slf4j
    @CrossOrigin(origins = "http://localhost:3000",methods = {RequestMethod.GET,RequestMethod.POST})
    public class LoginController {
    	final UsersRepo usersRepo;
    	ResponseEntity<?> resp;
    
    	@PostMapping("")
    	public ResponseEntity<?> login(String id,String pw,HttpSession session) {
    //		log.debug("{1}:{2}".format(id, pw));
    		log.debug("id:"+id);
    		log.debug("pw:"+pw);
    		
    		usersRepo.findByEmailAndPw(id, pw).ifPresentOrElse(entity->{
    			UsersVo bean=entity.toVo();
    //			resp=ResponseEntity.ok(bean);
    			resp=ResponseEntity.ok().body(bean);
    			session.setAttribute("result", true);
    			session.setAttribute("num", bean.getNum());
    			log.debug("로그인됨");
    		}, ()->{
    			resp=ResponseEntity.badRequest().build();
    		});
    		return resp;
    	}
    	
    	@GetMapping("logout")
    	public ResponseEntity logout(HttpSession session) {
    		session.invalidate();
    		return ResponseEntity.ok().build();
    	}
    	
    }

    LoginFilter

    package com.gimhae.sts15;
    
    import java.io.IOException;
    
    import org.springframework.http.HttpRequest;
    import org.springframework.stereotype.Component;
    
    import jakarta.servlet.Filter;
    import jakarta.servlet.FilterChain;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.ServletRequest;
    import jakarta.servlet.ServletResponse;
    import jakarta.servlet.annotation.WebFilter;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import jakarta.servlet.http.HttpSession;
    
    @Component
    public class LoginFilter implements Filter{
    
    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    
    		System.out.println("before filter...");
    		
    		HttpServletRequest req=(HttpServletRequest) request;
    		HttpServletResponse res=(HttpServletResponse) response;
    		
    		res.setHeader("Access-Control-Allow-Credentials", "true");
    		if(req.getRequestURI().startsWith("/dept")) {
    			HttpSession session=req.getSession();
    			if(session.getAttribute("result")!=null) {
    				chain.doFilter(request, response);
    			}else {
    				res.sendError(401);
    			}
    		}else {
    			chain.doFilter(request, response);
    		}
    		System.out.println("after filter...");			
    	}
    
    }

    React 프로젝트 만들기

    App.js

    import logo from './logo.svg';
    import './App.css';
    import { BrowserRouter, Route, Router, Routes } from 'react-router-dom';
    import Main from './pages/Main';
    import Intro from './pages/Intro';
    import Depts from './pages/Depts';
    import Login from './pages/Login';
    import { createContext, useState } from 'react';
    import Logout from './pages/Logout';
    import Dept from './pages/Dept';
    
    export const LoginContext=createContext();
    
    function App() {
      const [login,setLogin]=useState(false);
      return (
        <LoginContext.Provider value={[login,setLogin]}>
        <BrowserRouter>
          <Routes>
              <Route path='/' element={<Main/>}/>
              <Route path='/intro' element={<Intro/>}/>
              <Route path='/dept/' element={<Depts/>}/>
              <Route path='/dept/:deptno' element={<Dept/>}/>
              <Route path='/login/' element={<Login/>}/>
              <Route path='/logout/' element={<Logout/>}/>
          </Routes>
        </BrowserRouter>
        </LoginContext.Provider>
      );
    }
    
    export default App;

    Main.js

    import React, { useContext } from 'react'
    import { Link } from 'react-router-dom'
    import { LoginContext } from '../App';
    
    function Main() {
        const [login,setLogin]=useContext(LoginContext);
      return (
        <>
            <nav>
                <Link to={'/'}>home</Link>{'    '}
                <Link to={'/intro'}>intro</Link>{'    '}
                <Link to={'/dept/'}>dept</Link>{'    '}
                {login?<Link to={'/logout'}>logout</Link>:<Link to={'/login'}>login</Link>}
                
            </nav>
            <div className='container'>
                <h2>Index page</h2>
            </div>
        </>
      )
    }
    
    export default Main

    Intro.js

    import React, { useContext } from 'react'
    import { Link } from 'react-router-dom'
    import { LoginContext } from '../App';
    
    function Main() {
        const [login,setLogin]=useContext(LoginContext);
      return (
        <>
            <nav>
                <Link to={'/'}>home</Link>{'    '}
                <Link to={'/intro'}>intro</Link>{'    '}
                <Link to={'/dept/'}>dept</Link>{'    '}
                {login?<Link to={'/logout'}>logout</Link>:<Link to={'/login'}>login</Link>}
                
            </nav>
            <div className='container'>
                <h2>Index page</h2>
            </div>
        </>
      )
    }
    
    export default Main

    Depts.js

    import React, { useEffect, useState } from 'react'
    import { Link, useNavigate } from 'react-router-dom'
    
    function Depts() {
        const navigate=useNavigate();
        const [arr,setArr]=useState([]);
        useEffect(()=>{
            fetch('http://localhost:8080/dept/',{
                method:'GET',
                credentials:'include'
            })
            .then(res=>res.json())
            .then(json=>setArr(json))
            .catch(err=>{
                console.log(err);
                navigate('/login/');
            })
        },[]);
      return (
        <>
            <nav>
                <Link to={'/'}>home</Link>{'    '}
                <Link to={'/intro'}>intro</Link>{'    '}
                <Link to={'/dept/'}>dept</Link>{'    '}
                <Link to={'/login/'}>login</Link>
            </nav>
            <div className='container'>
                <h2>list page</h2>
                {arr.map(ele=>(
                <Link to={'/dept/'+ele.deptno}>
                <dl>
                    <dt>{ele.dname}</dt>
                    <dd>{ele.loc}</dd>
                </dl>
                </Link>
            ))}
            </div>
        </>
      )
    }
    
    export default Depts

    Dept.js

    import React, { useEffect, useState } from 'react'
    import { Link, useNavigate, useParams } from 'react-router-dom'
    
    function Dept() {
        const navigate=useNavigate();
        const {deptno}=useParams();
        const [bean,setBean]=useState(null);
        const [editable,setEditable]=useState(true);
        useEffect(()=>{
            fetch('http://localhost:8080/dept/'+deptno,{
                method:'GET',
                credentials:'include'
            })
            .then(res=>res.json()).then(json=>setBean(json)).catch(alert);
        },[]);
    
        const editAction=(e)=>{
            e.preventDefault();
            if(editable)
                setEditable(!editable);
            else
                navigate('/dept/');
        };
      return (
        <>
            <nav>
                <Link to={'/'}>home</Link>{'    '}
                <Link to={'/intro'}>intro</Link>{'    '}
                <Link to={'/dept/'}>dept</Link>{'    '}
                <Link to={'/login/'}>login</Link>
            </nav>
            <div className='container'>
                <h2>{editable?'edit':'detail'} page</h2>
                <form onSubmit={editAction}>
                    <div>
                        <input name='deptno' value={bean==null?'':bean.deptno} readOnly={true}/>
                    </div>
                    <div>
                        <input name='dname' value={bean==null?'':bean.dname} 
                            onChange={e=>setBean({...bean,dname:e.target.value})} readOnly={editable}/>
                    </div>
                    <div>
                        <input name='loc' value={bean==null?'':bean.loc}
                            onChange={e=>setBean({...bean,loc:e.target.value})} readOnly={editable}/>
                    </div>
                    <div>
                        <button type='submit'>수정</button>
                    </div>
                </form>
            </div>
        </>
      )
    }
    
    export default Dept

    Login.js

    import React, { useContext, useRef } from 'react'
    import { Link, useNavigate } from 'react-router-dom'
    import { LoginContext } from '../App';
    
    function Login() {
        const [login,setLogin]=useContext(LoginContext);
        const navigate=useNavigate();
        const refId=useRef();
        const refPw=useRef();
        const loginAction=(e)=>{
            console.log(`id=${refId.current.value}&pw=${refPw.current.value}`);
            e.preventDefault();
            fetch('http://localhost:8080/login/',{
                method:'POST',
                body:`id=${refId.current.value}&pw=${refPw.current.value}`,
                credentials:'include',
                headers:{
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            })
            .then(res=>res.json())
            .then(json=>{
                setLogin(true);
                navigate('/');
            })
            .catch(alert);
        };
      return (
        <>
            <nav>
                <Link to={'/'}>home</Link>{'    '}
                <Link to={'/intro'}>intro</Link>{'    '}
                <Link to={'/dept/'}>dept</Link>{'    '}
                <Link to={'/login/'}>login</Link>
            </nav>
            <div className='container'>
                <h2>login page</h2>
                <form onSubmit={loginAction}>
                    <div>
                        <input ref={refId} name='id' placeholder='email'/>
                    </div>
                    <div>
                        <input ref={refPw} name='pw' placeholder='password'/>
                    </div>
                    <div>
                        <button>로그인</button>
                    </div>
                </form>
            </div>
        </>
      )
    }
    
    export default Login

    Logout.js

    import React, { useContext, useEffect } from 'react'
    import { Link } from 'react-router-dom'
    import { LoginContext } from '../App';
    
    function Logout() {
        const [login,setLogin]=useContext(LoginContext);
        useEffect(()=>{
            fetch('http://localhost:8080/login/logout',{
                method:'GET',
                credentials:'include'
            })
            .then(res=>res.ok)
            .catch(alert)
            .finally(()=>{
                setLogin(false);
            });
        },[]);
      return (
        <>
            <nav>
                <Link to={'/'}>home</Link>{'    '}
                <Link to={'/intro'}>intro</Link>{'    '}
                <Link to={'/dept/'}>dept</Link>{'    '}
                <Link to={'/login/'}>login</Link>
            </nav>
            <div className='container'>
                <h2>logout page</h2>
                <p>이용해주셔서 감사합니다</p>
            </div>
        </>
      )
    }
    
    export default Logout

    Oauth

    jwt(표준규격모델) 토큰을 이용해 로그인을 관리한다. 

    https://jwt.io/libraries?language=Java

     

    JWT.IO

    JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

    jwt.io

     

    pom.xml

    		<dependency>
    			<groupId>oi.jsonwebtoken</groupId>
    			<artifactId>jjwt-api</artifactId>
    			<vresion>0.11.2</vresion>
    		</dependency>
    		<dependency>
    		    <groupId>io.jsonwebtoken</groupId>
    		    <artifactId>jjwt-impl</artifactId>
    		    <version>0.11.2</version>
    		    <scope>runtime</scope>
    		</dependency>
    		<dependency>
    		    <groupId>io.jsonwebtoken</groupId>
    		    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    		    <version>0.11.2</version>
    		    <scope>runtime</scope>
    		</dependency>

    JwtService

    package com.gimhae.sts15.service;
    
    import java.security.Key;
    import java.util.Base64;
    import java.util.Date;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap.KeySetView;
    
    import org.springframework.stereotype.Service;
    
    import io.jsonwebtoken.JwtParser;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import io.jsonwebtoken.security.Keys;
    
    @Service
    public class JwtService {
    	final Key key=Keys.secretKeyFor(SignatureAlgorithm.HS256);
    	
    	public String createToken(String email) {
    		System.out.println(key.toString());
    		System.out.println(Base64.getEncoder().encode(key.getEncoded()));
    		String token=Jwts.builder()
    				.addClaims(Map.of("email",email))
    				.setExpiration(new Date(System.currentTimeMillis()+1000*100))
    				.signWith(key)
    				.compact();
    		
    		return token;
    	}
    	
    	public String getAuthEmail(String token) {
    		JwtParser parser=Jwts.parserBuilder()
    				.setSigningKey(key)
    				.build();
    		
    		String email=(String) parser.parseClaimsJws(token).getBody().get("email");
    		
    		return email;
    	}
    
    }

    Project Application

    package com.gimhae.sts15;
    
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    
    import com.gimhae.sts15.model.DeptRepo;
    import com.gimhae.sts15.model.Deptvo;
    import com.gimhae.sts15.model.UsersRepo;
    import com.gimhae.sts15.model.UsersVo;
    import com.gimhae.sts15.service.JwtService;
    
    @SpringBootApplication
    public class Sts15Application implements CommandLineRunner{
    
    	public static void main(String[] args) {
    		SpringApplication.run(Sts15Application.class, args);
    	}
    	
    	@Autowired
    	UsersRepo usersRepo;
    	@Autowired
    	DeptRepo deptRepo;
    
    	@Override
    	public void run(String... args) throws Exception {
    		List<UsersVo> userList=List.of(
    			UsersVo.builder().email("user1@localhost").pw("1234").name("user1").build(),
    			UsersVo.builder().email("user2@localhost").pw("1234").name("user2").build()
    		);
    		
    		userList.forEach(vo->usersRepo.save(vo.toEntity()));		
    //		usersRepo.saveAll(()->userList.iterator());
    		
    		List.of(
    				Deptvo.builder().dname("test1").loc("test").build(),
    				Deptvo.builder().dname("test2").loc("test").build(),
    				Deptvo.builder().dname("test3").loc("test").build(),
    				Deptvo.builder().dname("test4").loc("test").build()
    				).forEach(vo->deptRepo.save(vo.toEntity()));
    		
    		String token=jwtService.createToken("user1@localhost");
    		System.out.println("token:"+token);
    		String email=jwtService.getAuthEmail(token);
    		System.out.println("email:"+email);
    	}
    	
    	@Autowired
    	JwtService jwtService;
    	
    	
    
    }

    LoginFilter

    package com.gimhae.sts15;
    
    import java.io.IOException;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpRequest;
    import org.springframework.stereotype.Component;
    
    import com.gimhae.sts15.model.UsersRepo;
    import com.gimhae.sts15.model.entity.Users;
    import com.gimhae.sts15.service.JwtService;
    
    import jakarta.servlet.Filter;
    import jakarta.servlet.FilterChain;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.ServletRequest;
    import jakarta.servlet.ServletResponse;
    import jakarta.servlet.annotation.WebFilter;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import jakarta.servlet.http.HttpSession;
    
    @Component
    public class LoginFilter implements Filter{
    	@Autowired
    	JwtService jwtService;
    	@Autowired
    	UsersRepo usersRepo;
    
    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    
    		System.out.println("before filter...");
    		
    		HttpServletRequest req=(HttpServletRequest) request;
    		HttpServletResponse res=(HttpServletResponse) response;
    		System.out.println(req.getRequestURI());
    		
    		res.setHeader("Access-Control-Allow-Credentials", "true");
    		if(req.getRequestURI().startsWith("/dept")) {
    //			HttpSession session=req.getSession();
    //			if(session.getAttribute("result")!=null) {
    //				chain.doFilter(request, response);
    //			}else {
    //				res.sendError(401);
    //			}
    			try {
    			String token=req.getHeader("Authorization");
    //			if(!token.startsWith("Bearer ")) {
    //				throw new RuntimeException();
    //			}
    			System.out.println("test"+token);
    			token=token.split(" ")[1];
    			System.out.println("token:"+token);
    			String email=jwtService.getAuthEmail(token);
    			System.out.println("email:"+email);
    			Users user=usersRepo.findByEmail(email);
    			System.out.println("user:"+user);
    			chain.doFilter(request, response);
    			}catch(Exception e) {
    				res.sendError(401);
    			}
    		}else {
    			chain.doFilter(request, response);
    		}
    		System.out.println("after filter...");			
    	}
    
    }

    LoginControl

    package com.gimhae.sts15.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.gimhae.sts15.model.UsersRepo;
    import com.gimhae.sts15.model.UsersVo;
    import com.gimhae.sts15.service.JwtService;
    
    import jakarta.servlet.http.HttpSession;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    
    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.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    
    
    @RestController
    @RequestMapping("/login/")
    @RequiredArgsConstructor
    @Slf4j
    @CrossOrigin(origins = "http://localhost:3000",methods = {RequestMethod.GET,RequestMethod.POST})
    public class LoginController {
    	final UsersRepo usersRepo;
    	final JwtService jwtService;
    	ResponseEntity<?> resp;
    
    	@PostMapping("")
    	public ResponseEntity<?> login(String id,String pw,HttpSession session) {
    		log.debug("id:"+id);
    		log.debug("pw:"+pw);
    		
    		usersRepo.findByEmailAndPw(id, pw).ifPresentOrElse(entity->{
    			UsersVo bean=entity.toVo();
    			String token=jwtService.createToken(id);
    			resp=ResponseEntity.ok().body(token);
    			log.debug("로그인됨");
    		}, ()->{
    			resp=ResponseEntity.badRequest().build();
    		});
    		return resp;
    	}
    	
    	@GetMapping("logout")
    	public ResponseEntity logout(HttpSession session) {
    		session.invalidate();
    		return ResponseEntity.ok().build();
    	}
    	
    }

    UsersRepo

    package com.gimhae.sts15.model;
    
    import java.util.Optional;
    
    import org.springframework.data.repository.CrudRepository;
    
    import com.gimhae.sts15.model.entity.Users;
    
    public interface UsersRepo extends CrudRepository<Users, Integer> {
    
    	Optional<Users> findByEmailAndPw(String email,String pw);
    
    	Users findByEmail(String email);
    }

    DeptControl

    package com.gimhae.sts15.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.gimhae.sts15.model.DeptRepo;
    import com.gimhae.sts15.model.Deptvo;
    
    import jakarta.servlet.http.HttpSession;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestParam;
    
    
    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/dept/")
    @CrossOrigin(origins = "http://localhost:3000",methods = {RequestMethod.GET,RequestMethod.POST})
    @Slf4j
    public class DeptController {
    	final DeptRepo deptRepo;
    	ResponseEntity resp=ResponseEntity.status(401).build();
    
    	@GetMapping("")
    	public ResponseEntity<?> list(HttpSession session) {
    //		log.debug(session.getAttribute("result").toString());
    //		if(session.getAttribute("result")==null) return resp;
    		log.debug("login ok");
    		List<Object> list=new ArrayList<>();
    		deptRepo.findAll().forEach(entity->list.add(entity.toVo()));
    		return ResponseEntity.ok().body(list);
    	}
    	
    	@PostMapping("")
    	public ResponseEntity<?> add(@RequestBody Deptvo bean,HttpSession session) {
    		if(session.getAttribute("result")==null) return resp;
    		deptRepo.save(bean.toEntity());
    		return ResponseEntity.ok().build();
    	}
    	
    	@GetMapping("{deptno}")
    	public ResponseEntity<?> detail(@PathVariable int deptno,HttpSession session) {
    		if(session.getAttribute("result")==null) return resp;
    		deptRepo.findById(deptno).ifPresentOrElse(t->{
    			resp=ResponseEntity.ok().body(t.toVo());
    		}, ()->{
    			resp=ResponseEntity.badRequest().build();
    		});
    		return resp;
    	}
    }

    Login.js

    import React, { useContext, useRef } from 'react'
    import { Link, useNavigate } from 'react-router-dom'
    import { LoginContext } from '../App';
    
    function Login() {
        const [login,setLogin]=useContext(LoginContext);
        const navigate=useNavigate();
        const refId=useRef();
        const refPw=useRef();
    
        const loginAction=(e)=>{
            console.log(`id=${refId.current.value}&pw=${refPw.current.value}`);
            e.preventDefault();
            fetch('http://localhost:8080/login/',{
                method:'POST',
                body:`id=${refId.current.value}&pw=${refPw.current.value}`,
                credentials:'include',
                headers:{
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            })
            .then(res=>res.text())
            .then(token=>{
                console.log('login token:'+token);
                setLogin('Bearer '+token);
                navigate('/');
            })
            .catch(alert);
        };
      return (
        <>
            <nav>
                <Link to={'/'}>home</Link>{'    '}
                <Link to={'/intro'}>intro</Link>{'    '}
                <Link to={'/dept/'}>dept</Link>{'    '}
                <Link to={'/login/'}>login</Link>
            </nav>
            <div className='container'>
                <h2>login page</h2>
                <form onSubmit={loginAction}>
                    <div>
                        <input ref={refId} name='id' placeholder='email'/>
                    </div>
                    <div>
                        <input ref={refPw} name='pw' placeholder='password'/>
                    </div>
                    <div>
                        <button>로그인</button>
                    </div>
                </form>
            </div>
        </>
      )
    }
    
    export default Login

    Depts.js

    import React, { useContext, useEffect, useState } from 'react'
    import { Link, useNavigate } from 'react-router-dom'
    import { LoginContext } from '../App';
    
    function Depts() {
    
        const [login,setLogin]=useContext(LoginContext);
        const navigate=useNavigate();
        const [arr,setArr]=useState([]);
    
        useEffect(()=>{
            console.log(login);
            //if(login==null) return navigate('/login/');
            fetch('http://localhost:8080/dept/',{
                method:'GET',
                credentials:'include',
                withCredentials:true,
                headers:{
                    'Authorization':login,
                    'Content-Type':'application/json'
                }
            })
            .then(res=>{res.json();
                console.log('login then:'+login);})
            .then(json=>setArr(json))
            .catch(err=>{
                console.log(err);
                // navigate('/login/');
            });
        },[]);
    
      return (
        <>
            <nav>
                <Link to={'/'}>home</Link>{'    '}
                <Link to={'/intro'}>intro</Link>{'    '}
                <Link to={'/dept/'}>dept</Link>{'    '}
                <Link to={'/login/'}>login</Link>
            </nav>
            <div className='container'>
                <h2>list page</h2>
                {arr.map(ele=>(
                <Link to={'/dept/'+ele.deptno}>
                <dl>
                    <dt>{ele.dname}</dt>
                    <dd>{ele.loc}</dd>
                </dl>
                </Link>
            ))}
            </div>
        </>
      )
    }
    
    export default Depts

    App.js

    import logo from './logo.svg';
    import './App.css';
    import { BrowserRouter, Route, Router, Routes } from 'react-router-dom';
    import Main from './pages/Main';
    import Intro from './pages/Intro';
    import Depts from './pages/Depts';
    import Login from './pages/Login';
    import { createContext, useState } from 'react';
    import Logout from './pages/Logout';
    import Dept from './pages/Dept';
    
    export const LoginContext=createContext();
    
    function App() {
      const [login,setLogin]=useState(null);
      return (
        <LoginContext.Provider value={[login,setLogin]}>
        <BrowserRouter>
          <Routes>
              <Route path='/' element={<Main/>}/>
              <Route path='/intro' element={<Intro/>}/>
              <Route path='/dept/' element={<Depts/>}/>
              <Route path='/dept/:deptno' element={<Dept/>}/>
              <Route path='/login/' element={<Login/>}/>
              <Route path='/logout/' element={<Logout/>}/>
          </Routes>
        </BrowserRouter>
        </LoginContext.Provider>
      );
    }
    
    export default App;