Vue前后端分离项目 【实战篇】

一、shiro中session的共享问题🍉

在这里插入图片描述

1.演示问题🥝

(1)启动shiro-springboot的集群项目🍓

在这里插入图片描述

(2)修改nginx的配置🍓

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

(3)测试🍓

使用swagger测试需要在过滤器中放行

//测试路径
http://localhost:8080/doc.html

在这里插入图片描述
在这里插入图片描述

登录成功后访问某些资源时,出现了未登录的json提示

在这里插入图片描述

2.如何解决session共享问题🥝

默认session存储再各自服务的内存中,可以让session统一存储再redis中。

疯狂的蛋糕的依赖。—提供了redis存储session的类。

修改shiro的配置类。

在这里插入图片描述

package com.lzq.config;


import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.lzq.filter.LoginFilter;
import com.lzq.filter.MyWebSessionManagerextends;
import com.lzq.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;


import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;

import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;


import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;


@Configuration
public class ShiroConfig {
    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());
        //设置session管理器
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }
    @Bean
    public SessionManager sessionManager(){
        MyWebSessionManagerextends sessionManager = new MyWebSessionManagerextends();
        //SessionDao用于操作session对象,在容器中对session对象进行CRUD操作
        sessionManager.setSessionDAO(sessionDAO());
        return sessionManager;
    }
    @Bean
    public SessionDAO sessionDAO(){
        //该类会对session对象进行crud操作
        RedisSessionDAO sessionDAO = new RedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());
        return sessionDAO;
    }

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */


    @Bean
    public MyRealm myRealm(){
        MyRealm myRealm=new MyRealm();
        //设置密码加密器
        myRealm.setCredentialsMatcher(credentialsMatcher());
        return myRealm;
    }
    @Value("${shiro.hashAlgorithmName}")
    private String hashAlgorithmName;
    @Value("${shiro.hashIterations}")
    private int hashIterations;
    @Bean
    public HashedCredentialsMatcher credentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName(hashAlgorithmName);
        credentialsMatcher.setHashIterations(hashIterations);
        return credentialsMatcher;
    }


    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean factoryBean(){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        shiroFilterFactoryBean.setLoginUrl("/login.html");
        //设置过滤规则
        Map<String,String> map=new HashMap<>();
        map.put("/login","anon");
        map.put("/doc.html","anon");
        map.put("/webjars/**","anon");
        map.put("/swagger-resources/**","anon");
        map.put("/v2/**","anon");
        map.put("/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        Map<String,Filter> filterMap=new HashMap<>();
        filterMap.put("authc",new LoginFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        return shiroFilterFactoryBean;
    }

    //springboot如何注册web三大组件。
    @Bean
    public FilterRegistrationBean<Filter> filterRegistrationBean(){
        FilterRegistrationBean<Filter> filterRegistrationBean=new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new DelegatingFilterProxy());
        filterRegistrationBean.setName("shiroFilter");
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }
    /**
     * 这里是为了能在html页面引用shiro标签,
     */
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    @Bean
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    @Bean
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("192.168.179.129");
        redisManager.setDatabase(1);
        return redisManager;
    }
}

在这里插入图片描述

二、解决前端不支持cookie的效果🍉

在这里插入图片描述

原因: 默认DefaultWebSessionManager它只接受Cookie中存储的JsessionId. 查询发现再redis中不存在对应的key.

客户发送请求时,再请求头中携带sessionId, 然后重写DefaultWebSessionManager中getSessionId()的方法。

思考:1. 如何把sessionId放入请求头。

​ 2. 重写getSessionId方法如何获取请求头的sessionID。

1.如何把sessionId放入请求头🥝

修改登录的接口

在这里插入图片描述

 @PostMapping("/login")
    @ResponseBody
    public Result login(@RequestBody LoginVo loginVo){
        Subject subject = SecurityUtils.getSubject();
        //判斷当前用户是否登陆过
        if (subject.isAuthenticated()){
            return new Result(200,"登陸成功",subject.getSession().getId());
        }
        UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getUsername(),loginVo.getPassword());
        try {
            subject.login(token);
            System.out.println("登录成功");
            return new Result(200,"登录成功",subject.getSession().getId());
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("登录失败");
            return new Result(500,"登陆失败",null);
        }

修改前端登录方法

在这里插入图片描述

 lzq(){
                this.$http.post("http://localhost:8080/login",this.formLabelAlign).then(requs=>{
                    console.log(requs)
                    if (requs.data.code==200){
                        this.$message.success("登陆成功")
                        sessionStorage.setItem("token",requs.data.data)
                        this.$router.push("/aaa")
                    }else {
                        this.$message.error("账户密码错误")
                    }
                })
            },

