ホームページ  >  記事  >  Java  >  SpringBoot が Sa-Token を使用してログイン認証を実装する方法

SpringBoot が Sa-Token を使用してログイン認証を実装する方法

WBOY
WBOY転載
2023-05-27 20:34:101011ブラウズ

1. 設計アイデア

ログイン後にのみアクセスできる一部のインターフェイス (アカウント情報のクエリなど) の場合、通常のアプローチはインターフェイス検証のレイヤーを追加することです。

  • 検証に合格した場合は、通常どおりデータを返します。

  • 検証が失敗した場合は、例外をスローして、最初にログインする必要があることを通知します。

では、セッションがログインしているかどうかを判断する基準は何でしょうか?まずログイン アクセス プロセスを簡単に分析してみましょう。

  • ユーザーは名前パスワード パラメータを送信し、ログイン インターフェイスを呼び出します。

  • ログインに成功すると、ユーザーのトークン セッション資格情報が返されます。

  • ユーザーによる後続の各リクエストには、このトークンが含まれます。

  • #サーバーは、トークンに基づいてこのセッションのログインが成功したかどうかを判断します。

いわゆるログイン認証とは、サーバーがアカウントのパスワードを検証し、ユーザーにトークン セッションの資格情報を発行するプロセスを指します。このトークンは、その後の判断の鍵でもありますセッションがログインしているかどうか。

ダイナミックダイアグラムのデモ:

SpringBoot が Sa-Token を使用してログイン認証を実装する方法

次に、Sa-Token を使用して SpringBoot でログイン認証操作を完了する方法を紹介します。

Sa-Token は、主にログイン認証、権限認証、シングル サインオン、OAuth3、マイクロサービス ゲートウェイ認証などの権限関連の一連の問題を解決する Java 権限認証フレームワークです。

Gitee オープンソース アドレス: https://gitee.com/dromara/sa-token

最初に Sa-Token 依存関係をプロジェクトに導入します:

<!-- Sa-Token 权限认证 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.34.0</version>
</dependency>

注: を使用している場合SpringBoot 3.x の場合、sa-token-spring-boot-starter を sa-token-spring-boot3-starter に変更するだけで済みます。

2. ログインとログアウト

上記の考え方に基づいて、セッション ログイン用の関数が必要になります:

// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object id);

このコード文だけで、セッション ログインは次のようになります。実際、Sa-Token は次のような多くの作業を舞台裏で行っていますが、これに限定されません:

  • このアカウントが以前にログインしたかどうかを確認する

  • は、アカウントがトークン資格情報とセッションを生成します

  • ##xx アカウントが正常にログインしたことをグローバル リスナーに通知します
  • #トークンをリクエスト コンテキストに挿入します
  • 他の作業を待ちます... トークンは、このアカウントのトークン資格情報を作成し、Cookie コンテキストを通じてフロントエンドに返します。
  • したがって、通常の状況では、ログイン インターフェイス コードは次のようになります。

    // 会话登录接口 
    @RequestMapping("doLogin")
    public SaResult doLogin(String name, String pwd) {
        // 第一步:比对前端提交的账号名称、密码
        if("zhang".equals(name) && "123456".equals(pwd)) {
            // 第二步:根据账号id,进行登录 
            StpUtil.login(10001);
            return SaResult.ok("登录成功");
        }
        return SaResult.error("登录失败");
    }

    上記のコードを読む必要がなければ、少し奇妙な点に気づくかもしれません。オフィスはセッション ログインのみを実行しましたが、トークン情報をフロントエンドに積極的に返しませんでした。
  • 必要ないからでしょうか?厳密に言えば、これは必須ですが、StpUtil.login(id) メソッドは Cookie の自動挿入機能を利用し、トークンを返すための手書きコードを省略します。

Cookie 関数についてあまり詳しくなくても、後ほど [バックエンドとバックエンドの分離] の章で Cookie 関数について詳しく説明しますので、ご心配なく。最も基本的な点:

