Spring Security + JWT 구현하기전에 생각해볼만한 지점
JWT 토큰을 보관할때는 주로 쿠키와 스토리지중에 하나를 선택하게됩니다.
웹에서는 쿠키에 보관할때 CSRF 에 취약하고 스토리지에 보관할때는 XSS 에 취약하기때문에 거기에 대한 보호조치가 필요합니다
또 스토리지에 보관하게되면 탈취될 우려가 있기에 Refresh 토큰을 도입해 새로운 토큰을 갱신받아서 사용해야합니다.
쿠키에 보관하게되면 httponly, secure 옵션등을 사용할수 있으므로 상대적으로 탈취가능성이 낮아지지만 모바일서비스나 외부사이트에서 API를 호출하는데 불편함이 생깁니다.
이런 보안+편의를 고려해 다음과같은 인증체계를 구현하려고합니다
| 웹 | 모바일 | |
| 토큰전달방법 | 쿠키 (secure, httponly) | HTTP 헤더 |
| 추가보안조치 | CSRF 토큰사용 | Refresh 토큰사용 |
우선은 웹부터 구현해보겠습니다
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'
}
그리고 아래와 같이 파일을 생성하고 코드를 작성합니다

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 를 추가하고 서비스를 생성합니다

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 를 호출할 수 있습니다.
'개발 > Spring' 카테고리의 다른 글
| [Spring 실전] 3. 인증 (모바일) (0) | 2025.06.19 |
|---|---|
| [Spring 실전] 1. 멀티모듈 (0) | 2025.06.18 |
| Itext 를 이용하여 PDF 에 QR코드 넣기 (0) | 2025.03.11 |
| [Swagger] Request가 Map인 경우 Controller 작성법 (0) | 2023.11.01 |
| Querydsl 에서 datetime과 date 비교하기 (0) | 2023.07.10 |