修改main.js文件

设置axios请求拦截器 进行拦截请求 将后端传过来的session id 赋值给 sessionStorage中 赋给请求头中

在这里插入图片描述

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'
import axios from "axios"
import it from "element-ui/src/locale/lang/it";

router.beforeEach(((to, from, next) =>{
  //to:到哪去 from:从哪来  next:下一站
  var path = to.path;
  if (path=="/login"){
    return next();
  }
  //设置变量token  用来存储session id
  var  token = sessionStorage.getItem("token");
  if (token){
    return  next();
  }
  return next("/login");
} ))
//设置axios的请求拦截器
axios.interceptors.request.use(config=>{
  //从sessionStorage中获取token  token存储的session id
  var item=sessionStorage.getItem("token");
  if (item){
    config.headers.token=item;
  }
  return config;
})

Vue.prototype.$http=axios

Vue.config.productionTip = false


new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

2.重写DefaultWebSessionManager的方法🥝

在这里插入图片描述

package com.lzq.filter;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

public class MyWebSessionManagerextends extends DefaultWebSessionManager {
        private static final String AUTHORIZATION = "token";
        private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
        protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
            //获取请求头中名称为token的内容
            String id = WebUtils.toHttp(request).getHeader("token");
            if (!StringUtils.isEmpty(id)) { //如果存在该token
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "Stateless request");
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
                return id;
            } else {
                //从cookie中获取sessionId.
                return super.getSessionId(request, response);
            }
        }
    }


