JWT 구조 이해
- 헤더(Header): 토큰의 타입 (주로 JWT)과 사용된 해싱 알고리즘 (예: HMAC, SHA256, RSA)을 명시합니다.
- 페이로드(Payload): 토큰에 담을 클레임(claims)을 포함합니다. 클레임은 토큰 사용자에 대한 속성이나 추가적인 필요한 정보입니다.
- 서명(Signature): 헤더의 인코딩된 값과 페이로드의 인코딩된 값을 합쳐 해싱 알고리즘과 비밀키로 서명합니다.

JWT 라이브러리 선택
다양한 프로그래밍 언어를 위한 JWT 라이브러리가 있습니다. 예를 들어, Java의 경우 java-jwt, Node.js의 경우 jsonwebtoken 등이 있습니다.
pom.xml
<dependencies>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JJWT Library -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
gradle:
dependencies {
// Spring Boot Starter for Security
implementation 'org.springframework.boot:spring-boot-starter-security'
// JJWT for handling JWT
implementation 'io.jsonwebtoken:jjwt:0.9.1'
// 기타 필요한 Spring Boot 및 프로젝트 의존성들...
}
JWT 생성
- 헤더 설정: 알고리즘과 토큰 타입 설정
- 페이로드 설정: 사용자 식별 정보, 유효 기간, 발행자 등을 설정
- 서명 생성: 지정한 알고리즘과 비밀키를 사용하여 서명
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtil {
private String secretKey = "your-secret-key"; // 비밀키 설정
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 유효 기간 10시간
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public Claims extractAllClaims(String token) throws Exception {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
}
public String extractUsername(String token) throws Exception {
return extractAllClaims(token).getSubject();
}
public Boolean isTokenExpired(String token) throws Exception {
return extractAllClaims(token).getExpiration().before(new Date());
}
public Boolean validateToken(String token, String username) throws Exception {
final String usernameInToken = extractUsername(token);
return (usernameInToken.equals(username) && !isTokenExpired(token));
}
}
함수 설명
public String generateToken(String username) // 사용자 이름을 받아 jwt를 생성
public Claims extractAllClaims(String token) // 주어진 jwt로부터 클레임을 추출
public String extractUsername(String token) // 주어진 토큰에서 사용자 이름 추출
public Boolean isTokenExpired(String token) // 토큰 만료 확인
public Boolean validateToken(String token, String username) //토큰 유효성 확인
generatetoken 함수
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 유효 기간 10시간
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
setSubject :토큰 주제설정. 예시 코드에서는 username을 이용
setIssuedAt :토큰의 issuedat 시간설정을합니다.
setExpiration: 토큰의 만료시간을 설정
signWith : 사용할 서명 알고리즘과 비밀키를 지정(HMAC을 사용하는 HS256 알고리즘을 이용)
compact : 위설정을 바탕으로 JWT 문자을 생성하여 반환한다.
extractAllClaims 함수
public Claims extractAllClaims(String token) throws Exception {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
}
Jwts.parser().setSigningKey(secretKey) : 비밀키를 사용하여 파싱를 설정
parserClaimsJws(token) : jwt를 파싱하여 클레임을 반환
extractUsername 함수
public String extractUsername(String token) throws Exception {
return extractAllClaims(token).getSubject();
}
extractAllClaims(token).getSubject() : 주어진 토큰에서 subject 추출 ( 예시 코드는 username)
isTokenExpired 함수
public Boolean isTokenExpired(String token) throws Exception {
return extractAllClaims(token).getExpiration().before(new Date());
}
extractAllClaims(token).getExpiration().before(new Date()) : 주어진 토큰 만료시간 확인
validateToken 함수
public Boolean validateToken(String token, String username) throws Exception {
final String usernameInToken = extractUsername(token);
return (usernameInToken.equals(username) && !isTokenExpired(token));
}
usernameInToken.equals(username) && !isTokenExpired(token) : 토큰이 만료되었는지 확인
JWT 인증 및 발급
spring security 클래스 생성
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/authenticate").permitAll()
.anyRequest().authenticated();
// 여기에 필터 추가 및 기타 구성
}
}
configure의 /authenticate 권한 허용
.authorizeRequests().antMatchers("/authenticate").permitAll()
인증 및 발급 클래스 AuthenticationController
@RestController
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtTokenUtil;
@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);
}
catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password", e);
}
final String jwt = jwtTokenUtil.generateToken(authenticationRequest.getUsername());
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}
AuthenticationManager : spring security에서 제공하는 인터페이스로, 사용자 인증을 관리합니다.
JwtUtil : JWT 생성 및 검증 로직이 포함된 유틸리티 클래스입니다.
인증과정:
authenticationManager.authenticate(...) :
username과 password를 포함하는 UsernamePasswordAuthenticationToken을 생성하여 전달
예외처리 :
BadCredentialsException :
이 발생하면, 잘못된 사용자명 또는 비밀번호로 인한 인증 실패를 나타냅니다. 이 경우 사용자에게 적절한 예외 메시지를 반환
jwt 생성 :
jwtTokenUtil.generateToken(authenticationRequest.getUsername())
유저명 입력 토큰생성
응답 반환:
return ResponseEntity.ok(new AuthenticationResponse(jwt));
토큰 생성 완료 200 반환 및 jwt 토큰 반환
JWT 필터의 추가 및 전송
필터의 추가 클래스
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private MyUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
JwtUtil : JWT를 생성하고 검증하는 메서드를 제공
MyUserDetailsService : 사용자 정보를 로드하는 서비스
UserDetailsService : 인터페이스의 구현체
실제 필터링 로직이 구현되는 곳
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain):
필터링 로직
생성된 JWT는 주로 HTTP 헤더를 통해 전송됩니다. 일반적으로 'Authorization' 헤더에 'Bearer' 스키마를 사용하여 추가됩니다.
JWT 추출:
Authorization 헤더를 확인 jwt는 보통 Bearer[토큰] 형식으로 전송
final String authorizationHeader = request.getHeader("Authorization");
사용자 인증 정보 확인 및 설정:
정보 확인 :
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
UserDetailsService를 사용하여 사용자의 UserDetails를 로드합니다.
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
jwt 유효성을 검증 :
if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
...
}
UserNamePasswordAuthenticationToken을 생성 및 spring security 보안 컨택스트에 설정
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
필터체인의 다음 필터로 요청과 응답
chain.doFilter(request, response);
JWT 검증 및 사용
서버 측에서는 받은 JWT의 서명을 검증하고, 페이로드의 클레임을 사용하여 사용자 인증이나 권한 부여 등을 수행합니다.
예시 고객정보를 가져온다고 가정합니다.
고객 정보 api :
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
@RestController
@RequestMapping("/user")
public class UsersController {
@PostMapping("/auth/createtoken")
public ResponseEntity<Users> savetoken(@Validated @RequestBody UsersDTO usersDTO){
// usersService.save(usersDTO)
return ResponseEntity.ok( authApplication.signup(usersDTO) );
}
@GetMapping("/auth/securitytest")
public ResponseEntity<?> securityTest(Authentication authentication){
String getName = authentication.getName();
return ResponseEntity.ok(getName + " Authentication에서 인증인 확인되었습니다");
}
@GetMapping("/auth/createtoken")
public ResponseEntity<String> getToken(@Validated @RequestBody UsersDTO usersDTO){
// usersService.save(usersDTO)
try {
String jwtToken = authApplication.signin(usersDTO);
if (jwtToken != null) {
return ResponseEntity.ok(jwtToken);
} else {
return ResponseEntity.badRequest().build();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
보안 고려사항
- 암호화된 연결: JWT를 전송할 때는 HTTPS와 같은 암호화된 연결을 사용하는 것이 중요합니다.
- 서명 비밀키 보안: 서명에 사용되는 비밀키는 안전하게 보관해야 합니다.
- 유효기간 설정: 토큰의 유효 기간을 짧게 설정하여 보안 위험을 줄일 수 있습니다.
결과화면


'웹 > Spring vue 웹 개발' 카테고리의 다른 글
jenkins 연결 작업 02 - cpu 이용률 문제 ㅠ (0) | 2024.01.21 |
---|---|
jenkins 연결 작업 01 (0) | 2024.01.13 |
Docker 필요성, 로컬 환경 (윈도우 10), aws e2c(리눅스) 연결 배포 후 실행 (1) | 2023.12.26 |
spring vue 예외처리 및 인터럽트 (0) | 2023.12.24 |
spring vue local db-> rds 로 변경(h2dabase->postgreSql) (0) | 2023.12.17 |