공부/Spring Boot

Spring Security + JWT 인증 인가 구현하기(로그인 API, 인증이 필요한 API)

son_i 2024. 8. 16. 01:51
728x90

1. 로그인 구현

비밀번호가 일치하면 토큰을 생성해서 내려준다.

 

구현해야할 것

- 로그인 컨트롤러

- 로그인 서비스

- JwtUtils 클래스에 token 생성 로직

 

컨트롤러

@PostMapping("/login")
    public ResponseEntity<SignIn.Response> signIn(
            @RequestBody @Valid SignIn.Request request) {

        return ResponseEntity.ok(memberService.signIn(request));
    }

 

서비스

public SignIn.Response signIn(SignIn.Request request) {

        Member member = getMemberByEmail(request.getEmail());

        validatedPassword(request, member);

        return SignIn.Response.toResponse(
                jwtUtils.generateToken(request.getEmail(), "ATK")
                , jwtUtils.generateToken(request.getEmail(), "RTK"));

    }

 

JwtUtils

 

 

JWT 개념과 전체적인 스프링 시큐리티 인증과정 참고

스프링 핵심가이드) 북스터디 8주차 : 13장 서비스의 인증과 권한 부여 (tistory.com)

 

스프링 핵심가이드) 북스터디 8주차 : 13장 서비스의 인증과 권한 부여

13장 서비스의 인증과 권한 부여 13.1 보안 용어 이해 13.1.1 인증 - 인증(authentication)은 사용자가 누구인지 확인하는 단계 ex) 로그인 로그인은 DB에 등록된 아이디와 pw를 사용자가 입력한 아이디와 p

soni-developer.tistory.com

 

JWT는 Header, Payload, Signature 구조로 되어있다.

  • Header
    • kid : 서명시 사용하는 키
    • typ : 토큰 유형
    • alg : 서명 암호화 알고리즘
  • Payload : 토큰에서 사용할 정보 조각들인 Claim이 담겨있음.
    • iss (issue) : 토큰 발급자
    • sub (subject) : 토큰 제목
    • iat (issued at) : 토큰 발급 시간
    • exp (expireation) : 토큰 만료 시간
    • roles : 권한 

여기서 roles는 public claims고 roles는 private claims다.

  • signature : 헤더에서 정의한 알고리즘 방식 사용.
package com.sj.Petory.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@RequiredArgsConstructor
@Slf4j
public class JwtUtils {
    private static final String TOKEN_TYPE = "token_type";
    private final UserDetailsService userDetailsService;

    @Value("${spring.jwt.expiredAt.ATK}")
    private String expiredAt_ATK;

    @Value("${spring.jwt.expiredAt.RTK}")
    private String expiredAt_RTK;

    @Value("${spring.jwt.secret}")
    private String secretKey;

    public String generateToken(final String email, final String tokenType) {

        Claims claims = Jwts.claims().setSubject(email);
        claims.put(TOKEN_TYPE, tokenType);

        Date now = new Date();
        Date expiredDate = setExpired(tokenType, now);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(expiredDate)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .compact();
    }

    private Date setExpired(String tokenType, Date now) {
        if (tokenType.equals("ATK")) {
            return new Date(now.getTime() + Long.parseLong(expiredAt_ATK));
        }

        if (tokenType.equals("RTK")) {
            return new Date(now.getTime() + Long.parseLong(expiredAt_RTK));
        }
        return null;
    }
}

 

1. application.yml에 ATK, RTK 만료시간과 암호화에 사용할 SecretKey를 등록하였다.

 

2. JWT의 payload에 들어갈 Claims 객체에 subject로 email을 넣었다.

   accesstoken과 refreshtoken을 구분하기 위해 token_type도 추가로 넣었다.

 

3. builder패턴으로 Jwt 토큰을 생성한다. 

   token 타입에 따른 만료시간을 다르게 설정해주기 위해 setExpired() 메소드를 추가로 구현하였다.

 

 

