Spring SecurityおよびOAuth 2.0を使用したログイン(03)

58661 ワード

03.Googleログインのバインド


3-1)ユーザークラスの作成:担当ユーザー情報


domainでユーザーパッケージを作成する(domain/user/user)
package com.cutehuman.springboot.domain.user;

import com.cutehuman.springboot.domain.BaseTimeEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@NoArgsConstructor
@Entity
public class User extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String email;

    @Column
    private String picture;

    @Enumerated(EnumType.STRING) // 1.
    @Column(nullable = false)
    private Role role;

    @Builder
    public User(String name, String email, String picture, Role role){
        this.name = name;
        this.email = email;
        this.picture = picture;
        this.role = role;
    }

    public User update(String name, String picture){
        this.name = name;
        this.picture = picture;

        return this;
    }

    public String getRoleKey(){
        return this.role.getKey();
    }
}
@Enumerated(EnumType.STRING)
  • JPAストレージデータベースを使用する場合、Enum値をどのように格納かを決定する:
  • .
  • デフォルト記憶整数
  • という数字が格納されている場合、データベースを使用して検証するときに、その値がどのコード
  • を表すか分からない.
  • だから宣言は文字列
  • として記憶することができる.

    3-2)EnumクラスRoleの作成:ユーザー権限の管理


    1. domin/user/Role
    package com.cutehuman.springboot.domain.user;
    
    import lombok.Getter;
    import lombok.RequiredArgsConstructor;
    
    @Getter
    @RequiredArgsConstructor
    public enum Role {
        GUEST("ROLE_GUEST", "손님"),
        USER("ROLE_USER", "일반 사용자");
    
        private final String key;
        private final String title;
    }
    ❗Spring Securityは、ROLEが常に権限コードの前にあることを要求する
    ex)ROLE GUEST、ROLE USER等
    2.ユーザーレポートインタフェースの作成:ユーザーがCRUDを担当する
    package com.cutehuman.springboot.domain.user;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import java.util.Optional;
    
    public interface UserRepository extends JpaRepository<User, Long> {
        Optional<User> findByEmail(String email); 
    }
  • findByEmail
    返される値にemailで生成されたユーザーがいるかどうか.
    最初の登録ユーザーであることを確認するための
  • メソッド

    3-3)スプリング安全設定


    1. build.スプリングの安全性に関連する依存項目をgradeに追加

    compile('org.springframework.boot:spring-boot-starter-oauth2-client')
    spring-boot-starter-oauth2-client
  • クライアントの観点からソーシャル機能を実現するために必要な依存性
  • デフォルト管理
  • spring-security-oauth 2-clientおよびspring-security-oauth 2-jose

    2.OAuthライブラリを使用したソーシャルログイン設定コードの作成


  • config.authパッケージ作成:セキュリティに関連するすべてのクラスがここに含まれます.


  • SecurityConfigクラスの作成
  • import com.cutehuman.springboot.domain.user.Role;
    import lombok.RequiredArgsConstructor;
    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;
    
    @RequiredArgsConstructor
    @EnableWebSecurity // 1.
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        private final CustomOAuth2UserService customOAuth2UserService;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception{
            http.csrf().disable()
                    .headers().frameOptions().disable() // 2.
                    .and().authorizeRequests() // 3.
                    .antMatchers("/", "/css/**", "/images/**",
                            "/js/**","h2-console/**").permitAll()
                    .antMatchers("/api/v1/**").hasRole(Role.USER.name()) // 4.
                    .anyRequest().authenticated() // 5.
                    .and().logout().logoutSuccessUrl("/") // 6.
                    .and().oauth2Login() // 7.
                    .userInfoEndpoint() // 8.
                    .userService(customOAuth2UserService); // 9.
        }
    }
    1. @EnableWebSecurity
  • Spring Security設定の有効化
    2. csrf().disable().headers().frameOptions().disable()
  • h 2-これらのオプションを無効にしてコンソール画面を使用
    3. authorizeRequests
  • オプション
  • URL固有の権限管理の開始点
  • を設定する.
    antMatchersオプションを使用するには、
  • authorizeRequestsを宣言する必要があります.
    4. antMatchers
  • 権限管理オプション
  • URL、HTTP方法で
  • を管理できる
    指定されたURL(例えば
  • "/")はpermitAll()オプションで完全な参照権限
  • を得ることができる.
    アドレスが
  • '/api/v 1/**のAPIは、USER権限を持つユーザのみが使用できます.
    5. anyRequest
  • は、設定値以外のURL
  • を示す.
  • は、ここに認証()を追加し、残りのURLがすべて認証されたユーザであることを許可する
  • である.
  • 認証ユーザとは、ログインユーザを指す
    6. logout().logoutSuccessUrl("/")
  • ログアウト機能の複数の設定エントリポイント
  • を正常にログアウトした場合は、"/"アドレスに移動してください.
    7. oauth2Login
  • OAuth 2ログイン機能の複数設定エントリポイント
    8. userInfoEndpoint
  • OAuth 2ログイン成功後にユーザ情報をインポートする場合の設定を担当する.
    9. userService
  • ユーザサービスインタフェースインプリメンテーションを登録することにより、ソーシャルログインが成功したときに後続の操作
  • を実行する.
  • は、リソースサーバ(ソーシャルサービス)からユーザ情報を取得するとともに、実行すべき他の機能
  • を明確にすることができる.
  • クライアントID 2 UserServiceクラスの作成
  • は、Googleログイン以降にユーザが取得した情報(email、name、pictureなど)に基づくサブスクリプションおよび変更情報、および保存セッションなどの機能を提供する.
    import com.cutehuman.springboot.domain.user.User;
    import com.cutehuman.springboot.domain.user.UserRepository;
    import lombok.RequiredArgsConstructor;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
    import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
    import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
    import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
    import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
    import org.springframework.security.oauth2.core.user.OAuth2User;
    import org.springframework.stereotype.Service;
    
    import javax.servlet.http.HttpSession;
    import java.util.Collections;
    
    @RequiredArgsConstructor
    @Service
    public class CustomOAuth2UserService
            implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
        private final UserRepository userRepository;
        private final HttpSession httpSession;
    
        @Override
        public OAuth2User loadUser(OAuth2UserRequest userRequest)
                throws OAuth2AuthenticationException{
            OAuth2UserService<OAuth2UserRequest, OAuth2User>
                    delegate = new DefaultOAuth2UserService();
            OAuth2User oAuth2User = delegate.loadUser(userRequest);
    
            String registrationId = userRequest.getClientRegistration().getRegistrationId(); // 1.
            String userNameAttributeName = userRequest.getClientRegistration()
                    .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); // 2.
    
            OAuthAttributes attributes = OAuthAttributes.of(registrationId,
                    userNameAttributeName, oAuth2User.getAttributes()); // 3.
    
            User user = saveOrUpdate(attributes);
            httpSession.setAttribute("user", new SessionUser(user)); // 4.
    
            return new DefaultOAuth2User(
                    Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
                    attributes.getAttributes(),
                    attributes.getNameAttributeKey());
        }
    
        private User saveOrUpdate(OAuthAttributes attributes){
            User user = userRepository.findByEmail(attributes.getEmail())
                    .map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
                    .orElse(attributes.toEntity());
    
            return userRepository.save(user);
        }
    }
    1. registrationId
  • コード
  • 現在ログイン中のサービスを区別する
  • 現在はGoogleの不要な値のみを使用しているのか、以降のNaverログイン連動時にログインしているのか、
    Googleにログインするかどうかを区別するために使用されます
    2. userNameAttributeName
  • OAuth 2ログインに必要なフィールド値.
  • 、プライマリ・キーと同じ
  • Google基本サポートコードですが、NAVERKACAなどはサポートされていません.
  • Googleの基本コードは「sub」
  • です
  • 以降、NAVERログインとGoogleログインを同時にサポート
    3. OAuthAttributes
  • クラス
  • 、Auth 2 UserServiceからインポートされたAuth 2 Userプロパティを含む
  • 以降、Naverなど他のソーシャルログインでも使用されている
    4. SessionUser
  • Dtoクラス、
  • セッションにユーザ情報を格納する
  • Userクラスを書かずに次のように再作成して書き込む理由
    +)Googleユーザー情報の更新時に使用するための更新機能も実装されている
    ユーザー名またはプロファイルの写真が変更された場合、ユーザーエンティティにも
  • が表示されます.
    ユーザー・クラスではなく新しいクラスを作成および使用する理由
  • ユーザークラスを使用すると、エラー
  • が発生します.
    Failed to convert from type [java.lang.Object] to type [byte[]] for value 'com.~~.book.springboot.domain.user.User@4a43d6'
    これは、ユーザー・クラスをシリアル化せずにセッションに保存することを意味します.
  • ユーザー・クラスにシリアル化コードを追加するには、ユーザー・クラスがエンティティであるため、他のエンティティとの関係がいつ確立されるか分からないため、シリアル化機能を持つセッションDtoを追加することは、操作とメンテナンスに非常に役立ちます.
  • アイデンティティ属性クラスの作成
    ここではDtoでAuthAttributesを見るのでconfig.auth.dtoパッケージを作成し、そのパッケージに
  • を作成します.
    import com.cutehuman.springboot.domain.user.Role;
    import com.cutehuman.springboot.domain.user.User;
    import lombok.Builder;
    import lombok.Getter;
    
    import java.util.Map;
    
    @Getter
    public class OAuthAttributes {
        private Map<String, Object> attributes;
        private String nameAttributeKey;
        private String name;
        private String email;
        private String picture;
    
        @Builder
        public OAuthAttributes(Map<String, Object> attributes,
                               String nameAttributeKey, String name,
                               String email, String picture){
            this.attributes = attributes;
            this.nameAttributeKey = nameAttributeKey;
            this.name = name;
            this.email = email;
            this.picture = picture;
        }
    
        // 1.
        public static OAuthAttributes of(String registrationId, String userNameAttributeName,
                                         Map<String, Object> attributes){
            return ofGoogle(userNameAttributeName, attributes);
        }
    
        private static OAuthAttributes ofGoogle(String userNameAttributeName,
                                                Map<String, Object> attributes){
            return OAuthAttributes.builder()
                    .name((String) attributes.get("name"))
                    .email((String) attributes.get("email"))
                    .picture((String) attributes.get("picture"))
                    .attributes(attributes)
                    .nameAttributeKey(userNameAttributeName)
                    .build();
        }
    
        // 2.
        public User toEntity(){
            return User.builder()
                    .name(name)
                    .email(email)
                    .picture(picture)
                    .role(Role.GUEST)
                    .build();
        }
    }
  • of()
  • Auth 2 Userが返すユーザ情報はMapであるため、各値
  • を変換する必要がある.
  • toEntity()
  • ユーザエンティティ作成ボックス
  • アイデンティティ属性からエンティティを作成する場合、初期登録時は
  • である.
  • 登録時のデフォルト権限をGUIとするために、キャラクタビルダー値はRoleとなります.GUI
  • の使用
  • AuthAttributesクラスの作成が完了した場合、同じパッケージにSessionUserクラス
  • が作成されます.
  • Session Userクラス:configを作成します.auth.dtoパッケージ
    SessionUserは認証されたユーザ情報のみを必要とするため、name、email、およびpictureフィールド
  • のみを宣言する
    import com.cutehuman.springboot.domain.user.User;
    import lombok.Getter;
    
    import java.io.Serializable;
    
    @Getter
    public class SessionUser implements Serializable {
        private String name;
        private String email;
        private String picture;
    
        public SessionUser(User user){
            this.name = user.getName();
            this.email = user.getEmail();
            this.picture = user.getPicture();
        }
    }

    3-4)テストログイン


    1. index.「ひげ」画面にログインボタンを追加


    スプリングの安全性の確認
    ...
    <h1>스프링부트로 시작하는 웹 서비스 Ver.2</h1>
    <div class="col-md-12">
        <!--로그인 기능 영역-->
        <div class="row">
            <div class="col-md-6">
                <a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
                {{#user}} 
                    Logged in as: <span id="user">{{user}}</span>
                    <a href="/logout" class="btn btn-info active" role="button">Logout</a>
                {{/user}}
                {{^user}}
                    <a href="/oauth2/authorization/google" class="btn btn-success active" role="button">Google Login</a>
                    <a href="/oauth2/authorization/naver" class="btn btn-secondary active" role="button">Naver Login</a>
                {{/user}}
            </div>
        </div>
        <br>
            <!--목록 출력 영역-->
            ...
  • {{#user}}
  • マストリッチはif文を提供せず、
  • である/誤りであると判断しただけである.
  • だからマストリッチはいつも最終値
  • を渡す
  • ユーザ(userName)が存在する場合、
  • は、ユーザを露出するように構成される
  • a href="/logout"
  • Spring Securityデフォルトで提供されるログアウトURL
    (開発者はログアウトURLのコントローラxを作成する必要がある)
  • SecurityConfigクラスでURLを変更できますが、デフォルトのURLを使用しても十分なので、
  • を引き続き使用します.
  • {{^user}}
  • マストリッチにこの値が存在しない場合は、^,合計
  • を使用します.
  • を含み、ユーザがいなければログインボタンの露出を許可する
  • a href="/oauth2/authorization/google"
    デフォルトのログインURL
  • はSpring Securityによって提供されます.
  • の登録解除URLと同様に、開発者は個別のコントローラx
  • を作成する必要がある.

    2.IndexControllerにuserNameをモデルに格納するコードを追加する


    index.UNAMEをひげに使えるようにする
    @RequiredArgsConstructor
    @Controller
    public class IndexController {
    
        private final PostsService postsService;
        private final HttpSession httpSession;
    
        @GetMapping("/")
        public String index(Model model){ // 1.
            model.addAttribute("posts", postsService.findAllDesc());
            SessionUser user = (SessionUser) httpSession.getAttribute("user"); // 2.
            if(user != null){ // 3.
                model.addAttribute("user", user.getName());
            }
            return "index";
        }
  • (SessionUser) httpSession.getAttribute("user")
  • は、以前に作成したCustomAuthe 2 UserServiceによって、ログインに成功したときにセッションにセッションユーザを保存するように構成されています.
  • 、すなわちログイン成功時のhttpSessionです.getAttribute(「user」)から値
  • を取得できます.
  • if(user != null)
  • セッションに値が格納されている場合にのみ、モデル内のユーザ
  • として登録する.
  • セッションに値が格納されていない場合、モデルに値がないため、ログインボタン
  • が表示される.

    3.テスト

  • 登録ボタン露出良好
  • をクリックして「Googleのログインを受け入れる」ページにアクセスします.
  • Googleログイン成功
    +) https://github.com/jojoldu/freelec-springboot2-webservice/issues/169
  • 会員確認:http://localhost:8080/h2-console2
  • SELECT email, name, picture, role FROM USER
  • 権限管理が正常かどうかを確認します.現在ログインしているユーザー権限はGUESTでposts機能が使用できません.投稿の登録を試行中に次の403(権限の拒否)エラーが発生しました.
  • 権限を変更した後、書き換えを試みます:http://localhost:8080/h2-console2
  • update user set role = 'USER';

    セッションはGUEST情報として保存されています.ログアウトして再ログインし、セッション情報を最新情報に更新し、記事を登録してください.