springboot+redis+mybatisplus+vue实现前后端分离的登陆互斥效果

实现的效果就是A在A点登陆在浏览器登陆了A账户,之后B又在B点登陆了A账户,此时,B可以正常登陆账户,A点的A账户也不会立即掉线,一旦A点的A账户执行操作也就是向后端发送请求时,A点A账户会在页面收到弹窗提示(账号已在别处登陆)点击登陆后,则会跳到登陆页面

1.整体思路    

       采用拦截器校验缓存的策略。当用户登陆的时候,首先校验用户名和密码是否正确,如果错误则直接向前端返回用户名或者密码错误.如果正确则生成一个UUID的token,以用户名为键,token为值,存入到redis!同时封装一个tokenUtil类,只存储String类型的用户名,还有String类型的token。将当前用户名和token封装到这个类,返回给前端将收到的tokenUtil封装到请求的请求头中,保证除了登陆请求除外的所有请求都会携带一个tokenUtil,里面包含了用户的用户名还有一个token值。然后后端编写拦截器,获取请求头中的token,通过工具类转化成TokenUtil类的对象,然后通过对象的中的用户名字段去获取redis中的值,这时候拿redis里面拿到的token值,和tokenUtil里面的token做比较,如果不一致,则意味着有人又登陆了这个账户,以这个账户为键,重新生成了一个token存入到了redis,所以匹配不上,这时候进行拦截,并且返回前端相对应的状态码,前端做出相对应提示。

2.代码

       有了思路,写代码就容易了。下面我把的代码展示一下,一些踩过的坑也会标注出来。

     2.1整个项目框架   ,新建项目过程就省略了

  2.2依赖导入 

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>SSOLogin</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SSOLogin</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

hutool是一个非常好用的工具包,大家可以尝试使用一下

2.3核心代码

@RestController
@CrossOrigin
public class UserController {
    @Autowired
    private UserService userService;


    @PostMapping("/login")
    public Result checkLogin(String account,String password){
        return userService.checkLogin(account,password);
    }

    @GetMapping("/test")
    public Result test(){
        return Result.ok();
    }
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired(required = false)
    private StringRedisTemplate redisTemplate;

    /**
     * 处理登陆,实现只能一处登陆的效果
     *
     * @param account
     * @param password
     * @return
     */
    //Redis 是一种菲关系型数据库  Map类型的数据结构  读写速度 每秒百万级别
    @Override
    public Result checkLogin(String account, String password) {
        //1.TODO 判断用户名密码是否正确
        LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getAccount,account)
                .eq(User::getPassword,password);
        User user = userMapper.selectOne(queryWrapper);
        if(user==null){
            //2.TODO  如果错误,直接后端返回fasle
            return Result.fail("用户名或者密码错误");
        }
        //3.TODO  如果正确 以账户的MD5加密格式为键 存储一个TOken进入redis
        //TODO 并且把账户的加密格式以及随机的Token封装成对象返回给前端;
        //TODO 前端每次请求携带在请求头中
        String key = MD5.create().digestHex(password);
        String token = UUID.randomUUID().toString();
        TokenUtil tokenUtil =new TokenUtil();
        tokenUtil.setPwd(key);
        tokenUtil.setToken(token);
        redisTemplate.opsForValue().set(key,token);
        //4.TODO 返回正确
        return Result.ok(tokenUtil);

        //拦截器里携带tokenUtil,用pwd匹配redis获取token,匹配不上,已经登陆过了
    }
}

我这里封装的是密码,大家改成用户名就好了,这里的mapper就是继承了baseMapper没有新增任何接口,就是单纯的查询用户信息的。这里就不展示了

拦截器

/**
 * @Description:
 * @author: RainMoon
 * @date: 2022年11月24日 22:58
 */
public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate redisTemplate;

    public LoginInterceptor(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if(HttpMethod.OPTIONS.toString().equals(request.getMethod())){
            System.out.println("OPTIONS请求,放行");
            return true;
        }
        String token = request.getHeader("token");
        String requestURI = request.getRequestURI();
        System.out.println("requestURI = " + requestURI);
        System.out.println("token = " + token);
        if(StringUtils.isBlank(token)){
            response.setStatus(401);
            System.out.println("拦截 401");
            return false;
        }
        TokenUtil tokenUtil = JSONUtil.toBean(token, TokenUtil.class);
        String tokenValue = redisTemplate.opsForValue().get(tokenUtil.getPwd());
        System.out.println("tokenValue = " + tokenValue);
        if(tokenValue!=null&&tokenValue.equals(tokenUtil.getToken())){
            System.out.println("token正确,放行");
            return true;
        }
        System.out.println("token错误,拦截 520");
        response.setStatus(520);
        return false;
    }
}