이렇게 구현해주면 로그인 시 accsstoken과 refreshtoken을 발급하여 응답으로 내려주게 된다. 

 

Trouble

2024-08-14T01:00:08.319+09:00 ERROR 22424 --- [Petory] [nio-8080-exec-5] c.s.P.exception.GlobalExceptionHandler : jakarta.servlet.ServletException : Handler dispatch failed: java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter

 

원인 : JAXB API가 java11에서부터는 완전히 제거되어 의존성을 추가로 넣어주어야 한다.

 

해결 : 

https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api/2.3.1에서 javax.xml.bind 검색 후 의존성 추가

implementation 'javax.xml.bind:jaxb-api:2.3.1'

 

 

2. 인증이 필요한 API 구현

회원 정보를 조회하는 페이지에 들어가기 위해서는 로그인 한 회원 정보가 필요하다.

 

직접 컨트롤러 파라미터로 요청 헤더에 담겨오는 토큰을 받아서 파싱해도 되지만 @AuthenticationPrincipal을 사용해서 로그인한 Authentication 객체를 가져오기로 했다.

 

@AuthenticationPrincipal에 대한 정보는 아래 포스팅으로 확인 

GiftFunding) 헤더에 JWT 토큰으로 로그인 후 사용자 정보 얻기 (tistory.com)

 

GiftFunding) 헤더에 JWT 토큰으로 로그인 후 사용자 정보 얻기

프로젝트에 로그인 기능을 구현하고 추후 권한이 필요한 리소스에 접근할 때 헤더에 입력된 토큰을 컨트롤러에서 @ReqeustHeader로 받아와서 서비스 단에 넘겨준 후 토큰에서 claims를 파싱해서 subjec

soni-developer.tistory.com

 

해당 API를 구현하기 위해서는 아래의 것들을 만들어줘야 한다.

- 회원정보 조회 컨트롤러

- 회원정보 조회 서비스

- JwtUtils에 받아올 토큰을 파싱하는 로직

- Filter를 이용해 요청이 들어올 때마다 헤더에 토큰을 파싱해서 검증해주는 로직

- @AutenticationPrincipal은 스프링 시큐리티의 인증이 완료된 객체인 UserDetils를 가지고 있으므로 이를 상속받아 필요한 정보만 뽑아올 MemberAdapter dto 

- UserDetails 객체를 뽑아오기 위해 UserDetailsService의 loadByUser() 메소드를 사용해야한다. 이를 위해 memberService가 UserDetailsService를 implements해서 loadByUser()메소드를 구현해야 함.

 

컨트롤러

로그인 한 사용자 정보를 받아오기 위해서는 UserDetails를 상속받을 dto 객체에 @AuthenticationPrincipal을 붙이면 된다.

@GetMapping
    public ResponseEntity<MemberInfoResponse> getMembers(
            @AuthenticationPrincipal MemberAdapter memberAdapter) {

        return ResponseEntity.ok(memberService.getMembers(memberAdapter));
    }

 

서비스

public MemberInfoResponse getMembers(final MemberAdapter memberAdapter) {
        Member member = getMemberByEmail(memberAdapter.getEmail());

        return MemberInfoResponse.fromEntity(member);
    }

 

MemberInfoResponse

package com.sj.Petory.domain.member.dto;

import com.sj.Petory.domain.member.entity.Member;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MemberInfoResponse {
    private String name;
    private String phone;
    private String image;

    public static MemberInfoResponse fromEntity(Member member) {
        return MemberInfoResponse.builder()
                .name(member.getName())
                .phone(member.getPhone())
                .build();
    }
}

 

이렇게만 만들어주면 SecurityContext에 Authentication이 없어서 @AuthenticationPrincipal로 MemberAdapter 객체를 받아오지 못 한다. 따라서 다음과 같은 오류가 난다.

 

 

