개발/Spring

[Spring 실전] 2. 인증 (웹)

희묭 2025. 6. 19. 14:20
반응형

Spring Security + JWT 구현하기전에 생각해볼만한 지점

JWT 토큰을 보관할때는 주로 쿠키와 스토리지중에 하나를 선택하게됩니다.

웹에서는 쿠키에 보관할때 CSRF 에 취약하고 스토리지에 보관할때는 XSS 에 취약하기때문에 거기에 대한 보호조치가 필요합니다

또 스토리지에 보관하게되면 탈취될 우려가 있기에 Refresh 토큰을 도입해 새로운 토큰을 갱신받아서 사용해야합니다.

쿠키에 보관하게되면 httponly, secure 옵션등을 사용할수 있으므로 상대적으로 탈취가능성이 낮아지지만 모바일서비스나 외부사이트에서 API를 호출하는데 불편함이 생깁니다.

 

이런 보안+편의를 고려해 다음과같은 인증체계를 구현하려고합니다

  모바일
토큰전달방법 쿠키 (secure, httponly) HTTP 헤더
추가보안조치 CSRF 토큰사용 Refresh 토큰사용

 

우선은 웹부터 구현해보겠습니다

 

domain 모듈에 유저 데이터베이스를 생성합니다.

domain 모듈구조

package com.example.domain.code;

import com.fasterxml.jackson.annotation.JsonProperty;

public interface DescriptionCode {

    default String getCode() {
        if (this instanceof Enum) {
            return ((Enum<?>) this).name();
        } else {
            return null;
        }
    }

    @JsonProperty("text")
    String getDescription();

    static DescriptionCode toIdentityDescriptionCode(String code) {
        return new DescriptionCode() {
            @Override
            public String getCode() {
                return code;
            }
            @Override
            public String getDescription() {
                return code;
            }
        };
    }
}
package com.example.domain.code;

public enum UserRoleCode implements DescriptionCode{

    USER ("일반"),
    ADMIN ("관리자")
    ;

    private final String description;

    UserRoleCode(String description) {
        this.description = description;
    }

    @Override
    public String getDescription() {
        return this.description;
    }
}
package com.example.domain.entity;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Comment;

import java.util.ArrayList;
import java.util.List;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "tbl_user")
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Comment("인덱스")
    private Long idx;

    @Comment("이름")
    private String name;

    @Column(unique = true)
    @Comment("이메일")
    private String email;

    @Column(unique = true)
    @Comment("유저ID")
    private String userId;

    @Comment("비밀번호")
    private String password;

    @OneToMany(mappedBy = "user", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private List<UserRoleEntity> userRoleList = new ArrayList<>();

    public void addRole(UserRoleEntity userRoleEntity){
        userRoleList.add(userRoleEntity);
    }

    @Builder
    public UserEntity(String email, Long idx, String name, String userId, String password, List<UserRoleEntity> userRoleList) {
        this.email = email;
        this.idx = idx;
        this.name = name;
        this.userId = userId;
        this.password = password;
        this.userRoleList = userRoleList;
    }

}
package com.example.domain.entity;

import com.example.domain.code.UserRoleCode;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Comment;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "tbl_user_role")
public class UserRoleEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Comment("인덱스")
    private Long userRoleIdx;

    @Enumerated(EnumType.STRING)
    @Comment("권한유형")
    private UserRoleCode userType;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "userIdx")
    @Comment("유저ID")
    private UserEntity user;

    @Builder
    public UserRoleEntity(UserRoleCode userType, UserEntity user) {
        this.userType = userType;
        this.user = user;
    }
}

package com.example.domain.repository;

import com.example.domain.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<UserEntity, Long> {
    Optional<UserEntity> findByUserId(String userID);
}
package com.example.domain.repository;

import com.example.domain.entity.UserRoleEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRoleRepository extends JpaRepository<UserRoleEntity, Long> {
}

 

 

 

 

User 모듈을 생성하고 build.gradle 에 다음과같이 세팅해줍니다

