Sa-Token学习笔记-03

注解鉴权

尽管使用代码鉴权非常方便,但是我仍希望把鉴权逻辑和业务逻辑分离开来,我可以使用注解鉴权吗?当然可以!

注解鉴权 —— 优雅的将鉴权与业务代码分离!

  • @SaCheckLogin:登陆校验 —— 只有登陆之后才能进入该方法。
  • @SaCheckRole:角色校验 —— 必须具有指定角色标识才能进入该方法。
  • @SaCheckPermission("user.add"):权限校验 —— 当前用户必须具有user.add权限才可以访问2这个方法。
  • @SaCheckSafe:二级认证校验 —— 必须二级认证之后才可以进入该方法。
  • @SaCheckHttpBasic: HttpBasic校验 —— 只有通过 HttpBasic 认证后才能进入该方法。
  • @SaCheckHttpDigest: HttpDigest校验 —— 只有通过 HttpDigest 认证后才能进入该方法。
  • @SaIgnore:忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。
  • @SaCheckDisable("comment"):账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。

将 Sa-Token 的全局拦截器注册到项目中

package com.hayaizo.satoken.config;

import cn.dev33.satoken.interceptor.SaInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author moxiao
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册Sa-Token 拦截器,打开注解式鉴权功能

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
    }
}

测试代码

package com.hayaizo.satoken.controller;

import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author moxiao
 */
@RestController
@RequestMapping("/at-check/")
public class AtCheckController {

    /*
     * 前提1:首先调用登录接口进行登录,代码在 com.pj.cases.use.LoginAuthController 中有详细解释,此处不再赘述
     *     ---- http://localhost:8081/acc/doLogin?username=zhang&password=123456
     *
     * 前提2:项目在配置类中注册拦截器 SaInterceptor ,代码在  com.hayaizo.satoken.config.SaTokenConfigure
     *     此拦截器将打开注解鉴权功能
     *
     * 然后我们就可以使用以下示例中的代码进行注解鉴权了
     */

    // 登录鉴权   ---- http://localhost:8081/at-check/checkLogin
    //     登录认证后才可以进入方法
    @SaCheckLogin
    @RequestMapping("checkLogin")
    public SaResult checkLogin(){
        // 通过注解鉴权后才可以进入这个方法
        return SaResult.ok("@SaCheckLogin鉴权通过");
    }

    // 权限校验   ---- http://localhost:8081/at-check/checkPermission
    //     只有具有 user.add 权限的账号才可以进入方法
    @SaCheckPermission("user.add")
    @RequestMapping("checkPermission")
    public SaResult checkPermission(){
        // ...
        return SaResult.ok();
    }

    // 权限校验2   ---- http://localhost:8081/at-check/checkPermission2
    //     一次性校验多个权限,必须全部拥有,才可以进入方法
    @SaCheckPermission(value = {"user.add","user.delete","user.update"},mode = SaMode.AND)
    @RequestMapping("checkPermission2")
    public SaResult checkPermission2(){
        // ...
        return SaResult.ok("\"user.add\",\"user.delete\",\"user.update\",AND");
    }

    // 权限校验3   ---- http://localhost:8081/at-check/checkPermission3
    //     一次性校验多个权限,只要拥有其中一个,就可以进入方法
    @SaCheckPermission(value = {"user.add","user.delete","user.update"},mode = SaMode.OR)
    @RequestMapping("checkPermission3")
    public SaResult checkPermission3(){
        // ...
        return SaResult.ok("\"user.add\",\"user.delete\",\"user.update\",OR");
    }

    // 角色校验   ---- http://localhost:8081/at-check/checkRole
    //     只有具有 super-admin 角色的账号才可以进入方法
    @SaCheckRole("super-admin")
    @RequestMapping("checkRole")
    public SaResult checkRole(){
        // ...
        return SaResult.ok();
    }

    // 角色权限双重 “or校验”   ---- http://localhost:8081/at-check/userAdd
    //     具备 "user.add"权限 或者 "admin"角色 即可通过校验
    @SaCheckPermission(value = "user.add" , orRole = "admin")
    @RequestMapping("userAdd")
    public SaResult userAdd(){
        // ...
        return SaResult.data(StpUtil.getTokenInfo());
    }

    // 忽略校验 ---- http://localhost:8081/at-check/ignore
    //     使用 @SaIgnore 修饰的方法,无需任何校验即可进入,具体使用示例可参照在线文档
    @SaIgnore
    @SaCheckLogin
    @RequestMapping("ignore")
    public SaResult ignore(){
        // ...
        return SaResult.ok();
    }

}

路由拦截鉴权

给每个接口加上鉴权注解?手写全局拦截器?似乎都不是非常方便。

在这个需求中我们真正需要的是一种基于路由拦截的鉴权模式,那么在Sa-Token怎么实现路由拦截鉴权呢?

新建配置类SaTokenConfiguration.java

package com.hayaizo.satoken.config;

import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author moxiao
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaInterceptor(handler -> {

            // 跳过静态资源路径
            SaRouter.match("/**")
                    .notMatch("/acc/doLogin")
                    .check(r -> StpUtil.checkLogin());

            // 用户模块鉴权
            SaRouter.match("/user/**", r -> StpUtil.checkPermissionOr("user", "admin"));

            // 管理员模块鉴权
            SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));

        }).isAnnotation(false)).addPathPatterns("/**");
    }
}

