{"id":2367,"date":"2021-07-24T20:05:35","date_gmt":"2021-07-24T11:05:35","guid":{"rendered":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2367"},"modified":"2021-07-31T11:22:15","modified_gmt":"2021-07-31T02:22:15","slug":"spring-boot-oauth2-client","status":"publish","type":"post","link":"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2367","title":{"rendered":"Spring Boot Oauth2 Client Server"},"content":{"rendered":"<h1>Spring Boot Oauth2 Client Server<\/h1>\n<p>\ucc38\uc870\uc0ac\uc774\ud2b8 : <a href=\"https:\/\/yeonyeon.tistory.com\/34?category=920206\">https:\/\/yeonyeon.tistory.com\/34?category=920206<\/a><\/p>\n<p>\ucc38\uc870\uc0ac\uc774\ud2b8 : <a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=205\">https:\/\/www.skyer9.pe.kr\/wordpress\/?p=205<\/a><\/p>\n<h2>\ubaa9\ud45c<\/h2>\n<p>\ucd94\uac00\ub418\ub294 \uc18c\uc2a4\ucf54\ub4dc\uc758 \uc591\uc744 \ubd84\uc0b0\uc2dc\ud0a4\uae30 \uc704\ud574 \uc544\ub798\uc640 \uac19\uc774 3\ub2e8\uacc4\ub85c \uad6c\uc131\ud569\ub2c8\ub2e4.<\/p>\n<ol start=\"0\">\n<li>\n<p>\ub2e8\uc21c \ub85c\uadf8\uc778 \uae30\ub2a5(\uc544\uc774\ub514\/\ud328\uc2a4\uc6cc\ub4dc \uae30\ubc18)\uc744 \uad6c\ud604\ud569\ub2c8\ub2e4.<\/p>\n<\/li>\n<li>\n<p>\uad6c\uae00 Oauth2 \ub85c\uadf8\uc778\uc744 \uc5f0\ub3d9\ud569\ub2c8\ub2e4.<\/p>\n<\/li>\n<li>\n<p>\uc790\uccb4 \uad6c\ucd95\ud55c Oauth2 Authorization Server \uc640 \uc5f0\ub3d9\ud569\ub2c8\ub2e4.<\/p>\n<\/li>\n<\/ol>\n<h2>\uc18c\uc2a4\ucf54\ub4dc<\/h2>\n<p><a href=\"https:\/\/github.com\/skyer9\/SpringBootOauth2ClientServer\">\uc5ec\uae30<\/a> \uc5d0 \uc804\uccb4 \uc18c\uc2a4\ucf54\ub4dc\uac00 \uc62c\ub77c\uac00 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<h2>hosts \ud30c\uc77c \uc218\uc815<\/h2>\n<p>\ud074\ub77c\uc774\uc5b8\ud2b8\/\ub9ac\uc18c\uc2a4\uc11c\ubc84\/\uc778\uc99d\uc11c\ubc84 3\uac1c\uc758 \uc11c\ubc84\uc5d0 \uac01\uac01 \ub2e4\ub978 \ub3c4\uba54\uc778\uc744 \ubd80\uc5ec\ud569\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-text\">127.0.0.1    auth.localhost\n127.0.0.1    res.localhost<\/code><\/pre>\n<h2>\ub2e8\uc21c \ub85c\uadf8\uc778 \uae30\ub2a5 \uad6c\ud604<\/h2>\n<h3>\ud504\ub85c\uc81d\ud2b8 \uc0dd\uc131<\/h3>\n<p>\uc2e0\uaddc \ud504\ub85c\uc81d\ud2b8\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.<\/p>\n<p>\ud504\ub85c\uc81d\ud2b8\uba85\uc740 oauth2client \ub85c \ud569\ub2c8\ub2e4.<\/p>\n<p>\uc758\uc874\uc131\uc740 DevTools, Lombok, Spring Data JPA, MySQL, Oauth2 Client, Spring Web, Thymeleaf \uc744 \uc120\ud0dd\ud569\ub2c8\ub2e4.<\/p>\n<h3>\ud14c\uc774\ube14 \uc0dd\uc131<\/h3>\n<pre><code class=\"language-sql\">DROP TABLE `user`;\n\nCREATE TABLE `user` (\n    `id` bigint(20) NOT NULL AUTO_INCREMENT,\n    `email` varchar(255) NOT NULL,\n    `username` varchar(255) NOT NULL,\n    `picture` varchar(255) DEFAULT NULL,\n    `role` varchar(255) NOT NULL,\n    `password` varchar(255) NULL,\n    PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nINSERT INTO `user`(email, username, role, password)\nVALUES(&#039;test@gmail.com&#039;, &#039;user&#039;, &#039;USER&#039;, &#039;{noop}pass&#039;);\nINSERT INTO `user`(email, username, role, password)\nVALUES(&#039;admin@gmail.com&#039;, &#039;admin&#039;, &#039;ADMIN&#039;, &#039;{noop}pass&#039;);<\/code><\/pre>\n<h3>application.yml \uc218\uc815<\/h3>\n<pre><code class=\"language-yaml\">spring:\n  datasource:\n    url: jdbc:mysql:\/\/${MYSQL_HOST:localhost}:3306\/db_oauth2\n    username: root\n    password: abcd1234\n    driver-class-name: com.mysql.jdbc.Driver\n  jpa:\n    hibernate:\n      ddl-auto: none\n    show-sql: false<\/code><\/pre>\n<h3>\uc5d4\ud130\ud2f0 \ud074\ub798\uc2a4 \uc0dd\uc131<\/h3>\n<p>Role.java<\/p>\n<pre><code class=\"language-java\">@Getter\n@RequiredArgsConstructor\npublic enum Role {\n\n    GUEST(&quot;ROLE_GUEST&quot;, &quot;\uc190\ub2d8&quot;),\n    USER(&quot;ROLE_USER&quot;, &quot;\uc77c\ubc18 \uc0ac\uc6a9\uc790&quot;),\n    ADMIN(&quot;ROLE_ADMIN&quot;, &quot;\uad00\ub9ac\uc790&quot;);\n\n    private final String key;\n    private final String title;\n}<\/code><\/pre>\n<p>User.java<\/p>\n<pre><code class=\"language-java\">@Getter\n@NoArgsConstructor\n@Entity\npublic class User {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private Long id;\n\n    @Column(nullable = false)\n    private String username;\n\n    @Column(nullable = false)\n    private String email;\n\n    @Column\n    private String picture;\n\n    @Enumerated(EnumType.STRING)\n    @Column(nullable = false)\n    private Role role;\n\n    @Column\n    private String password;\n\n    @Builder\n    public User(String username, String email, String picture, Role role, String password) {\n        this.username = username;\n        this.email = email;\n        this.picture = picture;\n        this.role = role;\n        this.password = password;\n    }\n\n    public User update(String name, String email, String picture) {\n        this.username = name;\n        this.email = email;\n        this.picture = picture;\n\n        return this;\n    }\n\n    public String getRoleKey() {\n        return this.role.getKey();\n    }\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; findByUsername(String username);\n}<\/code><\/pre>\n<h3>\uc11c\ube44\uc2a4 \ud074\ub798\uc2a4 \uc0dd\uc131<\/h3>\n<p>UserService.java<\/p>\n<pre><code class=\"language-java\">@Service\n@RequiredArgsConstructor\npublic class UserService implements UserDetailsService {\n\n    private final UserRepository userRepository;\n    private final HttpSession httpSession;\n\n    @Override\n    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {\n        Optional&lt;User&gt; userWrapper = userRepository.findByUsername(username);\n\n        if(userWrapper.isEmpty()) {\n            throw new UsernameNotFoundException(&quot;Username not found&quot;);\n        }\n\n        User user = userWrapper.get();\n\n        List&lt;GrantedAuthority&gt; authorities = new ArrayList&lt;&gt;();\n        httpSession.setAttribute(&quot;user&quot;, new SessionUserDto(user));\n\n        if ((&quot;admin&quot;).equals(username)) {\n            authorities.add(new SimpleGrantedAuthority(Role.ADMIN.getKey()));\n        } else {\n            authorities.add(new SimpleGrantedAuthority(Role.USER.getKey()));\n        }\n\n        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);\n    }\n}<\/code><\/pre>\n<h3>Spring Security \uc124\uc815<\/h3>\n<p><code>auth.userDetailsService(userService)<\/code> \ub97c \uc124\uc815\ud574\uc11c,<br \/>\nJDBC \uc0c1\uc758 \uc0ac\uc6a9\uc790\uc815\ubcf4\ub85c \ub85c\uadf8\uc778\ud558\ub3c4\ub85d \uc124\uc815\ub429\ub2c8\ub2e4.<\/p>\n<p>SecurityConfig.java<\/p>\n<pre><code class=\"language-java\">@Configuration\n@EnableWebSecurity\n@RequiredArgsConstructor\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    private final UserService userService;\n\n    @Override\n    protected void configure(HttpSecurity httpSecurity) throws Exception {\n        httpSecurity.csrf().disable()\n                .headers().frameOptions().disable()\n                .and()\n                    .authorizeRequests()\n                    .antMatchers(&quot;\/&quot;, &quot;\/css\/**&quot;, &quot;\/images\/**&quot;, &quot;\/js\/**&quot;, &quot;\/login&quot;, &quot;\/profile&quot;).permitAll()\n                    .antMatchers(&quot;\/api\/v1\/**&quot;, &quot;\/posts\/save&quot;).hasRole(Role.USER.name())\n                    .anyRequest().authenticated()\n                .and()\n                    .logout()\n                    .logoutSuccessUrl(&quot;\/&quot;)\n                .and()\n                    .formLogin();\n    }\n\n    @Override\n    public void configure(AuthenticationManagerBuilder auth) throws Exception {\n        auth.userDetailsService(userService);\n    }\n}<\/code><\/pre>\n<h3>\uc138\uc158\uc815\ubcf4<\/h3>\n<p>Entity \uc5d0 \uc0dd\uc131\ub41c \uc0ac\uc6a9\uc790\uc815\ubcf4\ub97c Web \uc5d0\uc11c \uc0ac\uc6a9\ud558\uae30 \uc704\ud574,<br \/>\nDTO \ud074\ub798\uc2a4\ub97c \uc0dd\uc131\ud574\uc90d\ub2c8\ub2e4.<\/p>\n<p>SessionUserDto.java<\/p>\n<pre><code class=\"language-java\">@Getter\npublic class SessionUserDto implements Serializable {\n\n    private String username;\n    private String email;\n    private String picture;\n\n    public SessionUserDto(User user) {\n        this.username = user.getUsername();\n        this.email = user.getEmail();\n        this.picture = user.getPicture();\n    }\n}<\/code><\/pre>\n<h3>\ucee8\ud2b8\ub864\ub7ec<\/h3>\n<p>IndexController.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Controller\npublic class IndexController {\n\n    private final HttpSession httpSession;\n\n    @GetMapping(&quot;\/&quot;)\n    public String index(Model model, @PageableDefault Pageable pageable, @RequestParam Map&lt;String, String&gt; params) {\n\n        SessionUserDto sessionUserDto = (SessionUserDto) httpSession.getAttribute(&quot;user&quot;);\n        if (sessionUserDto != null) {\n            model.addAttribute(&quot;userName&quot;, sessionUserDto.getUsername());\n        }\n\n        return &quot;index&quot;;\n    }\n}<\/code><\/pre>\n<pre><code class=\"language-html\">&lt;!DOCTYPE html&gt;\n&lt;html lang=&quot;en&quot;&gt;\n&lt;head&gt;\n    &lt;meta charset=&quot;UTF-8&quot;&gt;\n    &lt;title&gt;Title&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n&lt;div class=&quot;col-md-12&quot;&gt;\n    &lt;div class=&quot;row&quot;&gt;\n        &lt;div class=&quot;col-md-6&quot;&gt;\n            &lt;div th:if=&quot;${userName != null}&quot; th:inline=&quot;text&quot;&gt;\n                [[${userName}]] \ub2d8, \uc548\ub155\ud558\uc138\uc694.\n                &lt;a href=&quot;\/logout&quot; class=&quot;btn btn-info active&quot; role=&quot;button&quot;&gt;\ub85c\uadf8\uc544\uc6c3&lt;\/a&gt;\n            &lt;\/div&gt;\n            &lt;div th:if=&quot;${userName == null}&quot;&gt;\n                &lt;a href=&quot;\/login&quot; class=&quot;btn btn-success active&quot; role=&quot;button&quot;&gt;\ub85c\uadf8\uc778&lt;\/a&gt;\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n&lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<h3>\ud504\ub85c\uc81d\ud2b8 \uc2e4\ud589<\/h3>\n<p>\ud504\ub85c\uc81d\ud2b8\ub97c \uc2e4\ud589\ud569\ub2c8\ub2e4.<br \/>\n<a href=\"http:\/\/localhost:8080\/\">http:\/\/localhost:8080\/<\/a> \uc5d0 \uc811\uc18d\ud558\uba74 \ub85c\uadf8\uc778 \ub9c1\ud06c\uac00 \ud45c\uc2dc\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\ub85c\uadf8\uc778 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud558\uba74 \ub85c\uadf8\uc778\ud654\uba74\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<br \/>\n\ub85c\uadf8\uc778\uc744 \ud558\uba74 \ub85c\uadf8\uc778\ud55c username \uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<\/p>\n<p>\ub85c\uadf8\uc544\uc6c3 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc11c \ub85c\uadf8\uc544\uc6c3\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<h3>\uc138\uc158\uc815\ubcf4 \uc5b4\ub178\ud14c\uc774\uc158\uc73c\ub85c \uac00\uc838\uc624\uae30<\/h3>\n<p>\uc138\uc158\uc5d0\uc11c DTO \ud074\ub798\uc2a4\ub85c \ub9e4\ud551\ud574 \uc8fc\ub294 \uacfc\uc815\uc744 <code>\uc5b4\ub178\ud14c\uc774\uc158<\/code> \uc73c\ub85c \ub2e8\uc21c\ud654 \ud574\uc90d\ub2c8\ub2e4.<\/p>\n<p>LoginUser.java<\/p>\n<pre><code class=\"language-java\">@Target(ElementType.PARAMETER)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface LoginUser {\n}<\/code><\/pre>\n<p>LoginUserArgumentResolver.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Component\npublic class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {\n\n    private final HttpSession httpSession;\n\n    @Override\n    public boolean supportsParameter(MethodParameter parameter) {\n        boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;\n        boolean isUserClass = SessionUserDto.class.equals(parameter.getParameterType());\n\n        return isLoginUserAnnotation &amp;&amp; isUserClass;\n    }\n\n    @Override\n    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {\n        return httpSession.getAttribute(&quot;user&quot;);\n    }\n}<\/code><\/pre>\n<p>WebMvcConfig.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Configuration\npublic class WebMvcConfig implements WebMvcConfigurer {\n\n    private final LoginUserArgumentResolver loginUserArgumentResolver;\n\n    @Override\n    public void addArgumentResolvers(List&lt;HandlerMethodArgumentResolver&gt; resolvers) {\n        resolvers.add(loginUserArgumentResolver);\n    }\n}<\/code><\/pre>\n<p><code>@LoginUser SessionUserDto user<\/code> \uc640 \uac19\uc774,<br \/>\n\uc0ac\uc6a9\uc790 \uc815\ubcf4\uac00 DTO \ud074\ub798\uc2a4\uc5d0 \uc790\ub3d9\ud560\ub2f9 \ub429\ub2c8\ub2e4.<\/p>\n<p>IndexController.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Controller\npublic class IndexController {\n\n    private final HttpSession httpSession;\n\n    @GetMapping(&quot;\/&quot;)\n    public String index(Model model, @PageableDefault Pageable pageable, @LoginUser SessionUserDto user, @RequestParam Map&lt;String, String&gt; params) {\n\n        if (user != null) {\n            model.addAttribute(&quot;userName&quot;, user.getUsername());\n        }\n\n        return &quot;index&quot;;\n    }\n}<\/code><\/pre>\n<p>\ub85c\uadf8\uc778 \uc815\ubcf4\uac00 \ud30c\ub77c\ubbf8\ud130\ub85c \uc804\ub2ec\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<h2>\uad6c\uae00 \ub85c\uadf8\uc778 \uc5f0\ub3d9<\/h2>\n<h3>\ud074\ub77c\uc774\uc5b8\ud2b8 \uc544\uc774\ub514 \uc0dd\uc131<\/h3>\n<p><a href=\"https:\/\/yeonyeon.tistory.com\/34?category=920206\">\uc5ec\uae30<\/a> \ub97c \ucc38\uc870\ud558\uc5ec \ud074\ub77c\uc774\uc5b8\ud2b8 \uc544\uc774\ub514\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4.<\/p>\n<h3>application-oauth.yml \uc0dd\uc131<\/h3>\n<p>google\/facebook \ub4f1\uc758 \uc720\uba85\ud55c \uc778\uc99d\uc11c\ubc84\uc5d0 \ub300\ud574\uc11c\ub294,<br \/>\n\uc2a4\ud504\ub9c1 \ubd80\ud2b8\uc5d0\uc11c \uacf5\ud1b5\uc124\uc815\uc744 \ubbf8\ub9ac \ud574\ub450\uc5c8\uae30\uc5d0,<br \/>\n\ud074\ub77c\uc774\uc5b8\ud2b8 \uc124\uc815\ub9cc \ud574\uc90c\uc73c\ub85c \ubaa8\ub4e0 \uc124\uc815\uc774 \ub05d\ub0a9\ub2c8\ub2e4.<\/p>\n<p>application-oauth.yml<\/p>\n<pre><code class=\"language-yaml\">spring:\n  security:\n    oauth2:\n      client:\n        registration:\n          google:\n            client-id: XXXXXXXXXX\n            client-secret: XXXXXXXXXXXXX<\/code><\/pre>\n<h3>application.yml \uc218\uc815<\/h3>\n<p><code>profiles<\/code> \ub97c \uc124\uc815\ud568\uc73c\ub85c <code>application-oauth.yml<\/code> \uc774 \uc778\uc2dd\ub429\ub2c8\ub2e4.<\/p>\n<p>application.yml<\/p>\n<pre><code class=\"language-yaml\">spring:\n  datasource:\n    url: jdbc:mysql:\/\/${MYSQL_HOST:localhost}:3306\/db_oauth2\n    username: root\n    password: abcd1234\n    driver-class-name: com.mysql.jdbc.Driver\n  jpa:\n    hibernate:\n      ddl-auto: none\n    show-sql: false\n  profiles:\n    include: oauth<\/code><\/pre>\n<h3>.gitignore \uc218\uc815<\/h3>\n<p>\uc124\uc815\ud30c\uc77c\uc774 git \uc5d0 \uc62c\ub77c\uac00\uc9c0 \uc54a\ub3c4\ub85d \uc124\uc815\ud569\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-text\">application.yml\napplication-oauth.yml<\/code><\/pre>\n<h3>\uc5d4\ud130\ud2f0 \ud074\ub798\uc2a4 \uc218\uc815<\/h3>\n<p>password \ud544\ub4dc\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4.<\/p>\n<p>User.java<\/p>\n<pre><code class=\"language-java\">@Getter\n@NoArgsConstructor\n@Entity\npublic class User {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private Long id;\n\n    @Column(nullable = false)\n    private String username;\n\n    @Column(nullable = false)\n    private String email;\n\n    @Column\n    private String picture;\n\n    @Enumerated(EnumType.STRING)\n    @Column(nullable = false)\n    private Role role;\n\n    @Builder\n    public User(String username, String email, String picture, Role role) {\n        this.username = username;\n        this.email = email;\n        this.picture = picture;\n        this.role = role;\n    }\n\n    public User update(String name, String picture) {\n        this.username = name;\n        this.picture = picture;\n\n        return this;\n    }\n\n    public String getRoleKey() {\n        return this.role.getKey();\n    }\n}<\/code><\/pre>\n<h3>Spring Security \uc124\uc815<\/h3>\n<p>UserService.java \uc81c\uac70<\/p>\n<p>OAuthAttributes.java<\/p>\n<pre><code class=\"language-java\">@Getter\npublic class OAuthAttributes {\n    private Map&lt;String, Object&gt; attributes;\n    private String nameAttributeKey;\n    private String username;\n    private String email;\n    private String picture;\n\n    @Builder\n    public OAuthAttributes(Map&lt;String, Object&gt; attributes,\n                           String nameAttributeKey, String username,\n                           String email, String picture) {\n        this.attributes = attributes;\n        this.nameAttributeKey = nameAttributeKey;\n        this.username = username;\n        this.email = email;\n        this.picture = picture;\n    }\n\n    public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map&lt;String, Object&gt; attributes) {\n        return OAuthAttributes.builder()\n                .username((String) attributes.get(&quot;name&quot;))\n                .email((String) attributes.get(&quot;email&quot;))\n                .picture((String) attributes.get(&quot;picture&quot;))\n                .attributes(attributes)\n                .nameAttributeKey(userNameAttributeName)\n                .build();\n    }\n\n    public User toEntity() {\n        return User.builder()\n                .username(username)\n                .email(email)\n                .picture(picture)\n                .role(Role.GUEST)\n                .build();\n    }\n}<\/code><\/pre>\n<p><code>delegate.loadUser<\/code> \uc5d0 \uc758\ud574,<br \/>\n<code>user-info-uri<\/code> \uc5d0\uc11c \uc81c\uacf5\ud558\ub294 \uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \ubc1b\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\ubc1b\uc544\uc628 \uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc790\uccb4 \ub514\ube44\uc5d0 \uc800\uc7a5\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>CustomOAuth2UserService.java<\/p>\n<pre><code class=\"language-java\">@RequiredArgsConstructor\n@Service\npublic class CustomOAuth2UserService implements OAuth2UserService&lt;OAuth2UserRequest, OAuth2User&gt; {\n    private final UserRepository userRepository;\n    private final HttpSession httpSession;\n\n    @Override\n    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {\n\n        OAuth2UserService&lt;OAuth2UserRequest, OAuth2User&gt; delegate = new DefaultOAuth2UserService();\n        OAuth2User oAuth2User = delegate.loadUser(userRequest);\n\n        String registrationId = userRequest.getClientRegistration().getRegistrationId();\n\n        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()\n                .getUserInfoEndpoint().getUserNameAttributeName();\n\n        OAuthAttributes attributes = OAuthAttributes.\n                of(registrationId, userNameAttributeName, oAuth2User.getAttributes());\n\n        User user = saveOrUpdate(attributes);\n\n        httpSession.setAttribute(&quot;user&quot;, new SessionUserDto(user));\n\n        return new DefaultOAuth2User(\n                Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),\n                attributes.getAttributes(),\n                attributes.getNameAttributeKey());\n    }\n\n    private User saveOrUpdate(OAuthAttributes attributes) {\n        User user = userRepository.findByUsername(attributes.getUsername())\n                .map(entity-&gt; entity.update(attributes.getUsername(), attributes.getEmail(), attributes.getPicture()))\n                .orElse(attributes.toEntity());\n\n        return userRepository.save(user);\n    }\n}<\/code><\/pre>\n<p>SecurityConfig.java<\/p>\n<pre><code class=\"language-java\">@Configuration\n@RequiredArgsConstructor\n@EnableWebSecurity\npublic class SecurityConfig extends WebSecurityConfigurerAdapter {\n\n    private final CustomOAuth2UserService customOAuth2UserService;\n\n    @Override\n    protected void configure(HttpSecurity http) throws Exception{\n        http.csrf().disable()\n                .headers().frameOptions().disable()\n                .and()\n                    .authorizeRequests()\n                    .antMatchers(&quot;\/&quot;,&quot;\/css\/**&quot;,&quot;\/images\/**&quot;,&quot;\/js\/**&quot;,&quot;\/h2-console\/**&quot;,&quot;\/profile&quot;).permitAll()\n                    .antMatchers(&quot;\/api\/v1\/**&quot;).hasRole(Role.USER.name())\n                    .anyRequest().authenticated()\n                .and()\n                    .logout().logoutSuccessUrl(&quot;\/&quot;)\n                .and()\n                    .oauth2Login()\n                    .userInfoEndpoint()\n                    .userService(customOAuth2UserService);\n    }\n}<\/code><\/pre>\n<h3>\ud504\ub85c\uc81d\ud2b8 \uc2e4\ud589<\/h3>\n<p>\ud504\ub85c\uc81d\ud2b8\ub97c \uc2e4\ud589\ud569\ub2c8\ub2e4.<br \/>\n<a href=\"http:\/\/localhost:8080\/\">http:\/\/localhost:8080\/<\/a> \uc5d0 \uc811\uc18d\ud558\uba74 \ub85c\uadf8\uc778 \ub9c1\ud06c\uac00 \ud45c\uc2dc\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\ub85c\uadf8\uc778 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud558\uba74 \ub85c\uadf8\uc778\ud654\uba74\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<br \/>\n\ub85c\uadf8\uc778\uc744 \ud558\uba74 \ub85c\uadf8\uc778\ud55c username \uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<\/p>\n<p>\ub85c\uadf8\uc544\uc6c3 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc11c \ub85c\uadf8\uc544\uc6c3\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<h2>\uc790\uccb4 \uc778\uc99d\uc11c\ubc84 \uc5f0\ub3d9<\/h2>\n<h3>\ucf5c\ubc31 URI \uc218\uc815<\/h3>\n<pre><code class=\"language-sql\">UPDATE oauth_client_details\nSET\n    web_server_redirect_uri = &#039;http:\/\/localhost:8080\/login\/oauth2\/code\/local&#039;,\n    scope = &#039;profile,email&#039;\nWHERE client_id = &#039;foo&#039;;<\/code><\/pre>\n<h3>application-oauth.yml \uc218\uc815<\/h3>\n<p>\ud074\ub77c\uc774\uc5b8\ud2b8 \uc815\ubcf4 <code>local<\/code> \uc744 \uc124\uc815\ud574 \uc90d\ub2c8\ub2e4.<\/p>\n<pre><code class=\"language-yaml\">spring:\n  security:\n    oauth2:\n      client:\n        registration:\n          google:\n            client-id: XXXXXXXXXX\n            client-secret: XXXXXXXXXXXXX\n            scope: profile, email\n          local:\n            client-id: foo\n            client-secret: bar\n            scope: profile, email\n            redirect-uri: http:\/\/localhost:8080\/login\/oauth2\/code\/local\n            authorization-grant-type: authorization_code\n            client-name: Local\n        provider:\n          local:\n            authorization-uri: http:\/\/auth.localhost:9000\/oauth\/authorize\n            token-uri: http:\/\/auth.localhost:9000\/oauth\/token\n            user-info-uri: http:\/\/res.localhost:9001\/api\/userinfo\n            user-name-attribute: name<\/code><\/pre>\n<h3>\ud504\ub85c\uc81d\ud2b8 \uc2e4\ud589<\/h3>\n<p>\ud504\ub85c\uc81d\ud2b8\ub97c \uc2e4\ud589\ud569\ub2c8\ub2e4.<br \/>\n<a href=\"http:\/\/localhost2:8080\/\">http:\/\/localhost2:8080\/<\/a> \uc5d0 \uc811\uc18d\ud558\uba74 \ub85c\uadf8\uc778 \ub9c1\ud06c\uac00 \ud45c\uc2dc\ub418\ub294 \uac83\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<p>\ub85c\uadf8\uc778 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud558\uba74 \ub85c\uadf8\uc778\ud654\uba74\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<br \/>\n\ub85c\uadf8\uc778\uc744 \ud558\uba74 \ub85c\uadf8\uc778\ud55c username \uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4.<\/p>\n<p>\ub85c\uadf8\uc544\uc6c3 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc11c \ub85c\uadf8\uc544\uc6c3\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Spring Boot Oauth2 Client Server \ucc38\uc870\uc0ac\uc774\ud2b8 : https:\/\/yeonyeon.tistory.com\/34?category=920206 \ucc38\uc870\uc0ac\uc774\ud2b8 : https:\/\/www.skyer9.pe.kr\/wordpress\/?p=205 \ubaa9\ud45c \ucd94\uac00\ub418\ub294 \uc18c\uc2a4\ucf54\ub4dc\uc758 \uc591\uc744 \ubd84\uc0b0\uc2dc\ud0a4\uae30 \uc704\ud574 \uc544\ub798\uc640 \uac19\uc774 3\ub2e8\uacc4\ub85c \uad6c\uc131\ud569\ub2c8\ub2e4. \ub2e8\uc21c \ub85c\uadf8\uc778 \uae30\ub2a5(\uc544\uc774\ub514\/\ud328\uc2a4\uc6cc\ub4dc \uae30\ubc18)\uc744 \uad6c\ud604\ud569\ub2c8\ub2e4. \uad6c\uae00 Oauth2 \ub85c\uadf8\uc778\uc744 \uc5f0\ub3d9\ud569\ub2c8\ub2e4. \uc790\uccb4 \uad6c\ucd95\ud55c Oauth2 Authorization Server \uc640 \uc5f0\ub3d9\ud569\ub2c8\ub2e4. \uc18c\uc2a4\ucf54\ub4dc \uc5ec\uae30 \uc5d0 \uc804\uccb4 \uc18c\uc2a4\ucf54\ub4dc\uac00 \uc62c\ub77c\uac00 \uc788\uc2b5\ub2c8\ub2e4. hosts \ud30c\uc77c \uc218\uc815 \ud074\ub77c\uc774\uc5b8\ud2b8\/\ub9ac\uc18c\uc2a4\uc11c\ubc84\/\uc778\uc99d\uc11c\ubc84 3\uac1c\uc758 \uc11c\ubc84\uc5d0 \uac01\uac01 \ub2e4\ub978 \ub3c4\uba54\uc778\uc744 \ubd80\uc5ec\ud569\ub2c8\ub2e4. 127.0.0.1\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.skyer9.pe.kr\/wordpress\/?p=2367\">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,1],"tags":[],"class_list":["post-2367","post","type-post","status-publish","format-standard","hentry","category-spring-boot-2-5","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2367","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=2367"}],"version-history":[{"count":42,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2367\/revisions"}],"predecessor-version":[{"id":2505,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2367\/revisions\/2505"}],"wp:attachment":[{"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2367"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2367"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skyer9.pe.kr\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2367"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}