Old Branch

스프링 부트(Spring Boot)와 Security, MySQL, React를 사용한 Spring Polling App (2)

woolbro 2019. 7. 18. 11:10
반응형

이전포스팅에 이어서 작성하도록 하겠습니다.

(원본글은 https://www.callicoder.com/spring-boot-spring-security-jwt-mysql-react-app-part-1/)

[Java/Spring-framework] - 스프링 부트(Spring Boot)와 Security, MySQL, React를 사용한 Spring Polling App (1)

 

스프링 부트(Spring Boot)와 Security, MySQL, React를 사용한 Spring Polling App (1)

이번 포스팅에서는 스프링 부트(Spring Boot )와 스프링 시큐리티(Spring Security)를 활용해서 Spring Polling App을 만들어 보도록 하겠습니다! 데이터베이스로는 MySQL를 사용 할 것이고, 프론트엔드로는 React..

woolbro.tistory.com

이전 포스팅에서는, 기본 도메인 모델과 저장소를 만들었습니다.

이번에는 JWT 인증과 함께 스프링 보안을 구성하고, 사용자가 애플리케이션에 등록하고 로그인 할 수 있도록 API를 작성 해 보겠습니다.

 

포스팅의 모든 코드는 Github 에 있습니다.


프로젝트 전체 구조

이번 포스팅에서 추가 해 줄 클래스가 많기 때문에, src/main/java 의 패키지 구조만 보여드리도록 하겠습니다.


커스텀 스프링 보안 클래스, 필터, 어노테이션 만들기

com.woolbro.security 패키지를 만들고, 내부에 CurrentUser, CustomUserDetailsService, JwtAuthenticationEntryPoint, JwtAuthenticatioFilter, JwtTokenProvider, UserPrincipal 의 자바 클래스를 만들어 줍니다.

 

JwtAuthenticationEntryPoint.java

AuthenticationEntryPoint 인터페이스를 구현하는데요, 이 인터페이스는 아래의 링크에 나와있습니다.

https://docs.spring.io/spring-security/site/docs/4.2.12.RELEASE/apidocs/org/springframework/security/web/AuthenticationEntryPoint.html

 

AuthenticationEntryPoint 인터페이스를 사용함으로, 인증이 필요한 resource에 엑세스 하려고 시도 할 때에 호출하게 되는데요, 그 중에서 예외가 발생 할 때마다 이 메소드가 호출됩니다.

package com.woolbro.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

	private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);

	@Override
	public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
			AuthenticationException e) throws IOException, ServletException {
		logger.error("Responding with unauthorized error. Message - {}", e.getMessage());
		httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
	}
}

 

 

UserPrincipal.java

다음으로는 UserDetails를 구현할 UserPrincipal클래스를 정의하도록 하겠습니다.

 

Spring Security는 지금 작성하는 UserPrincipal 객체에 저장 된 정보를 사용하여 인증 및 권한부여를 수행 하게 됩니다.

package com.woolbro.security;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.woolbro.model.User;

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

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class UserPrincipal implements UserDetails {
	private Long id;

	private String name;

	private String username;

	@JsonIgnore
	private String email;

	@JsonIgnore
	private String password;

	private Collection<? extends GrantedAuthority> authorities;

	public UserPrincipal(Long id, String name, String username, String email, String password,
			Collection<? extends GrantedAuthority> authorities) {
		this.id = id;
		this.name = name;
		this.username = username;
		this.email = email;
		this.password = password;
		this.authorities = authorities;
	}

	public static UserPrincipal create(User user) {
		List<GrantedAuthority> authorities = user.getRoles().stream()
				.map(role -> new SimpleGrantedAuthority(role.getName().name())).collect(Collectors.toList());

		return new UserPrincipal(user.getId(), user.getName(), user.getUsername(), user.getEmail(), user.getPassword(),
				authorities);
	}

	public Long getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public String getEmail() {
		return email;
	}

	@Override
	public String getUsername() {
		return username;
	}

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

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

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

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

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

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

	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;
		UserPrincipal that = (UserPrincipal) o;
		return Objects.equals(id, that.id);
	}

	@Override
	public int hashCode() {

		return Objects.hash(id);
	}
}

 

JWT 생성 및 검증을 위한 JwtTokenProvider.java

 

사용자가 성공적으로 로그인 한 후 JWT를 생성하고, JWT의 유효성을 검사하는데 사용 됩니다.