dependencies {
    implementation project(":demo-domain")
    implementation project(":demo-common")
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'

    //Jwt
    implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
}

 

그리고 아래와 같이 파일을 생성하고 코드를 작성합니다

User 모듈구조

package com.example.user;

import com.example.user.csrf.CustomCsrfTokenRequestHandler;
import com.example.user.jwt.JwtFilter;
import com.example.user.jwt.JwtUserDetailsService;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@Configuration
@EnableWebSecurity
@AllArgsConstructor
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {
    private final JwtUserDetailsService jwtUserDetailsService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();

        http
                .csrf(csrf -> csrf.csrfTokenRepository(tokenRepository).csrfTokenRequestHandler(new CustomCsrfTokenRequestHandler()))
                .cors(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)

                .addFilterBefore(new JwtFilter(jwtUserDetailsService), UsernamePasswordAuthenticationFilter.class)

                .authorizeHttpRequests((authorize) -> authorize
                        .anyRequest().permitAll() // 그 외 모든 요청은 허용
                );

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
package com.example.user.jwt;

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

import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
    private final JwtUserDetailsService jwtUserDetailsService;

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

        // 쿠키에 넣는경우
        try {
            if (request.getCookies() != null) {
                for (Cookie cookie : request.getCookies()) {
                    if ("X-TOKEN".equals(cookie.getName())) {
                        var token = cookie.getValue();
                        var userDetails = jwtUserDetailsService.loadUserByUsername(token);
                        if (userDetails != null) {
                            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                        }
                    }
                }
            }
        }catch(Exception e){
            log.error("JWT authentication failed: {}", e.getMessage());
        }
        
        filterChain.doFilter(request, response);
    }
}
package com.example.user.jwt;

import com.example.common.exception.BusinessException;
import com.example.domain.repository.UserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
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 java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class JwtUserDetailsService implements UserDetailsService {
    private final JwtService jwtService;

    @Override
    public UserDetails loadUserByUsername(String token) throws UsernameNotFoundException, BusinessException {

        Long userId = jwtService.getUserId(token);
        String userName = jwtService.getUserName(token);
        LocalDateTime issuedAt = jwtService.getIssuedAt(token).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        List<String> roles = jwtService.getUserRole(token);

        JwtUserDto dto = JwtUserDto.builder().idx(userId).name(userName).issuedAt(issuedAt).build();
        dto.setRoles(roles);
        return new JwtUserDetails(dto);
    }
}
package com.example.user.jwt;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Getter
@RequiredArgsConstructor
public class JwtUserDetails implements UserDetails {
    private final JwtUserDto jwtUserInfoDto;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<String> roles = new ArrayList<>();
        jwtUserInfoDto.getRoles().forEach(p->{roles.add("ROLE_" + p);});
        return roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return "";
    }

    public Long getUserId() {
        return jwtUserInfoDto.getIdx();
    }

    @Override
    public String getUsername() {
        return jwtUserInfoDto.getName();
    }

    public LocalDateTime getIssueDateTime(){
        return jwtUserInfoDto.getIssuedAt();
    }
}
package com.example.user.jwt;

import com.example.common.exception.BusinessException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.security.Key;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.List;

@Slf4j
@Service
public class JwtService {
    private final Key accessKey;
    private final long accessTokenExpTime;

