天天看点

SpringBoot整合Vue(二)完成登录(shiro)、退出、左侧菜单(基于springboot+shiro+swagger+redis+vue+elementui)

1.添加前置路由守卫

前置路由守卫:就是在路由跳转前加上自己得一些业务代码。(放在main.js中)

//前置路由守卫 to:到某个路由 from 从哪个路由出来 next() 放行到指定的路由
router.beforeEach((to,from,next)=>{
  //获取跳转的路径
  var path = to.path;
  //判断是否为登录路由路径
  if(path==="/login"){
    //放行
    return next();
  }
  //其他路由路径 判断是否登录过
  var token = sessionStorage.getItem("token");
  if(token){
    return next();
  }
  //跳转登录
  return next("/login");
})
           

2.整合shiro实现登录

1.添加shiro依赖

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.7.0</version>
        </dependency>
           

2.增加一个realm类对象

public class MyRealm extends AuthorizingRealm {


    @Autowired
    private IUserService userServicet;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
       /* User primaryPrincipal = (User) principals.getPrimaryPrincipal();
        //根据账号查询该用户具有哪些权限
        List<String> list = userServicet.findPermission(primaryPrincipal.getUserid());
        if (list!=null&&list.size()>0){
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addStringPermissions(list);
            return info;
        }*/
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.根据token获取账号
        String username = (String) token.getPrincipal();
        //2.根据账号查询用户信息
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("username",username);
        wrapper.eq("is_deleted",0);
        User user = userServicet.getOne(wrapper);
        if (user!=null){
            //从数据库中获取的密码
            ByteSource byteSource = ByteSource.Util.bytes(user.getSalt());  //获取盐
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),byteSource,this.getName());
            return info;
        }
        return null;
    }
}
           

3.shiro的配置类

package com.zsy.system.config;

import com.zsy.system.filter.LoginFilter;
import com.zsy.system.realm.MyRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.filter.DelegatingFilterProxy;

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

/**
 * @Author zsy
 * @Date 2022/8/5 18:36
 * @PackageName:com.zsy.config
 * @ClassName: ShiroConfig
 * @Version 1.0
 */
@Configuration
public class ShiroConfig {

    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        return securityManager;
    }

    @Bean
    public Realm realm(){
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(credentialsMatcher());
        return myRealm;
    }

    @Bean
    public CredentialsMatcher credentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashIterations(1024);
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        return hashedCredentialsMatcher;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean filterFactoryBean(){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());

        //设置shiro过滤规则
        Map<String, String> map = new HashMap<>();
        map.put("/system/login","anon");
        map.put("/doc.html","anon");
        map.put("/swagger-ui.html", "anon");
        map.put("/webjars/**", "anon");
        map.put("/swagger-resources/**", "anon");
        map.put("/swagger/**", "anon");
        map.put("/swagger2/**", "anon");
        map.put("/v2/**", "anon");
        map.put("/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);


        //设置未登录过滤器
        Map<String, Filter> filters = new HashMap<>();
        filters.put("authc",new LoginFilter(redisTemplate));
        shiroFilterFactoryBean.setFilters(filters);

        return shiroFilterFactoryBean;
    }

    @Bean
    public FilterRegistrationBean<Filter> filterProxy(){
        FilterRegistrationBean<Filter> filterRegistrationBean=new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new DelegatingFilterProxy());
        filterRegistrationBean.setName("shiroFilter");
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }

}
           

4.修改controller代码

SpringBoot整合Vue(二)完成登录(shiro)、退出、左侧菜单(基于springboot+shiro+swagger+redis+vue+elementui)

 登录成功后获取用户信息时出现如下得错误

SpringBoot整合Vue(二)完成登录(shiro)、退出、左侧菜单(基于springboot+shiro+swagger+redis+vue+elementui)

被shiro得拦截器给拦截器了。

//如果类没有交于spring容器来管理 那么该类中得属性也不能让spring帮你注入
public class LoginFilter extends FormAuthenticationFilter {

    private RedisTemplate redisTemplate;  //LoginFilter必须交于spring容器来管理。

    public LoginFilter(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    //当登录成功后执行得方法,如果该方法返回false,则执行onAccessDenied
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        System.out.println(redisTemplate);
        HttpServletRequest req = (HttpServletRequest) request;
        //1.请求方式是否为OPTIONS
        String method = req.getMethod();
        if(method!=null && method.equals("OPTIONS")){
            return true;
        }

        //2.判断请求头是否有token值
        String token = req.getHeader("token");
        if(token!=null && redisTemplate.hasKey(token)){
            return true;
        }
        return false;
    }

    //未登录时调用该方法? 为什么进入没有登录方法:
    // --->第一个请求是OPTIONS,没有携带token  第二个因为前端和后端不是用得同一个session.默认shiro以sessionId为是否登录得标准
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("*******************************");
        return false;
    }
}
           

 3.主页布局