这里要注意一下,因为拦截器的加载优先级比较高,所以不能直接注入RedisTemplate,所以给他配成属性,生成一个有参构造方法,然后在配置类里面注入RedisTemplate,添加拦截器的时候从构造器赋值

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Autowired(required = false)
    private StringRedisTemplate redisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor(redisTemplate))
                .excludePathPatterns("/login");
    }
}

然后是对应实体类,tokenUtil类,返回工具类,TokenUtil类中的pwd换成用户的账号就好了

@Data
@TableName("t_user")
public class User {
    //用户ID
    private Integer id;
    //用户名字
    private String username;
    //密码
    private String password;
    //账户
    private String account;

}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    private Boolean success;
    private String errorMsg;
    private Object data;

    public static Result ok(){
        return new Result(true, null, null);
    }
    public static Result ok(Object  data){
        return new Result(true, null, data);
    }
    public static Result fail(String errorMsg){
        return new Result(false, errorMsg, null);
    }
}
@Data
public class TokenUtil {

    private String pwd;

    private String token;
}

然后是前端代码,就是两个请求,大家也可以把axios的请求拦截器和响应拦截器封装到main函数里面或者单独封装axios工具然后全局引用

登陆组件

<template>
<div>
  <h1>SSO单点登陆测试</h1>
    账户:<input type="text" v-model="account"> <br>
    密码:<input type="text" v-model="password"> <br>
    <el-button @click="login"  >登陆</el-button>
</div>
</template>

<script>
export default {
  name: "LoginTest",
  data(){
    return{
      url:' http://localhost:8086',
      account:'',
      password:''
    }
  },
  methods:{
    login(){
      this.$axios({
        headers:{},
        url:this.url+"/login",
        method:'post',
        params:{account:this.account,password:this.password},
        data:{}
      }).then(re=>{
        console.log(re.data.data)
        sessionStorage.setItem("token",JSON.stringify(re.data.data))
        this.$router.push("/TestRequest")
      })
    }
  },
  destroyed() {

  }
}
</script>

<style scoped>

</style>

测试组件

<template>
<h1>
  <el-button @click="test">拿到数据</el-button>
</h1>
</template>

<script>
export default {
  name: "TestRequest",
  data(){
    return{
      url:' http://localhost:8086',
    }
  },
  methods:{
    test(){
      var token = sessionStorage.getItem("token");
      //token=JSON.parse(token)
      this.$axios({
        headers:{token:token},
        url:this.url+"/test",
        method:'get',
      }).then(re=>{
        this.$message.success(re.data)
      }).catch(re=>{
        if(re.request.status===401){
          alert("请先登陆")
        }else if(re.request.status===520){
          alert("您的账号已在别处登陆,请重新登陆")
          this.$router.push("/LoginTest")
        }
      })
    }
  }
}
</script>

<style scoped>

</style>

大家如果有别的解决方案,欢迎评论区留言,想要源码请私聊

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
权限管理系统是一种用于管理用户权限和角色的系统,可以根据用户的身份和角色来控制其访问系统中的各种资源。基于SpringBootVueRedis前后端分离模式,可以更好地实现权限管理系统的功能。 在这个系统中,SpringBoot作为后端框架,提供了强大的功能和稳定的性能,可以处理用户的请求并进行权限验证。Vue作为前端框架,提供了友好的界面和良好的用户体验,可以让用户方便地进行权限管理操作。而Redis作为缓存数据库,可以用来存储权限信息和用户的登录状态,加快系统的响应速度和提高系统的性能。 在权限管理系统中,我们可以使用RBAC(基于角色的权限控制)模型,将用户分配到不同的角色,再将角色分配到不同的权限,从而实现对用户访问资源的控制。通过这种方式,可以实现灵活的权限管理,并且可以根据实际需求动态地调整用户的权限和角色。 通过使用SpringBootVue,我们可以实现前后端分离,让前端和后端分别进行开发和维护,降低了系统的耦合度,同时也增加了系统的灵活性和可维护性。而通过使用Redis,我们可以充分利用其高速的读取和写入能力,有效地提升系统的性能和响应速度。 综上所述,基于SpringBootVueRedis的权限管理系统,可以实现灵活、高效和安全的权限管理功能,满足用户对于权限管理的各种需求。同时,前后端分离模式也使得系统更加灵活和易于维护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值