정상적인 인증 과정을 거치기 위해 추가해야할 작업은 다음과 같다.

JwtAuthenticationFilter
- OncePerRequestFilter를 상속받아 매 요청시마다 한 번씩 필터를 거치도록 한다.
- HttpServletRequest header에서 토큰을 가져온다. (JwtUtils.resolveToken())
- 가져온 토큰이 유효한지 확인한다.(JwtUtils.validateToken())
- 토큰을 통해 Authentication 객체를 얻는다. (JwtUtils.getAuthentication())
- 가져온 Authentication을SecurityContextHolder에 추가한다. 

JwtUtils
- resolveToken() : HttpServletRequest Header에서 토큰을 뽑아오는 메소드
- validateToken() : 토큰의 만료시간을 검증하여 토큰이 유효한지 확인하는 메소드
- getAuthentication() : Authentication을 만드는 메소드. 여기서 UserDetailsService의 loadUserByUsername()이 사용됨
- parseClaims() : jwt토큰에서 claims를 파싱하는 메소드

 

JwtAuthenticationFilter

package com.sj.Petory.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtUtils jwtUtils;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String token = jwtUtils.resolveToken(request);

        if (StringUtils.hasText(token) && jwtUtils.validateToken(token)) {

            Authentication authentication = jwtUtils.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);

        }
        filterChain.doFilter(request, response);
    }
}

 

1. jwtUtils.resolveToken(request)

: 헤더를 메소드의 파라미터로 보내 token값을 얻는다.

 

2. if 조건문

: 토큰 값이 있고 validateToken() 메소드를 통해 만료되지 않았으면 안의 코드를 실행

 

3. Authentication authentication = jwtUtils.getAuthentication(token);

: getAuthentication() 메소드를 통해 Authentication 객체를 얻어온다.

 

4. 3에서 얻어온 Authentication 객체를 SecurityContextHolder에 저장한다.

 

 

JwtUtils

public String resolveToken(HttpServletRequest request) {
        String header = request.getHeader("Authorization");

        if (!StringUtils.hasText(header) || !header.startsWith(TOKEN_PREFIX)) {
            return null;
        }

        return header.substring(TOKEN_PREFIX.length());
    }

resolveToken() : Http 요청 헤더에서 Authorization으로 들어온 값을 Token prefix를 빼고 반환합니다.

 

public boolean validateToken(String token) {

        Date exp = parseClaims(token).getExpiration();

        if (exp.before(new Date())) {//날짜 개념 학습
            throw new MemberException(ErrorCode.TOKEN_EXPIRED);
        }
        return true;
    }

validateToken() : Jwts.parser()를 이용해 토큰을 파싱한다.

   .setSigningKey()로 토큰 생성할 때 넣어줬던 secretKey를 넣어줘야하고

   .parseClaimsJws(token)으로 토큰의 Claims를 파싱해준다.

   거기서 .getBody().getExpiration()으로 만료일자를 가져와 조건문으로 비교해준다.

 

parseClaims()메소드는 아래 코드에 있다.

 

public Authentication getAuthentication(String token) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(
                parseEmail(token));

        return new UsernamePasswordAuthenticationToken(
                userDetails
                , ""
                , userDetails.getAuthorities()
        );
    }
    private String parseEmail(String token) {
        return parseClaims(token).getSubject();
    }

    private Claims parseClaims(String token) {
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token).getBody();
    }

getAuthentication() : UserDetailsService의 loadUserByUsername() 메소드를 통해 UserDetails 객체를 받아오고

Authentication을 반환한다.

 

Authentication 객체를 만들 때 UsernamePasswordAuthenticationToken(principal, credentials, authorities)을 넣어준다.

 

여기서 pricipal에는 UserDetails객체를, authorities에는 userDetails.getAuthorities()로

 

권한 정보를 넣어줘야한다.