package com.woolbro.security;

import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
public class JwtTokenProvider {

	private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);

	@Value("${app.jwtSecret}")
	private String jwtSecret;

	@Value("${app.jwtExpirationInMs}")
	private int jwtExpirationInMs;

	public String generateToken(Authentication authentication) {

		UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();

		Date now = new Date();
		Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);

		return Jwts.builder().setSubject(Long.toString(userPrincipal.getId())).setIssuedAt(new Date())
				.setExpiration(expiryDate).signWith(SignatureAlgorithm.HS512, jwtSecret).compact();
	}

	public Long getUserIdFromJWT(String token) {
		Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();

		return Long.parseLong(claims.getSubject());
	}

	public boolean validateToken(String authToken) {
		try {
			Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
			return true;
		} catch (SignatureException ex) {
			logger.error("Invalid JWT signature");
		} catch (MalformedJwtException ex) {
			logger.error("Invalid JWT token");
		} catch (ExpiredJwtException ex) {
			logger.error("Expired JWT token");
		} catch (UnsupportedJwtException ex) {
			logger.error("Unsupported JWT token");
		} catch (IllegalArgumentException ex) {
			logger.error("JWT claims string is empty.");
		}
		return false;
	}
}

위의 JWT를 사용하기 위해서 application.properties에 JWT 등록 정보를 저장 해 줍니다.

 

application.properties

## App Properties
app.jwtSecret= JWTSuperSecretKey
app.jwtExpirationInMs = 604800000

 

보안인증필터 JWTAuthenthicationFilter.java

package com.woolbro.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

	@Autowired
	private JwtTokenProvider tokenProvider;

	@Autowired
	private CustomUserDetailsService customUserDetailsService;

	private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		try {
			String jwt = getJwtFromRequest(request);

			if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
				Long userId = tokenProvider.getUserIdFromJWT(jwt);

				UserDetails userDetails = customUserDetailsService.loadUserById(userId);
				UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
						userDetails, null, userDetails.getAuthorities());
				authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

				SecurityContextHolder.getContext().setAuthentication(authentication);
			}
		} catch (Exception ex) {
			logger.error("Could not set user authentication in security context", ex);
		}

		filterChain.doFilter(request, response);
	}

	private String getJwtFromRequest(HttpServletRequest request) {
		String bearerToken = request.getHeader("Authorization");
		if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
			return bearerToken.substring(7, bearerToken.length());
		}
		return null;
	}
}

filter의 Authoriztion 헤더에서 가져온 JWT를 파싱하고, 사용자의 ID를 읽습니다.

그 후 데이터베이스에서 사용자의 세부 정보를 가져오고 스프링 시큐리티(Spring Security) 내에서 인증을 설정 해 줍니다.

 

현재 로그인 한 사용자에게 접근 할 수 있는 사용자 정의 Annotaion

CurrentUser.java

package com.woolbro.security;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import java.lang.annotation.*;

@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {

}

Spring Security와 관련된 Annotation을 너무 많이 사용해서 의존성이 높아지는 것을 막아주기 위해 CurrentUser Annotation을 설정 했습니다. 프로젝트에서 Spring Security를 제거해야 한다면, CurrentUser Annotation을 변경함으로써 쉽게 제거 할 수 있습니다. - 추후에 어노테이션을 삭제하는 방법을 자세히 보도록 하겠습니다.

 

 

Spring Security, WebMVC, Spring JWT 설정(Config) 하기

프로젝트의 모든 보안 구성을 포함하는 클래스를 작성하도록 하겠습니다.

 

SecurityConfig.java

package com.woolbro.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.woolbro.security.CustomUserDetailsService;
import com.woolbro.security.JwtAuthenticationEntryPoint;
import com.woolbro.security.JwtAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	CustomUserDetailsService customUserDetailsService;

	@Autowired
	private JwtAuthenticationEntryPoint unauthorizedHandler;

	@Bean
	public JwtAuthenticationFilter jwtAuthenticationFilter() {
		return new JwtAuthenticationFilter();
	}

	@Override
	public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
		authenticationManagerBuilder.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
	}

	@Bean(BeanIds.AUTHENTICATION_MANAGER)
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
				.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
				.antMatchers("/", "/favicon.ico", "/**/*.png", "/**/*.gif", "/**/*.svg", "/**/*.jpg", "/**/*.html",
						"/**/*.css", "/**/*.js")
				.permitAll().antMatchers("/api/auth/**").permitAll()
				.antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability").permitAll()
				.antMatchers(HttpMethod.GET, "/api/polls/**", "/api/users/**").permitAll().anyRequest().authenticated();

		// Add our custom JWT security filter
		http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

	}
}

