[Study-10, 11주차] SpringSecurity 적용
[Study-10, 11주차] SpringSecurity 적용
[Study-8, 9주차] @RestControllerAdvice 활용 Exception + 동적쿼리 적용 및 고찰 + N+1 문제에 대한 고찰 - ② [Study-8, 9주차] @RestControllerAdvice 활용 Exception + 동적쿼리 적용 및 고찰 + N+1 문제에 대한 고찰 - ② [S
soohykeee.tistory.com
우선 긴 기간동안 학교 시험기간이 겹쳐 스터디 진행을 하지 못하다, 시험도 끝이 나고 더 이상 미룰 수 없어 스터디 준비를 하였다.
그 전 스터디에서 해오기로 한 과제는 다음과 같다.
- Spring Security 활용해서 Member Entity에 관한 API 기능 구현
- 권한 : ROLE_USER, ROLE_ADMIN
- 로그인, 회원가입 (*)
- 로그아웃 (인증된 사용자)
- 회원 조회 (ROLE_ADMIN)
Spring Security 만을 활용하여 회원가입, 로그인을 해보려 했지만 정보를 찾기도 힘들었고 JWT를 사용하지 않고 오로지 Security 만을 사용하는 정보는 적을 뿐더러, 구 버전의 정보가 많기에 애를 먹었다.
그러다 결국 JWT를 먼저 공부한 후, 적용하는 편이 더 이로울 것 같아 그렇게 진행하였다. 우선 해당 부분은 블로그에 따로 정리해두었던 JWT를 이용한 로그인 구현 예제를 보며 천천히 적용하였다. 해당 글의 전문은 아래에 첨부해놓겠다.
SpringBoot 로그인 구현 예제 - (Spring Security + JWT, 최신 버전)
SpringBoot 로그인 구현 예제 - (Spring Security + JWT, 최신 버전)
구현에 앞서며.. Spring Security + jwt 최신 버전으로 구현하나요? Spring Security와 JWT를 이용한 로그인 구현을 해보기 위해 많은 블로그와 강의를 찾아보았지만, Spring Security의 version upgrade로 인해, 기존
soohykeee.tistory.com
JWT 설정
[개념] JWT란 무엇인가?
JWT를 공부하기 전, 우선으로 Session 인증 방식과 Token 인증 방식에 대해 알아둘필요가 있다. [개념] Session 기반 인증 vs Token 기반 인증 [개념] Session 기반 인증 vs Token 기반 인증 인증 방식 종류 JWT에
soohykeee.tistory.com
JWT를 프로젝트에 적용해주기 위해서는 우선 build.gradle에 dependency를 추가해줘야 한다.
// jwt
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
application.yml 파일에도 JWT 사용할 때 필요한 secret key 값을 적어주었다.
해당 secret key는 외부에 유출해서도 안되고, github에도 올리지 않는 것이 좋지만 해당 프로젝트에서만 예외적으로 설명을 위해 이 처럼 application.yml 파일에 작성해주었고, github에도 올려놓았다.
jwt:
secret:
key: H+MbQeThWmZq3t6w9z$C&F)J@NcRfUjX
이러한 Secret Key를 사용할 때는 지켜야할 주의할 점이 있다.
- 랜덤하고 예측할 수 없는 값 사용 : Secret Key는 가능한 한 랜덤하고 예측할 수 없는 값을 사용해야 한다. 이는 보안성을 강화하고 암호화된 토큰을 안전하게 유지하는 데 도움이 된다.
- 길이와 복잡성 : Secret Key의 길이와 복잡성이 중요하다. 일반적으로 256비트 이상의 길이를 갖는 Secret Key를 권장하는데, 길이가 길수록 암호 분석에 대한 저항력이 더 강해진다.
- 보안 저장 : Secret Key는 반드시 안전한 장소에 보관되어야 한다. 코드 내에 하드코딩하지 않고, 환경 변수, 시크릿 매니저 또는 별도의 보안 저장소 등을 통해 관리해야 한다.
- Key의 주기적인 변경 : 주기적으로 Secret Key를 변경하는 것이 좋은데, 일정 기간마다 Key를 업데이트하면 기존에 유출된 Key로 인한 위험을 최소화할 수 있다.
- Key 공유 : Secret Key는 애플리케이션의 모든 서버 또는 서비스 간에 공유되어야 한다. 각 서버 또는 서비스가 동일한 Key를 사용하여 토큰을 검증할 수 있어야 한다.
Member와 Role은 앞서도 생성했었지만, 다시한번 코드를 첨부하겠다.
Member
package kr.co.jshpetclinicstudy.persistence.entity;
@Entity
@AttributeOverride(name = "id", column = @Column(name = "member_id", length = 4))
@Getter
@NoArgsConstructor
@Table(name = "tbl_members")
public class Member extends BaseEntity{
@Column(name = "identity", unique = true)
@NotNull
private String identity;
@Column(name = "password")
@NotNull
private String password;
@Column(name = "name", length = 100)
@NotNull
private String name;
@Enumerated(EnumType.STRING)
@Column(name = "role")
private Role role;
@Builder
public Member(String name,
String identity,
String password,
Role role) {
this.name = name;
this.identity = identity;
this.password = password;
this.role = role;
}
public void updateMember(MemberRequestDto.UPDATE update) {
this.name = update.getName();
this.role = Role.valueOf(update.getRole());
}
}
Role
package kr.co.jshpetclinicstudy.persistence.entity.enums;
@Getter
@AllArgsConstructor
public enum Role {
ROLE_USER("일반"),
ROLE_ADMIN("관리자");
String userRole;
public static Role of(String userRole) {
return Arrays.stream(Role.values())
.filter(role -> role.toString().equalsIgnoreCase(userRole))
.findAny().orElseThrow(() -> new NotFoundException(ResponseStatus.FAIL_MEMBER_ROLE_NOT_FOUND));
}
}
MemberRepository
package kr.co.jshpetclinicstudy.persistence.repository;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findMemberByIdentity(String identity);
boolean existsByIdentity(String identity);
}
여기서는 Member를 구분할 수 있는 unique한 값이 identity 이기에, 이를 통해 Member를 찾는 findMemberByIdnetity, 유효성 검사를 위한 existsByIdentity 메서드를 JPA를 통해 생성해주었다.
JWT를 사용해주기 위해서 새로 생성해준 클래스들의 위치와 클래스 명은 다음과 같다.
CustomUserDetails
package kr.co.jshpetclinicstudy.service.model.custom;
public class CustomUserDetails implements UserDetails {
private final Member member;
public CustomUserDetails(Member member) {
this.member = member;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority(String.valueOf(member.getRole())));
}
@Override
public String getPassword() {
return member.getPassword();
}
@Override
public String getUsername() {
return member.getIdentity();
}
// jwt 사용하기에, true
@Override
public boolean isAccountNonExpired() {
return true;
}
// jwt 사용하기에, true
@Override
public boolean isAccountNonLocked() {
return true;
}
// jwt 사용하기에, true
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// jwt 사용하기에, true
@Override
public boolean isEnabled() {
return true;
}
}
다른곳에서 사용하는 예제를 보게되면, UserDetails를 실제 사용하는 Member 엔티티나, User 엔티티에 상속해서 사용하는 경우도 있다.
하지만, 위의 방법으로 사용하게 된다면 실제 유저를 담는 엔티티가 오염되어, 구분하기 힘들 수 있고, 사용이 어려워질 수 있기에 따로 CustomUserDetails란 클래스를 생성해서 해당 클래스에 UserDetails를 상속받아 사용했다.
CustomUserDetails 클래스는 UserDetails 인터페이스를 구현한 사용자 정의 클래스로, Spring Security에서 인증 및 권한 정보를 제공하기 위해 사용되는 클래스이다.
해당 클래스는 Member 엔티티를 기반으로 사용자 정보를 제공하며, JWT를 사용하기 위해 isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled() 메서드를 항상 true를 반환하도록 구현해 주었다. 이렇게 하면 JWT를 통해 인증된 사용자의 계정이 만료되었거나 잠긴 상태인지를 확인하지 않게 된다.
또한 JWT를 사용하면 토큰 자체가 인증 정보를 포함하고 있기 때문에 기존의 세션 기반 인증과는 다른 방식으로 동작하게 된다. 그렇기에 SecurityConfiguration에서 Session 정책을 사용하지 않도록 설정해줄 것이다.
getAuthorities() 메서드에서는 member.getRole()을 SimpleGrantedAuthority로 변환하여 사용자의 권한 정보를 제공하는데, 이를 통해 인증된 사용자의 역할을 확인할 수 있다. 해당 메서드를 통해서 접근하는 곳의 권한이 올바른 사용자인지 확인할 수 있다. 또한 getPassword()와 getUsername() 메서드는 각각 사용자의 비밀번호와 식별자를 반환한다.
JpaUserDetailsService
package kr.co.jshpetclinicstudy.service.model.custom;
@Service
@RequiredArgsConstructor
public class JpaUserDetailsService implements UserDetailsService {
private final MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member member = memberRepository.findMemberByIdentity(username).orElseThrow(
() -> new UsernameNotFoundException("This Member is Invalid Authentication")
);
return new CustomUserDetails(member);
}
}
JpaUserDetailsService 클래스는 UserDetailsService 인터페이스를 구현한 클래스로, Spring Security에서 사용자 정보를 가져오는 역할을 한다.
loadUserByUsername 메서드는 주어진 사용자 이름(식별자)에 해당하는 사용자를 조회하고, 조회된 사용자를 기반으로 CustomUserDetails 객체를 생성하여 반환한다.
위의 코드에서 loadUserByUsername 메서드에서는 memberRepository를 사용하여 username을 기준으로 사용자를 조회한다. 여기서의 username은 우리는 앞서 identity로 해주었기에 해당 값을 통해 조회한다. 만약 조회된 member가 없을 경우 UsernameNotFoundException을 던지며, 조회된 member가 있을 경우에는 CustomUserDetails 객체를 생성하여 반환한다.
JpaUserDetailsService는 Spring Security의 인증 과정 중에 호출되어 사용자 정보를 제공하는 역할을 수행한다. 사용자의 인증 요청이 들어오면 loadUserByUsername 메서드가 호출되어 사용자를 조회하고, 조회된 사용자 정보를 Spring Security에게 제공하여 인증을 수행한다.
JwtProvider
package kr.co.jshpetclinicstudy.infra.jwt;
@RequiredArgsConstructor
@Component
public class JwtProvider {
@Value("${jwt.secret.key}")
private String salt;
private Key secretKey;
// 만료시간 1hour
private final Long exp = 1000L * 60 * 60;
private final JpaUserDetailsService userDetailsService;
@PostConstruct
protected void init() {
secretKey = Keys.hmacShaKeyFor(salt.getBytes(StandardCharsets.UTF_8));
}
// 토큰 생성
public String createToken(String identity, String role) {
Claims claims = Jwts.claims().setSubject(identity);
claims.put("role", role);
Date now = new Date();
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + exp))
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
// 권한 정보 획득 + Spring Security 인증 과정에서 권한 확인을 위한 기능
public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getIdentity(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
// Token 에 담겨있는 유저 Identity get
public String getIdentity(String token) {
return Jwts
.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
// Authorization Header 를 통해 인증
public String resolveToken(HttpServletRequest request) {
return request.getHeader("Authorization");
}
// Token 검증
public boolean validateToken(String token) {
try {
// Bearer 검증 + equalsIgnoreCase()를 사용하여 대소문자 구분없이 비교
if (!token.substring(0, "BEARER ".length()).equalsIgnoreCase("BEARER ")) {
return false;
} else {
token = token.split(" ")[1].trim();
}
Jws<Claims> claims = Jwts
.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token);
// 만료되었는지 확인 -> 만료라면 false, 만료 전이라면 true
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
@Value 어노테이션을 통해서, 앞서 application.yml에서 작성해주었던 secret key에 접근하도록 설정해준다. 그렇게하면 salt 필드에 해당 값이 저장이 된다. @Value 어노테이션은 아래의 값으로 import 해주지 않으면 오류가 발생하니 주의하자
import org.springframework.beans.factory.annotation.Value;
JwtProvider 클래스는 JWT 토큰 생성, 검증, 인증 등의 기능을 제공하는 클래스이다. 해당 클래스의 메서드들을 살펴보면 다음과 같다.
- init(): Bean이 생성된 후에 호출되는 @PostConstruct 메서드로, 주어진 salt 값을 바탕으로 시크릿 키를 초기화한다.
* @PostConstruct는 의존성 주입이 완료된 후 실행되어야 하는 메서드에 사용하는 어노테이션인데, 다른 리소스에서 호출되지 않아도 수행이 된다. 이러한 어노테이션을 사용해주는 이유는, 생성자가 호출되었을 때 bean은 초기화 되기 전이다. 하지만 해당 어노테이션을 사용해주게 되면 bean이 초기화 됨과 동시에 의존성을 확인할 수 있다. 추가적으로, bean lifeCycle에서 여러번 초기화되는 문제를 방지하기 위해서 오직 한번만 수행된다. (호출 순서: 생성자 호출 -> 의존성 주입완료 -> @PostConstruct) - createToken(String identity, String role): 주어진 식별자(identity)와 역할(role)을 기반으로 JWT 토큰을 생성한다. 토큰에는 subject로 식별자가 설정되고, claims에 역할 정보가 추가된다. 토큰의 만료 시간은 exp 변수에 지정된 값에 따라 설정되는데, 위에 주석으로 작성했듯 1시간으로 설정해주었다.
- getAuthentication(String token): 주어진 토큰을 이용하여 권한 정보를 획득하고, Spring Security의 인증을 위해 Authentication 객체를 생성하여 반환한다. 이를 통해 JWT 토큰을 이용한 인증 과정에서 권한 확인이 가능하다.
- getIdentity(String token): 주어진 토큰에서 유저의 식별자(identity)를 추출하여 반환한다.
- resolveToken(HttpServletRequest request): 주어진 HttpServletRequest 객체에서 Authorization 헤더를 추출하여 토큰을 반환한다.
- validateToken(String token): 주어진 토큰의 유효성을 검증한다. 토큰이 "BEARER "로 시작하지 않거나 만료되었을 경우에는 유효하지 않은 토큰으로 판단한다. 유효한 토큰인 경우 true를 반환한다.
JwtAuthenticationFilter
package kr.co.jshpetclinicstudy.infra.jwt;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
public JwtAuthenticationFilter(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtProvider.resolveToken(request);
if (token != null && jwtProvider.validateToken(token)) {
token = token.split(" ")[1].trim();
Authentication authentication = jwtProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
JwtAuthenticationFilter 클래스는 JWT 인증을 위한 필터이다. 이 필터는 OncePerRequestFilter 클래스를 상속하여 구현되었는데, OncePerRequestFilter 는 단 한번의 요청에 단 한번만 동작하도록 보장된 필터이다
- doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain): 모든 요청에 대해 한 번씩 호출되는 필터 메서드이다. 해당 메서드는 주어진 요청으로부터 JWT 토큰을 추출하고, 토큰이 유효하면 해당 토큰을 이용하여 사용자 인증을 수행한다.
- resolveToken(HttpServletRequest request): 주어진 HttpServletRequest 객체에서 Authorization 헤더를 추출하여 토큰을 반환한다. 해당 메서드는 JwtProvider 객체의 resolveToken 메서드를 호출하여 토큰을 추출한다.
- validateToken(String token): 주어진 토큰의 유효성을 검증한다. 해당 메서드는 JwtProvider 객체의 validateToken 메서드를 호출하여 토큰의 유효성을 검사한다.
- getAuthentication(String token): 주어진 토큰을 이용하여 사용자의 인증 정보를 가져온다. 이 메서드는 JwtProvider 객체의 getAuthentication 메서드를 호출하여 인증 정보를 반환한다.
- SecurityContextHolder.getContext().setAuthentication(authentication): 인증 정보를 SecurityContextHolder에 저장하여 현재 사용자를 인증된 사용자로 설정한다.
위의 코드를 통해 JwtAuthenticationFilter 클래스는 JWT 토큰을 추출하고 검증하여 사용자 인증을 수행하는 역할을 한다. 이 필터는 Spring Security의 인증 체인에서 동작하며, 요청이 들어올 때마다 필터가 실행되어 JWT 토큰을 처리한다. 인증이 완료되면 SecurityContextHolder에 인증 정보를 저장하여 애플리케이션 전반에서 인증된 사용자를 사용할 수 있게 한다.
SecurityConfiguration
package kr.co.jshpetclinicstudy.infra.config;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {
private final JwtProvider jwtProvider;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.cors().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Authorization (인가)
http
.authorizeHttpRequests()
.requestMatchers("/api/v1/register", "/api/v1/login").permitAll()
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.requestMatchers("/api/v1/members/**").hasAnyRole("USER", "ADMIN")
// 완벽하게 회원가입, 로그인이 구현되면 다른 요청에는 접근이 불가능하게 설정해줘야 한다
//.anyRequest().denyAll();
.anyRequest().permitAll();
// Authentication (인증)
http
.addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
// 권한 없을 때 오류 발생 - 요청한 경로의 USER_ROLE이 적합하지 않을 때
.accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(ResponseStatus.FAIL_FORBIDDEN.getStatusCode().value());
response.getWriter().write(ResponseStatus.FAIL_FORBIDDEN.getMessage());
}
})
// 인증이 올바르지 않을 때 오류 발생 - token 오류
.authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setStatus(ResponseStatus.FAIL_UNAUTHORIZED.getStatusCode().value());
response.getWriter().write(ResponseStatus.FAIL_UNAUTHORIZED.getMessage());;
}
});
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
* 주의 !
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.requestMatchers("/api/v1/members/**").hasAnyRole("USER", "ADMIN")
여기서 기존에 "ROLE_ADMIN", "ROLE_USER" 이런 식으로 작성을 해주었다.
하지만 이렇게 작성해준 후, 회원가입과 로그인 진행 후, token 정보도 넘겨주었는데 member 조회할 때 403 권한 에러가 발생하였다. 이러한 문제를 해결하기 위해 몇시간 동안 삽질을 했다.. 디버깅도 찍어보고 chatGPT 에게도 물어보았지만 해결방법이 도통 나오지 않았다. 포기하려던 찰나에 마지막에 chatGPT에게서 다음과 같은 답변을 듣게 되었다.
보내주신 코드를 확인해보았습니다.
문제는 `.requestMatchers("/api/v1/members/**").hasRole("ROLE_USER")` 부분입니다. `hasRole()` 메서드를 사용할 때에는 "ROLE_" 접두사를 붙이지 않아야 합니다. Spring Security는 자동으로 "ROLE_" 접두사를 추가합니다. 따라서 제거된 문자열로 권한을 설정해야 합니다.
해당 부분을 아래와 같이 수정해보세요:
.requestMatchers("/api/v1/members/**").hasRole("USER")
이렇게 수정하면 ROLE이 ROLE_USER인 사용자가 해당 경로에 접근할 수 있어야 합니다.
--------------------------------------------------------------------
죄송합니다. 이해에 오해가 생길 수 있는 점을 제먹해서 죄송합니다. 이전에는 사용자의 권한을 `String.valueOf(Role.ROLE_USER)`로 설정하셨기 때문에 "ROLE_" 접두사를 붙여야 했습니다. 하지만 실제로는 Spring Security가 자동으로 "ROLE_" 접두사를 추가하기 때문에 오류가 발생했던 것입니다.
다시 한번 사과드리며, 수정된 코드로 테스트해보시기 바랍니다. 혹시 문제가 계속되거나 추가적인 도움이 필요하시면 알려주세요. 죄송합니다.
Spring Security에서는 hasRole을 통해 접근 권한을 찾을 때 "ROLE_" 을 접두사로 붙여주기에, 여태 "ROLE_ROLE_USER", "ROLE_ROLE_ADMIN" 이러한 role로 값이 넘어오는지 확인을 하고 있어 403에러가 발생했던 것이다.
SecurityConfiguration 클래스는 Spring Security의 구성을 담당하는 클래스이다.
- filterChain(HttpSecurity http): SecurityFilterChain을 생성하는 메서드이다. 이 메서드는 HttpSecurity 객체를 매개변수로 받아 필터 체인을 설정한다.
- 인증과 관련된 설정:
- .httpBasic().disable(): HTTP 기본 인증을 비활성화.
- .csrf().disable(): CSRF(Cross-Site Request Forgery) 보호 기능을 비활성화.
- .cors().disable(): CORS(Cross-Origin Resource Sharing) 설정을 비활성화.
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS): JWT를 사용하기에, 세션 관리 정책을 상태 없음으로 설정하여 세션을 사용하지 않도록.
- 인가(Authorization) 설정:
- .authorizeHttpRequests(): 요청에 대한 인가 설정을 시작.
- .requestMatchers("/api/v1/register", "/api/v1/login").permitAll(): `/api/v1/register`와 `/api/v1/login` 경로에 대해서는 인증 없이 접근을 허용.
- .requestMatchers("/api/v1/admin/**").hasRole("ADMIN"): `/api/v1/admin/**` 경로에 대해서는 `ADMIN` 권한을 가진 사용자만 접근을 허용.
- .requestMatchers("/api/v1/members/**").hasAnyRole("USER", "ADMIN"): `/api/v1/members/**` 경로에 대해서는 `USER` 또는 `ADMIN` 권한을 가진 사용자만 접근을 허용.
- .anyRequest().denyAll(): 나머지 요청에 대해서는 접근을 차단 -> 추후 이것으로 변경 필요
- .anyRequest().permitAll(): 나머지 요청에 대해서는 인증 없이 접근을 허용
- 인증(Authentication) 관련 설정:
- .addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class): JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 이전에 추가한다. 이 필터는 JWT 인증을 처리하는 역할을 수행.
- .exceptionHandling(): 인증 및 인가 오류 처리를 설정.
- .accessDeniedHandler(): 권한이 없는 경우에 대한 처리를 설정합니다. AccessDeniedHandler를 구현하여 필요한 로직을 처리.
- .authenticationEntryPoint(): 올바르지 않은 인증 정보가 제공된 경우에 대한 처리를 설정. AuthenticationEntryPoint를 구현하여 필요한 로직을 처리한다.
- passwordEncoder(): 비밀번호 인코더를 생성하는 메서드. 기본적으로 Spring Security에서 제공하는 PasswordEncoderFactories를 사용하여 인코더를 생성.