• Sa-Token


    介绍

    类似于Security的认证授权的解决方案,但是使用起来非常方便,1.支持登录认证,授权权限验证,踢人;2.支持自定义Token,并且能够结合Redis完成前后端分离认证方案;3.支持单点登录(前端同域,后端同Redis+前端不同域,后端不同Redis+两者都不同)

    请添加图片描述

    2.快速上手

    1.demo所用依赖

    <dependencies>
    
            
            <dependency>
                <groupId>cn.dev33groupId>
                <artifactId>sa-token-spring-boot-starterartifactId>
                <version>1.33.0version>
            dependency>
    
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
    
            
            <dependency>
                <groupId>org.apache.commonsgroupId>
                <artifactId>commons-pool2artifactId>
            dependency>
    
            
            <dependency>
                <groupId>io.springfoxgroupId>
                <artifactId>springfox-swagger2artifactId>
                <version>2.9.2version>
                <exclusions>
                    <exclusion>
                        <groupId>io.swaggergroupId>
                        <artifactId>swagger-annotationsartifactId>
                    exclusion>
                    <exclusion>
                        <groupId>io.swaggergroupId>
                        <artifactId>swagger-modelsartifactId>
                    exclusion>
                exclusions>
            dependency>
            
            <dependency>
                <groupId>io.springfoxgroupId>
                <artifactId>springfox-swagger-uiartifactId>
                <version>2.9.2version>
            dependency>
            
            <dependency>
                <groupId>io.swaggergroupId>
                <artifactId>swagger-annotationsartifactId>
                <version>1.5.21version>
            dependency>
    
            <dependency>
                <groupId>io.swaggergroupId>
                <artifactId>swagger-modelsartifactId>
                <version>1.5.21version>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintagegroupId>
                        <artifactId>junit-vintage-engineartifactId>
                    exclusion>
                exclusions>
            dependency>
        dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-dependenciesartifactId>
                    <version>${spring-boot.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
            dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    2.登录相关测试

    1.登录接口

    这里是直接进行username和password比对,然后将id传入登录参数得到token返回——>我们一样可以结合数据库,通过用户名密码得到用户信息,存在就将用户唯一标识传入StpUtil.login()

    // 1.测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
        @ApiOperation(value = "登录测试")
        @RequestMapping("doLogin")
        public SaResult doLogin(String username, String password) {
            // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 
            if ("zhang".equals(username) && "123456".equals(password)) {
                //1.这里我们可以数据库判断业务后,StpUtil.login()用户的id
                StpUtil.login(10001,false);
                SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
                return SaResult.ok().data(tokenInfo).setMsg("登录成功");
            }
            return SaResult.error("登录失败");
        }
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    2.查询登录状态

    // 2.查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
        @RequestMapping("isLogin")
        @ApiOperation(value = "登录状态查询")
        public String isLogin() {
            return "当前会话是否登录:" + StpUtil.isLogin();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.退出登录
    单体架构一般就是删除session中相关信息,结合token的话就是将redis中的token删除,如果是token放在cookie中保存,就删除cookie即可

    这里注销的是当前用户

      @RequestMapping("logout")
        @ApiOperation(value = "退出登录")
        public SaResult logout() {
            StpUtil.logout();
            return SaResult.ok();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    源码

    1.我们来看下Sa-Token中注销用户的核心代码:
    它这里是引入了一个Storage存储器的概念,这个存储器每个用户都会有一个类似Session——>这里是由请求进行封装的

    	/** 
    	 * 会话注销 
    	 */
    	public void logout() {
    		// 如果连 Token 都没有,那么无需执行任何操作
    		String tokenValue = getTokenValue();
     		if(SaFoxUtil.isEmpty(tokenValue)) {
     			return;
     		}
     		
     		// 如果打开了 Cookie 模式,则把 Cookie 清除掉
     		if(getConfig().getIsReadCookie()){
     			SaCookieConfig cookie = getConfig().getCookie();
     			SaHolder.getResponse().deleteCookie(getTokenName(), cookie.getPath(), cookie.getDomain());
    		}
    
     		// 从当前 [Storage存储器] 里删除 Token
     		SaHolder.getStorage().delete(splicingKeyJustCreatedSave());
    
     		// 清除当前上下文的 [临时有效期check标记]
     	 	SaHolder.getStorage().delete(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY);
    
     		// 清除这个 Token 的相关信息
     		logoutByTokenValue(tokenValue);
    	}
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2.会话注销token

    	/**
    	 * 会话注销,根据指定 Token 
    	 * 
    	 * @param tokenValue 指定token
    	 */
    	public void logoutByTokenValue(String tokenValue) {
    		// 1. 清理 token-last-activity
    		clearLastActivity(tokenValue); 	
    		
    		// 2. 注销 Token-Session 
    		deleteTokenSession(tokenValue);
    
    		// 3. 清理 token -> id 索引
     		String loginId = getLoginIdNotHandle(tokenValue);
     		if(loginId != null) {
     			deleteTokenToIdMapping(tokenValue);
     		}
    
    		// if. 无效 loginId 立即返回
     	 	if(isValidLoginId(loginId) == false) {
     			return;
     		}
     	 	
     	 	// $$ 发布事件:某某Token注销下线了 
     		SaTokenEventCenter.doLogout(loginType, loginId, tokenValue);
     		
    		// 4. 清理User-Session上的token签名 & 尝试注销User-Session 
     	 	SaSession session = getSessionByLoginId(loginId, false);
     	 	if(session != null) {
     	 	 	session.removeTokenSign(tokenValue); 
     			session.logoutByTokenSignCountToZero();
     	 	}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    3.可以看出来我们的storage就是由request进行封装的,并且可以进行封装kv

        public SaStorage getStorage() {
            return new SaStorageForServlet(SpringMVCUtil.getRequest());
        }
    
    • 1
    • 2
    • 3
    3.查询用户权限

    首先我们赋予用户所有权限

    这里我们可以给每个用户调用getPermissionList(Object id,String Type)方法,给指定用户传入权限,重写StpInterface接口即可得到权限

    package com.wyhtoken.quicktoken.service;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.stereotype.Component;
    
    import cn.dev33.satoken.stp.StpInterface;
    
    /**
     * 自定义权限认证接口扩展,Sa-Token 将从此实现类获取每个账号拥有的权限码 
     * 
     * @author kong
     * @since 2022-10-13
     */
    @Component	// 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展 
    public class StpInterfaceImpl implements StpInterface {
    
    	/**
    	 * 1.添加权限集合
    	 * @param loginId:账户id
    	 * @param loginType:账户类型
    	 * @return
    	 */
    	@Override
    	public List<String> getPermissionList(Object loginId, String loginType) {
    		// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
    		List<String> list = new ArrayList<String>();	
    		list.add("101");
    		list.add("user.add");
    		list.add("user.update");
    		list.add("user.get");
    		// list.add("user.delete");
    		list.add("art.*");
    		return list;
    	}
    
    	/**
    	 * 2.返回指定id的用户权限
    	 * @param loginId
    	 * @param loginType
    	 * @return
    	 */
    	@Override
    	public List<String> getRoleList(Object loginId, String loginType) {
    		// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
    		List<String> list = new ArrayList<String>();	
    		list.add("admin");
    		list.add("super-admin");
    		return list;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    1.用户所有权限

     @RequestMapping("checkPermission")
        @ApiOperation(value = "用户所有权限")
        public SaResult getPermissionList() {
            List<String> permissionList = StpUtil.getPermissionList();
            return SaResult.ok().setData(permissionList).setMsg("所有权限返回成功");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.判断用户是否有某权限
    这里我们可以跟上述结合起来一起用,查询用户所有权限后,判断用户是否有某权限返回状态,进行兜底服务

    //1.返回boolean
    StpUtil.hasPermission("user.addd");
    //2.抛出异常
    StpUtil.checkPermission("user.add");
    
    • 1
    • 2
    • 3
    • 4

    3.检验用户是否有指定的所有权限

        /**
         * 4.该账号判断是否有指定的多个权限(必须全部通过)
         *
         * @return
         */
        @ApiOperation(value = "判断用户是否有所有权限")
        @RequestMapping("checkAll")
        public SaResult checkPermissionAnd() {
            StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");
            return SaResult.ok().setData("所有指定权限都有");
        }
    
        @ApiOperation(value = "判断用户是否有所有权限其中之一")
        @RequestMapping("checkOr")
        public SaResult checkPermissionOr(){
            StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");
            return SaResult.ok().setData("所有指定权限有其中之一");
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    检验权限的源码

    你会发现调用了getPermissionList(用户的标识)方法,而这个方法是StpInterface的实现类重写的方法,可以根据用户标识得到所有权限

     	/** 
     	 * 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] 
     	 * @param permissionArray 权限码数组
     	 */
     	public void checkPermissionOr(String... permissionArray){
     		Object loginId = getLoginId();
     		List<String> permissionList = getPermissionList(loginId);
     		for (String permission : permissionArray) {
     			if(hasElement(permissionList, permission)) {
     				// 有的话提前退出
     				return;		
     			}
     		}
    		if(permissionArray.length > 0) {
    	 		throw new NotPermissionException(permissionArray[0], this.loginType).setCode(SaErrorCode.CODE_11051);
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    串起来所有东西就能理解了

    	/**
    	 * 获取:指定账号的权限码集合 
    	 * @param loginId 指定账号id
    	 * @return / 
    	 */
    	public List<String> getPermissionList(Object loginId) {
    		return SaManager.getStpInterface().getPermissionList(loginId, loginType);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    5.踢人下线

    给指定用户踢出下线

    @ApiOperation(value = "踢人下线")
        @RequestMapping("Kick")
        public SaResult KickOut(Integer id){
            StpUtil.kickout(10001);
            return SaResult.ok().setMsg("踢人下线成功");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    6.忽略权限能够访问接口的注解(游客访问)
     // 此接口加上了 @SaIgnore 可以游客访问 
        @SaIgnore
        @RequestMapping("getList")
        public SaResult getList() {
            // ... 
            return SaResult.ok(); 
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.注解测试权限

    package com.wyhtoken.quicktoken.controller;
    
    import cn.dev33.satoken.annotation.*;
    import cn.dev33.satoken.util.SaResult;
    import io.swagger.annotations.Api;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @Api(tags = "Sa-Token接口测试")
    @RestController
    @RequestMapping("/api/")
    public class ApiController {
    
        // 登录校验:只有登录之后才能进入该方法
        @SaCheckLogin
        @RequestMapping("info")
        public String info() {
            return "查询用户信息";
        }
    
        // 角色校验:必须具有指定角色才能进入该方法
        @SaCheckRole("super-admin")
        @RequestMapping("add1")
        public String add1() {
            return "用户增加";
        }
    
        // 权限校验:必须具有指定权限才能进入该方法
        @SaCheckPermission("user-add")
        @RequestMapping("add2")
        public String add2() {
            return "用户增加";
        }
    
        // 二级认证校验:必须二级认证之后才能进入该方法
        @SaCheckSafe()
        @RequestMapping("add3")
        public String add3() {
            return "用户增加";
        }
    
        // Http Basic 校验:只有通过 Basic 认证后才能进入该方法
        @SaCheckBasic(account = "sa:123456")
        @RequestMapping("add4")
        public String add4() {
            return "用户增加";
        }
    
        // 校验当前账号是否被封禁 comment 服务,如果已被封禁会抛出异常,无法进入方法
        @SaCheckDisable("comment")
        @RequestMapping("send")
        public String send() {
            return "查询用户信息";
        }
    
        // 注解式鉴权:只要具有其中一个权限即可通过校验
        @RequestMapping("atJurOr")
        @SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
        public SaResult atJurOr() {
            return SaResult.data("用户信息");
        }
    
        // 角色权限双重 “or校验”:具备指定权限或者指定角色即可通过校验
        @RequestMapping("userAdd")
        @SaCheckPermission(value = "user.add", orRole = "admin")
        public SaResult userAdd() {
            return SaResult.data("用户信息");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    4.全局拦截器

    
    @Configuration
    public class SaTokenConfigure implements WebMvcConfigurer {
        // 注册拦截器
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
            registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                    .addPathPatterns("/**")
                    .excludePathPatterns("/user/doLogin","/swagger-ui.html");
        }
    
        /**
         * 注册全局过滤器
         */
        @Bean
        public SaServletFilter getSaServletFilter(){
            return new SaServletFilter()
                    .setAuth(obj->{
                        System.out.println("--------进入Sa-Token全局认证--------");
                        //1.登录认证 拦截所有路由,并且排除/user/doLogin用于开放登录
                        SaRouter.match("/**", "/user/doLogin", () -> StpUtil.checkLogin());
                    })
    
                    .setError(e->{
                        System.out.println("---------- 进入Sa-Token异常处理 -----------");
                        return SaResult.error(e.getMessage());
                    })
    
                    // 前置函数:在每次认证函数之前执行
                    .setBeforeAuth(r -> {
                        // ---------- 设置一些安全响应头 ----------
                        SaHolder.getResponse()
                                // 服务器名称
                                .setServer("sa-server")
                                // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                                .setHeader("X-Frame-Options", "SAMEORIGIN")
                                // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
                                .setHeader("X-XSS-Protection", "1; mode=block")
                                // 禁用浏览器内容嗅探
                                .setHeader("X-Content-Type-Options", "nosniff")
                        ;
                    });
    
        }
    
        /**
         * 重写 Sa-Token 框架内部算法策略
         */
        @Autowired
        public void rewriteSaStrategy() {
            // 重写 Token 生成策略
            SaStrategy.me.createToken = (loginId, loginType) -> {
                return SaFoxUtil.getRandomString(60);    // 随机60位长度字符串
            };
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    5.重写算法策略
    @Autowired
        public void rewriteSaStrategy() {
            // 重写 Token 生成策略
            SaStrategy.me.createToken = (loginId, loginType) -> {
                return SaFoxUtil.getRandomString(60);    // 随机60位长度字符串
            };
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.前后端分离

    常规 Web 端鉴权方法,一般由 Cookie模式 完成,而 Cookie 有两个特性:

    可由后端控制写入。
    每次请求自动提交。
    这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)
    而在app、小程序等前后台分离场景中,一般是没有 Cookie 这一功能的,此时大多数人都会一脸懵逼,咋进行鉴权啊?

    我们也可以加一个验证是否登录

    // 登录接口
    @RequestMapping("doLogin")
    public SaResult doLogin() {
        // 第1步,先登录上 
        StpUtil.login(10001);
        // 第2步,获取 Token  相关参数 
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        // 第3步,返回给前端 
        return SaResult.data(tokenInfo);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    6.记住我

    Sa-Token默认StpUil.login(id,type)实现了记住我功能

    实现原理:

    Cookie作为浏览器提供的默认会话跟踪机制,其生命周期有两种形式,分别是:

    临时Cookie:有效期为本次会话,只要关闭浏览器窗口,Cookie就会消失。
    持久Cookie:有效期为一个具体的时间,在时间未到期之前,即使用户关闭了浏览器Cookie也不会消失。
    利用Cookie的此特性,我们便可以轻松实现 [记住我] 模式:

    勾选 [记住我] 按钮时:调用StpUtil.login(10001, true),在浏览器写入一个持久Cookie储存 Token,此时用户即使重启浏览器 Token 依然有效。
    不勾选 [记住我] 按钮时:调用StpUtil.login(10001, false),在浏览器写入一个临时Cookie储存 Token,此时用户在重启浏览器后 Token 便会消失,导致会话失效。

    我们可以设置token的有效时间

    // 示例1:
    // 指定token有效期(单位: 秒),如下所示token七天有效
    StpUtil.login(10001, new SaLoginModel().setTimeout(60 * 60 * 24 * 7));
    
    // ----------------------- 示例2:所有参数
    // `SaLoginModel`为登录参数Model,其有诸多参数决定登录时的各种逻辑,例如:
    StpUtil.login(10001, new SaLoginModel()
                .setDevice("PC")                // 此次登录的客户端设备类型, 用于[同端互斥登录]时指定此次登录的设备类型
                .setIsLastingCookie(true)        // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
                .setTimeout(60 * 60 * 24 * 7)    // 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的 timeout 值)
                .setToken("xxxx-xxxx-xxxx-xxxx") // 预定此次登录的生成的Token 
                .setIsWriteHeader(false)         // 是否在登录后将 Token 写入到响应头
                );
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    7.同端互斥功能实现

    1.介绍: 请添加图片描述
    如果你经常使用腾讯QQ,就会发现它的登录有如下特点:它可以手机电脑同时在线,但是不能在两个手机上同时登录一个账号。
    同端互斥登录,指的就是:像腾讯QQ一样,在同一类型设备上只允许单地点登录,在不同类型设备上允许同时在线。

    首先在配置文件中,将 isConcurrent 配置为false,然后调用登录等相关接口时声明设备类型即可:
    StpUtil.login(用户id标识,"PC");

    2.查询当前登录的设备类型

    // 返回当前token的登录设备类型
    StpUtil.getLoginDevice();    
    
    • 1
    • 2

    3.获取指定登录设备类型的tokenValue

    // 获取指定loginId指定设备类型端的tokenValue 
    StpUtil.getTokenValueByLoginId(10001, "APP");    
    
    • 1
    • 2

    8.账号封禁

    // 封禁指定账号 
    StpUtil.disable(10001, 86400); 
    
    • 1
    • 2

    参数含义:

    参数1:要封禁的账号id。
    参数2:封禁时间,单位:秒,此为 86400秒 = 1天(此值为 -1 时,代表永久封禁)

    1.我们一般采用先踢掉,再封禁的方法

    // 先踢下线
    StpUtil.kickout(10001); 
    // 再封禁账号
    StpUtil.disable(10001, 86400); 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.然后我们再下次登录可以验证一下

    // 校验指定账号是否已被封禁,如果被封禁则抛出异常 `DisableServiceException`
    StpUtil.checkDisable(10001); 
    
    // 通过校验后,再进行登录:
    StpUtil.login(10001); 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    9.禁用用户的某个权限

    // 封禁指定用户评论能力,期限为 1天
    StpUtil.disable(10001, "comment", 86400);
    
    • 1
    • 2

    1、封禁评价能力:账号A 因为多次虚假好评,被限制订单评价功能。
    2、封禁下单能力:账号B 因为多次薅羊毛,被限制下单功能。
    3、封禁开店能力:账号C 因为店铺销售假货,被限制开店功能。

    参数释义:

    参数1:要封禁的账号id。
    参数2:针对这个账号,要封禁的服务标识(可以是任意的自定义字符串)。
    参数3:要封禁的时间,单位:秒,此为 86400秒 = 1天(此值为 -1 时,代表永久封禁)。

    /*
     * 以下示例中:"comment"=评论服务标识、"place-order"=下单服务标识、"open-shop"=开店服务标识
     */
    
    // 封禁指定用户评论能力,期限为 1天
    StpUtil.disable(10001, "comment", 86400);
    
    // 在评论接口,校验一下,会抛出异常:`DisableServiceException`,使用 e.getService() 可获取业务标识 `comment` 
    StpUtil.checkDisable(10001, "comment");
    
    // 在下单时,我们校验一下 下单能力,并不会抛出异常,因为我们没有限制其下单功能
    StpUtil.checkDisable(10001, "place-order");
    
    // 现在我们再将其下单能力封禁一下,期限为 7天 
    StpUtil.disable(10001, "place-order", 86400 * 7);
    
    // 然后在下单接口,我们添加上校验代码,此时用户便会因为下单能力被封禁而无法下单(代码抛出异常)
    StpUtil.checkDisable(10001, "place-order");
    
    // 但是此时,用户如果调用开店功能的话,还是可以通过,因为我们没有限制其开店能力 (除非我们再调用了封禁开店的代码)
    StpUtil.checkDisable(10001, "open-shop");
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    10.密码加密

    Sa-Token支持非对称和对称加密两种方式

    1.对称加密:AES加密

    // 定义秘钥和明文
    String key = "123456";
    String text = "Sa-Token 一个轻量级java权限认证框架";
    
    // 加密 
    String ciphertext = SaSecureUtil.aesEncrypt(key, text);
    System.out.println("AES加密后:" + ciphertext);
    
    // 解密 
    String text2 = SaSecureUtil.aesDecrypt(key, ciphertext);
    System.out.println("AES解密后:" + text2);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.非对称加密:RSA加密

    // 定义私钥和公钥 
    String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
    String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";
    
    // 文本
    String text = "Sa-Token 一个轻量级java权限认证框架";
    
    // 使用公钥加密
    String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
    System.out.println("公钥加密后:" + ciphertext);
    
    // 使用私钥解密
    String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
    System.out.println("私钥解密后:" + text2); 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    11.全局侦听器

    实现SaTokenListener接口即可
    思路:我们可以每次下线将日志写入文件中,就可以用这个全局监听器

    /**
     * 自定义侦听器的实现 
     */
    @Component
    public class MySaTokenListener implements SaTokenListener {
    
        /** 每次登录时触发 */
        @Override
        public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
            System.out.println("---------- 自定义侦听器实现 doLogin");
        }
    
        /** 每次注销时触发 */
        @Override
        public void doLogout(String loginType, Object loginId, String tokenValue) {
            System.out.println("---------- 自定义侦听器实现 doLogout");
        }
    
        /** 每次被踢下线时触发 */
        @Override
        public void doKickout(String loginType, Object loginId, String tokenValue) {
            System.out.println("---------- 自定义侦听器实现 doKickout");
        }
    
        /** 每次被顶下线时触发 */
        @Override
        public void doReplaced(String loginType, Object loginId, String tokenValue) {
            System.out.println("---------- 自定义侦听器实现 doReplaced");
        }
    
        /** 每次被封禁时触发 */
        @Override
        public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
            System.out.println("---------- 自定义侦听器实现 doDisable");
        }
    
        /** 每次被解封时触发 */
        @Override
        public void doUntieDisable(String loginType, Object loginId, String service) {
            System.out.println("---------- 自定义侦听器实现 doUntieDisable");
        }
    
        /** 每次二级认证时触发 */
        @Override
        public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
            System.out.println("---------- 自定义侦听器实现 doOpenSafe");
        }
    
        /** 每次退出二级认证时触发 */
        @Override
        public void doCloseSafe(String loginType, String tokenValue, String service) {
            System.out.println("---------- 自定义侦听器实现 doCloseSafe");
        }
    
        /** 每次创建Session时触发 */
        @Override
        public void doCreateSession(String id) {
            System.out.println("---------- 自定义侦听器实现 doCreateSession");
        }
    
        /** 每次注销Session时触发 */
        @Override
        public void doLogoutSession(String id) {
            System.out.println("---------- 自定义侦听器实现 doLogoutSession");
        }
    
        /** 每次Token续期时触发 */
        @Override
        public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
            System.out.println("---------- 自定义侦听器实现 doRenewTimeout");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
  • 相关阅读:
    发现很多人分不清 jwt session token 的区别?
    在线问诊 Python、FastAPI、Neo4j — 问题咨询
    java计算机毕业设计基于ssm的火车订票管理系统(源代码+数据库+Lw文档)
    【重拾C语言】六、批量数据组织(一)数组(数组类型、声明与操作、多维数组;典例:杨辉三角、矩阵乘积、消去法)
    3.2 网络协议
    【飞控开发基础教程9】疯壳·开源编队无人机-PWM(电机控制)
    超预期!新能源细分市场搭载率逼近20%!5G上车,按下加速键
    华为云云耀云服务器L实例评测 | Linux系统宝塔运维部署H5游戏
    Taro:微信小程序通过获取手机号实现一键登录
    队列的基本操作(C语言实现)
  • 原文地址:https://blog.csdn.net/weixin_57128596/article/details/128041582