위의 클래스에 대해서 Annotation 과 구성의 의미를 적어보도록 하겠습니다.

내용이 조금 길기 때문에 아래의 접은글에 넣도록 할게요 :)

더보기

1.@EnableWebSecurity

프로젝트에서 웹 보안을 가능하게 하는 기본 Spring Security Annotation 입니다.

 

2.@EnableGlobalMethodSecurity

 

메소드 보안을 위해 사용합니다. 

@Secured, @RolesAllowd, @PreAuthorize, @PostAuthorize 에 사용됩니다.

아래는 예시입니다.

//secureEnalbed : @Secured 컨트롤러 / 메소드를 보호 할 수 있는 어노테이션

@Secured("ROLE_ADMIN")
public User getAllUsers() {}

@Secured({"ROLE_USER", "ROLE_ADMIN"})
public User getUser(Long id) {}

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public boolean isUsernameAvailable() {}


//jsr250Enabled : @RolesAllowed 어노테이션 활성화
@RolesAllowed("ROLE_ADMIN")
public Poll createPoll() {}  


//prePostEnabled : PreAuthorize , PostAuthorize 어노테이션 활성화
@PreAuthorize("isAnonymous()")
public boolean isUsernameAvailable() {}

@PreAuthorize("hasRole('USER')")
public Poll createPoll() {}

3. WebSecurityConfigurerAdapter

이 클래스는 Spring Security의 WebSecurityConfigurer 인터페이스를 구현합니다. 기본 보안 구성을 제공합니다.

위에서 작성한 SecurityConfig클래스에 적용된 WebSecurityConfigurerAdapter 는 사용자 정의 보안 구성을 제공하기 위해서 메소드를 확장하고 재정의 합니다.

 

4. JwtAuthenticationEntryPoint

이 클래스틑 인증절차 없이 자원에 엑세스 하려고 시도하는 클라이언트에게 401 오류를 반환하는데 사용됩니다.

Sprint Security의 AuthenticationEntyPoint 인터페이스를 구현합니다.

 

5. JwtAuthenticationFilter

JWT인증 토큰을 읽고, 유효성을 검사하고, 토큰과 관련된 세부사항을 로드합니다.

 

6. AuthenticationMangerBuilder / AuthenticationManger

사용자 인증을 위한 Spring Security 를 생성하는데 사용됩니다. 메모리 내 인증, JDBC 인증, 사용자 정의 인증 등을 사용 할 수 있지만, 이 예시에서는 passwordEncoder를 사용했습니다.

 

7. HttpSecurity Config

HttpSecurity 구성과 같은 보안기능을 구성하는데 사용됩니다.

csrf, sessionManagement 등의 보호기능 및 규칙을 추가 할 수 있습니다.

 

로그인 및 회원가입 API 작성

지금까지 작성 한 클래스로 보안 구성이 완료되었습니다. 이제 로그인과 가입 API를 작성하겠습니다.

 

com.woolbro.payload 패키지를 만들고 내부에 작성하도록 하곘습니다.

 

LoginRequest.java

package com.woolbro.payload;

import javax.validation.constraints.NotBlank;

public class LoginRequest {
    @NotBlank
    private String usernameOrEmail;

    @NotBlank
    private String password;

    public String getUsernameOrEmail() {
        return usernameOrEmail;
    }