Cookie は、バックエンド コントロールからブラウザーにトークン値を書き込むことができます。

Cookie は、フロントエンドがリクエストを開始するたびにトークン値を自動的に送信します。
  • したがって、Cookie 関数のサポートにより、たった 1 行のコード StpUtil.login(id) でログイン認証を完了できます。
  • ログイン メソッドに加えて、次のものも必要です。

    // 当前会话注销登录
    StpUtil.logout();
    
    // 获取当前会话是否已经登录,返回true=已登录,false=未登录
    StpUtil.isLogin();
    
    // 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
    StpUtil.checkLogin();

    Exception NotLoginException は、現在のセッションがログインしていないことを意味します。考えられる理由は数多くあります。
  • フロントエンドトークンを送信しませんでした。フロントエンドによって送信されたトークンは無効です。、フロントエンドによって送信されたトークンの有効期限が切れています...など。

Sa-Token非ログインシナリオ値参照表:


シナリオ値

対応する定数

意味説明-1NotLoginException.NOT_TOKENリクエストからのトークンの読み取りに失敗しました#それでは、シーンの値を取得するにはどうすればよいでしょうか?ナンセンスな話はやめて、コードに直接進みましょう: 注: 上記のコードは、ロジックを処理する最良の方法ではありません。最も単純な方法でシーン値の取得と適用を示すだけです。コード. 独自のプロジェクトのニーズに応じてカスタマイズできます. カスタマイズされた処理3. セッション クエリ
// 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.getLoginId();

// 类似查询API还有:
StpUtil.getLoginIdAsString();    // 获取当前会话账号id, 并转化为`String`类型
StpUtil.getLoginIdAsInt();       // 获取当前会话账号id, 并转化为`int`类型
StpUtil.getLoginIdAsLong();      // 获取当前会话账号id, 并转化为`long`类型

// ---------- 指定未登录情形下返回的默认值 ----------

// 获取当前会话账号id, 如果未登录,则返回null 
StpUtil.getLoginIdDefaultNull();

// 获取当前会话账号id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型)
StpUtil.getLoginId(T defaultValue);
4. トークン クエリ
#-2 NotLoginException.INVALID_TOKEN トークンは読み取られましたが、トークンが無効です
-3 NotLoginException.TOKEN_TIMEOUT トークンは読み取られましたが、トークンの有効期限が切れています
-4 NotLoginException.BE_REPLACED has トークンは読み取られましたが、トークンはオフラインになりました
-5 NotLoginException.KICK_OUT トークンは読み取られましたが、トークンはオフラインにキックされました
// 全局异常拦截(拦截项目中的NotLoginException异常)
@ExceptionHandler(NotLoginException.class)
public SaResult handlerNotLoginException(NotLoginException nle)
        throws Exception {

    // 打印堆栈,以供调试
    nle.printStackTrace(); 
    
    // 判断场景值,定制化异常信息 
    String message = "";
    if(nle.getType().equals(NotLoginException.NOT_TOKEN)) {
        message = "未提供token";
    }
    else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
        message = "token无效";
    }
    else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
        message = "token已过期";
    }
    else if(nle.getType().equals(NotLoginException.BE_REPLACED)) {
        message = "token已被顶下线";
    }
    else if(nle.getType().equals(NotLoginException.KICK_OUT)) {
        message = "token已被踢下线";
    }
    else {
        message = "当前会话未登录";
    }
    
    // 返回给前端
    return SaResult.error(message);
}
// 获取当前会话的token值
StpUtil.getTokenValue();

// 获取当前`StpLogic`的token名称
StpUtil.getTokenName();

// 获取指定token对应的账号id,如果未登录,则返回 null
StpUtil.getLoginIdByToken(String tokenValue);

// 获取当前会话剩余有效期(单位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();

// 获取当前会话的token信息参数
StpUtil.getTokenInfo();

TokenInfo はトークン情報モデルです。トークンの共通パラメータを説明するために使用されます:

{
    "tokenName": "satoken",           // token名称
    "tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633",      // token值
    "isLogin": true,                  // 此token是否已经登录
    "loginId": "10001",               // 此token对应的LoginId,未登录时为null
    "loginType": "login",              // 账号类型标识
    "tokenTimeout": 2591977,          // token剩余有效期 (单位: 秒)
    "sessionTimeout": 2591977,        // User-Session剩余有效时间 (单位: 秒)
    "tokenSessionTimeout": -2,        // Token-Session剩余有效时间 (单位: 秒) (-2表示系统中不存在这个缓存)
    "tokenActivityTimeout": -1,       // token剩余无操作有效时间 (单位: 秒)
    "loginDevice": "default-device"   // 登录设备类型 
}

5. 理解を深めるために小さなテストをしてみましょう

新しい LoginAuthController を作成し、次のコードをコピーします

package com.pj.cases.use;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;

/**
 * Sa-Token 登录认证示例 
 * 
 * @author kong
 * @since 2022-10-13
 */
@RestController
@RequestMapping("/acc/")
public class LoginAuthController {

    // 会话登录接口  ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
    @RequestMapping("doLogin")
    public SaResult doLogin(String name, String pwd) {
        
        // 第一步:比对前端提交的 账号名称 & 密码 是否正确,比对成功后开始登录 
        //         此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 
        if("zhang".equals(name) && "123456".equals(pwd)) {
            
            // 第二步:根据账号id,进行登录 
            //         此处填入的参数应该保持用户表唯一,比如用户id,不可以直接填入整个 User 对象 
            StpUtil.login(10001);
            
            // SaResult 是 Sa-Token 中对返回结果的简单封装,下面的示例将不再赘述 
            return SaResult.ok("登录成功");
        }
        
        return SaResult.error("登录失败");
    }

    // 查询当前登录状态  ---- http://localhost:8081/acc/isLogin
    @RequestMapping("isLogin")
    public SaResult isLogin() {
        // StpUtil.isLogin() 查询当前客户端是否登录,返回 true 或 false 
        boolean isLogin = StpUtil.isLogin();
        return SaResult.ok("当前客户端是否登录:" + isLogin);
    }

    // 校验当前登录状态  ---- http://localhost:8081/acc/checkLogin
    @RequestMapping("checkLogin")
    public SaResult checkLogin() {
        // 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
        StpUtil.checkLogin();

        // 抛出异常后,代码将走入全局异常处理(GlobalException.java),如果没有抛出异常,则代表通过了登录校验,返回下面信息 
        return SaResult.ok("校验登录成功,这行字符串是只有登录后才会返回的信息");
    }

    // 获取当前登录的账号是谁  ---- http://localhost:8081/acc/getLoginId
    @RequestMapping("getLoginId")
    public SaResult getLoginId() {
        // 需要注意的是,StpUtil.getLoginId() 自带登录校验效果
        // 也就是说如果在未登录的情况下调用这句代码,框架就会抛出 `NotLoginException` 异常,效果和 StpUtil.checkLogin() 是一样的 
        Object userId = StpUtil.getLoginId();
        System.out.println("当前登录的账号id是:" + userId);
        
        // 如果不希望 StpUtil.getLoginId() 触发登录校验效果,可以填入一个默认值
        // 如果会话未登录,则返回这个默认值,如果会话已登录,将正常返回登录的账号id 
        Object userId2 = StpUtil.getLoginId(0);
        System.out.println("当前登录的账号id是:" + userId2);
        
        // 或者使其在未登录的时候返回 null 
        Object userId3 = StpUtil.getLoginIdDefaultNull();
        System.out.println("当前登录的账号id是:" + userId3);
        
        // 类型转换:
        // StpUtil.getLoginId() 返回的是 Object 类型,你可以使用以下方法指定其返回的类型 
        int userId4 = StpUtil.getLoginIdAsInt();  // 将返回值转换为 int 类型 
        long userId5 = StpUtil.getLoginIdAsLong();  // 将返回值转换为 long 类型 
        String userId6 = StpUtil.getLoginIdAsString();  // 将返回值转换为 String 类型 
        
        // 疑问:数据基本类型不是有八个吗,为什么只封装以上三种类型的转换?
        // 因为大多数项目都是拿 int、long 或 String 声明 UserId 的类型的,实在没见过哪个项目用 double、float、boolean 之类来声明 UserId 
        System.out.println("当前登录的账号id是:" + userId4 + " --- " + userId5 + " --- " + userId6);
        
        // 返回给前端 
        return SaResult.ok("当前客户端登录的账号id是:" + userId);
    }

