基础使用


@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.getTokenActiveTimeout()); // 单位:秒,-1代表永久有效,-2代表值不存在
		System.out.println("当前 Account-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("退出登录成功");
	}
	
}

权限使用

package com.pj.cases.use;

import java.util.List;

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

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

/**
 * Sa-Token 权限认证示例 
 * 
 * @author click33
 * @since 2022-10-13
 */
@RestController
@RequestMapping("/jur/")
public class JurAuthController {

	/*
	 * 前提1:首先调用登录接口进行登录,代码在 com.pj.cases.use.LoginAuthController 中有详细解释,此处不再赘述 
	 * 		---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
	 * 
	 * 前提2:项目实现 StpInterface 接口,代码在  com.pj.satoken.StpInterfaceImpl
	 * 		Sa-Token 将从此实现类获取 每个账号拥有哪些权限。
	 * 
	 * 然后我们就可以使用以下示例中的代码进行鉴权了 
	 */
	
	// 查询权限   ---- http://localhost:8081/jur/getPermission
	@RequestMapping("getPermission")
	public SaResult getPermission() {
		// 查询权限信息 ,如果当前会话未登录,会返回一个空集合 
		List<String> permissionList = StpUtil.getPermissionList();
		System.out.println("当前登录账号拥有的所有权限:" + permissionList);
		
		// 查询角色信息 ,如果当前会话未登录,会返回一个空集合 
		List<String> roleList = StpUtil.getRoleList();
		System.out.println("当前登录账号拥有的所有角色:" + roleList);
		
		// 返回给前端 
		return SaResult.ok()
				.set("roleList", roleList)
				.set("permissionList", permissionList);
	}
	
	// 权限校验  ---- http://localhost:8081/jur/checkPermission
	@RequestMapping("checkPermission")
	public SaResult checkPermission() {
		
		// 判断:当前账号是否拥有一个权限,返回 true 或 false
		// 		如果当前账号未登录,则永远返回 false 
		StpUtil.hasPermission("user.add");
		StpUtil.hasPermissionAnd("user.add", "user.delete", "user.get");  // 指定多个,必须全部拥有才会返回 true 
		StpUtil.hasPermissionOr("user.add", "user.delete", "user.get");	 // 指定多个,只要拥有一个就会返回 true 
		
		// 校验:当前账号是否拥有一个权限,校验不通过时会抛出 `NotPermissionException` 异常 
		// 		如果当前账号未登录,则永远校验失败 
		StpUtil.checkPermission("user.add");
		StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");  // 指定多个,必须全部拥有才会校验通过 
		StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");  // 指定多个,只要拥有一个就会校验通过 
		
		return SaResult.ok();
	}

	// 角色校验  ---- http://localhost:8081/jur/checkRole
	@RequestMapping("checkRole")
	public SaResult checkRole() {
		
		// 判断:当前账号是否拥有一个角色,返回 true 或 false
		// 		如果当前账号未登录,则永远返回 false 
		StpUtil.hasRole("admin");
		StpUtil.hasRoleAnd("admin", "ceo", "cfo");  // 指定多个,必须全部拥有才会返回 true 
		StpUtil.hasRoleOr("admin", "ceo", "cfo");	  // 指定多个,只要拥有一个就会返回 true 
		
		// 校验:当前账号是否拥有一个角色,校验不通过时会抛出 `NotRoleException` 异常 
		// 		如果当前账号未登录,则永远校验失败 
		StpUtil.checkRole("admin");
		StpUtil.checkRoleAnd("admin", "ceo", "cfo");  // 指定多个,必须全部拥有才会校验通过 
		StpUtil.checkRoleOr("admin", "ceo", "cfo");  // 指定多个,只要拥有一个就会校验通过 
		
		return SaResult.ok();
	}

	// 权限通配符  ---- http://localhost:8081/jur/wildcardPermission
	@RequestMapping("wildcardPermission")
	public SaResult wildcardPermission() {
		
		// 前提条件:在 StpInterface 实现类中,为账号返回了 "art.*" 泛权限
		StpUtil.hasPermission("art.add");  // 返回 true 
		StpUtil.hasPermission("art.delete");  // 返回 true 
		StpUtil.hasPermission("goods.add");  // 返回 false,因为前缀不符合  
		
		// * 符合可以出现在任意位置,比如权限码的开头,当账号拥有 "*.delete" 时  
		StpUtil.hasPermission("goods.add");        // false
		StpUtil.hasPermission("goods.delete");     // true
		StpUtil.hasPermission("art.delete");      // true
		
		// 也可以出现在权限码的中间,比如当账号拥有 "shop.*.user" 时  
		StpUtil.hasPermission("shop.add.user");  // true
		StpUtil.hasPermission("shop.delete.user");  // true
		StpUtil.hasPermission("shop.delete.goods");  // false,因为后缀不符合 

		// 注意点:
		// 1、上帝权限:当一个账号拥有 "*" 权限时,他可以验证通过任何权限码
		// 2、角色校验也可以加 * ,指定泛角色,例如: "*.admin",暂不赘述 
		
		return SaResult.ok();
	}

}