    public JwtService(
            @Value("${jwt.access_secret}") String accessKey,
            @Value("${jwt.access_secret_expiration_time}") long accessTokenExpTime
    ) {
        this.accessKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(accessKey));
        this.accessTokenExpTime = accessTokenExpTime;
    }

    //ACCESS KEY 생성
    public String createAccessToken(JwtUserDto member) {

        ZonedDateTime now = ZonedDateTime.now();
        ZonedDateTime tokenValidity = now.plusSeconds(accessTokenExpTime);

        return Jwts.builder()
                .claim("idx", member.getIdx())
                .claim("name", member.getName())
                .claim("role", member.getRoles())
                .setIssuedAt(Date.from(now.toInstant()))
                .setExpiration(Date.from(tokenValidity.toInstant()))
                .signWith(accessKey, SignatureAlgorithm.HS256)
                .compact();
    }

    //파싱
    public Long getUserId(String token) {
        return parseAccessClaims(token).get("idx", Long.class);
    }
    public String getUserName(String token) {
        return parseAccessClaims(token).get("name", String.class);
    }
    public List<String> getUserRole(String token) {
        return parseAccessClaims(token).get("role", List.class);
    }
    public Date getIssuedAt(String token){
        return  parseAccessClaims(token).getIssuedAt();
    }

    public Claims parseAccessClaims(String accessToken) {
        try{
            return Jwts.parser().setSigningKey(accessKey).build().parseClaimsJws(accessToken).getBody();
        } catch (ExpiredJwtException e) {
            throw new BusinessException("expired access token", e);
        } catch (Exception e) {
            throw new BusinessException("Invalid access token", e);
        }
    }
}
package com.example.user.jwt;

import com.example.domain.entity.UserRoleEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.time.LocalDateTime;
import java.util.List;

@Getter
@Setter
@Builder
@ToString
public class JwtUserDto {
    private Long idx;
    private String name;
    private List<String> roles;
    private LocalDateTime issuedAt;

    public void updateRoleEntity(List<UserRoleEntity> roles){
        this.roles = roles.stream().map(p->p.getUserType().getCode()).toList();
    }
}
package com.example.user.jwt;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class JwtResultDto {
    String accessToken;
}
package com.example.user.csrf;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.Optional;
import java.util.function.Supplier;

public class CustomCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
    private final XorCsrfTokenRequestAttributeHandler xor = new XorCsrfTokenRequestAttributeHandler();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
        this.xor.handle(request, response, csrfToken);
        csrfToken.get();
    }

    @Override
    public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
        boolean hasTokenInCookie = Arrays.stream(Optional.ofNullable(request.getCookies()).orElse(new Cookie[0])).anyMatch(cookie -> cookie.getName().equals("XSRF-TOKEN"));
        if(!hasTokenInCookie){
            return csrfToken.getToken();
        }else{
            return xor.resolveCsrfTokenValue(request, csrfToken);
        }
    }
}

 

application-user.yml

jwt:
  access_secret: VlwEyVBsYt9V7zq57TejMnVUyzblYcfPQye08f7MGVA9XkHa
  access_secret_expiration_time: 86400 #1일
  refresh_secret : VlwEyVBsYt9V7zq57TejMnVUyzblYcfPQye08f7MGVA9XkHb
 

 

로그인테스트를 위해서 api 도메인에 swagger 를 추가하고 서비스를 생성합니다

api 모듈구조

application.yml

server:
  port: 8080

spring:
  profiles:
    include:
      - domain
      - user

 

build.gradle

description = "demo-api"

dependencies {
    implementation project(":demo-common")
    implementation project(":demo-user")
    implementation project(":demo-domain")

    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'

    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
}

 

package com.example.api;

import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import java.util.Collections;

@EntityScan("com.example.domain.entity")
@EnableJpaRepositories(value = {"com.example.domain.repository"})
@ComponentScan(basePackages = {"com.example.user", "com.example.api"})
@SpringBootApplication
@SecurityScheme(type = SecuritySchemeType.HTTP, in = SecuritySchemeIn.HEADER, name = "Bearer Authentication", scheme = "bearer", bearerFormat = "JWT")
@SecurityScheme(type = SecuritySchemeType.APIKEY, in = SecuritySchemeIn.HEADER, name = "XSRF-TOKEN", paramName = "X-XSRF-TOKEN")
public class DemoApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApiApplication.class, args);
    }

    @Bean
    public OpenAPI openAPI() {

        return new OpenAPI()
                .components(new Components())
                .info(new Info()
                        .title("Springdoc")
                        .description("DEMO")
                        .version("1.0.0")
                );
    }
}
package com.example.api.controller;