<template>
        <el-container>
            <el-header>
                <span id="logo" style="display: inline-block;width: 50%;height: 100%;float: left" >
                     <a href="https://www.bilibili.com/video/BV14g41197PY/" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" ><img src="../assets/logo.png" height="100%" width="180px"></a>
                </span>
                <span id="avatar" style="float: right">
                    <el-dropdown @command="handleCommand">
                        <span class="el-dropdown-link" style="margin-top: 10px; display: inline-block;">
                            <el-avatar ></el-avatar>
                        </span>
                        <el-dropdown-menu slot="dropdown">
                            <el-dropdown-item command="info">个人信息</el-dropdown-item>
                            <el-dropdown-item command="logout">退出登录</el-dropdown-item>
                        </el-dropdown-menu>
                    </el-dropdown>
                </span>
            </el-header>
            <el-container>
                <el-aside width="200px">

                </el-aside>
                <el-main>

                </el-main>
            </el-container>
            <el-footer>Footer</el-footer>
        </el-container>
</template>

<script>
    export default {
        name: "Home",
        methods:{
              getInfo(){
                   this.$http.get("http://localhost:8808/system/user/getInfo").then(result=>{
                         console.log(result)
                   })
              }
        }
    }
</script>
<!--当前vue有效-->
<style>
    html,body,#app{
         height: 100%;
    }
    body,#app{
        padding: 0px;
        margin:0px;
    }
    .el-container{
         height: 100%;
    }
    .el-header, .el-footer {
        background-color: #1F272F;
        color: #333;
        line-height: 60px;
    }

    .el-aside {
        background-color: #545c64;
        color: #333;
        line-height: 560px;
    }
    .el-aside>.el-menu{
        border: none;
    }
    .el-main {
        background-color: #E9EEF3;
        color: #333;
        line-height: 560px;
    }

    body > .el-container {
        margin-bottom: 40px;
    }

    .el-container:nth-child(5) .el-aside,
    .el-container:nth-child(6) .el-aside {
        line-height: 260px;
    }

    .el-container:nth-child(7) .el-aside {
        line-height: 320px;
    }
</style>
           
SpringBoot整合Vue(二)完成登录(shiro)、退出、左侧菜单(基于springboot+shiro+swagger+redis+vue+elementui)

4.退出功能实现

前端代码 

//下拉触发事件
            handleCommand(command){
              if(command==='logout'){
                  this.$axios.get("/system/logout").tehn(result=>{
                      if(result.data.code===2000){
                          //移除存储的token
                          sessionStorage.removeItem("token");
                          this.$axios.push("/login")
                      }
                  })
              }
            },
           

后端代码

@GetMapping("/logout")
    @ApiOperation(value = "退出接口")
    public CommonResult logout(HttpServletRequest request){
        //获取登录者唯一的token
        String token = request.getHeader("token");
        if(redisTemplate.hasKey(token)){
            redisTemplate.delete(token);
            return new CommonResult(2000,"退出成功",null);
        }
        return new CommonResult(5000,"无效的token",null);
    }
           

5.左侧菜单

1.使用递归完成前端

components创建左侧菜单组件

SpringBoot整合Vue(二)完成登录(shiro)、退出、左侧菜单(基于springboot+shiro+swagger+redis+vue+elementui)
<template>
    <div class="navMenu">

        <template v-for="navMenu in menuData">
            <!-- 最后一级菜单 叶子菜单 -->
            <el-menu-item v-if="navMenu.children.length==0"
                          :index="navMenu.path"
            >
                <i :class="navMenu.icon"></i>
                <span slot="title">{{navMenu.name}}</span>
            </el-menu-item>

            <!-- 此菜单下还有子菜单 -->
            <el-submenu v-if="navMenu.children.length!=0"
                        :index="navMenu.path">
                <template slot="title">
                    <i :class="navMenu.icon"></i>
                    <span> {{navMenu.name}}</span>
                </template>
                <!-- 递归 -->
                <LeftMenu :menuData="navMenu.children"></LeftMenu>
            </el-submenu>
        </template>

    </div>
</template>

<script>
    export default {
        name: 'LeftMenu',
        //接受使用者传递的数据
        props: ['menuData'],
        data() {
            return {}
        },
        methods: {}
    }
</script>

<style>
</style>
           

在Home组件中使用LeftMenu组件