    // 查询 Token 信息  ---- http://localhost:8081/acc/tokenInfo
    @RequestMapping("tokenInfo")
    public SaResult tokenInfo() {
        // TokenName 是 Token 名称的意思,此值也决定了前端提交 Token 时应该使用的参数名称 
        String tokenName = StpUtil.getTokenName();
        System.out.println("前端提交 Token 时应该使用的参数名称:" + tokenName);
        
        // 使用 StpUtil.getTokenValue() 获取前端提交的 Token 值 
        // 框架默认前端可以从以下三个途径中提交 Token:
        //         Cookie         (浏览器自动提交)
        //         Header头    (代码手动提交)
        //         Query 参数    (代码手动提交) 例如: /user/getInfo?satoken=xxxx-xxxx-xxxx-xxxx 
        // 读取顺序为: Query 参数 --> Header头 -- > Cookie 
        // 以上三个地方都读取不到 Token 信息的话,则视为前端没有提交 Token 
        String tokenValue = StpUtil.getTokenValue();
        System.out.println("前端提交的Token值为:" + tokenValue);
        
        // TokenInfo 包含了此 Token 的大多数信息 
        SaTokenInfo info = StpUtil.getTokenInfo();
        System.out.println("Token 名称:" + info.getTokenName());
        System.out.println("Token 值:" + info.getTokenValue());
        System.out.println("当前是否登录:" + info.getIsLogin());
        System.out.println("当前登录的账号id:" + info.getLoginId());
        System.out.println("当前登录账号的类型:" + info.getLoginType());
        System.out.println("当前登录客户端的设备类型:" + info.getLoginDevice());
        System.out.println("当前 Token 的剩余有效期:" + info.getTokenTimeout()); // 单位:秒,-1代表永久有效,-2代表值不存在
        System.out.println("当前 Token 的剩余临时有效期:" + info.getTokenActivityTimeout()); // 单位:秒,-1代表永久有效,-2代表值不存在
        System.out.println("当前 User-Session 的剩余有效期" + info.getSessionTimeout()); // 单位:秒,-1代表永久有效,-2代表值不存在
        System.out.println("当前 Token-Session 的剩余有效期" + info.getTokenSessionTimeout()); // 单位:秒,-1代表永久有效,-2代表值不存在
        
        // 返回给前端 
        return SaResult.data(StpUtil.getTokenInfo());
    }
    
    // 会话注销  ---- http://localhost:8081/acc/logout
    @RequestMapping("logout")
    public SaResult logout() {
        // 退出登录会清除三个地方的数据:
        //         1、Redis中保存的 Token 信息
        //         2、当前请求上下文中保存的 Token 信息 
        //         3、Cookie 中保存的 Token 信息(如果未使用Cookie模式则不会清除)
        StpUtil.logout();
        
        // StpUtil.logout() 在未登录时也是可以调用成功的,
        // 也就是说,无论客户端有没有登录,执行完 StpUtil.logout() 后,都会处于未登录状态 
        System.out.println("当前是否处于登录状态:" + StpUtil.isLogin());
        
        // 返回给前端 
        return SaResult.ok("退出登录成功");
    }
    
}

以上がSpringBoot が Sa-Token を使用してログイン認証を実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。