import com.example.api.dto.request.UserLoginRequestDto;
import com.example.api.dto.request.UserRegisterRequestDto;
import com.example.api.dto.response.UserInfoResponseDto;
import com.example.api.service.UserService;
import com.example.common.response.ApiResponse;
import com.example.common.response.ApiResponseGenerator;
import com.example.user.jwt.JwtResultDto;
import com.example.user.jwt.JwtUserDetails;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.bind.annotation.*;

@Tag(name="USER")
@RequiredArgsConstructor
@RestController()
@RequestMapping("user")
@SecurityRequirement(name = "Bearer Authentication")
@SecurityRequirement(name = "XSRF-TOKEN")
public class UserController {
    private final UserService userService;

    @Operation(summary = "회원가입")
    @PostMapping("/register")
    public ApiResponse<Void> register(@RequestBody @Valid UserRegisterRequestDto userRegisterDto)
    {
        userService.register(userRegisterDto);
        return ApiResponseGenerator.success();
    }

    @Operation(summary = "로그인")
    @PostMapping("/login")
    public ApiResponse<JwtResultDto> login(@Valid @RequestBody UserLoginRequestDto userLoginRequestDto, HttpServletResponse response){
        return userService.login(userLoginRequestDto, response);
    }

    @Operation(summary = "회원정보")
    @PostMapping("/info")
    @Secured({"ROLE_USER"})
    public ApiResponse<UserInfoResponseDto> info(@AuthenticationPrincipal JwtUserDetails jwtUserDetails){
        return userService.info(jwtUserDetails);
    }

    @GetMapping("/csrf")
    public CsrfToken csrf(CsrfToken token) {
        // 이 객체는 Spring Security가 자동으로 주입해줌
        return token;
    }


}
package com.example.api.service;

import com.example.api.dto.request.UserLoginRequestDto;
import com.example.api.dto.request.UserRegisterRequestDto;
import com.example.api.dto.response.UserInfoResponseDto;
import com.example.common.exception.BusinessException;
import com.example.common.exception.BusinessParmException;
import com.example.common.response.ApiResponse;
import com.example.common.response.ApiResponseGenerator;
import com.example.domain.code.UserRoleCode;
import com.example.domain.entity.UserEntity;
import com.example.domain.entity.UserRoleEntity;
import com.example.domain.repository.UserRepository;
import com.example.domain.repository.UserRoleRepository;
import com.example.user.jwt.JwtResultDto;
import com.example.user.jwt.JwtService;
import com.example.user.jwt.JwtUserDetails;
import com.example.user.jwt.JwtUserDto;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseCookie;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Locale;

@RequiredArgsConstructor
@Service
@Transactional
public class UserService {
    private final UserRepository userRepository;
    private final UserRoleRepository userRoleRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtService jwtService;

    public void register(UserRegisterRequestDto userRegisterDto) {

        var userEntity = UserEntity.builder()
                .name(userRegisterDto.getUserName())
                .email(userRegisterDto.getEmail())
                .userId(userRegisterDto.getUserId())
                .password(passwordEncoder.encode(userRegisterDto.getPassword()))
                .build();
        userRepository.save(userEntity);

        //유저권한부여
        userRoleRepository.save(UserRoleEntity.builder().user(userEntity).userType(UserRoleCode.USER).build());


    }