SpringBoot整合Vue(二)完成登录(shiro)、退出、左侧菜单(基于springboot+shiro+swagger+redis+vue+elementui)
<template>
    <el-container>
        <el-header>
                <span id="logo" style="display: inline-block;width: 50%;height: 100%;float: left" >
                     <a href="https://www.bilibili.com/video/BV14g41197PY/" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" ><img src="../assets/logo.png" height="100%" width="180px"></a>
                </span>
            <span id="avatar" style="float: right">
                    <el-dropdown @command="handleCommand">
                        <span class="el-dropdown-link" style="margin-top: 10px; display: inline-block;">
                    <el-avatar ></el-avatar>
                  </span>
                    <el-dropdown-menu slot="dropdown">
                        <el-dropdown-item command="info">个人信息</el-dropdown-item>
                        <el-dropdown-item command="logout">退出登录</el-dropdown-item>
                    </el-dropdown-menu>
                </el-dropdown>
                </span>
        </el-header>
        <el-container>
            <el-aside width="200px">
                <el-menu
                        default-active="2"
                        class="el-menu-vertical-demo"
                        background-color="#545c64"
                        text-color="#fff"
                        :router="true"
                        unique-opened="unique-opened"
                        active-text-color="#ffd04b">

                    <LeftMenu :menu-data="leftMenus"></LeftMenu>
                </el-menu>
            </el-aside>
            <el-main>
                <!--视图渲染-->
                <router-view/>
            </el-main>
        </el-container>
        <el-footer>Footer</el-footer>
    </el-container>
</template>

<script>
    import LeftMenu from "@/components/LeftMenu";
    export default {
        name: "Home",
        components: {LeftMenu},
        comments:{
            LeftMenu
        },
        data(){
            return{
                leftMenus:[],
            }
        },
        created() {
            this.initLeftMenu();
        },
        methods:{

            initLeftMenu(){
                this.$axios.get("/system/permission/leftMenu").then(result=>{
                    if(result.data.code===2000){
                        this.leftMenus=result.data.data;
                    }
                })
            },

            getInfo(){
                this.$axios.get("http://localhost:8809/system/user/getInfo").then(result=>{
                    console.log(result)
                })
            },

            //下拉触发事件
            handleCommand(command){
              if(command==='logout'){
                  this.$axios.get("/system/logout").then(result=>{
                      if(result.data.code===2000){
                          //移除存储的token
                          sessionStorage.removeItem("token");
                          this.$router.push("/login")
                      }
                  })
              }
            },
        }
    }
</script>
<!--当前vue有效-->
<style>
    html,body,#app{
        height: 100%;
    }
    body,#app{
        padding: 0px;
        margin:0px;
    }
    .el-container{
        height: 100%;
    }
    .el-header, .el-footer {
        background-color: #1F272F;
        color: #333;
        line-height: 60px;
    }

    .el-aside {
        background-color: #545c64;
        color: #333;
        line-height: 560px;
    }
    .el-aside>.el-menu{
        border: none;
    }
    .el-main {
        background-color: #E9EEF3;
        color: #333;
        line-height: 560px;
    }

    body > .el-container {
        margin-bottom: 40px;
    }

    .el-container:nth-child(5) .el-aside,
    .el-container:nth-child(6) .el-aside {
        line-height: 260px;
    }

    .el-container:nth-child(7) .el-aside {
        line-height: 320px;
    }
</style>
           

后端

先在 Permission实体类定义一个 children属性

SpringBoot整合Vue(二)完成登录(shiro)、退出、左侧菜单(基于springboot+shiro+swagger+redis+vue+elementui)

controller层:

@RestController
@RequestMapping("/system/permission")
public class PermissionController {

    @Autowired
    private IPermissionService permissionService;

    @GetMapping("/leftMenu")
    public CommonResult leftMenu(HttpServletRequest request){
        String token = request.getHeader("token");
        return permissionService.findPermissionById(token);
    }

}
           

service层:

@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements IPermissionService {

    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public CommonResult findPermissionById(String token) {
        //根据token获取用户信息
        ValueOperations forValue = redisTemplate.opsForValue();
        User user = (User) forValue.get(token);
        String id = user.getId();
        //根据用户的id查询用户的权限
        List<Permission> lsit = permissionMapper.selectPermissionById(id);
        //设置层级关系
        List<Permission> firstMenus = new ArrayList<>();
        for(Permission first : lsit){
            if(first.getPid().equals("1"));
            firstMenus.add(first);
        }

        //设置二级菜单
        for(Permission first : firstMenus){
            //根据一级菜单可以查询二级菜单 若不确定有几级菜单 那可以使用递归调用
            first.setChildren(findChildren(lsit,first.getId()));
        }

        return new CommonResult(2000,"查询成功",firstMenus);
    }

    //方法递归
    private List<Permission> findChildren(List<Permission> permissionList,String id){
        List<Permission> children = new ArrayList<>();
        for(Permission p : permissionList){
            if(p.getPid().equals(id)){
                children.add(p);
            }
        }
        for(Permission child : children){
            child.setChildren(findChildren(permissionList,child.getId()));
        }
        return children;
    }
}
           

sql语句: 

select distinct p.* from acl_user_role ur join acl_role_permission rp on ur.role_id=rp.role_id join acl_permission p on rp.permission_id=p.id
        where ur.id=#{id} and type=1 and rp.is_deleted=0
           

表结构: 

SpringBoot整合Vue(二)完成登录(shiro)、退出、左侧菜单(基于springboot+shiro+swagger+redis+vue+elementui)