    public void setUsernameOrEmail(String usernameOrEmail) {
        this.usernameOrEmail = usernameOrEmail;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

 

SignUpRequest.java

package com.woolbro.payload;

import javax.validation.constraints.*;

public class SignUpRequest {
    @NotBlank
    @Size(min = 4, max = 40)
    private String name;

    @NotBlank
    @Size(min = 3, max = 15)
    private String username;

    @NotBlank
    @Size(max = 40)
    @Email
    private String email;

    @NotBlank
    @Size(min = 6, max = 20)
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

 

JwtAuthenticationResponse.java

package com.woolbro.payload;

public class JwtAuthenticationResponse {
    private String accessToken;
    private String tokenType = "Bearer";

    public JwtAuthenticationResponse(String accessToken) {
        this.accessToken = accessToken;
    }

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public String getTokenType() {
        return tokenType;
    }

    public void setTokenType(String tokenType) {
        this.tokenType = tokenType;
    }
}

 

ApiResponse.java

package com.woolbro.payload;

public class ApiResponse {
    private Boolean success;
    private String message;

    public ApiResponse(Boolean success, String message) {
        this.success = success;
        this.message = message;
    }

    public Boolean getSuccess() {
        return success;
    }

    public void setSuccess(Boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

 

 

예외처리

요청이 유효하지 않거나 예외적인 상황이 발생하면 예외를 throw 하게 해야 합니다.

com.woolbro.exception 패키지에 작성 해 주겠습니다.

 

AppException.java

package com.woolbro.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class AppException extends RuntimeException {
	public AppException(String message) {
		super(message);
	}

	public AppException(String message, Throwable cause) {
		super(message, cause);
	}
}

 

BadRequestException.java

package com.woolbro.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {

	public BadRequestException(String message) {
		super(message);
	}

	public BadRequestException(String message, Throwable cause) {
		super(message, cause);
	}
}

 

ResourceNotFoundException.java

package com.woolbro.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {

	public BadRequestException(String message) {
		super(message);
	}

	public BadRequestException(String message, Throwable cause) {
		super(message, cause);
	}
}

 

 

인증컨트롤러

마지막으로, AuthController를 작성하여 로그인 및 가입을 위한 API 가 포함 된 컨트롤러를 작성 해 보도록 하겠습니다.

com.woolbro.controller 에 작성하도록 하겠습니다.

 

AuthController.java

package com.woolbro.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.woolbro.exception.AppException;
import com.woolbro.model.Role;
import com.woolbro.model.RoleName;
import com.woolbro.model.User;
import com.woolbro.payload.ApiResponse;
import com.woolbro.payload.JwtAuthenticationResponse;
import com.woolbro.payload.LoginRequest;
import com.woolbro.payload.SignUpRequest;
import com.woolbro.repository.RoleRepository;
import com.woolbro.repository.UserRepository;
import com.woolbro.security.JwtTokenProvider;

import javax.validation.Valid;
import java.net.URI;
import java.util.Collections;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

	@Autowired
	AuthenticationManager authenticationManager;

	@Autowired
	UserRepository userRepository;

	@Autowired
	RoleRepository roleRepository;

	@Autowired
	PasswordEncoder passwordEncoder;

	@Autowired
	JwtTokenProvider tokenProvider;

	@PostMapping("/signin")
	public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {

		Authentication authentication = authenticationManager.authenticate(
				new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmail(), loginRequest.getPassword()));

		SecurityContextHolder.getContext().setAuthentication(authentication);

		String jwt = tokenProvider.generateToken(authentication);
		return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
	}

	@PostMapping("/signup")
	public ResponseEntity<?> registerUser(@Valid @RequestBody SignUpRequest signUpRequest) {
		if (userRepository.existsByUsername(signUpRequest.getUsername())) {
			return new ResponseEntity(new ApiResponse(false, "Username is already taken!"), HttpStatus.BAD_REQUEST);
		}

		if (userRepository.existsByEmail(signUpRequest.getEmail())) {
			return new ResponseEntity(new ApiResponse(false, "Email Address already in use!"), HttpStatus.BAD_REQUEST);
		}

		// Creating user's account
		User user = new User(signUpRequest.getName(), signUpRequest.getUsername(), signUpRequest.getEmail(),
				signUpRequest.getPassword());

		user.setPassword(passwordEncoder.encode(user.getPassword()));

		Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
				.orElseThrow(() -> new AppException("User Role not set."));

		user.setRoles(Collections.singleton(userRole));

		User result = userRepository.save(user);

		URI location = ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/users/{username}")
				.buildAndExpand(result.getUsername()).toUri();

		return ResponseEntity.created(location).body(new ApiResponse(true, "User registered successfully"));
	}
}

 

CORS 사용

WebMvcConfig를 추가 해 주도록 하겠습니다. com.woolbro.config 패키지에 추가 해 주도록 하겠습니다.

 

WebMvcConfig.java

package com.woolbro.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

	private final long MAX_AGE_SECS = 3600;

	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**").allowedOrigins("*")
				.allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE").maxAge(MAX_AGE_SECS);
	}
}