    public ApiResponse<JwtResultDto> login(UserLoginRequestDto userLoginRequestDto, HttpServletResponse response) {
        var userEntity = userRepository.findByUserId(userLoginRequestDto.getUserId()).orElseThrow(()->{
            var errMap = new HashMap<String, String>();
            errMap.put("userId", "아이디를 확인해주세요");
            return new BusinessParmException("error", errMap);
        });

        if(passwordEncoder.matches(userLoginRequestDto.getPassword(), userEntity.getPassword())){
            var roles = userEntity.getUserRoleList().stream().map(p->p.getUserType().getCode()).toList();
            var info = JwtUserDto.builder().idx(userEntity.getIdx()).name(userEntity.getName()).roles(roles).build();
            String accessToken = jwtService.createAccessToken(info);

            var cookie = ResponseCookie.from("X-TOKEN", accessToken)
                    .path("/")
                    .sameSite("None")
                    .httpOnly(true)
                    .secure(true)
//                    .maxAge(60 * 60 * 24)
                    .build();;
            response.addHeader("Set-Cookie", cookie.toString());

            var result = JwtResultDto.builder().accessToken(accessToken).build();
            return ApiResponseGenerator.success(result);
        }else{
            var errMap = new HashMap<String, String>();
            errMap.put("password", "비밀번호를 확인해주세요");
            throw new BusinessParmException("error", errMap);
        }
    }

    public ApiResponse<UserInfoResponseDto> info(JwtUserDetails jwtUserDetails) {
        var user = userRepository.findById(jwtUserDetails.getUserId()).orElseThrow(()->new BusinessException("사용자가없습니다"));
        var time = jwtUserDetails.getIssueDateTime();

//        ZonedDateTime utcZoned = time.atZone(ZoneId.of("UTC"));
//        ZonedDateTime kstZoned = utcZoned.withZoneSameInstant(ZoneId.of("Asia/Seoul"));

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd (E) HH:mm", Locale.KOREAN);
        String formattedDate = time.format(formatter);

        var userInfoDto = UserInfoResponseDto.builder()
                .userId(jwtUserDetails.getJwtUserInfoDto().getIdx())
                .name(jwtUserDetails.getJwtUserInfoDto().getName())
                .userType(jwtUserDetails.getJwtUserInfoDto().getRoles().stream().map(p->UserRoleCode.valueOf(p)).toList())
                .issueDateTime(jwtUserDetails.getIssueDateTime().format(formatter))
                .build();
        return ApiResponseGenerator.success(userInfoDto);
    }
}
package com.example.api.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserLoginRequestDto {
    @Schema(description = "사용자ID")
    @NotEmpty(message = "아이디를 입력해주세요")
    private String userId;

    @Schema(description = "비밀번호", example = "stringst")
    @NotEmpty(message = "비밀번호를 입력해주세요")
    private String password;
}
package com.example.api.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserRegisterRequestDto {

    @Size(max = 20, message = "20자리내로 입력해주세요")
    @NotBlank(message = "이름은 필수 값 입니다.")
    @Schema(description = "이름")
    private String userName;

    @Size(max = 50, message = "50자리내로 입력해주세요")
    @Email(message = "올바른값을 입력해주세요")
    @NotBlank(message = "이메일은 필수 값 입니다.")
    @Schema(description = "이메일", example = "biozz@naver.com")
    private String email;

    @Size(max = 50, message = "50자리내로 입력해주세요")
    @NotBlank(message = "아이디는 필수 값 입니다.")
    @Schema(description = "아이디")
    private String userId;

    @Size(min = 8, max = 20, message = "8~20자리 사이로 입력해주세요")
    @Schema(description = "비밀번호")
    private String password;
}
package com.example.api.dto.response;

import com.example.domain.code.UserRoleCode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
@Builder
public class UserInfoResponseDto {

    @Schema(description = "사용자ID")
    private Long userId;

    @Schema(description = "이름")
    private String name;

    @Schema(description = "타입 : USER, ADMIN")
    private List<UserRoleCode> userType;

    @Schema(description = "접속시간")
    private String issueDateTime;
}

 

이제 swagger 에 접속해서 /user/register 로 회원가입을 하고

/user/login 으로 로그인해서 X-TOKEN 과 XSRF-TOKEN 쿠키가 생성된걸 확인할수 있습니다.

/user/csrf 를 호출하고 해당 값을 header (swagger 의 authorize 를 활용)에 넣으면 user/info 를 호출할 수 있습니다.

반응형