登出后再访问一些没有权限的接口试试。

匹配特征

// 基础写法样例:匹配一个path,执行一个校验函数 
SaRouter.match("/user/**").check(r -> StpUtil.checkLogin());

// 根据 path 路由匹配   ——— 支持写多个path,支持写 restful 风格路由 
// 功能说明: 使用 /user , /goods 或者 /art/get 开头的任意路由都将进入 check 方法
SaRouter.match("/user/**", "/goods/**", "/art/get/{id}").check( /* 要执行的校验函数 */ );

// 根据 path 路由排除匹配 
// 功能说明: 使用 .html , .css 或者 .js 结尾的任意路由都将跳过, 不会进入 check 方法
SaRouter.match("/**").notMatch("*.html", "*.css", "*.js").check( /* 要执行的校验函数 */ );

// 根据请求类型匹配 
SaRouter.match(SaHttpMethod.GET).check( /* 要执行的校验函数 */ );

// 根据一个 boolean 条件进行匹配 
SaRouter.match( StpUtil.isLogin() ).check( /* 要执行的校验函数 */ );

// 根据一个返回 boolean 结果的lambda表达式匹配 
SaRouter.match( r -> StpUtil.isLogin() ).check( /* 要执行的校验函数 */ );

// 多个条件一起使用 
// 功能说明: 必须是 Get 请求 并且 请求路径以 `/user/` 开头 
SaRouter.match(SaHttpMethod.GET).match("/user/**").check( /* 要执行的校验函数 */ );

// 可以无限连缀下去 
// 功能说明: 同时满足 Get 方式请求, 且路由以 /admin 开头, 路由中间带有 /send/ 字符串, 路由结尾不能是 .js 和 .css
SaRouter
    .match(SaHttpMethod.GET)
    .match("/admin/**")
    .match("/**/send/**") 
    .notMatch("/**/*.js")
    .notMatch("/**/*.css")
    // ....
    .check( /* 只有上述所有条件都匹配成功,才会执行最后的check校验函数 */ );

提前退出匹配链

registry.addInterceptor(new SaInterceptor(handler -> {
    SaRouter.match("/**").check(r -> System.out.println("进入1"));
    SaRouter.match("/**").check(r -> System.out.println("进入2")).stop();
    SaRouter.match("/**").check(r -> System.out.println("进入3"));
    SaRouter.match("/**").check(r -> System.out.println("进入4"));
    SaRouter.match("/**").check(r -> System.out.println("进入5"));
})).addPathPatterns("/**");

如上示例,代码运行至第2条匹配链时,会在stop函数处提前退出整个匹配函数,从而忽略掉剩余的所有match匹配,直接进入Controller层

除了stop()函数,SaRouter还提供了 back() 函数,用于:停止匹配,结束执行,直接向前端返回结果

// 执行back函数后将停止匹配,也不会进入Controller,而是直接将 back参数 作为返回值输出到前端
SaRouter.match("/user/back").back("要返回到前端的内容");

stop() 与 back() 函数的区别在于:

  • SaRouter.stop() 会停止匹配,进入Controller。
  • SaRouter.back() 会停止匹配,直接返回结果到前端。

free独立作用域

如果调用了stop之后还需要继续鉴权验证,可以使用free来开辟一个独立的作用域

// 进入 free 独立作用域 
SaRouter.match("/**").free(r -> {
    SaRouter.match("/a/**").check(/* --- */);
    SaRouter.match("/b/**").check(/* --- */).stop();
    SaRouter.match("/c/**").check(/* --- */);
});
// 执行 stop() 函数跳出 free 后继续执行下面的 match 匹配 
SaRouter.match("/**").check(/* --- */);

@SaIgnore忽略路由拦截校验

@SaIgnore
@RequestMapping("/user/getList")
public SaResult getList() {
    System.out.println("------------ 访问进来方法"); 
    return SaResult.ok(); 
}
注解 @SaIgnore 的忽略效果只针对 SaInterceptor拦截器 和 AOP注解鉴权 生效,对自定义拦截器与过滤器不生效。

关闭注解校验

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(
        new SaInterceptor(handle -> {
            SaRouter.match("/**").check(r -> StpUtil.checkLogin());
        }).isAnnotation(false)  // 指定关闭掉注解鉴权能力,这样框架就只会做路由拦截校验了 
    ).addPathPatterns("/**");
}

放行前端资源

package com.hayaizo.satoken.config;

import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author moxiao
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaInterceptor(handler -> {

            // 跳过静态资源路径
            SaRouter.match("/**")
                    .notMatch("/acc/doLogin")
                    .notMatch("/*.html")
                    .notMatch("/*.js")
                    .notMatch("/*.css")
                    .notMatch("/*.jpeg")
                    .notMatch("/*.png")
                    .notMatch("/*.jpg")
                    .check(r -> StpUtil.checkLogin());

            // 用户模块鉴权
            SaRouter.match("/user/**", r -> StpUtil.checkPermissionOr("user", "admin"));

            // 管理员模块鉴权
            SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));

        }).isAnnotation(false)).addPathPatterns("/**");
    }
}
分类: Sa-Token 标签: Sa-Token

评论

暂无评论数据

暂无评论数据

目录