zoukankan      html  css  js  c++  java
  • springboot,vue,shiro整合 关于登录认证功能

    首先是session问题

    传统session认证
    http协议是一种无状态协议,即浏览器发送请求到服务器,服务器是不知道这个请求是哪个用户发来的。为了让服务器知道请求是哪个用户发来的,需要让用户提供用户名和密码来进行认证。当浏览器第一次访问服务器(假设是登录接口),服务器验证用户名和密码之后,服务器会生成一个sessionid(只有第一次会生成,其它会使用同一个sessionid),并将该session和用户信息关联起来,然后将sessionid返回给浏览器,浏览器收到sessionid保存到Cookie中,当用户第二次访问服务器是就会携带Cookie值,服务器获取到Cookie值,进而获取到sessionid,根据sessionid获取关联的用户信息。

    由于前后的分离开发,传统的session方式不能用来做登录认证了,因为存在前端是部署在ngnix代理服务器中,springboot是部署tomcat服务器中,两个项目存在跨域问题,所以session不能共享,每次获取session地址不同,session对象就不同

    解决思路
    1、首次登录时,后端服务器判断用户账号密码正确之后, 生成 sessionId,设置过期时间保存到redis服务器中并返回给前端

        @ApiOperation(value = "登入")
        @PostMapping("/login")
        public ResultVO loginPost(@RequestParam("userName")String userName, @RequestParam("userPass")String userPass){
            //根据前端传递过来的name和passowrd生成shrio的UsernamePasswordToken
            userPass = new Md5Hash(userPass,userName,3).toString();
            UsernamePasswordToken token = new UsernamePasswordToken(userName, userPass);
            //写shiro的认证逻辑
            Subject subject = SecurityUtils.getSubject();
    
            //因为可能出现异常,所以直接try catch
            try {
                //调用login方法,传入token
                subject.login(token);
                String sid = (String) subject.getSession().getId();
                //如果登录没有出现异常的话,就可以通过getPrincinpal()获取登录用户
                User user= (User)subject.getPrincipal();
                //获取sessionId
                String sessionId = (String) subject.getSession().getId();
                //成功返回id和对象
                LoginVo loginVo=new LoginVo();
                loginVo.setUser(user);
                loginVo.setSessionId(sessionId);
                AppUser appUser=new AppUser();
                BeanUtils.copyProperties(user,appUser);
                return ResultVOUtil.success(loginVo);
            } catch (AuthenticationException e) {
                return ResultVOUtil.error(400,e.getMessage());
            }
        }
    

    2、前端拿到后端返回的 sessionId, 存储在 localStroage/sessionStroage里

    const user = {
        state: {
            id:window.sessionStorage.getItem('id'),
            userId:null,
            userName:null,
            userNickname:null,
            userIcon:null,
            userNumber:null,
            mail:null,
            roleId:null
        },
        mutations: {
            //将id保存到sessionStorage里,id表示登陆状态
            SET_ID: (state, data) => {
                state.id = data
                window.sessionStorage.setItem('id', data)
            },
            //获取用户信息
            SET_USER: (state, data) => {
                // 把用户信息存起来
                state.userId = data.userId
                state.userName = data.userName
                state.userNickname = data.userNickname
                state.userIcon = data.userIcon
                state.userNumber = data.userNumber
                state.mail = data.mail
                state.roleId = data.roleId
            },
            //登出
            LOGOUT: (state) => {
                // 登出的时候要清除token
                state.id = null
                state.userId = null
                state.userName = null
                state.userNickname = null
                state.userIcon = null
                state.userNumber = null
                window.sessionStorage.removeItem('id')
            }
        },
        actions: {
    
        }
    };
    
    export default user;
    
     getUser(name,pass){
                    login(name,pass).then(res => {
                        if(res.code==0){
                            this.$message({ message: "登录成功", type: 'success' })
                            this.$store.commit('SET_ID', res.data.sessionId)
                            this.$store.commit('SET_USER', res.data.user)
                            this.centerDialogVisible=false
                        }else{
                            this.$message({ message: res.msg, type: 'error' })
                        }
                    })
                },
    

    3、前端每次路由跳转到需要认证的页面, 判断 localStroage/sessionStroage有无 sessionId,没有则跳转到登录页

    我的是局部路由的,大家根据不同需求写

     beforeRouteEnter:(to,from,next)=>{
               next(vm => {
                   vm.init()
                   if (vm.$store.state.user.id==null){
                       vm.$message.error('你还没有登录,请先登录');
                       next('/home');
                   }
               })
    

    4、封装拦截器,每次请求接口,在请求头里携带 sessionId

    import axios from 'axios'
    import { Notification } from 'element-ui';
    
    export function request(config) {
      const instance = axios.create({
        baseURL: '/api',
        tiemout: 5000
      })
    
      instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
    
      instance.interceptors.request.use(config => {
        const id =window.sessionStorage.getItem('id')
        //  const id  =this.$store.state.user.id
        id && (config.headers.common['Authorization']  = 'beared'+id)
        return config
      }, err => {
        console.log(err);
      })
    
      instance.interceptors.response.use(res => {
        if (res.data.code == 401) {
          Notification({
            title: '温馨提示',
            message: '您还没有登录,请登录后再进行相关操作',
            position: 'bottom-right'
          })
        }
        return res.data
      }, err => {
        console.log(err);
      })
    
      return instance(config)
    }
    

    5、后端统一拦截判断 请求头有无 sessionId ,没有或者 sessionId过期,返回401

    6、前端得到 401 状态码,重定向到登录页面

    登录认证总结
    用到的技术有路由守卫,axiox封装请求响应拦截器,安全框架shiro,vuex和持久化插件(VuexPersistence)
    关键点是session会话
    把传统的session会话交给shiro管理,重写session校验机制,设置成根据请求头(sessionId)获取session信息

    public class CustomSessionManager extends DefaultWebSessionManager {
        /**
         * 设置会话时间
         */
        public CustomSessionManager() {
            super();
            setGlobalSessionTimeout(DEFAULT_GLOBAL_SESSION_TIMEOUT * 48);
        }
    
        /**
         * 请求头Authorization:sessionid
         * 指定sessionid的获取方法
         */
        @Override
        protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
            //获取请求头信息
            String id= WebUtils.toHttp(request).getHeader("Authorization");
            if (StringUtils.isEmpty(id)) {
                //为空则创建新的sessionid
                return super.getSessionId(request,response);
            }else {
                id=id.replaceAll("beared","");
                //在哪里获取
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
                //id是什么
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
                //是否要验证
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
                return id;
            }
        }
    
    }
    
    

    配置shiro配置redis存储session,这样的好处,根据sessionId把session保存在数据库中,既使重启服务器也不会丢失session

    前端实现:安装vuex持久化插件VuexPersistence,把用户信息和sessionId保存到vuex里面

    封装拦截器,每次请求接口,在请求头里携带 vuex取出的sessionId,后端根据路径判断是否有权限或是否认证

    我的shiro配置

    @Configuration
    public class ShiroConfig {
    
    
    	/**
    	 * 创建ShiroFilterFactoryBean
    	 */
    	@Bean
    	public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
    		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    		
    		//设置安全管理器
    		shiroFilterFactoryBean.setSecurityManager(securityManager);
    		
    		//添加Shiro内置过滤器
    		/**
    		 * Shiro内置过滤器,可以实现权限相关的拦截器
    		 *    常用的过滤器:
    		 *       anon: 无需认证(登录)可以访问
    		 *       authc: 必须认证才可以访问
    		 *       user: 如果使用rememberMe的功能可以直接访问
    		 *       perms: 该资源必须得到资源权限才可以访问
    		 *       roles: 该资源必须得到角色权限才可以访问
    		 */
            Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
            filters.put("roles",shiroRoleFilter());
            filters.put("authc",shiroLoginFilter());
    
            Map<String,String> filterMap = new LinkedHashMap<String,String>();
    
           filterMap.put("/byuser/**","authc");
    //        filterMap.put("/byuser/home","anon");
    
    		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
            shiroFilterFactoryBean.setFilters(filters);
    
    		return shiroFilterFactoryBean;
    	}
    
        @Bean(name="shiroLoginFilter")
        public ShiroLoginFilter shiroLoginFilter() {
            return new ShiroLoginFilter();
        }
        @Bean(name="shiroRoleFilter")
        public ShiroRoleFilter shiroRoleFilter() {
            return new ShiroRoleFilter();
        }
    
        @Bean(name="corsBasicFilter")
        public CorsBasicFilter corsBasicFilter() {
            return new CorsBasicFilter();
        }
    
    	/**
    	 * 创建DefaultWebSecurityManager
    	 */
    	@Bean(name="securityManager")
    	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("loginRealm")LoginRealm userRealm){
    		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    		//关联realm
    		securityManager.setRealm(userRealm);
            //将自定义的会话管理器注册到安全管理器中
            securityManager.setSessionManager(sessionManager());
            //将自定义的redis缓存管理器注册到安全管理器中
    //        securityManager.setCacheManager(cacheManager());
    		return securityManager;
    	}
    	
    	/**
    	 * 创建Realm
    	 */
    	@Bean(name="loginRealm")
    	public LoginRealm getRealm(){
    		return new LoginRealm();
    	}
    
    
    
        @Value("${spring.redis.host}")
        private String host;
        @Value("${spring.redis.port}")
        private int port;
    
        /**
         * 1.redis的控制器,操作redis
         */
        public RedisManager redisManager() {
            RedisManager redisManager = new RedisManager();
            redisManager.setHost(host);
            redisManager.setPort(port);
            redisManager.setTimeout(7000);
            return redisManager;
        }
    
        /**
         * 2.sessionDao
         */
        public RedisSessionDAO redisSessionDAO() {
            RedisSessionDAO sessionDAO = new RedisSessionDAO();
            sessionDAO.setRedisManager(redisManager());
            return sessionDAO;
        }
    
        /**
         * 3.会话管理器
         */
        public DefaultWebSessionManager sessionManager() {
            //自定义子类
            CustomSessionManager sessionManager = new CustomSessionManager();
           // sessionManager.setSessionDAO(redisSessionDAO());
            //禁用所有的Cookie 这样自己设置的session就不起作用了
    //        sessionManager.setSessionIdCookieEnabled(false);
    //        sessionManager.setSessionIdUrlRewritingEnabled(false);
            return sessionManager;
        }
    
        /**
         * 4.缓存管理器
         */
        public RedisCacheManager cacheManager() {
            RedisCacheManager redisCacheManager = new RedisCacheManager();
            redisCacheManager.setRedisManager(redisManager());
            return redisCacheManager;
        }
    
    
    
    
        //开启对shior注解的支持
    //    @Bean
    //    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    //        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
    //        advisor.setSecurityManager(securityManager);
    //        return advisor;
    //    }
    
    //	@Bean
    //	public ShiroDialect getShiroDialect(){
    //		return new ShiroDialect();
    //	}
    }
    
    
  • 相关阅读:
    存储过程的常见结构
    js 立即调用的函数表达式
    浏览器控制台换行
    js 函数声明和函数表达式
    JavaScript中把Json字符串转化为对象
    js判断对象还是数组
    Flex 基础语法(三)
    Flex 基础语法(二)
    Flex 基础语法(一)
    SVN提交文件的时候过滤指定文件
  • 原文地址:https://www.cnblogs.com/mtxcat/p/12191185.html
Copyright © 2011-2022 走看看