참고로 userDetails.getAuthorities를 안 넣어주면 인증 객체가 생성되어도 Autheticated = false로 뜨며 api 요청 시 forbidden이 뜨게 된다.

 

parseCliams는token에서 Claims를 가져오는 메소드이고 parseEmail은 토큰에서 subject(토큰 생성 시 subject로 토큰을 저장해놓음) 를 가져오는 메소드이다.

 

Trouble

2024-08-15T19:15:36.953+09:00 INFO 25608 --- [Petory] [nio-8080-exec-4] c.s.P.security.JwtAuthenticationFilter : context 잘 됐니 ?UsernamePasswordAuthenticationToken [Principal=com.sj.Petory.domain.member.dto.MemberAdapter@343c5163, Credentials=[PROTECTED], Authenticated=false, Details=null, Granted Authorities=[]]

해당 로그와 함께 인증 필요한 API 요청 시 403 Forbidden 에러 발생

 

UsernamePasswordAuthenticationToken 생성자는 세 번째 매개변수로 권한 목록을 받으며, 이를 통해 인증된 사용자로 설정 된다. 

 

해결 : getAuthentication()에 new UsernamePasswordAuthenticationToken()의 세 번째 인자로 userDetails.getAuthorities()를 넣어준다.

 

 

코드 전문

package com.sj.Petory.security;

import com.sj.Petory.domain.member.dto.MemberAdapter;
import com.sj.Petory.domain.member.service.UserDetailsServiceImpl;
import com.sj.Petory.exception.MemberException;
import com.sj.Petory.exception.type.ErrorCode;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@RequiredArgsConstructor
@Slf4j
public class JwtUtils {
    private static final String TOKEN_TYPE = "token_type";

    private final UserDetailsServiceImpl userDetailsService;

    @Value("${spring.jwt.expiredAt.ATK}")
    private String expiredAt_ATK;

    @Value("${spring.jwt.expiredAt.RTK}")
    private String expiredAt_RTK;

    @Value("${spring.jwt.secret}")
    private String secretKey;

    public String generateToken(final String email, final String tokenType) {

        Claims claims = Jwts.claims().setSubject(email);
        claims.put(TOKEN_TYPE, tokenType);

        Date now = new Date();
        Date expiredDate = setExpired(tokenType, now);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(expiredDate)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .compact();
    }

    private Date setExpired(String tokenType, Date now) {
        if (tokenType.equals("ATK")) {
            return new Date(now.getTime() + Long.parseLong(expiredAt_ATK));
        }

        if (tokenType.equals("RTK")) {
            return new Date(now.getTime() + Long.parseLong(expiredAt_RTK));
        }
        return null;
    }

    public String resolveToken(HttpServletRequest request) {
        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Bearer ")) {
            return null;
        }

        return header.substring("Bearer ".length());
    }

    public boolean validateToken(String token) {

        Date exp = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody()
                .getExpiration();

        if (exp.before(new Date())) {//날짜 개념 학습
            throw new MemberException(ErrorCode.TOKEN_EXPIRED);
        }
        return true;
    }

    public Authentication getAuthentication(String token) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(
                parseEmail(token));

        return new UsernamePasswordAuthenticationToken(
                userDetails
                , ""
                , userDetails.getAuthorities()
        );
    }
    private String parseEmail(String token) {
        return parseClaims(token).getSubject();
    }

    private Claims parseClaims(String token) {
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token).getBody();
    }
}

 

MemberAdapter

UserDetails를 인증 객체로 사용할 것이기 때문에 이를 상속한 dto가 필요하다. 

entity에 UserDetails를 받아오는 것은 영속성 문제로 좋지 않으니 꼭 dto를 만들어 상속받도록 하자.

GiftFunding) 헤더에 JWT 토큰으로 로그인 후 사용자 정보 얻기 (tistory.com)

 

GiftFunding) 헤더에 JWT 토큰으로 로그인 후 사용자 정보 얻기

