SpringBoot集成Sa | 您所在的位置:网站首页 › 机顶盒正在鉴权验证中 › SpringBoot集成Sa |
1 添加依赖 cn.dev33 sa-token-spring-boot-starter 1.28.0 2 配置文件server: # 端口 port: 8081 # Sa-Token配置 sa-token: # token名称 (同时也是cookie名称) token-name: satoken # token有效期,单位s 默认30天, -1代表永不过期 timeout: 2592000 # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 activity-timeout: -1 # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) is-concurrent: true # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) is-share: false # token风格 token-style: uuid # 是否输出操作日志 is-log: false 3 登录认证3.1 登录与注销 // 标记当前会话登录的账号id // 建议的参数类型:long | int | String, 不可以传入复杂类型,如:User、Admin等等 StpUtil.login(Object id); // 当前会话注销登录 StpUtil.logout(); // 获取当前会话是否已经登录,返回true=已登录,false=未登录 StpUtil.isLogin(); // 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException` StpUtil.checkLogin(); 3.2 会话查询 // 获取当前会话账号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); // 获取指定token对应的账号id,如果未登录,则返回 null StpUtil.getLoginIdByToken(String tokenValue); // 获取当前`StpLogic`的token名称 StpUtil.getTokenName(); // 获取当前会话的token值 StpUtil.getTokenValue(); // 获取当前会话的token信息参数 StpUtil.getTokenInfo(); 4 权限认证4.1 设置权限码和角色 import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Component; import cn.dev33.satoken.stp.StpInterface; /** * 自定义权限验证接口扩展 */ @Component public class StpInterfaceImpl implements StpInterface { /** * 返回一个账号所拥有的权限码集合 */ @Override public List getPermissionList(Object loginId, String loginType) { // 模拟权限码 List list = new ArrayList(); list.add("101"); list.add("user-add"); list.add("user-delete"); list.add("user-update"); list.add("user-get"); list.add("article-get"); return list; } /** * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验) */ @Override public List getRoleList(Object loginId, String loginType) { // 模拟角色 List list = new ArrayList(); list.add("admin"); list.add("super-admin"); return list; } } 4.2 权限认证 // 判断:当前账号是否含有指定权限, 返回true或false StpUtil.hasPermission("user-update"); // 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException StpUtil.checkPermission("user-update"); // 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过] StpUtil.checkPermissionAnd("user-update", "user-delete"); // 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] StpUtil.checkPermissionOr("user-update", "user-delete"); 4.3 角色认证 // 判断:当前账号是否拥有指定角色, 返回true或false StpUtil.hasRole("super-admin"); // 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException StpUtil.checkRole("super-admin"); // 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过] StpUtil.checkRoleAnd("super-admin", "shop-admin"); // 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] StpUtil.checkRoleOr("super-admin", "shop-admin"); 4.4 权限通配符 Sa-Token允许你根据通配符指定泛权限,例如当一个账号拥有user*的权限时,user-add、user-delete、user-update都将匹配通过 。 // 当拥有 user* 权限时 StpUtil.hasPermission("user-add"); // true StpUtil.hasPermission("user-update"); // true StpUtil.hasPermission("art-add"); // false // 当拥有 *-delete 权限时 StpUtil.hasPermission("user-add"); // false StpUtil.hasPermission("user-delete"); // true StpUtil.hasPermission("art-delete"); // true // 当拥有 *.js 权限时 StpUtil.hasPermission("index.js"); // true StpUtil.hasPermission("index.css"); // false StpUtil.hasPermission("index.html"); // false 上帝权限:当一个账号拥有 "*" 权限时,他可以验证通过任何权限码 (角色认证同理) 。 5 踢人下线5.1 强制注销 StpUtil.logout(10001); // 强制指定账号注销下线 StpUtil.logout(10001, "PC"); // 强制指定账号指定端注销下线 StpUtil.logoutByTokenValue("token"); // 强制指定 Token 注销下线 5.2 踢人下线 StpUtil.kickout(10001); // 将指定账号踢下线 StpUtil.kickout(10001, "PC"); // 将指定账号指定端踢下线 StpUtil.kickoutByTokenValue("token"); // 将指定 Token 踢下线 强制注销 和 踢人下线 的区别在于: 强制注销等价于对方主动调用了注销方法,再次访问会提示:Token无效。 踢人下线不会清除Token信息,而是将其打上特定标记,再次访问会提示:Token已被踢下线。 5.3 账号封禁 对于违规账号,有时候我们仅仅将其踢下线还是远远不够的,我们还需要对其进行账号封禁防止其再次登录 。 // 封禁指定账号 // 参数一:账号id // 参数二:封禁时长,单位:秒 (86400秒=1天,此值为-1时,代表永久封禁) StpUtil.disable(10001, 86400); // 获取指定账号是否已被封禁 (true=已被封禁, false=未被封禁) StpUtil.isDisable(10001); // 获取指定账号剩余封禁时间,单位:秒 StpUtil.getDisableTime(10001); // 解除封禁 StpUtil.untieDisable(10001); 注意 对于正在登录的账号,对其账号封禁时并不会使其立刻注销 如果需要将其封禁后立即掉线,可采取先踢再封禁的策略,例如: // 先踢下线 StpUtil.kickout(10001); // 再封禁账号 封一天1 StpUtil.disable(10001, 86400); 6 注解式鉴权注解鉴权 —— 优雅的将鉴权与业务代码分离! @SaCheckLogin: 登录认证 —— 只有登录之后才能进入该方法 @SaCheckRole("admin"): 角色认证 —— 必须具有指定角色标识才能进入该方法 @SaCheckPermission("user:add"): 权限认证 —— 必须具有指定权限才能进入该方法 @SaCheckSafe: 二级认证校验 —— 必须二级认证之后才能进入该方法 @SaCheckBasic: HttpBasic认证 —— 只有通过 Basic 认证后才能进入该方法 Sa-Token使用全局拦截器完成注解鉴权功能,为了不为项目带来不必要的性能负担,拦截器默认处于关闭状态 因此,为了使用注解鉴权,你必须手动将Sa-Token的全局拦截器注册到你项目中。 6.1 注册拦截器 注:注册拦截器方式的注解鉴权只能加在controller层。 @Configuration public class SaTokenConfigure implements WebMvcConfigurer { // 注册Sa-Token的注解拦截器,打开注解式鉴权功能 @Override public void addInterceptors(InterceptorRegistry registry) { // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关) registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**"); } } 6.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 认证:只有通过 Basic 认证后才能进入该方法 @SaCheckBasic(account = "sa:123456") @RequestMapping("add") public String add() { return "用户增加"; } 注:以上注解都可以加在类上,代表为这个类所有方法进行鉴权 。 6.3 设定校验模式 @SaCheckRole与@SaCheckPermission注解可设置校验模式,例如: // 注解式鉴权:只要具有其中一个权限即可通过校验 @RequestMapping("atJurOr") @SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR) public AjaxJson atJurOr() { return AjaxJson.getSuccessData("用户信息"); } mode有两种取值: SaMode.AND, 标注一组权限,会话必须全部具有才可通过校验。 SaMode.OR, 标注一组权限,会话只要具有其一即可通过校验。 6.4 角色权限双重"or校验" // 注解式鉴权:只要具有其中一个权限即可通过校验 @RequestMapping("userAdd") @SaCheckPermission(value = "user-add", orRole = "admin") public AjaxJson userAdd() { return AjaxJson.getSuccessData("用户信息"); } orRole 字段代表权限认证未通过时的次要选择,两者只要其一认证成功即可通过校验,其有三种写法: 写法一:orRole = "admin",代表需要拥有角色 admin 。 写法二:orRole = {"admin", "manager", "staff"},代表具有三个角色其一即可。 写法三:orRole = {"admin, manager, staff"},代表必须同时具有三个角色。 6.5 在业务层使用注解鉴权 使用拦截器模式,只能在Controller层进行注解鉴权,如需在任意层级使用注解鉴权,请参考:AOP注解鉴权 7 路由拦截鉴权需求:项目中所有接口均需要登录认证,只有’登录接口’本身对外开放 。 7.1 注册路由拦截器 建配置类 @Configuration public class SaTokenConfigure implements WebMvcConfigurer { // 注册拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { // 注册Sa-Token的路由拦截器 registry.addInterceptor(new SaRouteInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/user/doLogin"); } } 以上代码,我们注册了一个登录认证拦截器,并且排除了/user/doLogin接口用来开放登录(除了/user/doLogin以外的所有接口都需要登录才能访问)。 7.2 校验函数详解 可以使用函数式编程自定义认证规则,例如: @Configuration public class SaTokenConfigure implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 注册路由拦截器,自定义认证规则 registry.addInterceptor(new SaRouteInterceptor((req, res, handler)->{ // 根据路由划分模块,不同模块不同鉴权 SaRouter.match("/user/**", r -> StpUtil.checkPermission("user")); SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin")); SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods")); SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders")); SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice")); SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment")); })).addPathPatterns("/**"); } } SaRouter.match() 匹配函数有两个参数: 参数一:要匹配的path路由。 参数二:要执行的校验函数。 在校验函数内不只可以使用 StpUtil.checkPermission("xxx") 进行权限校验,你还可以写任意代码,例如: @Configuration public class SaTokenConfigure implements WebMvcConfigurer { // 注册Sa-Token的拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { // 注册路由拦截器,自定义认证规则 registry.addInterceptor(new SaRouteInterceptor((req, res, handler) -> { // 登录认证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin()); // 角色认证 -- 拦截以 admin 开头的路由,必须具备 admin 角色或者 super-admin 角色才可以通过认证 SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin")); // 权限认证 -- 不同模块认证不同权限 SaRouter.match("/user/**", r -> StpUtil.checkPermission("user")); SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin")); SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods")); SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders")); SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice")); SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment")); // 甚至你可以随意的写一个打印语句 SaRouter.match("/**", r -> System.out.println("----啦啦啦----")); // 连缀写法 SaRouter.match("/**").check(r -> System.out.println("----啦啦啦----")); })).addPathPatterns("/**"); } } 7.3 匹配特征详解 // 基础写法样例:匹配一个path,执行一个校验函数 SaRouter.match("/user/**").check(r -> StpUtil.checkLogin()); // 根据 path 路由匹配 ——— 支持写多个path,支持写 restful 风格路由 SaRouter.match("/user/**", "/goods/**", "/art/get/{id}").check( /* 要执行的校验函数 */ ); // 根据 path 路由排除匹配 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( /* 要执行的校验函数 */ ); // 多个条件一起使用 SaRouter.match(SaHttpMethod.GET).match("/**").check( /* 要执行的校验函数 */ ); // 可以无限连缀下去 SaRouter .match(SaHttpMethod.GET) .match("/admin/**") .match("/user/**") .notMatch("/**/*.js") .notMatch("/**/*.css") // .... .check( /* 只有上述所有条件都匹配成功,才会执行最后的check校验函数 */ ); 7.4 提前退出匹配链 使用 SaRouter.stop() 可以提前退出匹配链,例: registry.addInterceptor(new SaRouteInterceptor((req, res, 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")); })).addPathPatterns("/**"); 如上示例,代码运行至第2条匹配链时,会在stop函数处提前退出整个匹配函数,从而忽略掉剩余的所有match匹配。 除了stop()函数,SaRouter还提供了 back() 函数,用于:停止匹配,结束执行,直接向前端返回结果。 // 执行back函数后将停止匹配,也不会进入Controller,而是直接将 back参数 作为返回值输出到前端 SaRouter.match("/user/back").back("参数"); stop() 与 back() 函数的区别在于: SaRouter.stop() 会停止匹配,进入Controller。 SaRouter.back() 会停止匹配,直接返回结果到前端。 7.5 使用free打开一个独立的作用域 // 进入 free 独立作用域 SaRouter.match("/**").free(r -> { SaRouter.match("/a/**").check(/* --- */); SaRouter.match("/a/**").check(/* --- */).stop(); SaRouter.match("/a/**").check(/* --- */); }); // 执行 stop() 函数跳出 free 后继续执行下面的 match 匹配 SaRouter.match("/**").check(/* --- */); free() 的作用是:打开一个独立的作用域,使内部的 stop() 不再一次性跳出整个 Auth 函数,而是仅仅跳出当前 free 作用域。 8 Session会话Session是会话中专业的数据缓存组件,通过 Session 我们可以很方便的缓存一些高频读写数据,提高程序性能,例如: // 在登录时缓存user对象 StpUtil.getSession().set("user", user); // 然后我们就可以在任意处使用这个user对象 SysUser user = (SysUser) StpUtil.getSession().get("user") 在 Sa-Token 中,Session 分为三种,分别是: User-Session: 指的是框架为每个 账号id 分配的 Session Token-Session: 指的是框架为每个 token 分配的 Session Custom-Session: 指的是以一个 特定的值 作为SessionId,来分配的 Session 有关User-Session与Token-Session的详细区别,请参考:Session模型详解 8.1 User-Session 有关账号Session的API如下: // 获取当前账号id的Session (必须是登录后才能调用) StpUtil.getSession(); // 获取当前账号id的Session, 并决定在Session尚未创建时,是否新建并返回 StpUtil.getSession(true); // 获取账号id为10001的Session StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session, 并决定在Session尚未创建时,是否新建并返回 StpUtil.getSessionByLoginId(10001, true); // 获取SessionId为xxxx-xxxx的Session, 在Session尚未创建时, 返回null StpUtil.getSessionBySessionId("xxxx-xxxx"); 8.2 Token-Session 有关令牌Session的API如下: // 获取当前token的专属Session StpUtil.getTokenSession(); // 获取指定token的专属Session StpUtil.getTokenSessionByToken(token); 在未登录状态下是否可以获取Token-Session?这取决于你配置的tokenSessionCheckLogin值是否为false,详见:框架配置 8.3 自定义Session 自定义Session指的是以一个特定的值作为SessionId来分配的Session, 借助自定义Session,你可以为系统中的任意元素分配相应的session 例如以商品id作为key为每个商品分配一个Session,以便于缓存和商品相关的数据,其相关API如下: // 查询指定key的Session是否存在 SaSessionCustomUtil.isExists("goods-10001"); // 获取指定key的Session,如果没有,则新建并返回 SaSessionCustomUtil.getSessionById("goods-10001"); // 获取指定key的Session,如果没有,第二个参数决定是否新建并返回 SaSessionCustomUtil.getSessionById("goods-10001", false); // 删除指定key的Session SaSessionCustomUtil.deleteSessionById("goods-10001"); 8.4 Session相关操作 // 返回此Session的id session.getId(); // 返回此Session的创建时间 (时间戳) session.getCreateTime(); // 在Session上获取一个值 session.getAttribute('name'); // 在Session上获取一个值,并指定取不到值时返回的默认值 session.getAttribute('name', 'zhang'); // 在Session上写入一个值 session.setAttribute('name', 'zhang'); // 在Session上移除一个值 session.removeAttribute('name'); // 清空此Session的所有值 session.clearAttribute(); // 获取此Session是否含有指定key (返回true或false) session.containsAttribute('name'); // 获取此Session会话上所有key (返回Set) session.attributeKeys(); // 返回此Session会话上的底层数据对象(如果更新map里的值,请调用session.update()方法避免产生脏数据) session.getDataMap(); // 将这个Session从持久库更新一下 session.update(); // 注销此Session会话 (从持久库删除此Session) session.logout(); 8.5 类型转换API 由于Session存取值默认的类型都是Object,因此我们通常会写很多不必要类型转换代码 为了简化操作,Sa-Token自v1.15.0封装了存取值API的类型转换,你可以非常方便的调用以下方法: // 写值 session.set("name", "zhang"); // 写值 (只有在此key原本无值的时候才会写入) session.setDefaultValue("name", "zhang"); // 取值 session.get("name"); // 取值 (指定默认值) session.get("name", ""); // 取值 (转String类型) session.getString("name"); // 取值 (转int类型) session.getInt("age"); // 取值 (转long类型) session.getLong("age"); // 取值 (转double类型) session.getDouble("result"); // 取值 (转float类型) session.getFloat("result"); // 取值 (指定转换类型) session.getModel("key", Student.class); // 取值 (指定转换类型, 并指定值为Null时返回的默认值) session.getModel("key", Student.class, ); // 是否含有某个key session.has("key"); 8.6 Sesion环境隔离说明 有同学经常会把 SaSession 与 HttpSession 进行混淆,例如: @PostMapping("/resetPoints") public void reset(HttpSession session) { // 在HttpSession上写入一个值 session.setAttribute("name", 66); // 在SaSession进行取值 System.out.println(StpUtil.getSession().getAttribute("name")); // 输出null } 要点: SaSession 与 HttpSession 没有任何关系,在HttpSession上写入的值,在SaSession中无法取出。 HttpSession并未被框架接管,在使用Sa-Token时,请在任何情况下均使用SaSession,不要使用HttpSession。 9 框架配置框架配置 10 集成Redis集成Redis 11 前后端分离前后端分离 12 自定义Token风格12.1 内置风格 Sa-Token默认的token生成策略是uuid风格,其模样类似于:623368f0-ae5e-4475-a53f-93e4225f16ae 如果你对这种风格不太感冒,还可以将token生成设置为其他风格 怎么设置呢?只需要在yml配置文件里设置 sa-token.token-style=风格类型 即可,其有多种取值: // 1. token-style=uuid —— uuid风格 (默认风格) "623368f0-ae5e-4475-a53f-93e4225f16ae" // 2. token-style=simple-uuid —— 同上,uuid风格, 只不过去掉了中划线 "6fd4221395024b5f87edd34bc3258ee8" // 3. token-style=random-32 —— 随机32位字符串 "qEjyPsEA1Bkc9dr8YP6okFr5umCZNR6W" // 4. token-style=random-64 —— 随机64位字符串 "v4ueNLEpPwMtmOPMBtOOeIQsvP8z9gkMgIVibTUVjkrNrlfra5CGwQkViDjO8jcc" // 5. token-style=random-128 —— 随机128位字符串 "nojYPmcEtrFEaN0Otpssa8I8jpk8FO53UcMZkCP9qyoHaDbKS6dxoRPky9c6QlftQ0pdzxRGXsKZmUSrPeZBOD6kJFfmfgiRyUmYWcj4WU4SSP2ilakWN1HYnIuX0Olj" // 6. token-style=tik —— tik风格 "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__" 12.2 自定义token生成策略 如果觉着以上风格都不是你喜欢的类型,那么还可以自定义token生成策略,来定制化token生成风格 怎么做呢?只需要重写 SaStrategy 策略类的 createToken 算法即可 步骤: 1、在SaTokenConfigure配置类中添加代码: @Configuration public class SaTokenConfigure { /** * 重写 Sa-Token 框架内部算法策略 */ @Autowired public void rewriteSaStrategy() { // 重写 Token 生成策略 SaStrategy.me.createToken = (loginId, loginType) -> { return SaFoxUtil.getRandomString(60); // 随机60位长度字符串 }; } } 2、再次调用 StpUtil.login(10001)方法进行登录,观察其生成的token样式: gfuPSwZsnUhwgz08GTCH4wOgasWtc3odP4HLwXJ7NDGOximTvT4OlW19zeLH 13 自定义Token前缀在某些系统中,前端提交token时会在前面加个固定的前缀,例如: { "satoken": "Bearer xxxx-xxxx-xxxx-xxxx" } 此时后端如果不做任何特殊处理,框架将会把Bearer视为token的一部分,无法正常读取token信息,导致鉴权失败 为此,我们需要在yml中添加如下配置: sa-token: # token前缀 token-prefix: Bearer 此时 Sa-Token 便可在读取 Token 时裁剪掉 Bearer,成功获取xxxx-xxxx-xxxx-xxxx 注意: Token前缀 与 Token值 之间必须有一个空格。 一旦配置了 Token前缀,则前端提交token时,必须带有前缀,否则会导致框架无法读取token。 由于Cookie中无法存储空格字符,也就意味配置token前缀后,Cookie鉴权方式将会失效,此时只能将token提交到header里进行传输。 |
今日新闻 |
推荐新闻 |
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 |