일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Tomcat
- IntelliJ
- SpringBoot
- spring
- Eclipse
- oracle
- Source
- AJAX
- ubuntu
- Exception
- STS
- maven
- git
- JavaScript
- Open Source
- error
- 설정
- Spring Boot
- jpa
- 오픈소스
- Core Java
- PostgreSQL
- MySQL
- myBatis
- MSSQL
- Python
- Docker
- 문서
- Thymeleaf
- JDBC
- Today
- Total
헤르메스 LIFE
[Spring Boot] Spring Boot + JWT를 이용한 Token 처리 본문
개발환경
Spring Boot : 2.6.5 + JPA
Spring Web : 2.6.5
Spring Security : 5.6.2
Spring Tomcat
java-JWT : 3.18.3
Lombok : 1.18.22
DB : Postgresql
개발목표
1. Postgresql 에 접속해서 사용자의 정보를 조회한다.
2. JWT를 이욯해서 Token을 생성한다.
3. 생성된 Token을 이용해서 인증에 성공한다.
CorsConfig.java
package com.study.jwt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
//config.addAllowedOrigin("*"); // java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
config.addAllowedOriginPattern("*"); // e.g. http://domain1.com
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
SecurityConfig.java
Spring Security 5.70 이후부터 WebSecurityConfigurerAdapter를 상속 받는 방식은 deprecated
참조 : https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter/
package com.study.jwt.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.study.jwt.config.jwt.JwtAuthenticationFilter;
import com.study.jwt.config.jwt.JwtAuthorizationFilter;
import com.study.jwt.repository.UserRepository;
/*
* Spring Security 5.70 이후부터 WebSecurityConfigurerAdapter를 상속 받는 방식은 deprecated
*/
@Configuration
@EnableWebSecurity // 시큐리티 활성화 -> 기본 스프링 필터체인에 등록
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserRepository userRepository;
@Autowired
private CorsConfig corsConfig;
// BCryptPasswordEncoder는 Spring Security에서 제공하는 비밀번호 암호화 객체입니다.
// Service에서 비밀번호를 암호화할 수 있도록 Bean으로 등록합니다.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(corsConfig.corsFilter())
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin().disable()
.httpBasic().disable()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), userRepository))
.authorizeRequests()
.antMatchers("/**")
.access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
.anyRequest().permitAll();
}
}
PrincipalDetails.java
package com.study.jwt.config.auth;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.study.jwt.model.User;
/**
* Spring Security가 /login.do 요청이 들어오면
* 로그인이 완료되면 Security Session 을 생성한다. ( SecurityHolder )
* Object Type => Authentication 타입 객체
* Authentication 안에 User 정보가 있어야 함.
* User Object Type => UserDetails Type 객체
*
* Security Session -> Authentication -> UserDetails(PrincipalDetails)
*/
@SuppressWarnings("serial")
public class PrincipalDetails implements UserDetails{
private User user;
public PrincipalDetails(User user){
this.user = user;
}
public User getUser() {
return user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authList = new ArrayList<>();
authList.add(new SimpleGrantedAuthority(user.getRole()));
return authList;
}
}
PrincipalDetailsService.java
package com.study.jwt.config.auth;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.study.jwt.model.User;
import com.study.jwt.repository.UserRepository;
import lombok.RequiredArgsConstructor;
//UserDetailsService는 IoC로 찾음
///loginProcess.do 가 찾아오는 클래스임.
@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 어썬티케이션 매니저가 낚아챔
// JPA는 기본적인 CRUD만 있어서 다른걸 쓰려면 만들어줘야함
logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
logger.info("username :: {}", username);
User user = userRepository.findByUserId(username);
logger.info("user :: {}", user);
if ( user != null ) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
String roles[] = user.getRole().split(",");
for ( int i = 0; i < roles.length; i++ ) {
authorities.add(new SimpleGrantedAuthority(roles[i]));
}
logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
return new PrincipalDetails(user); // SecurityContext의 Authertication에 등록되어 인증정보를 가진다.
}
logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
return null;
}
}
JwtAuthenticationFilter.java
package com.study.jwt.config.jwt;
import java.io.IOException;
import java.util.Date;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.web.authentication.UsernamePasswordAuthenticationFilter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.study.jwt.config.auth.PrincipalDetails;
import com.study.jwt.dto.LoginRequestDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final AuthenticationManager authenticationManager;
// Authentication 객체 만들어서 리턴 => 의존 : AuthenticationManager
// 인증 요청시에 실행되는 함수 => /login
@Override
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response )
throws AuthenticationException {
logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
logger.info("JwtAuthenticationFilter : 진입");
// request에 있는 username과 password를 파싱해서 자바 Object로 받기
ObjectMapper om = new ObjectMapper();
LoginRequestDto loginRequestDto = null;
try {
loginRequestDto = om.readValue(request.getInputStream(), LoginRequestDto.class);
} catch (Exception e) {
e.printStackTrace();
}
logger.debug("JwtAuthenticationFilter :: {}", loginRequestDto);
// 유저네임패스워드 토큰 생성
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginRequestDto.getUserId(), loginRequestDto.getPassword());
logger.debug("JwtAuthenticationFilter : 토큰생성완료");
// authenticate() 함수가 호출 되면 인증 프로바이더가 유저 디테일 서비스의
// loadUserByUsername(토큰의 첫번째 파라메터) 를 호출하고
// UserDetails를 리턴받아서 토큰의 두번째 파라메터(credential)과
// UserDetails(DB값)의 getPassword()함수로 비교해서 동일하면
// Authentication 객체를 만들어서 필터체인으로 리턴해준다.
// Tip: 인증 프로바이더의 디폴트 서비스는 UserDetailsService 타입
// Tip: 인증 프로바이더의 디폴트 암호화 방식은 BCryptPasswordEncoder
// 결론은 인증 프로바이더에게 알려줄 필요가 없음.
Authentication authentication = authenticationManager.authenticate(authenticationToken);
PrincipalDetails principalDetailis = (PrincipalDetails) authentication.getPrincipal();
logger.debug("Authentication :: {}", principalDetailis.getUser().getUserName());
logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
return authentication;
}
// JWT Token 생성해서 response에 담아주기
@Override
protected void successfulAuthentication( HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult ) throws IOException, ServletException {
logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
PrincipalDetails principalDetailis = (PrincipalDetails) authResult.getPrincipal();
logger.debug("UserId :: {}", principalDetailis.getUser().getUserId());
logger.debug("UserName :: {}", principalDetailis.getUser().getUserName());
String jwtToken = JWT.create().withSubject(principalDetailis.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
.withClaim("userId", principalDetailis.getUser().getUserId())
.withClaim("username", principalDetailis.getUser().getUserName())
.sign(Algorithm.HMAC512(JwtProperties.SECRET));
logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
response.addHeader(JwtProperties.HEADER_STRING, JwtProperties.TOKEN_PREFIX + jwtToken);
}
}
JwtAuthorizationFilter.java
package com.study.jwt.config.jwt;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.study.jwt.config.auth.PrincipalDetails;
import com.study.jwt.model.User;
import com.study.jwt.repository.UserRepository;
// 인가
public class JwtAuthorizationFilter extends BasicAuthenticationFilter{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private UserRepository userRepository;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
super(authenticationManager);
this.userRepository = userRepository;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
String header = request.getHeader(JwtProperties.HEADER_STRING);
if(header == null || !header.startsWith(JwtProperties.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
logger.debug("header :: {}", header);
String token = request.getHeader(JwtProperties.HEADER_STRING)
.replace(JwtProperties.TOKEN_PREFIX, "");
logger.debug("token :: {}", token);
// 토큰 검증 (이게 인증이기 때문에 AuthenticationManager도 필요 없음)
// 내가 SecurityContext에 집적접근해서 세션을 만들때 자동으로 UserDetailsService에 있는 loadByUsername이 호출됨.
String username = JWT.require(Algorithm.HMAC512(JwtProperties.SECRET)).build().verify(token)
.getClaim("userId").asString();
logger.debug("username :: {}", username);
if(username != null) {
User user = userRepository.findByUserId(username);
// 인증은 토큰 검증시 끝. 인증을 하기 위해서가 아닌 스프링 시큐리티가 수행해주는 권한 처리를 위해
// 아래와 같이 토큰을 만들어서 Authentication 객체를 강제로 만들고 그걸 세션에 저장!
PrincipalDetails principalDetails = new PrincipalDetails(user);
Authentication authentication =
new UsernamePasswordAuthenticationToken(
principalDetails, //나중에 컨트롤러에서 DI해서 쓸 때 사용하기 편함.
null, // 패스워드는 모르니까 null 처리, 어차피 지금 인증하는게 아니니까!!
principalDetails.getAuthorities());
// 강제로 시큐리티의 세션에 접근하여 값 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
}
logger.info("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
chain.doFilter(request, response);
}
}
JwtProperties.java
package com.study.jwt.config.jwt;
public interface JwtProperties {
String SECRET = "1234"; // 우리 서버만 알고 있는 비밀값
int EXPIRATION_TIME = 864000000; // 10일 (1/1000초)
String TOKEN_PREFIX = "Bearer ";
String HEADER_STRING = "Authorization";
}
LogingRequestDto.java
package com.study.jwt.dto;
import lombok.Data;
@Data
public class LoginRequestDto {
private String userId;
private String password;
}
User.java
Postgresql 은 User 테이블이 예약테이블입니다. 그래서, User 객체를 사용할 수 없습니다. 또한 Sequence 를 사용하기 보다 UserId 를 이용해서 사용자를 조회하도록 변경했습니다.
package com.study.jwt.model;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.CreationTimestamp;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity(name = "member")
@NoArgsConstructor
@Getter
public class User {
@Id
@Column(name = "user_id")
private String userId;
@Column(name = "user_name")
private String userName;
private String password;
private String email;
private String role; // ROLE_USER, ROLE_ADMIN
@CreationTimestamp
private Timestamp createDate;
/**
* @param username
* @param password
*/
public User(String userId, String userName, String password) {
this.userId = userId;
this.userName = userName;
this.password = password;
}
/**
* @param username
* @param password
*/
public User(String userId, String userName, String password, String email) {
this.userId = userId;
this.userName = userName;
this.password = password;
this.email = email;
}
/**
* @param username
* @param password
*/
public User(String userId, String userName, String password, String email, String role) {
this.userId = userId;
this.userName = userName;
this.password = password;
this.email = email;
this.role = role;
}
public void setRole( String role ) {
this.role = role;
}
public List<String> getRoles() {
if ( this.role.length() > 0 ) {
return Arrays.asList(this.role.split(","));
}
return new ArrayList<>();
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + ", password=" + password + ", email=" + email
+ ", role=" + role + ", createDate=" + createDate + "]";
}
}
UserRepository.java
Sequence 를 사용하기 보다 UserId 를 이용해서 사용자를 조회하도록 변경했습니다.
package com.study.jwt.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.study.jwt.model.User;
public interface UserRepository extends JpaRepository<User, Long>{
// Jpa Naming 전략
// SELECT * FROM member WHERE id = ?
// Insert into member ( user_id, password, user_name, createdate, email, role) values ( 'hermeswing', '$2a$10$C4BZPH4raAlKGrvy9dtyyufBp56af2W6fge0hD1wLctWvNEjrK.AG', '홍길동', now(), 'hermeswing@test.com', 'ROLE_ADMIN')
// id:1, pw:1234
User findByUserId( String userId ); // JPA Query Method
}
http://localhost:9090/login
- 사실 어떤 URL을 호출해도 Body에 정확한 사용자 ID와 비밀번호만 있으면 Authorization 을 받을 수 있습니다.
CorsFilter 와 SecurityConfig 에 특별한 경로를 제한하지 않았기 때문입니다.
Authorization 의 생성은 JwtAuthenticationFilter의 successfulAuthentication() 에서 처리합니다.
필자는 userId를 Key로 사용할 예정이기 때문에 아래와 같이 처리했습니다.
String jwtToken = JWT.create().withSubject(principalDetailis.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + JwtProperties.EXPIRATION_TIME))
.withClaim("userId", principalDetailis.getUser().getUserId())
.withClaim("username", principalDetailis.getUser().getUserName())
.sign(Algorithm.HMAC512(JwtProperties.SECRET));
정상 처리된 로그가 보여집니다.
01:36:48.657 INFO c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(39) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:48.657 INFO c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(40) - JwtAuthenticationFilter : 진입
01:36:48.658 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(51) - JwtAuthenticationFilter :: LoginRequestDto(userId=hermeswing, password=1234)
01:36:48.658 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(57) - JwtAuthenticationFilter : 토큰생성완료
01:36:48.659 INFO c.s.j.c.auth.PrincipalDetailsService.loadUserByUsername(33) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:48.659 INFO c.s.j.c.auth.PrincipalDetailsService.loadUserByUsername(34) - username :: hermeswing
01:36:48.660 DEBUG org.hibernate.SQL.logStatement(144) -
select
user0_.user_id as user_id1_0_,
user0_.createDate as createda2_0_,
user0_.email as email3_0_,
user0_.password as password4_0_,
user0_.role as role5_0_,
user0_.user_name as user_nam6_0_
from
member user0_
where
user0_.user_id=?
Hibernate:
select
user0_.user_id as user_id1_0_,
user0_.createDate as createda2_0_,
user0_.email as email3_0_,
user0_.password as password4_0_,
user0_.role as role5_0_,
user0_.user_name as user_nam6_0_
from
member user0_
where
user0_.user_id=?
01:36:48.661 TRACE o.h.type.descriptor.sql.BasicBinder.bind(64) - binding parameter [1] as [VARCHAR] - [hermeswing]
01:36:48.662 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([user_id1_0_] : [VARCHAR]) - [hermeswing]
01:36:48.663 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([createda2_0_] : [TIMESTAMP]) - [2022-03-08 13:58:33.884322]
01:36:48.663 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([email3_0_] : [VARCHAR]) - [hermeswing@test.com]
01:36:48.663 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([password4_0_] : [VARCHAR]) - [$2a$10$C4BZPH4raAlKGrvy9dtyyufBp56af2W6fge0hD1wLctWvNEjrK.AG]
01:36:48.663 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([role5_0_] : [VARCHAR]) - [ROLE_ADMIN]
01:36:48.664 TRACE o.h.t.descriptor.sql.BasicExtractor.extract(60) - extracted value ([user_nam6_0_] : [VARCHAR]) - [홍길동]
01:36:48.664 INFO c.s.j.c.auth.PrincipalDetailsService.loadUserByUsername(36) - user :: User [userId=hermeswing, userName=홍길동, password=$2a$10$C4BZPH4raAlKGrvy9dtyyufBp56af2W6fge0hD1wLctWvNEjrK.AG, email=hermeswing@test.com, role=ROLE_ADMIN, createDate=2022-03-08 13:58:33.884322]
01:36:48.664 INFO c.s.j.c.auth.PrincipalDetailsService.loadUserByUsername(45) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:49.014 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(71) - Authentication :: 홍길동
01:36:49.014 INFO c.s.j.c.jwt.JwtAuthenticationFilter.attemptAuthentication(73) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:49.014 INFO c.s.j.c.jwt.JwtAuthenticationFilter.successfulAuthentication(82) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
01:36:49.014 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.successfulAuthentication(86) - UserId :: hermeswing
01:36:49.014 DEBUG c.s.j.c.jwt.JwtAuthenticationFilter.successfulAuthentication(87) - UserName :: 홍길동
01:36:49.015 INFO c.s.j.c.jwt.JwtAuthenticationFilter.successfulAuthentication(94) - ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
역시 암호나 아이디가 틀린경우에는 403 Forbidden 오류가 발생합니다.
역시 특별한 URL의 제약을 두지 않았기 때문에 http://localhost:9090/login2 를 호출해 봤습니다.
Header 에 Authorization 정보를 http://localhost:9090/login 에서 받았던 Authorization 로 채워넣습니다.
성공 후 이동 페이지 및 내용이 없기 때문에 오류(?)가 발생하지 않은 정도의 결과가 보여집니다. ( 404 Not Found 가 발생 합니다. - 권한 없음이 뜨지는 않았죠. )
잘못된 Authorization 정보를 넣으면 403 Forbidden 오류 결과가 보여집니다.
서버에서는 SignatureVerificationException 이 발생합니다.
01:32:30.877 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet].log(175) - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
com.auth0.jwt.exceptions.SignatureVerificationException: The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA512
참고 : 인프런 - 최주호 님의 스프링 부트 시큐리티 & JWT 강의
https://github.com/codingspecialist/Springboot-Security-JWT-Easy
'Spring Boot Framework' 카테고리의 다른 글
[Spring Boot] 개발환경 구축 - Hello Thymeleaf (0) | 2023.01.29 |
---|---|
[Spring Boot] Embedded Server Port 변경 (0) | 2023.01.29 |
[Spring Boot] Spring Boot + Thymeleaf + Thymeleaf Layout Dialect (0) | 2022.03.10 |
[Spring Boot] Spring Boot + Thymeleaf (0) | 2022.03.09 |
[Spring Boot] Spring Security 에서 antMatchers 를 Array 로 처리 (0) | 2022.03.09 |