프로젝트에 로그인 기능을 구현하고 추후 권한이 필요한 리소스에 접근할 때 헤더에 입력된 토큰을 컨트롤러에서 @ReqeustHeader로 받아와서 서비스 단에 넘겨준 후 토큰에서 claims를 파싱해서 subjec

soni-developer.tistory.com

package com.sj.Petory.domain.member.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Getter
@RequiredArgsConstructor
@Builder
public class MemberAdapter implements UserDetails{

    private final String email;
    private final String password;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.email;
    }

    @Override
    public boolean isAccountNonExpired() {
        return UserDetails.super.isAccountNonExpired();
    }

    @Override
    public boolean isAccountNonLocked() {
        return UserDetails.super.isAccountNonLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return UserDetails.super.isCredentialsNonExpired();
    }

    @Override
    public boolean isEnabled() {
        return UserDetails.super.isEnabled();
    }
}

 

 

UserDetailsServiceImpl

UserDetailsService인터페이스를 상속받을 클래스를 생성한다. 

loadByUsername() 메소드를 구현하여 위에서 만든 MemberAdapter 객체를 반환하도록 해야한다.

package com.sj.Petory.domain.member.service;

import com.sj.Petory.domain.member.dto.MemberAdapter;
import com.sj.Petory.domain.member.entity.Member;
import com.sj.Petory.domain.member.repository.MemberRepository;
import com.sj.Petory.exception.MemberException;
import com.sj.Petory.exception.type.ErrorCode;
import lombok.RequiredArgsConstructor;
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;

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

        Member member = memberRepository.findByEmail(email)
                .orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND));


        return new MemberAdapter(member.getEmail(), member.getPassword());
    }
}

loadUserByUsername 메소드는 DB에서 인증된 객체인 UserDetails를 반환하는데 MemberAdapter는 위에서 UserDetails를 상속받았으므로 반환에 꼭 MemberAdapter를 사용한다.

 

(이래야 나중에 컨트롤러에서 @AuthenticationPrincipal로 로그인 한 사용자 정보를 받아올 때 사용할 수 있다.)

 

SeurityConfig

package com.sj.Petory.config;

import com.sj.Petory.security.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .sessionManagement(session ->
                        session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .headers(headers ->
                        headers
                                .frameOptions(
                                        HeadersConfigurer.FrameOptionsConfig::sameOrigin)
                )
                .authorizeHttpRequests((authz) ->

                        authz
                                .requestMatchers(HttpMethod.GET, "/members").authenticated()
                                .requestMatchers(HttpMethod.POST, "/members").permitAll()
                                .requestMatchers(
                                        "/members/check-email"
                                        , "/members/check-name"
                                        , "members/login"
                                        , "/h2-console/**"
                                        , "/docs/**", "/v3/api-docs/**", "/swagger-ui/**").permitAll()
                                .anyRequest().authenticated()
                )
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);  // JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 추가

        return http.build();
    }
}

제일 마지막에 addFilterBefore가 없으면 403 forbidden 에러가 나게 된다.

 

해당 코드는 커스텀으로 생성한 JwtAuthenticationFilter를 Spring Security의 필터 체인에서 실행되도록 하는 것이다.

이 설정이 없으면 JwtAuthenticationFilter가 Spring Security의 필터 체인에서 실행되지 않아 JWT 인증/검증 과정이 이루어지지 않는다.

 

JwtAuthenticationFilter는 인증을 담당하는 필터이기 때문에 UsernamePasswordAuthenticationFilter 전에 실행되어야 한다.

UsernamePasswordAuthenticationFilter는 기본적으로 폼 기반 로그인 처리에 사용되는 필터이다.

따라서 JWT를 기반으로 한 인증을 먼저 처리하기 위해 JwtAuthenticationFilter가 UsernamePasswordAuthenticationFilter보다 먼저 실행되도록 설정한다.

 

위와 같이 코드를 작성하면

