詳細なシングル サインオン (SSO)

記事ディレクトリ

序文

1. シングル サインオンとは何ですか?

2 つ目は、シングル サインオンの実装です。

1. クッキースキーム:

2. セッションスキーム:

3. トークンスキーム:

3.JWTとは

1. JWTの概要

2. JWTの構成

3. JWTの使用方法

4. JWTのメリットとデメリット

4. トークンでシングルサインオンを実現(コード)

1. JWT 依存関係と JWT ツール クラスを追加する

2. ログインメソッドを書く

要約する


序文

分散プロジェクトアーキテクチャでは、ユーザーエクスペリエンスを向上させるため、複数のシステムにアクセス権を持ってアクセスする際に一度の認証で済むようにする機能をシングルサインオン(SSO)と呼びます。現在、市場の多くはシングルサインオンを実現するためにOAuth2.0を採用しており、サブプランでは別途統一認証センターシステムを用意する必要があります。この記事は SSO の単純な実装であるため、OAuth2.0 は使用されません。OAuth2.0 について詳しくは、次の記事をご覧ください。


1. シングル サインオンとは何ですか?

シングル サインオン (SSO) は SSO と呼ばれ、現在、最も人気のあるエンタープライズ ビジネス統合ソリューションの 1 つです。SSO の定義は、複数のアプリケーション システムにおいて、ユーザーは一度ログインするだけで、相互に信頼されているすべてのアプリケーション システム (淘宝網と天猫の関係など) にアクセスできることです。つまり、複数のシステムでの統一ログインです。

シングル サインオンでは、ログイン後のさまざまなシステム間でのデータ共有の問題が主に強調されます。


2 つ目は、シングル サインオンの実装です。

シングル サインオンの実装には通常、Cookie、セッション、トークンが含まれます。

1. クッキースキーム:

プロセス:

ユーザーの認証情報を保存する媒体として Cookie を使用します。ユーザーがシステムにログインすると、暗号化された Cookie が返されます。ユーザーがサブアプリケーションにアクセスすると、Cookie の復号化と検証を承認するために Cookie が取得されます。検証に合格した後、現在のユーザーは次の操作を実行できます。ログインしていること。

欠点:

(1) Cookie は安全ではありません。攻撃者は Cookie を偽造することで特定のユーザー ID を偽造できますが、これは暗号化によって回避できます (暗号化アルゴリズムが漏洩しない限り)。

