{"id":2508,"date":"2021-08-01T20:52:31","date_gmt":"2021-08-01T11:52:31","guid":{"rendered":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2508"},"modified":"2021-08-01T21:19:02","modified_gmt":"2021-08-01T12:19:02","slug":"profile-server-%ec%97%90-%ed%9a%8c%ec%9b%90%ea%b0%80%ec%9e%85-api-%ec%b6%94%ea%b0%80","status":"publish","type":"post","link":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2508","title":{"rendered":"Profile Server \uc5d0 \ud68c\uc6d0\uac00\uc785 API \ucd94\uac00"},"content":{"rendered":"<h1>Profile Server \uc5d0 \ud68c\uc6d0\uac00\uc785 API \ucd94\uac00<\/h1>\n<h2>\ubaa9\ud45c<\/h2>\n<p>Profile Server \uc5d0 \ud68c\uc6d0\uac00\uc785 API \ucd94\uac00\ud569\ub2c8\ub2e4.<\/p>\n<h2>build.gradle \uc218\uc815<\/h2>\n<pre><code class=\"language-gradle\">dependencies {\n    implementation &#039;org.springframework.boot:spring-boot-starter-oauth2-resource-server:2.5.3&#039;\n    implementation &#039;org.springframework.boot:spring-boot-starter-web:2.5.3&#039;\n    implementation &#039;org.springframework.boot:spring-boot-starter-validation:2.5.3&#039;\n    implementation &#039;org.springframework.boot:spring-boot-starter-thymeleaf:2.5.3&#039;\n    implementation &#039;org.springframework.security:spring-security-jwt:1.1.1.RELEASE&#039;\n    implementation &#039;org.springframework.boot:spring-boot-starter-data-jpa:2.5.3&#039;\n    implementation &#039;commons-io:commons-io:20030203.000550&#039;\n    implementation &#039;org.mapstruct:mapstruct:1.4.2.Final&#039;\n    implementation &#039;org.passay:passay:1.6.1&#039;\n    implementation &#039;com.google.guava:guava:30.1.1-jre&#039;\n\n    \/\/ \ubc84\uc804\uc744 \uba85\uc2dc\uc801\uc73c\ub85c \uc9c0\uc815\ud574\uc57c \ud55c\ub2e4(?)\n    implementation &#039;org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.5.2&#039;\n\n    runtimeOnly &#039;mysql:mysql-connector-java:8.0.25&#039;\n    compileOnly &#039;org.projectlombok:lombok:1.18.20&#039;\n    compileOnly &#039;org.projectlombok:lombok-mapstruct-binding:0.2.0&#039;\n    developmentOnly &#039;org.springframework.boot:spring-boot-devtools:2.5.3&#039;\n    annotationProcessor &#039;org.projectlombok:lombok:1.18.20&#039;\n    annotationProcessor &#039;org.mapstruct:mapstruct-processor:1.4.2.Final&#039;\n    testImplementation &#039;org.springframework.boot:spring-boot-starter-test:2.5.3&#039;\n}<\/code><\/pre>\n<h2>Entity \ub808\uc774\uc5b4 \ucd94\uac00<\/h2>\n<p>\ud14c\uc774\ube14 \uc2a4\ud0a4\ub9c8\ub97c \uc218\uc815\ud569\ub2c8\ub2e4.<\/p>\n<p>schema.sql<\/p>\n<pre><code class=\"language-sql\">CREATE TABLE `tbl_user` (\n    `user_id` BIGINT(20) NOT NULL AUTO_INCREMENT,\n    `email` varchar(255) NOT NULL,\n    `password` VARCHAR(100) NULL DEFAULT NULL,\n    `first_name` VARCHAR(100) NOT NULL,\n    `last_name` VARCHAR(100) NOT NULL,\n    `roles` VARCHAR(255) NULL DEFAULT NULL,\n    PRIMARY KEY (`user_id`),\n    UNIQUE INDEX `UK_tbl_user_email` (`email`)\n)\nCOLLATE=&#039;utf8_general_ci&#039;\nENGINE=InnoDB;<\/code><\/pre>\n<p>User.java<\/p>\n<pre><code class=\"language-java\">@Getter\n@Setter\n@Entity\n@Table(name = &quot;tbl_user&quot;)\npublic class User {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private long userId;\n\n    @Column(nullable = false, unique = true)\n    private String email;\n\n    @Column(nullable = false, length = 100)\n    private String password;\n\n    @Column(nullable = false, length = 100)\n    private String firstName;\n\n    @Column(nullable = false, length = 100)\n    private String lastName;\n\n    @Column(nullable = false)\n    private String roles;\n}<\/code><\/pre>\n<p>UserRepository.java<\/p>\n<pre><code class=\"language-java\">public interface UserRepository extends JpaRepository&lt;User, Long&gt; {\n\n    Optional&lt;User&gt; findByEmail(String email);\n}<\/code><\/pre>\n<h2>Service \ub808\uc774\uc5b4 \ucd94\uac00<\/h2>\n<p>UserService.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Service\npublic class UserService {\n\n    private final UserMapper mapper = Mappers.getMapper(UserMapper.class);\n\n    private final UserRepository userRepository;\n\n    private final PasswordEncoder passwordEncoder;\n\n    public Optional&lt;User&gt; findByEmail(String email) {\n        return userRepository.findByEmail(email);\n    }\n\n    public void registerNewUserAccount(UserDto userDto) {\n        if (userRepository.findByEmail(userDto.getEmail()).isPresent()) {\n            throw new UserAlreadyExistException(&quot;\uc774\ubbf8 \ub4f1\ub85d\ub41c \uc774\uba54\uc77c\uc785\ub2c8\ub2e4.&quot;);\n        }\n        User user = mapper.toEntity(userDto);\n        user.setPassword(passwordEncoder.encode(user.getPassword()));\n        user.setRoles(&quot;USER&quot;);\n        userRepository.save(user);\n    }\n}<\/code><\/pre>\n<h2>Web \ub808\uc774\uc5b4 \ucd94\uac00<\/h2>\n<p>UserInfoController.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@RestController\n@RequestMapping(&quot;\/api&quot;)\npublic class UserInfoController {\n\n    private final UserService userService;\n\n    \/\/ ......\n\n    @PostMapping(&quot;\/register&quot;)\n    public ResponseEntity&lt;?&gt; register(@RequestBody @Valid UserDto userDto) {\n\n        GenericResponse response = new GenericResponse(&quot;&quot;, &quot;&quot;);\n\n        userService.registerNewUserAccount(userDto);\n\n        return ResponseEntity.ok(response);\n    }\n}<\/code><\/pre>\n<h2>DTO \ucd94\uac00<\/h2>\n<h3>Annotation \ucd94\uac00<\/h3>\n<p>ValidEmail.java<\/p>\n<pre><code class=\"language-java\">@Target({TYPE, FIELD, ANNOTATION_TYPE})\n@Retention(RUNTIME)\n@Constraint(validatedBy = EmailValidator.class)\n@Documented\npublic @interface ValidEmail {\n    String message() default &quot;\uc774\uba54\uc77c \ud615\uc2dd\uc774 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.&quot;;\n    Class&lt;?&gt;[] groups() default {};\n    Class&lt;? extends Payload&gt;[] payload() default {};\n}<\/code><\/pre>\n<p>EmailValidator.java<\/p>\n<pre><code class=\"language-java\">public class EmailValidator implements ConstraintValidator&lt;ValidEmail, String&gt; {\n\n    private Pattern pattern;\n    private Matcher matcher;\n\n    private static final String EMAIL_PATTERN\n            = &quot;^[_A-Za-z0-9-+]+(.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(.[A-Za-z0-9]+)*(.[A-Za-z]{2,})$&quot;;\n\n    @Override\n    public void initialize(ValidEmail constraintAnnotation) {\n    }\n\n    @Override\n    public boolean isValid(String email, ConstraintValidatorContext context){\n        return (validateEmail(email));\n    }\n\n    private boolean validateEmail(String email) {\n        pattern = Pattern.compile(EMAIL_PATTERN);\n        matcher = pattern.matcher(email);\n        return matcher.matches();\n    }\n}<\/code><\/pre>\n<p>PasswordMatches.java<\/p>\n<pre><code class=\"language-java\">@Target({TYPE,ANNOTATION_TYPE})\n@Retention(RUNTIME)\n@Constraint(validatedBy = PasswordMatchesValidator.class)\n@Documented\npublic @interface PasswordMatches {\n    String message() default &quot;\ube44\ubc00\ubc88\ud638\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.&quot;;\n    Class&lt;?&gt;[] groups() default {};\n    Class&lt;? extends Payload&gt;[] payload() default {};\n}<\/code><\/pre>\n<p>PasswordMatchesValidator.java<\/p>\n<pre><code class=\"language-java\">public class PasswordMatchesValidator implements ConstraintValidator&lt;PasswordMatches, Object&gt; {\n\n    @Override\n    public void initialize(PasswordMatches constraintAnnotation) {\n    }\n\n    @Override\n    public boolean isValid(Object obj, ConstraintValidatorContext context){\n        UserDto user = (UserDto) obj;\n        return user.getPassword().equals(user.getMatchingPassword());\n    }\n}<\/code><\/pre>\n<h3>DTO \ud074\ub798\uc2a4 \ucd94\uac00<\/h3>\n<pre><code class=\"language-java\">@PasswordMatches\n@Getter\n@Setter\npublic class UserDto {\n    @NotNull\n    @NotEmpty\n    private String firstName;\n\n    @NotNull\n    @NotEmpty\n    private String lastName;\n\n    @NotNull\n    @NotEmpty\n    \/\/ @ValidPassword\n    private String password;\n\n    private String matchingPassword;\n\n    @ValidEmail\n    @NotNull\n    @NotEmpty\n    private String email;\n\n    private String roles;\n}<\/code><\/pre>\n<h2>Exception \ucc98\ub9ac<\/h2>\n<p>UserAlreadyExistException.java<\/p>\n<pre><code class=\"language-java\">public final class UserAlreadyExistException extends RuntimeException {\n\n    private static final long serialVersionUID = 5861310537366287163L;\n\n    public UserAlreadyExistException() {\n        super();\n    }\n\n    public UserAlreadyExistException(final String message, final Throwable cause) {\n        super(message, cause);\n    }\n\n    public UserAlreadyExistException(final String message) {\n        super(message);\n    }\n\n    public UserAlreadyExistException(final Throwable cause) {\n        super(cause);\n    }\n}<\/code><\/pre>\n<p>GenericResponse.java<\/p>\n<pre><code class=\"language-java\">public class GenericResponse {\n    private String message;\n    private String error;\n\n    public GenericResponse(final String message) {\n        super();\n        this.message = message;\n    }\n\n    public GenericResponse(final String message, final String error) {\n        super();\n        this.message = message;\n        this.error = error;\n    }\n\n    public GenericResponse(List&lt;ObjectError&gt; allErrors, String error) {\n        this.error = error;\n        String temp = allErrors.stream().map(e -&gt; {\n            if (e instanceof FieldError) {\n                return &quot;{\\&quot;field\\&quot;:\\&quot;&quot; + ((FieldError) e).getField() + &quot;\\&quot;,\\&quot;defaultMessage\\&quot;:\\&quot;&quot; + e.getDefaultMessage() + &quot;\\&quot;}&quot;;\n            } else {\n                return &quot;{\\&quot;object\\&quot;:\\&quot;&quot; + e.getObjectName() + &quot;\\&quot;,\\&quot;defaultMessage\\&quot;:\\&quot;&quot; + e.getDefaultMessage() + &quot;\\&quot;}&quot;;\n            }\n        }).collect(Collectors.joining(&quot;,&quot;));\n        this.message = &quot;[&quot; + temp + &quot;]&quot;;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public void setMessage(final String message) {\n        this.message = message;\n    }\n\n    public String getError() {\n        return error;\n    }\n\n    public void setError(final String error) {\n        this.error = error;\n    }\n}<\/code><\/pre>\n<p>RestResponseEntityExceptionHandler.java<\/p>\n<pre><code class=\"language-java\">@ControllerAdvice\npublic class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {\n\n    private final MessageSource messages;\n\n    public RestResponseEntityExceptionHandler(MessageSource messages) {\n        super();\n        this.messages = messages;\n    }\n\n    \/\/ API\n\n    \/\/ 400\n    @Override\n    protected ResponseEntity&lt;Object&gt; handleBindException(final BindException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {\n        \/\/ logger.error(&quot;400 Status Code&quot;, ex);\n        final BindingResult result = ex.getBindingResult();\n        final GenericResponse bodyOfResponse = new GenericResponse(result.getAllErrors(), &quot;Invalid&quot; + result.getObjectName());\n        return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);\n    }\n\n    @Override\n    protected ResponseEntity&lt;Object&gt; handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {\n        \/\/ logger.error(&quot;400 Status Code&quot;, ex);\n        final BindingResult result = ex.getBindingResult();\n        final GenericResponse bodyOfResponse = new GenericResponse(result.getAllErrors(), &quot;Invalid&quot; + result.getObjectName());\n        return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);\n    }\n\n    @ExceptionHandler({ UserAlreadyExistException.class })\n    public ResponseEntity&lt;Object&gt; handleUserAlreadyExist(final RuntimeException ex, final WebRequest request) {\n        \/\/ logger.error(&quot;500 Status Code&quot;, ex);\n        final GenericResponse bodyOfResponse = new GenericResponse(&quot;[{\\&quot;object\\&quot;:\\&quot;userDto\\&quot;,\\&quot;defaultMessage\\&quot;:\\&quot;\uc774\ubbf8 \ub4f1\ub85d\ub41c \uc774\uba54\uc77c\uc785\ub2c8\ub2e4.\\&quot;}]&quot;, &quot;InvaliduserDto&quot;);\n        return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);\n    }\n\n    @ExceptionHandler({ Exception.class })\n    public ResponseEntity&lt;Object&gt; handleInternal(final RuntimeException ex, final WebRequest request) {\n        logger.error(&quot;500 Status Code&quot;, ex);\n        final GenericResponse bodyOfResponse = new GenericResponse(messages.getMessage(&quot;message.error&quot;, null, request.getLocale()), &quot;InternalError&quot;);\n        return new ResponseEntity&lt;&gt;(bodyOfResponse, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);\n    }\n}<\/code><\/pre>\n<h2>config \uc218\uc815<\/h2>\n<p>\ud074\ub77c\uc774\uc5b8\ud2b8 \uc11c\ubc84\uc5d0\uc11c\uc758 \uc811\uc18d\uc744 \ud5c8\uc6a9\ud558\ub3c4\ub85d \uac1c\ubc1c\uc6a9 \uc124\uc815\uc744 \ucd94\uac00\ud574 \uc90d\ub2c8\ub2e4.<\/p>\n<p>DevConfig.java<\/p>\n<pre><code class=\"language-java\">@Configuration\n@Profile(&quot;dev&quot;)\npublic class DevConfig {\n\n    @Bean\n    public WebMvcConfigurer corsConfigurer() {\n        return new WebMvcConfigurerAdapter() {\n            @Override\n            public void addCorsMappings(CorsRegistry registry) {\n                registry.addMapping(&quot;\/**&quot;).allowedOrigins(&quot;http:\/\/localhost:8080&quot;);\n            }\n        };\n    }\n}<\/code><\/pre>\n<p><code>\/api\/register<\/code> API \uc5d4\ub4dc \ud3ec\uc778\ud2b8\ub97c \ucd94\uac00\ud574 \uc90d\ub2c8\ub2e4.<\/p>\n<p>ProfileServerConfig.java<\/p>\n<pre><code class=\"language-java\">@Configuration\n@EnableResourceServer\npublic class ProfileServerConfig extends ResourceServerConfigurerAdapter {\n\n    \/\/ key-uri: http:\/\/auth.localhost:9000\/oauth\/token_key\n    \/\/ \uc704 \uc124\uc815\uc73c\ub85c \uc778\ud574, JWT \ud1a0\ud070\uc778\uac83\uacfc, \uc554\ud638\ud654\ubc29\uc2dd, \uadf8\ub9ac\uace0 \uacf5\uac1c\ud0a4\uae4c\uc9c0 \uc81c\uacf5\ub429\ub2c8\ub2e4.\n    \/\/ \ub530\ub77c\uc11c, \ucd94\uac00\uc124\uc815 \uc5c6\uc774 \uc554\ud638\ud654 JWT \ud1a0\ud070\uc744 \uc774\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\n    @Override\n    public void configure(HttpSecurity http) throws Exception {\n\n        http.headers().frameOptions().disable();\n        http\n                .authorizeRequests()\n                    .antMatchers(&quot;\/api\/register&quot;).permitAll()\n                    .and()\n                .authorizeRequests()\n                    .antMatchers(&quot;\/api\/**&quot;).access(&quot;#oauth2.hasScope(&#039;profile&#039;)&quot;)\n                    .and()\n                .authorizeRequests()\n                    .antMatchers(&quot;\/user\/**&quot;, &quot;\/login&quot;).permitAll()\n                    .and()\n                .authorizeRequests()\n                    .anyRequest().authenticated();\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return PasswordEncoderFactories.createDelegatingPasswordEncoder();\n    }\n}<\/code><\/pre>\n<h2>Client \uc11c\ubc84 \uc218\uc815<\/h2>\n<p>UserDto.java<\/p>\n<pre><code class=\"language-java\">@Getter\n@Setter\npublic class UserDto {\n    private String firstName;\n\n    private String lastName;\n\n    private String password;\n\n    private String matchingPassword;\n\n    private String email;\n\n    private String roles;\n}<\/code><\/pre>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Controller\n@RequestMapping(&quot;\/join&quot;)\npublic class JoinController {\n\n    private final String PROFILE_SERVER = &quot;http:\/\/profile.localhost:9002\/user\/join&quot;;\n\n    private final HttpSession httpSession;\n\n    @GetMapping(&quot;\/user&quot;)\n    public String join(Model model) {\n\n        UserDto userDto = new UserDto();\n        model.addAttribute(&quot;user&quot;, userDto);\n\n        return &quot;join\/user&quot;;\n    }\n}<\/code><\/pre>\n<pre><code class=\"language-html\">&lt;!DOCTYPE html&gt;\n&lt;html xmlns:th=&quot;http:\/\/www.thymeleaf.org&quot; lang=&quot;us&quot;&gt;\n&lt;head&gt;\n&lt;meta charset=&quot;UTF-8&quot;&gt;\n&lt;title&gt;\ud68c\uc6d0\uac00\uc785&lt;\/title&gt;\n&lt;script src=&quot;https:\/\/ajax.googleapis.com\/ajax\/libs\/jquery\/1.12.4\/jquery.min.js&quot;&gt;&lt;\/script&gt;\n&lt;script&gt;\nfunction jsCheckJoin() {\n    var frm = document.frm;\n\n    $.ajax({\n        url: &quot;http:\/\/localhost:9002\/api\/register&quot;,\n        type: &quot;post&quot;,\n        accept: &quot;application\/json&quot;,\n        contentType: &quot;application\/json; charset=utf-8&quot;,\n        data: JSON.stringify($(&quot;#frm&quot;).serializeObject()),\n        dataType: &quot;json&quot;,\n        success: function(data) {\n            \/\/ success handle\n            alert(&#039;\ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4.&#039;);\n            document.location.href = &#039;http:\/\/localhost:8080\/oauth2\/authorization\/local&#039;;\n        },\n        error:function(request,status,error){\n            \/\/ alert(&quot;code:&quot;+request.status);\n            \/\/alert(request.responseText);\n            \/\/ alert(&quot;error:&quot;+error);\n\n            var result = JSON.parse(request.responseText);\n            var errorList = JSON.parse(result.message);\n            \/\/alert(result.message);\n            for (var i = 0; i &lt; errorList.length; i++) {\n                \/\/alert(errorList[i].object);\n                alert(errorList[i].defaultMessage);\n            }\n            \/\/alert(errorList);\n        }\n    });\n\n    return false;\n}\n\njQuery.fn.serializeObject = function() {\n    var obj = null;\n    try {\n        if (this[0].tagName &amp;&amp; this[0].tagName.toUpperCase() === &quot;FORM&quot;) {\n            var arr = this.serializeArray();\n            if (arr) {\n                obj = {};\n                jQuery.each(arr, function() {\n                    obj[this.name] = this.value;\n                });\n            }\/\/if ( arr ) {\n        }\n    } catch (e) {\n        alert(e.message);\n    } finally {\n    }\n\n    return obj;\n};\n&lt;\/script&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n\n&lt;h1&gt;\ud68c\uc6d0\uac00\uc785&lt;\/h1&gt;\n\n&lt;form id=&quot;frm&quot; name=&quot;frm&quot; action=&quot;\/join\/user&quot; th:object=&quot;${user}&quot; method=&quot;POST&quot; onsubmit=&quot;return jsCheckJoin()&quot;&gt;\n    &lt;div&gt;\n        &lt;label&gt;\uc774\ub984&lt;\/label&gt;\n        &lt;input th:field=&quot;*{firstName}&quot;\/&gt;\n        &lt;p th:each=&quot;error: ${#fields.errors(&#039;firstName&#039;)}&quot;\n           th:text=&quot;${error}&quot;&gt;Validation error&lt;\/p&gt;\n    &lt;\/div&gt;\n    &lt;div&gt;\n        &lt;label&gt;\uc131&lt;\/label&gt;\n        &lt;input th:field=&quot;*{lastName}&quot;\/&gt;\n        &lt;p th:each=&quot;error : ${#fields.errors(&#039;lastName&#039;)}&quot;\n           th:text=&quot;${error}&quot;&gt;Validation error&lt;\/p&gt;\n    &lt;\/div&gt;\n    &lt;div&gt;\n        &lt;label&gt;\uc774\uba54\uc77c&lt;\/label&gt;\n        &lt;input type=&quot;email&quot; th:field=&quot;*{email}&quot;\/&gt;\n        &lt;p th:each=&quot;error : ${#fields.errors(&#039;email&#039;)}&quot;\n           th:text=&quot;${error}&quot;&gt;Validation error&lt;\/p&gt;\n    &lt;\/div&gt;\n    &lt;div&gt;\n        &lt;label&gt;\ube44\ubc00\ubc88\ud638&lt;\/label&gt;\n        &lt;input type=&quot;password&quot; th:field=&quot;*{password}&quot;\/&gt;\n        &lt;p th:each=&quot;error : ${#fields.errors(&#039;password&#039;)}&quot;\n           th:text=&quot;${error}&quot;&gt;Validation error&lt;\/p&gt;\n    &lt;\/div&gt;\n    &lt;div&gt;\n        &lt;label&gt;\ube44\ubc00\ubc88\ud638 \ud655\uc778&lt;\/label&gt;\n        &lt;input type=&quot;password&quot; th:field=&quot;*{matchingPassword}&quot;\/&gt;\n    &lt;\/div&gt;\n    &lt;button type=&quot;submit&quot;&gt;\ud68c\uc6d0\uac00\uc785&lt;\/button&gt;\n&lt;\/form&gt;\n\n&lt;a href=&quot;\/login&quot;&gt;\ub85c\uadf8\uc778&lt;\/a&gt;\n\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Profile Server \uc5d0 \ud68c\uc6d0\uac00\uc785 API \ucd94\uac00 \ubaa9\ud45c Profile Server \uc5d0 \ud68c\uc6d0\uac00\uc785 API \ucd94\uac00\ud569\ub2c8\ub2e4. build.gradle \uc218\uc815 dependencies { implementation &#039;org.springframework.boot:spring-boot-starter-oauth2-resource-server:2.5.3&#039; implementation &#039;org.springframework.boot:spring-boot-starter-web:2.5.3&#039; implementation &#039;org.springframework.boot:spring-boot-starter-validation:2.5.3&#039; implementation &#039;org.springframework.boot:spring-boot-starter-thymeleaf:2.5.3&#039; implementation &#039;org.springframework.security:spring-security-jwt:1.1.1.RELEASE&#039; implementation &#039;org.springframework.boot:spring-boot-starter-data-jpa:2.5.3&#039; implementation &#039;commons-io:commons-io:20030203.000550&#039; implementation &#039;org.mapstruct:mapstruct:1.4.2.Final&#039; implementation &#039;org.passay:passay:1.6.1&#039; implementation &#039;com.google.guava:guava:30.1.1-jre&#039; \/\/ \ubc84\uc804\uc744 \uba85\uc2dc\uc801\uc73c\ub85c \uc9c0\uc815\ud574\uc57c \ud55c\ub2e4(?) implementation &#039;org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.5.2&#039; runtimeOnly &#039;mysql:mysql-connector-java:8.0.25&#039; compileOnly &#039;org.projectlombok:lombok:1.18.20&#039; compileOnly &#039;org.projectlombok:lombok-mapstruct-binding:0.2.0&#039; developmentOnly &#039;org.springframework.boot:spring-boot-devtools:2.5.3&#039; annotationProcessor &#039;org.projectlombok:lombok:1.18.20&#039; annotationProcessor\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2508\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29],"tags":[],"class_list":["post-2508","post","type-post","status-publish","format-standard","hentry","category-spring-boot-2-5"],"_links":{"self":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2508","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2508"}],"version-history":[{"count":3,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2508\/revisions"}],"predecessor-version":[{"id":2512,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2508\/revisions\/2512"}],"wp:attachment":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2508"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2508"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2508"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}