이렇게 인증이 필요한 객체에서 Authorization 으로 Bearer이 붙은 jwtToken을 보내줬을 때 정상적인 인증이 가능해진다 !

 

이번에 로그인 및 인증을 구현하면서 JWT의 구조와 생성, 파싱하는 것을 더 습득한 것 같다.

특히 Spring Security 인증 과정이 어렵기만 했는데 습득된 거 같아서 매우 뿌듯하다 !!! 😁😁  


참고

 

HandleDispatch fail 에러 해결

https://devthriver.tistory.com/13

 

[오류해결] jakarta.servlet.ServletException: Handler dispatch failed: java.lang.NoClassDefFoundError: javax/xml/bind/Datatyp

유튜브 서타몽님의 https://www.youtube.com/@jiraynorprogramming1589 서타몽 www.youtube.com SpringBoot + Reactjs(ts) + MySQL] 로그인 API 구현을 따라하면서 마지막 PostMan으로 로그인을 했을시 아래와 같은 에러가 뜬다.

devthriver.tistory.com

 

 

스프링 시큐리티 설정

https://velog.io/@dh1010a/Spring-Spring-Security%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-3.X-%EB%B2%84%EC%A0%84-1#%EC%9D%B8%EC%A6%9Dauthentication

 

[Spring] Spring Security를 이용한 로그인 구현 (스프링부트 3.X 버전) [1] - 동작 원리 및 Config 설정

스프링 부트 3.0 이상 버전의 시큐리티 사용법 및 바뀐 Config 작성법을 다루고 있습니다.

velog.io

 

스프링 시큐리티 기본 개념과 구조

https://velog.io/@dh1010a/SpringSecurity-%EC%8A%A4%ED%94%84%EB%A7%81%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90%EA%B3%BC-%EA%B5%AC%EC%A1%B0

 

[Spring]Security - 스프링시큐리티의 기본 개념과 구조

이번시간에는 스프링 시큐리티를 사용하는 법을 공부해보려고 합니다. 스프링 시큐리티의 로그인 진행방식을 먼저 살펴보겠습니다.우선 스프링 시큐리티를 설정을 통해, 기본적인 접근 제어와

velog.io

 

JWT 생성 참고

https://velog.io/@ads0070/JWT-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%9D%B8%EC%A6%9D#1-jwt-%EC%9D%B8%EC%A6%9D

 

JWT 생성 및 인증

JWT를 발급받고 인증해보자!JWT란?JWT는 Json Web Token으로, 인증에 필요한 정보들을 토큰에 담아 암호화시켜 사용하는 방식이다. 서명된 토큰이라는 점에서 쿠키보다 더 보안적이라고 할 수 있다.JWT

velog.io

 

Security Security Config

https://velog.io/@choidongkuen/Spring-Security-Spring-Security-Filter-Chain-%EC%97%90-%EB%8C%80%ED%95%B4#%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC

 

[Spring Security] Spring Security Filter Chain 에 대해

안녕하세요 오늘은 Spring Security 에서 인증,인가 과정을 담당하는 Security Filter Chain 에 대해 알아보겠습니다.Security Filter Chain 이란 Spring Security 에서 제공하는 인증,인가를 위한 필터들의 모음입니

velog.io

 

시큐리티 JWT 구성

https://velog.io/@yuureru/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-JWT-%EA%B5%AC%ED%98%84-%ED%95%B4%EB%B3%B4%EA%B8%B0#2%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-usernamepasswordauthenticationfilter-%EC%99%80-authenticationmanager-%EA%B5%AC%ED%98%84

 

스프링 시큐리티 JWT 구현 해보기

목차 JWT 인증방식 동작 원리 SecurityConfig 추가, DB연결, 회원가입 추가 회원가입 구현, AuthenticationFilter, AuthenticationManager 구현 DB 저장 구현, UserDetailService, UserDetail 구현

velog.io

 

728x90