(2)クロスドメインの無料ログインは不可能: Cookie は通常、ドメイン名ごとに分割されます。たとえば、baidu.com は、cnblogs.com によって書き込まれた Cookie を読み取ることができません。ドメイン名は上位ドメイン名の Cookie にアクセスできます。つまり、a.baidu.com はbaidu.comの Cookie にアクセスできます(第 1 レベルのドメイン名は同じですが、第 2 レベル以上のドメイン名は異なります。例: a. baidu.com、 b. baidu.com は、ドメインパラメーターを設定することで Cookie の読み取りと書き込みを共有できます

補足: ドメイン名を右から左に "."で区切ると、 トップレベル ドメイン名には"."が含まれず、ファーストレベル ドメイン名には "." が 1 つ含まれます (例: baidu.com) 2 つの「.」が含まれる は、 a. baidu .com などの第 2 レベルのドメイン名です

2.セッションプログラム:

プロセス:

分散システムであるため、複数のサーバーはセッションを共有せず、従来のスタンドアロン セッションは分散システムには適していないため、ここでは分散セッションが使用されます。セッションはステートフルであり、分散セッションを実装するには 4 つのソリューション (セッション レプリケーション、クライアント ストレージ、HASH 整合性、および統合ストレージ) があります。詳細は、分散セッションソリューション_Bange Coffee Blog-CSDN Blogをご覧ください。

プロセスの実行:

(1) ユーザーの初回ログイン時に、ユーザー ID をキーとしてセッション情報 (ユーザー ID とユーザー情報) が分散セッションに書き込まれます。

(2) ユーザが再度ログインすると、配布されたセッションを取得し、セッション情報があるかどうか、ない場合はログインページに転送します。

(3) 一般的にキャッシュミドルウェアを使用して実装しますが、分散セッションがダウンした後に永続ストレージからセッション情報をロードするのに便利な永続化機能を備えているため、Redis の使用が推奨されます。

(4) セッションを保存するときに、セッションの保持時間を 15 分などに設定でき、それを超えると自動的にタイムアウトになります。

欠点:

 (1)サーバーへの負荷の増加:通常、セッションはメモリに保存されます。各ユーザーが認証を通過すると、セッション データはサーバーのメモリに保存されます。ユーザーの数が増加すると、サーバーへの負荷が増加します。(データはディスクに保存できます)

(2)スケーラビリティが強くない:将来的に複数のサーバーをセットアップした場合、各サーバーは同じビジネス ロジックを実行しますが、セッション データはメモリ上に保存され (共有されず)、ユーザーが初めてアクセスするときは、セッション データがメモリ上に保存されます。ユーザが再度要求した場合、別のサーバ2にアクセスする可能性があります。サーバ2がセッション情報を取得できない場合、ユーザはログインしていないと判断されます。(分散セッションを使用すると、各クラスター内でセッションの一貫性を保つことができます)

(3) CSRF クロスサイト リクエスト フォージェリ攻撃:セッションはユーザー識別のための Cookie に基づいており、Cookie が傍受されると、ユーザーはクロスサイト リクエスト フォージェリ攻撃に対して脆弱になります。

3. トークンスキーム:

プロセス:

現在、Token方式のログイン認証はOAuth2.0方式が採用されていることが多いですが、ここではsso効果を実現するだけなのでOAuth2.0の処理は実装されていません。

 デメリット:
(1)帯域を占有します
(2)サーバー側で破棄できません (トークンは生成後にクライアントに返され、サーバー側で削除できません) 注
: マイクロサービス開発に基づいて、比較的多くのトークンが存在します。トークン選択の形式があるため、トークンを使用して sso を実装します


3.JWTとは

1. JWTの概要

平たく言えば、JWT と呼ばれる生存文字列のルールです。JWTとはJSON WEB TOKENの略で、RFC 7519規格に基づいて安全に送信できるJSONオブジェクトで、デジタル署名が使用されているため信頼性が高く安全です。

トークン(トークン)は、各人が異なるルールを作成し、異なる結果を生成するため、 ステートレスです現在、ほとんどの人が統一トークン標準としてJWTを使用していますが、私もトークン生成標準として JWT を使用しています。

2. JWTの構成

jwt インスタンスは、次の Web サイトで分析結果を取得できます: JSON Web Tokens - jwt.io

構成: 

 オブジェクトは非常に長い文字列であり、文字は"."区切り文字によって 3 つの部分文字列に分割されます。

 各部分文字列は機能ブロックを表し、合計 3 つの部分がありますJWT头:有效载荷および签名

JWT ヘッダー:

JWT ヘッダーは、JWT メタデータを記述する JSON オブジェクトであり、通常は次のようになります。

{
    "alg": "HS256",
    "typ": "JWT"
}

上記のコードでは、

alg属性署名を示します使用的算法。デフォルトはHMAC SHA256 (HS256 と書きます) です。

typ属性JWT トークンが一律に JWT として書き込まれることを示します令牌的类型

最後に、Base64 URL アルゴリズムを使用して、上記の JSON オブジェクトを文字列に変換し、保存します。

ペイロード [ユーザー情報]:

(平たく言えば用户信息、ペイロード部分は JWT の主要なコンテンツ部分であり、送信する必要があるデータを含む JSON オブジェクトでもあります。JWT は選択用に 7 つのデフォルトフィールドを指定します

iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

 上記のデフォルト フィールドに加えて、次の例のようにプライベート フィールドをカスタマイズすることもできます。

{
    "sub": "1234567890",
    "name": "Helen",
    "admin": true
}

JWT はデフォルトでは暗号化されておらず、誰でもその内容を解釈できるため、情報漏洩を防ぐために機密情報を保存する個人情報フィールドを構築しないでください。

JSON オブジェクトも、Base64 URL アルゴリズムを使用して文字列に変換されます。

署名ハッシュ [偽造防止マーク]:

署名ハッシュ部分は、データの上記 2 つの部分に署名し、指定されたアルゴリズムを通じてハッシュを生成して、データが改ざんされないことを保証します。

まず、パスワード(シークレット)を指定する必要があります。このパスワードはサーバー上にのみ保存され、ユーザーに開示されることはありません。次に、ヘッダーで指定された署名アルゴリズム (デフォルトでは HMAC SHA256) を使用して、次の式に従って署名を生成します。

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret)

署名ハッシュを計算した後、JWT ヘッダー、ペイロード、署名ハッシュの 3 つの部分が 1 つの文字列に結合され、各部分が「.」で区切られて JWT オブジェクト全体が形成されます。 

Base64URL アルゴリズム:
前述したように、JWT ヘッダーとペイロードのシリアル化アルゴリズムは両方とも Base64URL を使用します。このアルゴリズムは一般的な Base64 アルゴリズムに似ていますが、若干の違いがあります。JWT をトークンとして URL に配置できます (例: api.example/?token=xxx)。Base64 で使用される 3 つの文字は、「+」、「/」、および「=」です。これらは URL 内で特別な意味を持つため、Base64URL では「=」が削除され、「+」が「-」に置き換えられます。 「/」は「_」に置き換えられます。これは Base64URL アルゴリズムです。

3. JWTの使用方法

クライアントはサーバーから返された JWT を受信し、それを Cookie または localStorage に保存します。

それ以降、クライアントはサーバーと対話するときに JWT を使用するようになります。Cookie に保存されている場合は自動的に送信できますが、ドメインを越えることはできないため、通常は HTTP リクエストのヘッダー認証フィールドに入れられます。ドメインをまたぐ場合、JWT を POST リクエストのデータ本体に配置することもできます。

4. JWTのメリットとデメリット

  • JWTは認証だけでなく情報交換にも利用できます。JWT をうまく利用すると、データベースに対するサーバー リクエストの数を減らすことができます。
  • 生成されたトークンには、ライブラリの再チェックを避けるために、ID、ユーザーのニックネーム、アバター、その他の情報などの基本情報を含めることができます。
  • クライアント側に保存され、サーバー側のメモリ リソースを占有しない
  • JWT はデフォルトでは暗号化されませんが、暗号化することもできます。未加工のトークンが生成された後、再度暗号化できます。
  • JWT が暗号化されていない場合、一部のプライベート データは JWT 経由で送信できません。
  • JWT の最大の欠点は、サーバーがセッション状態を保存しないため、使用中にトークンをキャンセルしたり、トークンの権限を変更したりすることができないことです。つまり、JWT は一度発行されると、有効期間内は有効になります。
  • JWT自体には認証情報が含まれています。トークンはbase64でエンコードされているため、復号化できます。したがって、トークン暗号化前のオブジェクトには機密情報が含まれていてはなりません。情報が漏洩すると、誰でもトークンのすべての権限を取得できます。不正流用を減らすために、JWT の有効期間はあまり長く設定しないでください。一部の重要な操作については、ユーザーが使用するたびに認証する必要があります。
  • 悪用や盗難を減らすために、JWT はコードの送信に HTTP プロトコルを使用せず、暗号化された HTTPS プロトコルを使用して送信することを推奨します。

4. トークンでシングルサインオンを実現(コード)

1. JWT 依存関係と JWT ツール クラスを追加する

頼る:

       <!--  jwt依赖    -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

 ツール: 

public class JwtUtils {

    //token过期时间
    public static final long EXPIRE = 1000 * 60 * 60 * 24;

    //秘钥,每个公司生成规则不一样
    public static final String APP_SECRET = "rikka六花";

    //生成token字符串方法,参数根据自己实际要求指定
    public static String getJwtToken(String id, String nickname) {
        String JwtToken = Jwts.builder()
                //设置jwt头信息,红色部分,内容固定,不需要改
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")

                .setSubject("guli-user")//设置主题(可选)
                .setIssuedAt(new Date())//设置发布时间(可选)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//设置过期时间

                //设置token主体部分,存储用户信息,可设置多个值
                .claim("id", id)
                .claim("nickname", nickname)

                //设置签名哈希(防伪标志)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     *
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) return false;
        try {
            //根据设置的防伪码解析token
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     *
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if (StringUtils.isEmpty(jwtToken)) return false;
            //根据设置的防伪码解析token
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取id
     *
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if (StringUtils.isEmpty(jwtToken)) return "";
        //根据设置的防伪码解析token,获取对象
        Jws<Claims> claimsJws =
                Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        //获取token有效载荷【用户信息】
        Claims claims = claimsJws.getBody();
        return (String) claims.get("id");
    }


}

2. ログインメソッドを書く

    @Override
    public String login(LoginVo loginVo) {
        //写你自己的具体登录逻辑:
        //1.判断参数是否为空
        ...
        //2.判断数据库是否存在该用户
        ...
        //3.判断加密后的密码参数和数据库是否一致
        ...
        //4.判断用户是否禁用
        ...
        //5.生成jwt令牌返回给前端
        return JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getNickname());
    }

 JWT をフロントエンドに返すと、フロントエンドはトークンを Cookie に保存するか、アドレス バーに入れるかを選択します。以降、リクエストは毎回 JWT で送信されます。ID は JWT ペイロードに格納されているため、バックエンドは ID に基づいて対応するユーザー情報を検索し、次回それを返すことができます。トークンの有無でログイン可否を判断できるため、シングルサインオンが実現します。

要約する

シングルサインオンは、ログイン後の次のリクエストに対するトークンの有無を重視し、トークンに基づいたデータ共有を実現するものだと私は考えています。完全なプロセスは次のようになります。セキュリティと oauth2.0 を組み合わせて、最初にセキュリティにトークンを解析するリクエストをインターセプトさせます。トークンが正当な場合はコード プロセスを続行し、不正な場合はログイン ページにジャンプしてログインし、ログイン要求が検証のために統合認証センターに送信されてからトークンが発行され、シングル サインオンまたは詳細情報の要求はトークンに基づいて実現されます。

以上がシングルサインオンに対する個人的な理解ですが、セキュリティとoauth2.0の組み合わせについてはまた別の記事で書きたいと思います。ほとんどが私の理解ですので間違いも多々あるかもしれません。どなたでもコメント欄で修正していただけます。

おすすめ

転載: blog.csdn.net/weixin_58403235/article/details/129928096