@Slf4j
@Component
public class JwtTokenProvider {
@Autowired
private SignatureRepository signatureRepository;
@Autowired
private JWTTokenRepository jwtTokenRepository;
@Autowired
private HttpServletResponse response;
@Autowired
private UserRepository userRepository;
//서명키 저장
private Key key;
public void setKey(Key key){
this.key = key;
}
public JwtTokenProvider(){
}
@PostConstruct
public void init(){
List<Signature> list = signatureRepository.findAll();
if(list.isEmpty()){
byte[] keyBytes = KeyGenerator.getKeygen();
this.key = Keys.hmacShaKeyFor(keyBytes);
Signature signature = new Signature();
signature.setKeyBytes(keyBytes);
signature.setCreateAt(LocalDate.now());
signatureRepository.save(signature);
System.out.println("JwtTokenProvider Constructor 최초 Key init: " + key);
}else{
Signature signature = list.get(0);
byte[] keyBytes =signature.getKeyBytes();
this.key = Keys.hmacShaKeyFor(keyBytes);
System.out.println("JwtTokenProvider Constructor DB Key init: " + key);
}
}
// 유저 정보를 가지고 AccessToken, RefreshToken 을 생성하는 메서드
public TokenInfo generateToken(Authentication authentication) {
// 권한 가져오기
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
UserDto userDto = principalDetails.getUserDto();
// Access Token 생성
Date accessTokenExpiresIn = new Date(now + JwtProperties.EXPIRATION_TIME); // 60초후 만료
String accessToken = Jwts.builder()
.setSubject(authentication.getName())
.claim("username",userDto.getUsername()) //정보저장
.claim("auth", authorities)//정보저장
.claim("principal", authentication.getPrincipal())//정보저장
.claim("provider", userDto.getProvider())//정보저장
.claim("providerId", userDto.getProviderId())//정보저장
.claim("oauth2AccessToken", principalDetails.getAccessToken())//정보저장
.claim("oauth2Attributes", principalDetails.getAttributes())//정보저장
// .claim("credentials", authentication.getCredentials())//정보저장
// .claim("details", authentication.getDetails())//정보저장
.setExpiration(accessTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
// Refresh Token 생성
Date refreshTokenExpiresIn = new Date(now + JwtProperties.EXPIRATION_TIME_REFRESH); // 60초후 만료
String refreshToken = Jwts.builder()
.setSubject(authentication.getName())
.claim("username",userDto.getUsername()) //정보저장
.setExpiration(refreshTokenExpiresIn) //1일: 24 * 60 * 60 * 1000 = 86400000
.signWith(key, SignatureAlgorithm.HS256)
.compact();
//System.out.println("JwtTokenProvider.generateToken.accessToken : " + accessToken);
//System.out.println("JwtTokenProvider.generateToken.refreshToken : " + refreshToken);
return TokenInfo.builder()
.grantType("Bearer")
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
// JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 메서드
public Authentication getAuthentication(String accessToken) {
// 토큰 복호화
Claims claims = parseClaims(accessToken);
if (claims.get("auth") == null) {
throw new RuntimeException("권한 정보가 없는 토큰입니다.");
}
// 클레임에서 권한 정보 가져오기
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get("auth").toString().split(","))
.map(auth -> new SimpleGrantedAuthority(auth))
.collect(Collectors.toList());
String username = claims.getSubject(); //username
LinkedHashMap principal_tmp = (LinkedHashMap)claims.get("principal");
String provider = (String)claims.get("provider");
String providerId = (String)claims.get("providerId");
String auth = (String)claims.get("auth");
String oauth2AccessToken = (String)claims.get("oauth2AccessToken");
LinkedHashMap<String,Object> oauth2Attributes = (LinkedHashMap<String,Object>)claims.get("oauth2Attributes");
//System.out.println("oauth2Attributes : " + oauth2Attributes);
// String credentials = (String)claims.get("credentials");
// LinkedHashMap details = (LinkedHashMap)claims.get("details");
// System.out.println("principal_tmp : " + principal_tmp );
// System.out.println("provider : " + provider );
// System.out.println("credentials : " + credentials );
// System.out.println("details : " + details );
// UserDetails 객체를 만들어서 Authentication 리턴
PrincipalDetails principalDetails = new PrincipalDetails();
UserDto userDto = new UserDto();
userDto.setUsername(username);
userDto.setRole(auth);
userDto.setProvider(provider);
userDto.setProviderId(providerId);
principalDetails.setUserDto(userDto);
principalDetails.setAccessToken(oauth2AccessToken);
principalDetails.setAttributes(oauth2Attributes);
// System.out.println("JwtTokenProvider.getAuthentication UsernamePasswordAuthenticationToken : " + accessToken);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(principalDetails, claims.get("credentials"), authorities);
return usernamePasswordAuthenticationToken;
}
private Claims parseClaims(String accessToken) {
try {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody();
} catch (ExpiredJwtException e) {
return e.getClaims();
}
}
// 토큰 정보를 검증하는 메서드
public boolean validateToken(String token) throws IOException {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("Invalid JWT Token", e);
}
catch (ExpiredJwtException e) {
//log.info("Expired JWT Token", e);
log.info("Expired JWT Token..");
//RefreshToken 가져오기
JWTToken tokenEntity = jwtTokenRepository.findByAccessToken(token);
if(tokenEntity!=null){
String refreshToken = tokenEntity.getRefreshToken();
if(refreshToken!=null&&validateToken(refreshToken)){
// 토큰 만료 access x , refresh o
log.info("RefreshToken 유효함.. accessToken 다시발급");
//AccessToken 재발급---------------------------
String username = tokenEntity.getUsername();
Optional<User> userOptional = userRepository.findById(username);
String accessToken=null;
if(userOptional.isPresent()){
User user = userOptional.get();
long now = (new Date()).getTime();
Date accessTokenExpiresIn = new Date(now + JwtProperties.EXPIRATION_TIME); // 60초후 만료
PrincipalDetails principalDetails = new PrincipalDetails();
UserDto userDto = new UserDto();
userDto.setUsername(user.getUsername());
userDto.setRole(user.getRole());
userDto.setProvider(user.getProvider());
userDto.setProviderId(user.getProviderId());
principalDetails.setUserDto(userDto);
accessToken = Jwts.builder()
.setSubject(user.getUsername())
.claim("username",user.getUsername()) //정보저장
.claim("auth", user.getRole())//정보저장
.claim("principal", principalDetails)//정보저장
.claim("provider", user.getProvider())//정보저장
.claim("providerId", user.getProviderId())//정보저장
.setExpiration(accessTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
//---------------------------
tokenEntity.setAccessToken(accessToken);
jwtTokenRepository.save(tokenEntity);
//COOKIE 재전달
Cookie cookie = new Cookie(JwtProperties.COOKIE_NAME,tokenEntity.getAccessToken());
cookie.setMaxAge(JwtProperties.EXPIRATION_TIME);
cookie.setPath("/");
response.addCookie(cookie);
response.sendRedirect("/");
return true;
}else{
// 토큰 만료 access x , refresh x
log.info("RefreshToken 만료!..");
;
}
}else{
// DB에 내용없음..
log.info("DB에 JWT INFO 없음!..");
;
}
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT Token", e);
} catch (IllegalArgumentException e) {
log.info("JWT claims string is empty.", e);
}
return false;
}
}
토큰이 있는지 확인 → 유효성 검사 후 토큰 확인. → 토큰이 있다면 클라이언트에 쿠키형태로 토큰을 전달
없다면 토큰DB를 삭제
package com.example.demo.controller;
import com.example.demo.config.auth.jwt.JwtProperties;
import com.example.demo.config.auth.jwt.JwtTokenProvider;
import com.example.demo.config.auth.jwt.TokenInfo;
import com.example.demo.domain.dto.UserDto;
import com.example.demo.domain.entity.JWTToken;
import com.example.demo.domain.entity.User;
import com.example.demo.domain.repository.JWTTokenRepository;
import com.example.demo.domain.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
public class UserRestController {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JWTTokenRepository jwtTokenRepository;
@Autowired
private JwtTokenProvider jwtTokenProvider;
@PostMapping(value ="/join",produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<String> join_post(@RequestBody UserDto userDto){
log.info("POST /join..."+userDto);
//dto->entity
User user = User.builder()
.username(userDto.getUsername())
.password( passwordEncoder.encode(userDto.getPassword()) )
.role("ROLE_USER")
.build();
// save entity to DB
userRepository.save(user);
return new ResponseEntity<String>("success", HttpStatus.OK); //join 반환 데이터를 rest 하게 변경 , 프론트에서 사용해야 하므로 json 타입으로 전달
}
@PostMapping(value = "/login",consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity login(@RequestBody UserDto userDto) throws IOException {
log.info("포스트 로그인 !"+userDto);
Map<String,Object> response = new HashMap<>();
//1 접속중인 동일 계정이 있는지 확인
JWTToken token = jwtTokenRepository.findByUsername(userDto.getUsername());
if(token!=null){
//1-1 엑세스 토큰 만료 여부
//1-2 리프레시 토큰 만료 여부 //토큰이 있다면
if(jwtTokenProvider.validateTokenAsync(token.getAccessToken())){ //엑세스 토큰이 만료되면 리프레시 토큰 확인 , 있다면 엑세스 토큰 재갱신
//access token 을 클라이언트에게 전달 //토큰이 있다면
JWTToken reToken = jwtTokenRepository.findById(token.getId()).get();
response.put(JwtProperties.COOKIE_NAME, token.getAccessToken()); //쿠키 이름과 토큰값을 전달
response.put("state","success");
response.put("message","기존 로그인 정보가 존재합니다");
return new ResponseEntity(response,HttpStatus.OK);
}else{ //access 와 refresh 토큰이 만료되었다면 ,, 둘다 만료되었다면 DB에 있는 토큰 데이터 모두 삭제시키기
jwtTokenRepository.deleteById(token.getId());
// response.put("state","failed");
// response.put("message","엑세스 토큰이 만료됨. 새로 로그인 하세요"); //지우고 새로 로그인하는 작업가니까 return 필요 X
// return response;
}
}
//토큰이 null (DB 에 없는상태)
try{
//사용자 인증 처리 ID PW 일치 여부
Authentication authentication = //authentication 으로 이 내용을 받음
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(userDto.getUsername(),userDto.getPassword()) //ID PW 주면 위에 어덴트매니저가 확인을함
);
System.out.println("인증확인 : "+ authentication);
TokenInfo tokenInfo = jwtTokenProvider.generateToken(authentication);
System.out.println("JWT TOKEN : " + tokenInfo);
JWTToken tokenEntity = new JWTToken();
tokenEntity.setAccessToken(tokenInfo.getAccessToken());
tokenEntity.setRefreshToken(tokenInfo.getRefreshToken());
tokenEntity.setUsername(userDto.getUsername());
tokenEntity.setIssuedAt(LocalDateTime.now());
jwtTokenRepository.save(tokenEntity);
//
response.put("state","success");
response.put("message","인증성공");
response.put(JwtProperties.COOKIE_NAME,tokenEntity.getAccessToken());
}catch(AuthenticationException e){
System.out.println("인증실패 : " + e.getMessage());
response.put("state","failed");
response.put("message","인증실패");
return new ResponseEntity(response,HttpStatus.UNAUTHORIZED);
}
return new ResponseEntity(response,HttpStatus.OK);
}
}
Controller 코드.
DB에 토큰 테이블에 있는 토큰 정보 여부를 확인하여 리프레시 토큰 값이 있다면 엑세스코드를 재발급해주고 엑세스토큰과 리프레시 토큰값이 둘다 존재하지 않는다면 DB 에 있는 토큰 정보가 삭제되게끔 설정했다.
로컬 스토리지에 토큰 저장하기
import { useState,useEffect } from "react"
import axios from "axios"
const User = ()=>{
useEffect(()=>{
const token = localStorage.getItem('accessToken');
try{
axios.get
("<http://localhost:8090/user>",
{headers:{"Authorization":`Bearer ${token}`,'Content-Type':'application/json'}})
.then(resp=>{console.log(resp);})
}catch(error){
console.log(error);
}
},[])
return (
<h1>User Page</h1>
)
}
export default User
위처럼 로컬스토리지에 엑세스토큰을 담아준다
그럼 이런식으로 로컬 스토리지에 엑세스 토큰값이 담기게 된다.
리액트의 useNavigate 란 ?
리액트 훅 중에 하나로 페이지 이동(라우팅)을 구현할 때 사용한다.
라우터 내에서만 작동하기 때문에 BrowserRouter 나 HashRouter 와 함께 사용해야 한다.
import React from "react";
import { useNavigate } from "react-router-dom";
function Home() {
const navigate = useNavigate(); // useNavigate 훅 사용
const goToAbout = () => {
navigate("/about"); // "/about" 페이지로 이동
};
return (
<div>
<h1>Home Page</h1>
<button onClick={goToAbout}>Go to About</button>
</div>
);
}
export default Home;
위 코드처럼 Home 함수를 사용하면 /about 경로로 이동하게 해준다.
오전에 했던거 정리
join에서 가입을 하면 위 username , password 에 상태값으로 저장
로그인을 하면 username , password 를 기반으로 엑세스 토큰을 발급
프로젝트 진행중 알게 된 지식
OncePerRequestFilter 란?
이름만 보자면 Http Request 의 한 번의 요청에 대해 한번만 실행하는 Filter이다.
Filter 와 Interceptor 의 차이
- Filter :
- 서블릿 API 의 일부. 요청 및 응답 객체를 조작하거나 필터링 하는데 사용됨
- 일반적으로 서블릿이나 JSP 가 실행되기 전에 요청을 가로채거나 응답이 클라이언트로 전달되기 전에 가공하는 역할을 한다.
- 순수 레거시 자바 환경에서 사용한다
- Interceptor :
- 인터셉트는 스프링이나 Hibernate 와 같은 특정 프레임워크에서 제공하는 기능으로 , 요청 전/후에 추가적인 처리를 수행한다.
- 주로 Controller나 특정 메서드 호출 전/후에 동작한다.
주요 차이 : 서블릿 , 스프링 같은 사용하는 곳에서의 차이 ,
'Developer Note > 국비과정 수업내용 정리&저장' 카테고리의 다른 글
24년 12월 13일 (2) | 2024.12.18 |
---|---|
24년 12월 12일 (0) | 2024.12.18 |
24년 12월 10일 (0) | 2024.12.18 |
24년 12월 9일 (1) | 2024.12.18 |
24년 12월 6일 (2) | 2024.12.15 |