重写DefaultWebSessionManager的方法是为了获取前端传过来的session id
重写后需要将配置文件交给ioc容器进行管理
在这里插入图片描述

 @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean factoryBean(){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        shiroFilterFactoryBean.setLoginUrl("/login.html");
        //设置过滤规则
        Map<String,String> map=new HashMap<>();
        map.put("/login","anon");
        map.put("/doc.html","anon");
        map.put("/webjars/**","anon");
        map.put("/swagger-resources/**","anon");
        map.put("/v2/**","anon");
        map.put("/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        Map<String,Filter> filterMap=new HashMap<>();
        filterMap.put("authc",new LoginFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        return shiroFilterFactoryBean;
    }

修改shiro配置类

在这里插入图片描述

  @Bean
    public SessionManager sessionManager(){
        MyWebSessionManagerextends sessionManager = new MyWebSessionManagerextends();
        //SessionDao用于操作session对象,在容器中对session对象进行CRUD操作
        sessionManager.setSessionDAO(sessionDAO());
        return sessionManager;
    }

修改shiroFilter过滤器

我们发现跨域请求,会发送两个请求:第一个OPTIONS请求,第二个请求是真实的请求。

OPTIONS请求:先头部队。

所以我们对OPTIONS请求都要放行。

在这里插入图片描述
在这里插入图片描述

package com.lzq.filter;


import com.alibaba.fastjson.JSON;
import com.lzq.vo.Result;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;


public class LoginFilter extends FormAuthenticationFilter {
    //当未登录账号访问接口时,会触发该方法。
    //默认内容是重定向到登录页面---重写改为返回json数据
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //解决响应数据的中文乱码问题
        response.setContentType("application/json;charset=utf-8");
        //通过io流的方式将响应返还给前端
        PrintWriter writer = response.getWriter();
        Result result = new Result(401, "请先登录", null);
        //把java对象转换为json字符串  ----JSON java封装的工具类
        String jsonstring = JSON.toJSONString(result);
        writer.print(jsonstring);
        //刷新io流
        writer.flush();
        //关闭io流资源
        writer.close();
        return false;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //将请求转换格式  获取其中内容
        HttpServletRequest request1 = (HttpServletRequest) request;
        //获取请求方式
        String method = request1.getMethod();
        //判断将options访问类型进行放行
        if ("OPTIONS".equals(method)){
            return true;
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }
}

三、设置前端前置路由守卫🍉

在这里插入图片描述

router.beforeEach(((to, from, next) =>{
  //to:到哪去 from:从哪来  next:下一站
  var path = to.path;
  if (path=="/login"){
    return next();
  }
  //设置变量token  用来存储session id
  var  token = sessionStorage.getItem("token");
  if (token){
    return  next();
  }
  return next("/login");
} ))

四、如何防止恶意重复登录🍉

在这里插入图片描述

package com.lzq.controller;

import com.lzq.vo.LoginVo;
import com.lzq.vo.Result;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
//@CrossOrigin//origins:允许那些跨域访问该接口  allowedHeaders: 允许携带那些头信息的请求访问  methods:  允许那些请求跨域请求该接口
public class LoginController {
    @PostMapping("/login")
    @ResponseBody
    public Result login(@RequestBody LoginVo loginVo){
        Subject subject = SecurityUtils.getSubject();
        //判斷当前用户是否登陆过
        if (subject.isAuthenticated()){
            return new Result(200,"登陸成功",subject.getSession().getId());
        }
        UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getUsername(),loginVo.getPassword());
        try {
            subject.login(token);
            System.out.println("登录成功");
            return new Result(200,"登录成功",subject.getSession().getId());
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("登录失败");
            return new Result(500,"登陆失败",null);
        }

    }
    @PostMapping("/logout")
    @ResponseBody
    public Result logout(){
        Subject subject = SecurityUtils.getSubject();
        //清空redis
        subject.logout();
        return new Result(200,"退出成功",null);
    }
    @GetMapping("/unlogin")
    @ResponseBody
    public Result unlogin(){
        return new Result(401,"请先登录",null);
    }

}

五、退出功能🍉

编辑退出接口🥝

在这里插入图片描述

  @PostMapping("/logout")
    @ResponseBody
    public Result logout(){
        Subject subject = SecurityUtils.getSubject();
        //清空redis
        subject.logout();
        return new Result(200,"退出成功",null);
    }

编辑前端退出按钮🥝

在这里插入图片描述

<template>
    <div>
        <el-button type="primary" @click="logout">退出</el-button>
        <el-button @click="info" type="primary">获取用户信息</el-button>
        <el-button @click="query" type="primary">查询</el-button>
        <el-button @click="daochu" type="primary">导出</el-button>
        <span v-text="userinfo.username"></span>
    </div>
</template>

<script>
    export default {
        name: "Aaa",
        data(){
            return{
                userinfo:{}
            }
        },
        created() {
        },
        methods: {
            logout() {
                this.$http.post("http://localhost:8080/logout").then(requs=>{
                    if (requs.data.code==200){
                        this.$message.success("退出成功")
                        sessionStorage.clear()
                        this.$router.push("/login")
                    }
                })
            },
            info(){
                this.$http.get("http://localhost:8080/user/info").then(requs=>{
                    this.userinfo=requs.data.data;
                })

            },
            query(){
                this.$http.get("http://localhost:8080/user/query").then(requs=>{
                    if (requs.data.code==200){
                        this.$message.success("查询成功")

                    }
                })
            },
            daochu(){

            }

        }
    }

</script>

<style scoped>

</style>

六、获取当前登录用户的信息🍉

在这里插入图片描述

package com.lzq.controller;

import com.lzq.vo.Result;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/info")
    public Result inf(){

        Object principal = SecurityUtils.getSubject().getPrincipal();
        return new Result(200,"查询成功",principal);
    }

    @GetMapping("/query")
    @ResponseBody
//    @RequiresPermissions(value = {"user:query","user:delete"},logical = Logical.OR)
    @RequiresPermissions(value = "user:query")
    public Result query(){

        return new Result(200,"查詢成功",null);
    }

    @GetMapping("/update")
    @RequiresPermissions(value = "user:update")
    public String update(){
        return "user:update------------------------";
    }

    @GetMapping("/delete")
    @RequiresPermissions(value = "user:delete")
    public String delete(){
        return "user:delete------------------------";
    }

    @GetMapping("/insert")
    @RequiresPermissions(value = "user:insert")
    public String insert(){
        return "user:insert------------------------";
    }

    @GetMapping("/export")
    @RequiresPermissions(value = "user:export") //该注解不能别识别
    public String export(){
        return "user:export------------------------";
    }
}

在这里插入图片描述

<template>
    <div>
        <el-button type="primary" @click="logout">退出</el-button>
        <el-button @click="info" type="primary">获取用户信息</el-button>
        <el-button @click="query" type="primary">查询</el-button>
        <el-button @click="daochu" type="primary">导出</el-button>
        <span v-text="userinfo.username"></span>
    </div>
</template>

<script>
    export default {
        name: "Aaa",
        data(){
            return{
                userinfo:{}
            }
        },
        created() {
        },
        methods: {
            logout() {
                this.$http.post("http://localhost:8080/logout").then(requs=>{
                    if (requs.data.code==200){
                        this.$message.success("退出成功")
                        sessionStorage.clear()
                        this.$router.push("/login")
                    }
                })
            },
            info(){
                this.$http.get("http://localhost:8080/user/info").then(requs=>{
                    this.userinfo=requs.data.data;
                })

            },
            query(){
                this.$http.get("http://localhost:8080/user/query").then(requs=>{
                    if (requs.data.code==200){
                        this.$message.success("查询成功")

                    }
                })
            },
            daochu(){

            }

        }
    }

</script>

<style scoped>

</style>

七、设定登录设备的个数🍉

在这里插入图片描述

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值