1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Spring Boot + Vue + Shiro 實(shí)現(xiàn)前后端分離,寫得太好了!

        共 12187字,需瀏覽 25分鐘

         ·

        2021-12-13 03:07

        作者:_Yufan 來(lái)源:www.cnblogs.com/yfzhou/p/9813177.html

        本文總結(jié)自實(shí)習(xí)中對(duì)項(xiàng)目的重構(gòu)。原先項(xiàng)目采用 Springboot+freemarker 模版,開(kāi)發(fā)過(guò)程中覺(jué)得前端邏輯寫的實(shí)在惡心,后端 Controller 層還必須返回 Freemarker 模版的 ModelAndView,逐漸有了前后端分離的想法,由于之前,沒(méi)有接觸過(guò),主要參考的還是網(wǎng)上的一些博客教程等,初步完成了前后端分離,在此記錄以備查閱。

        一、前后端分離思想

        前端從后端剝離,形成一個(gè)前端工程,前端只利用 Json 來(lái)和后端進(jìn)行交互,后端不返回頁(yè)面,只返回 Json 數(shù)據(jù)。前后端之間完全通過(guò) public API 約定。

        二、后端 Springboot

        Springboot 就不再贅述了,Controller 層返回 Json 數(shù)據(jù)。

        @RequestMapping(value?=?"/add",?method?=?RequestMethod.POST)
        @ResponseBody
        public?JSONResult?addClient(@RequestBody?String?param)?{
        ????JSONObject?jsonObject?=?JSON.parseObject(param);
        ????String?task?=?jsonObject.getString("task");
        ????List?list?=?jsonObject.getJSONArray("attributes");
        ????List?attrList?=?new?LinkedList(list);
        ????Client?client?=?JSON.parseObject(jsonObject.getJSONObject("client").toJSONString(),new?TypeReference(){});
        ????clientService.addClient(client,?task,?attrList);
        ????return?JSONResult.ok();
        }

        Post 請(qǐng)求使用 @RequestBody 參數(shù)接收。

        三、前端 Vue + ElementUI + Vue router + Vuex + axios + webpack

        主要參考:

        https://cn.vuejs.org/v2/guide/ https://github.com/PanJiaChen/vue-admin-template/blob/master/README-zh.md https://github.com/PanJiaChen/vue-element-admin

        這里主要說(shuō)一下開(kāi)發(fā)工程中遇到的問(wèn)題:

        1. 跨域

        由于開(kāi)發(fā)中前端工程使用 webpack 啟了一個(gè)服務(wù),所以前后端并不在一個(gè)端口下,必然涉及到跨域:

        XMLHttpRequest 會(huì)遵守同源策略 (same-origin policy). 也即腳本只能訪問(wèn)相同協(xié)議 / 相同主機(jī)名 / 相同端口的資源, 如果要突破這個(gè)限制, 那就是所謂的跨域, 此時(shí)需要遵守 CORS(Cross-Origin Resource Sharing) 機(jī)制。

        解決跨域分兩種:

        1、server 端是自己開(kāi)發(fā)的,這樣可以在在后端增加一個(gè)攔截器

        @Component
        public?class?CommonIntercepter?implements?HandlerInterceptor?{

        ????private?final?Logger?logger?=?LoggerFactory.getLogger(this.getClass());

        ????@Override
        ????public?boolean?preHandle(HttpServletRequest?request,
        ?????????????????????????????HttpServletResponse?response,?Object?handler)?throws?Exception?{
        ????????//允許跨域,不能放在postHandle內(nèi)
        ????????response.setHeader("Access-Control-Allow-Origin",?"*");
        ????????if?(request.getMethod().equals("OPTIONS"))?{
        ????????????response.addHeader("Access-Control-Allow-Methods",?"GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH");
        ????????????response.addHeader("Access-Control-Allow-Headers",?"Content-Type,?Accept,?Authorization");
        ????????}
        ????????return?true;
        ????}
        }

        response.setHeader("Access-Control-Allow-Origin", "*");

        主要就是在 Response Header 中增加 "Access-Control-Allow-Origin: *"

        if?(request.getMethod().equals("OPTIONS"))?{
        ????????????response.addHeader("Access-Control-Allow-Methods",?"GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH");
        ????????????response.addHeader("Access-Control-Allow-Headers",?"Content-Type,?Accept,?Authorization");
        ????????}

        由于我們?cè)谇昂蠖朔蛛x中集成了 shiro,因此需要在 headers 中自定義一個(gè)'Authorization'字段,此時(shí)普通的 GET、POST 等請(qǐng)求會(huì)變成 preflighted request,即在 GET、POST 請(qǐng)求之前會(huì)預(yù)先發(fā)一個(gè) OPTIONS 請(qǐng)求,這個(gè)后面再說(shuō)。推薦一篇博客介紹 preflighted request。

        https://blog.csdn.net/cc1314_/article/details/78272329

        2、server 端不是自己開(kāi)發(fā)的,可以在前端加 proxyTable。

        不過(guò)這個(gè)只能在開(kāi)發(fā)的時(shí)候用,后續(xù)部署,可以把前端項(xiàng)目作為靜態(tài)資源放到后端,這樣就不存在跨域(由于項(xiàng)目需要,我現(xiàn)在是這么做的,根據(jù)網(wǎng)上博客介紹,可以使用 nginx,具體怎么做可以在網(wǎng)上搜一下)。

        遇到了網(wǎng)上很多人說(shuō)的,proxyTable 無(wú)論如何修改,都沒(méi)效果的現(xiàn)象。

        1、(非常重要)確保 proxyTable 配置的地址能訪問(wèn),因?yàn)槿绻荒茉L問(wèn),在瀏覽器 F12 調(diào)試的時(shí)候看到的依然會(huì)是提示 404。

        并且注意,在 F12 看到的 js 提示錯(cuò)誤的域名,是 js 寫的那個(gè)域名,并不是代理后的域名。(l 樓主就遇到這個(gè)問(wèn)題,后端地址缺少了查詢參數(shù),代理設(shè)置為后端地址,然而 F12 看到的錯(cuò)誤依然還是本地的域名,并不是代理后的域名)

        2、就是要手動(dòng)再執(zhí)行一次 npm run dev

        四、前后端分離項(xiàng)目中集成 shiro

        可以參考:

        blog.csdn.net/u013615903/article/details/78781166

        這里說(shuō)一下實(shí)際開(kāi)發(fā)集成過(guò)程中遇到的問(wèn)題:

        1、OPTIONS 請(qǐng)求不帶'Authorization'請(qǐng)求頭字段:

        前后端分離項(xiàng)目中,由于跨域,會(huì)導(dǎo)致復(fù)雜請(qǐng)求,即會(huì)發(fā)送 preflighted request,這樣會(huì)導(dǎo)致在 GET/POST 等請(qǐng)求之前會(huì)先發(fā)一個(gè) OPTIONS 請(qǐng)求,但 OPTIONS 請(qǐng)求并不帶 shiro 的'Authorization'字段(shiro 的 Session),即 OPTIONS 請(qǐng)求不能通過(guò) shiro 驗(yàn)證,會(huì)返回未認(rèn)證的信息。

        解決方法:給 shiro 增加一個(gè)過(guò)濾器,過(guò)濾 OPTIONS 請(qǐng)求

        public?class?CORSAuthenticationFilter?extends?FormAuthenticationFilter?{

        ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(CORSAuthenticationFilter.class);

        ????public?CORSAuthenticationFilter()?{
        ????????super();
        ????}

        ????@Override
        ????public?boolean?isAccessAllowed(ServletRequest?request,?ServletResponse?response,?Object?mappedValue)?{
        ????????//Always?return?true?if?the?request's?method?is?OPTIONSif?(request?instanceof?HttpServletRequest)?{
        ????????????if?(((HttpServletRequest)?request).getMethod().toUpperCase().equals("OPTIONS"))?{
        ????????????????return?true;
        ????????????}
        ????????}
        return?super.isAccessAllowed(request,?response,?mappedValue);
        ????}

        ????@Override
        ????protected?boolean?onAccessDenied(ServletRequest?request,?ServletResponse?response)?throws?Exception?{
        ????????HttpServletResponse?res?=?(HttpServletResponse)response;
        ????????res.setHeader("Access-Control-Allow-Origin",?"*");
        ????????res.setStatus(HttpServletResponse.SC_OK);
        ????????res.setCharacterEncoding("UTF-8");
        ????????PrintWriter?writer?=?res.getWriter();
        ????????Map?map=?new?HashMap<>();
        ????????map.put("code",?702);
        ????????map.put("msg",?"未登錄");
        ????????writer.write(JSON.toJSONString(map));
        ????????writer.close();
        ????????return?false;
        ????}
        }

        貼一下我的 config 文件:

        @Configuration
        public?class?ShiroConfig?{

        ????@Bean
        ????public?Realm?realm()?{
        ????????return?new?DDRealm();
        ????}

        ????@Bean
        ????public?CacheManager?cacheManager()?{
        ????????return?new?MemoryConstrainedCacheManager();
        ????}

        ????/**
        ?????*?cookie對(duì)象;
        ?????* rememberMeCookie()方法是設(shè)置Cookie的生成模版,比如cookie的name,cookie的有效時(shí)間等等。
        ?????*?@return
        ?????*/
        ????@Bean
        ????public?SimpleCookie?rememberMeCookie(){
        ????????//System.out.println("ShiroConfiguration.rememberMeCookie()");
        ????????//這個(gè)參數(shù)是cookie的名稱,對(duì)應(yīng)前端的checkbox的name?=?rememberMe
        ????????SimpleCookie?simpleCookie?=?new?SimpleCookie("rememberMe");
        ????????//
        ????????simpleCookie.setMaxAge(259200);
        ????????return?simpleCookie;
        ????}

        ????/**
        ?????*?cookie管理對(duì)象;
        ?????*?rememberMeManager()方法是生成rememberMe管理器,而且要將這個(gè)rememberMe管理器設(shè)置到securityManager中
        ?????*?@return
        ?????*/
        ????@Bean
        ????public?CookieRememberMeManager?rememberMeManager(){
        ????????//System.out.println("ShiroConfiguration.rememberMeManager()");
        ????????CookieRememberMeManager?cookieRememberMeManager?=?new?CookieRememberMeManager();
        ????????cookieRememberMeManager.setCookie(rememberMeCookie());
        ????????//rememberMe?cookie加密的密鑰?建議每個(gè)項(xiàng)目都不一樣?默認(rèn)AES算法?密鑰長(zhǎng)度(128?256?512?位)
        ????????cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
        ????????return?cookieRememberMeManager;
        ????}

        ????@Bean
        ????public?SecurityManager?securityManager()?{
        ????????DefaultWebSecurityManager?sm?=?new?DefaultWebSecurityManager();
        ????????sm.setRealm(realm());
        ????????sm.setCacheManager(cacheManager());
        ????????//注入記住我管理器
        ????????sm.setRememberMeManager(rememberMeManager());
        ????????//注入自定義sessionManager
        ????????sm.setSessionManager(sessionManager());
        ????????return?sm;
        ????}

        ????//自定義sessionManager
        ????@Bean
        ????public?SessionManager?sessionManager()?{
        ????????return?new?CustomSessionManager();
        ????}

        ????public?CORSAuthenticationFilter?corsAuthenticationFilter(){
        ????????return?new?CORSAuthenticationFilter();
        ????}

        ????@Bean(name?=?"shiroFilter")
        ????public?ShiroFilterFactoryBean?getShiroFilterFactoryBean(SecurityManager?securityManager)?{
        ????????ShiroFilterFactoryBean?shiroFilter?=?new?ShiroFilterFactoryBean();
        ????????shiroFilter.setSecurityManager(securityManager);
        ????????//SecurityUtils.setSecurityManager(securityManager);
        ????????Map?filterChainDefinitionMap?=?new?LinkedHashMap<>();
        ????????//配置不會(huì)被攔截的鏈接,順序判斷
        ????????filterChainDefinitionMap.put("/",?"anon");
        ????????filterChainDefinitionMap.put("/static/js/**",?"anon");
        ????????filterChainDefinitionMap.put("/static/css/**",?"anon");
        ????????filterChainDefinitionMap.put("/static/fonts/**",?"anon");
        ????????filterChainDefinitionMap.put("/login/**",?"anon");
        ????????filterChainDefinitionMap.put("/corp/call_back/receive",?"anon");
        ????????//authc:所有url必須通過(guò)認(rèn)證才能訪問(wèn),anon:所有url都可以匿名訪問(wèn)
        ????????filterChainDefinitionMap.put("/**",?"corsAuthenticationFilter");
        ????????shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        ????????//自定義過(guò)濾器
        ????????Map?filterMap?=?new?LinkedHashMap<>();
        ????????filterMap.put("corsAuthenticationFilter",?corsAuthenticationFilter());
        ????????shiroFilter.setFilters(filterMap);

        ????????return?shiroFilter;
        ????}

        ????/**
        ?????*?Shiro生命周期處理器?*?@return
        ?????*/
        ????@Bean
        ????public?LifecycleBeanPostProcessor?lifecycleBeanPostProcessor()?{
        ????????return?new?LifecycleBeanPostProcessor();
        ????}

        ????/**
        ?????*?開(kāi)啟Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP掃描使用Shiro注解的類,并在必要時(shí)進(jìn)行安全邏輯驗(yàn)證?*?配置以下兩個(gè)bean(DefaultAdvisorAutoProxyCreator(可選)和AuthorizationAttributeSourceAdvisor)即可實(shí)現(xiàn)此功能?*?@return
        ?????*/
        ????@Bean
        ????@DependsOn({"lifecycleBeanPostProcessor"})
        ????public?DefaultAdvisorAutoProxyCreator?advisorAutoProxyCreator()?{
        ????????DefaultAdvisorAutoProxyCreator?advisorAutoProxyCreator?=?new?DefaultAdvisorAutoProxyCreator();
        ????????advisorAutoProxyCreator.setProxyTargetClass(true);
        ????????return?advisorAutoProxyCreator;
        ????}

        ????@Bean
        ????public?AuthorizationAttributeSourceAdvisor?authorizationAttributeSourceAdvisor(SecurityManager?securityManager)?{
        ????????AuthorizationAttributeSourceAdvisor?authorizationAttributeSourceAdvisor?=?new?AuthorizationAttributeSourceAdvisor();
        ????????authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        ????????return?authorizationAttributeSourceAdvisor;
        ????}
        }

        2、設(shè)置 session 失效時(shí)間

        shiro session 默認(rèn)失效時(shí)間是 30min,我們?cè)谧远x的 sessionManager 的構(gòu)造函數(shù)中設(shè)置失效時(shí)間為其他值

        public?class?CustomSessionManager?extends?DefaultWebSessionManager?{

        ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(CustomSessionManager.class);

        ????private?static?final?String?AUTHORIZATION?=?"Authorization";

        ????private?static?final?String?REFERENCED_SESSION_ID_SOURCE?=?"Stateless?request";

        ????public?CustomSessionManager()?{
        ????????super();
        ????????setGlobalSessionTimeout(DEFAULT_GLOBAL_SESSION_TIMEOUT?*?48);
        ????}

        ????@Override
        ????protected?Serializable?getSessionId(ServletRequest?request,?ServletResponse?response)?{
        ????????String?sessionId?=?WebUtils.toHttp(request).getHeader(AUTHORIZATION);//如果請(qǐng)求頭中有?Authorization?則其值為sessionId
        ????????if?(!StringUtils.isEmpty(sessionId))?{
        ????????????request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,?REFERENCED_SESSION_ID_SOURCE);
        ????????????request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,?sessionId);
        ????????????request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,?Boolean.TRUE);
        ????????????return?sessionId;
        ????????}?else?{
        ????????????//否則按默認(rèn)規(guī)則從cookie取sessionId
        ????????????return?super.getSessionId(request,?response);
        ????????}
        ????}
        }

        五、部署項(xiàng)目

        前端項(xiàng)目部署主要分兩種方法:

        1、將前端項(xiàng)目打包(npm run build)成靜態(tài)資源文件,放入后端,一起打包。后端寫一個(gè) Controller 返回前端界面(我使用 Vue 開(kāi)發(fā)的是單頁(yè)面應(yīng)用),但是這樣其實(shí)又將前后端耦合在一起了,不過(guò)起碼做到前后端分離開(kāi)發(fā),方便開(kāi)發(fā)的目的已經(jīng)達(dá)成,也初步達(dá)成了要求,由于項(xiàng)目的需要,我是這樣做的,并且免去了跨域問(wèn)題。

        @RequestMapping(value?=?{"/",?"/index"},?method?=?RequestMethod.GET)
        public?String?index()?{
        ????return?"/index";
        }

        2. 將前端工程另啟一個(gè)服務(wù)(tomcat,nginx,nodejs),這樣有跨域的問(wèn)題。

        說(shuō)一下我遇到的問(wèn)題:

        1、nginx 反向代理,導(dǎo)致當(dāng)訪問(wèn)無(wú)權(quán)限的頁(yè)面時(shí),shiro 302 到 unauth 的 controller,訪問(wèn)的地址是 https,重定向地址是 http,導(dǎo)致了無(wú)法訪問(wèn)。

        不使用 shiro 的 shiroFilter.setLoginUrl("/unauth");

        當(dāng)頁(yè)面無(wú)權(quán)限訪問(wèn)時(shí),我們?cè)谶^(guò)濾器里直接返回錯(cuò)誤信息,不利用 shiro 自帶的跳轉(zhuǎn)。看過(guò)濾器中的 onAccessDenied 函數(shù)

        public?class?CORSAuthenticationFilter?extends?FormAuthenticationFilter?{

        ????private?static?final?Logger?logger?=?LoggerFactory.getLogger(CORSAuthenticationFilter.class);

        ????public?CORSAuthenticationFilter()?{
        ????????super();
        ????}

        ????@Override
        ????public?boolean?isAccessAllowed(ServletRequest?request,?ServletResponse?response,?Object?mappedValue)?{
        ????????//Always?return?true?if?the?request's?method?is?OPTIONS
        ????????if?(request?instanceof?HttpServletRequest)?{
        ????????????if?(((HttpServletRequest)?request).getMethod().toUpperCase().equals("OPTIONS"))?{
        ????????????????return?true;
        ????????????}
        ????????}
        ????????return?super.isAccessAllowed(request,?response,?mappedValue);
        ????}

        ????@Override
        ????protected?boolean?onAccessDenied(ServletRequest?request,?ServletResponse?response)?throws?Exception?{
        ????????HttpServletResponse?res?=?(HttpServletResponse)response;
        ????????res.setHeader("Access-Control-Allow-Origin",?"*");
        ????????res.setStatus(HttpServletResponse.SC_OK);
        ????????res.setCharacterEncoding("UTF-8");
        ????????PrintWriter?writer?=?res.getWriter();
        ????????Map?map=?new?HashMap<>();
        ????????map.put("code",?702);
        ????????map.put("msg",?"未登錄");
        ????????writer.write(JSON.toJSONString(map));
        ????????writer.close();
        ????????return?false;
        ????}
        }

        先記錄這么多,有不對(duì)的地方,歡迎指出!

        程序汪資料鏈接

        程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

        Java項(xiàng)目分享 最新整理全集,找項(xiàng)目不累啦 06版

        堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

        臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開(kāi)放下載!

        臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開(kāi)放下載!

        字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開(kāi)放下載!


        歡迎添加程序汪個(gè)人微信 itwang009? 進(jìn)粉絲群或圍觀朋友圈

        瀏覽 34
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亚洲日韩精品欧美一区二区三区 | 精品无码av一级爽爽爽爽 | 黑人狂躁翔田千里A片 | 涩涩涩涩色 | 男女无遮挡羞羞视频在线观看 | 奇米影视狠狠去 | 黄色片无码 | 亚洲免费大片 | 污污的网站免费观看 | 性videosgratis喷水 |