踢人下线功能

所谓踢人下线,核心操作就是找到指定 loginId 对应的 Token,并设置其失效

注解鉴权@RestController
@RequestMapping("/kickout/")
public class KickoutController {

	/*
	 * 前提:首先调用登录接口进行登录,代码在 com.pj.cases.use.LoginAuthController 中有详细解释,此处不再赘述 
	 * 		---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
	 */
	
	// 将指定账号强制注销   ---- http://localhost:8081/kickout/logout?userId=10001
	@RequestMapping("logout")
	public SaResult logout(long userId) {
		
		// 强制注销等价于对方主动调用了注销方法,再次访问会提示:Token无效。
		StpUtil.logout(userId);
		
		// 返回
		return SaResult.ok();
	}

	// 将指定账号踢下线   ---- http://localhost:8081/kickout/kickout?userId=10001
	@RequestMapping("kickout")
	public SaResult kickout(long userId) {
		
		// 踢人下线不会清除Token信息,而是将其打上特定标记,再次访问会提示:Token已被踢下线。
		StpUtil.kickout(userId);
		
		// 返回
		return SaResult.ok();
	}
	
	/* 
	 * 你可以分别在强制注销和踢人下线后,再次访问一下登录校验接口,对比一下两者返回的提示信息有何不同 
	 * 		---- http://localhost:8081/acc/checkLogin
	 */
	
	// 根据 Token 值踢人    ---- http://localhost:8081/kickout/kickoutByTokenValue?tokenValue=xxxx-xxxx-xxxx-xxxx已登录账号的token值
	@RequestMapping("kickoutByTokenValue")
	public SaResult kickoutByTokenValue(String tokenValue) {
		
		StpUtil.kickoutByTokenValue(tokenValue);
		
		// 返回
		return SaResult.ok();
	}
	
}

注解鉴权(重点)

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

  • @SaCheckLogin: 登录校验 —— 只有登录之后才能进入该方法。

  • @SaCheckRole("admin"): 角色校验 —— 必须具有指定角色标识才能进入该方法。

  • @SaCheckPermission("user:add"): 权限校验 —— 必须具有指定权限才能进入该方法。

  • @SaCheckSafe: 二级认证校验 —— 必须二级认证之后才能进入该方法。

  • @SaCheckHttpBasic: HttpBasic校验 —— 只有通过 HttpBasic 认证后才能进入该方法。

  • @SaCheckHttpDigest: HttpDigest校验 —— 只有通过 HttpDigest 认证后才能进入该方法。

  • @SaIgnore:忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。

  • @SaCheckDisable("comment"):账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。

Sa-Token 使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态
因此,为了使用注解鉴权,你必须手动将 Sa-Token 的全局拦截器注册到你项目中

1注册拦截器

SpringBoot2.0为例,新建配置类SaTokenConfigure.java

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册 Sa-Token 拦截器,打开注解式鉴权功能 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,打开注解式鉴权功能 
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");    
    }
}

2、使用注解鉴权

然后就可以愉快的使用注解鉴权了:

// 登录校验:只有登录之后才能进入该方法 
@SaCheckLogin                        
@RequestMapping("info")
public String info() {
    return "查询用户信息";
}

// 角色校验:必须具有指定角色才能进入该方法 
@SaCheckRole("super-admin")        
@RequestMapping("add")
public String add() {
    return "用户增加";
}

// 权限校验:必须具有指定权限才能进入该方法 
@SaCheckPermission("user-add")        
@RequestMapping("add")
public String add() {
    return "用户增加";
}

// 二级认证校验:必须二级认证之后才能进入该方法 
@SaCheckSafe()        
@RequestMapping("add")
public String add() {
    return "用户增加";
}

// Http Basic 校验:只有通过 Http Basic 认证后才能进入该方法 
@SaCheckHttpBasic(account = "sa:123456")
@RequestMapping("add")
public String add() {
    return "用户增加";
}

// Http Digest 校验:只有通过 Http Digest 认证后才能进入该方法 
@SaCheckHttpDigest(value = "sa:123456")
@RequestMapping("add")
public String add() {
    return "用户增加";
}

// 校验当前账号是否被封禁 comment 服务,如果已被封禁会抛出异常,无法进入方法 
@SaCheckDisable("comment")                
@RequestMapping("send")
public String send() {
    return "查询用户信息";
}

注:以上注解都可以加在类上,代表为这个类所有方法进行鉴权

详细见官方文档注解鉴权