팀프로젝트_PetHarmony

Spring Security 기록 (2)

이채림 2024. 8. 11. 17:16

전엔 JWT 인증 방법을 쓰지 않고 UserDetails에 대해서 기록했다.

JWT를 추가한 전체 코드를 기록하려고 한다.

 

1) build.gradle

필요한 Spring Security, JWT 관련 의존성을 추가한다.
이 단계는 프로젝트의 모든 보안 및 인증 관련 기능을 사용하기 위해 필수적이다.

주요 작업 : spring-boot-starter-security, jjwt 라이브러리 추가
ext {
	springSecurityVersion = '6.1.9'
}
dependencies {
	implementation "org.springframework.boot:spring-boot-starter-security"
	implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}

 

 

2) CustomUserDetails

Spring Security에서 사용자의 인증 정보를 담고 있는 클래스이다.
UserDetails 인터페이스를 구현하며, 사용자 객체를 기반으로 사용자명, 비밀번호, 권한 등을 제공한다.

주요 작업 : 사용자 정보 제공 메소드 정의
@Getter
public class CustomUserDetails implements UserDetails {
    private final User user;	// 이 객체를 통해 사용자 정보에 접근

    public CustomUserDetails(User user) {
        this.user = user;
    }

    // 사용자가 가진 권한 반환
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()));
    }

    // 사용자 비밀번호 반환 
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    // username(이메일) 반환
    @Override
    public String getUsername() {
        return user.getEmail();
    }

    // 사용자 이름 반환
    public String getUserName() {
        return user.getUserName();
    }

    /*
    * 계정의 상태를 확인하는 용도로 사용된다.
    * 모두 true를 반환하므로, 
    * 사용자 계정은 만료되지 않고,
    * 잠기지 않은 상태이며, 
    * 자격 증명이 만료되지 않고 
    * 활성화된 상태로 처리
    */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

 

3) CustomUserDetailService

UserDetailsService 인터페이스를 수현하여 사용자 정보를 로드하는 서비스이다.
Spring Security가 인증 시 사용자 정보를 확인할 때 사용된다.

주요 작업 : 사용자 정보 로드, UserDetails 반환
@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> new UsernameNotFoundException("찾을 수 없는 이메일: " + email));
        return new CustomUserDetails(user);
    }
}

loadUserByUsername() 메소드

- 이메일을 사용하여 UserRepository에서 사용자 정보를 조회한다.

- 만약 해당 이메일로 사용자를 찾지 못하면, UsernameNotFoundException을 던져 Spring Security에게 사용자가 없음을 알린다.

- 사용자가 존재하면, 조회된 User 객체를 CustomUserDetails 객체로 변환하여 반환한다.

- 반환된 CustomUserDetails 객체는 Spring Security가 사용자 인증 및 권한 부여를 처리하는 데 사용된다.

 

4) JwtTokenProvider

JWT 토큰을 생성, 파싱, 검증하는 역할을 담당하는 클래스이다.
인증이 성공하면 이 클래스에서 JWT 토큰을 발급하고 요청이 들어올 때 토큰을 검증한다.

주요 작업 : JWT 토크느 생성, 파싱, 검증 메소드 구현
@Component
public class JwtTokenProvider {
    // JWT 토큰 유효 기간 : 24시간
    private static final long JWT_EXPIRATION_MS = 86400000L;

    // JWT를 서명하기 위한 비밀키 (SignatureAlogrithm.HS512 알고리즘 사용하여 생성된 키)
    private final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS512);

    // 인증된 username(email)을 가져와 생성된 JWT 토큰 반환
    public String generateToken(Authentication authentication) {
        String username = authentication.getName();
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + JWT_EXPIRATION_MS);

        return Jwts.builder()
                .setSubject(username)       // 이메일
                .setIssuedAt(now)           // 발행시간
                .setExpiration(expiryDate)  // 만료시간
                .signWith(key)              // JWT 토큰
                .compact();
    }

    // JWT 토큰을 가져와 추출된 username(email) 반환
    public String getUsernameFromJWT(String token) {
        Claims claims = Jwts.parserBuilder()  // JWT 토큰을 파싱하여 토큰의 클레임을 가져온다
                .setSigningKey(key)  
                .build()
                .parseClaimsJws(token)        
                .getBody();

        return claims.getSubject();           // 클레임에서 subject 필드(username) 반환
    }

    // JWT 토큰이 유효한지 여부 반환
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }
}

 

 

5) JwtAuthenticationFilter

JWT 토큰을 사용하여 인증을 처리하는 필터이다. 
이 필터는 모든 요청에 대해 JWT 토큰을 확인하고, 유효한 경우 인증 객체를 설정한다.

주요 작업 : JWT 토큰 검증, SecurityContext에 인증 정보 설정
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtTokenProvider tokenProvider;
    private final UserDetailsService userDetailsService;

    public JwtAuthenticationFilter(JwtTokenProvider tokenProvider, UserDetailsService userDetailsService) {
        this.tokenProvider = tokenProvider;
        this.userDetailsService = userDetailsService;
    }

  
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String jwt = getJWTFromRequest(request);

        if (jwt != null && tokenProvider.validateToken(jwt)) {
            String username = tokenProvider.getUsernameFromJWT(jwt);

            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

    private String getJWTFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

 

다음 포스팅

https://chaereemee.tistory.com/25

 

Spring Security 기록 (최종 직전)

먼저 JWT(JSON Web Token)에 대해 알아보자.Spring Security는 여러 가지 인증 방법을 지원하며, JWT는 그 중 하나일 뿐이다.- 그 외 인증 방식 : 세션 기반 인증(상태 저장 방식) 등 JWT의 구조Header (헤더)토

chaereemee.tistory.com