<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>弦上的梦</title>
  
  <subtitle>树木是大地对星空的渴望</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://janche.github.io/"/>
  <updated>2020-03-08T14:10:32.500Z</updated>
  <id>https://janche.github.io/</id>
  
  <author>
    <name>Janche</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>spring-boot项目引入第三方依赖，如何打包</title>
    <link href="https://janche.github.io/2020/03/08/spring-boot%E9%A1%B9%E7%9B%AE%E5%BC%95%E5%85%A5%E7%AC%AC%E4%B8%89%E6%96%B9%E4%BE%9D%E8%B5%96%EF%BC%8C%E5%A6%82%E4%BD%95%E6%89%93%E5%8C%85/"/>
    <id>https://janche.github.io/2020/03/08/spring-boot项目引入第三方依赖，如何打包/</id>
    <published>2020-03-08T14:08:37.000Z</published>
    <updated>2020-03-08T14:10:32.500Z</updated>
    
    <content type="html"><![CDATA[<h4 id="项目环境："><a href="#项目环境：" class="headerlink" title="项目环境："></a>项目环境：</h4><ol><li>spring-boot</li><li>maven多模块项目</li><li>需要引入的外部jar包</li></ol><h4 id="目录结构图如下："><a href="#目录结构图如下：" class="headerlink" title="目录结构图如下："></a>目录结构图如下：</h4><p><img src="https://img-blog.csdnimg.cn/20200221205222477.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0OTk3OTA2,size_16,color_FFFFFF,t_70" alt="目录结构图"><br><a id="more"></a></p><h4 id="方式一，-打war包："><a href="#方式一，-打war包：" class="headerlink" title="方式一， 打war包："></a>方式一， 打war包：</h4><h5 id="1-在对应子模块的pom-xml文件中引入jar包，本例因为在common和web-模块都引入了，因此则都需要分分别引入，common的pom-xml配置如下："><a href="#1-在对应子模块的pom-xml文件中引入jar包，本例因为在common和web-模块都引入了，因此则都需要分分别引入，common的pom-xml配置如下：" class="headerlink" title="1. 在对应子模块的pom.xml文件中引入jar包，本例因为在common和web 模块都引入了，因此则都需要分分别引入，common的pom.xml配置如下："></a>1. 在对应子模块的<code>pom.xml</code>文件中引入jar包，本例因为在common和web 模块都引入了，因此则都需要分分别引入，common的pom.xml配置如下：</h5><p><img src="https://img-blog.csdnimg.cn/2020022121034247.png" alt="xml配置"><br>groupId、artifactId、version都是可以自己随意填的，当然最好还是按照一定得规律填写，方便区分，上图中红框中的部分就是需要特别注意的地方，<code>scope</code> 只能填写 <code>system</code>,<code>systemPath</code>则填写被引入jar包在项目中的位置。</p><blockquote><p><strong>注意：</strong><br><strong><code>${project.basedir}</code>和<code>${basedir}</code>都表示项目根目录，即包含pom.xml文件的目录，这两个都是maven预定义的内置属性,用户可以直接使用。</strong></p></blockquote><h5 id="common层pom-xml的全部内容："><a href="#common层pom-xml的全部内容：" class="headerlink" title="common层pom.xml的全部内容："></a><code>common</code>层<code>pom.xml</code>的全部内容：</h5><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>libii-identity<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>common<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>bcprov-jdk16-1.46.jar<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>system<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">systemPath</span>&gt;</span>$&#123;basedir&#125;/lib/bcprov-jdk16-1.46.jar<span class="tag">&lt;/<span class="name">systemPath</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>commons-beanutils-1.8.0.jar<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>system<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">systemPath</span>&gt;</span>$&#123;basedir&#125;/lib/commons-beanutils-1.8.0.jar<span class="tag">&lt;/<span class="name">systemPath</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2-配置web模块pom-xml"><a href="#2-配置web模块pom-xml" class="headerlink" title="2. 配置web模块pom.xml"></a>2. 配置<code>web</code>模块<code>pom.xml</code></h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>libii-identity<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">     <span class="comment">&lt;!--1. 将打包方式变为 war包--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">packaging</span>&gt;</span>war<span class="tag">&lt;/<span class="name">packaging</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!--编译跳过test--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">skipTests</span>&gt;</span>true<span class="tag">&lt;/<span class="name">skipTests</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!--2. 把内置的tomcat给注释掉 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-tomcat<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>provided<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>common<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>compile<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>backend<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>compile<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">&lt;!--3. 引入外部jar包 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>junit-4.12.jar<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>system<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">systemPath</span>&gt;</span>$&#123;basedir&#125;/lib/junit-4.12.jar<span class="tag">&lt;/<span class="name">systemPath</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">finalName</span>&gt;</span>identity<span class="tag">&lt;/<span class="name">finalName</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">           <span class="comment">&lt;!--4. 配置maven的war包插件 --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-war-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">webResources</span>&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!--5. 配置各模块的jar包资源目录，一个模块则只需配置一个resource --&gt;</span></span><br><span class="line">                    <span class="comment">&lt;!-- 把web模块的jar包打进去 --&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">resource</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">directory</span>&gt;</span>lib<span class="tag">&lt;/<span class="name">directory</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">targetPath</span>&gt;</span>WEB-INF/lib/<span class="tag">&lt;/<span class="name">targetPath</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">includes</span>&gt;</span></span><br><span class="line">                                <span class="tag">&lt;<span class="name">include</span>&gt;</span>**/*.jar<span class="tag">&lt;/<span class="name">include</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;/<span class="name">includes</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">resource</span>&gt;</span></span><br><span class="line">                        <span class="comment">&lt;!-- 把common模块的jar包打进去 --&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">resource</span>&gt;</span></span><br><span class="line">                        <span class="comment">&lt;!-- 特别声明： 此处需要使用相对路径，找到common模块 --&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">directory</span>&gt;</span>../common/lib<span class="tag">&lt;/<span class="name">directory</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">targetPath</span>&gt;</span>WEB-INF/lib/<span class="tag">&lt;/<span class="name">targetPath</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">includes</span>&gt;</span></span><br><span class="line">                                <span class="tag">&lt;<span class="name">include</span>&gt;</span>**/*.jar<span class="tag">&lt;/<span class="name">include</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;/<span class="name">includes</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">resource</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">webResources</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="comment">&lt;!--6. 下面的plugin为Springboot常规的打包插件，打war包时，加不加都没有关系，加上之后，会改变包结构，会让war包变大，所以推荐不加 --&gt;</span></span><br><span class="line">            <span class="comment">&lt;!--&lt;plugin&gt;</span></span><br><span class="line"><span class="comment">                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span></span><br><span class="line"><span class="comment">                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;</span></span><br><span class="line"><span class="comment">            &lt;/plugin&gt;--&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><blockquote><h4 id="使用误区："><a href="#使用误区：" class="headerlink" title="使用误区："></a>使用误区：</h4><h6 id="1-将jar包只放在web层，如果是其他子模块需要使用时候，则编译会出问题。"><a href="#1-将jar包只放在web层，如果是其他子模块需要使用时候，则编译会出问题。" class="headerlink" title="1. 将jar包只放在web层，如果是其他子模块需要使用时候，则编译会出问题。"></a>1. 将jar包只放在web层，如果是其他子模块需要使用时候，则编译会出问题。</h6><h6 id="2-将jar包只放在使用的模块，很多人会出现打包打不进去，因为你配置打包插件时没有使用子模块的相对路径，默认会使用当前模块的地址，"><a href="#2-将jar包只放在使用的模块，很多人会出现打包打不进去，因为你配置打包插件时没有使用子模块的相对路径，默认会使用当前模块的地址，" class="headerlink" title="2. 将jar包只放在使用的模块，很多人会出现打包打不进去，因为你配置打包插件时没有使用子模块的相对路径，默认会使用当前模块的地址，"></a>2. 将jar包只放在使用的模块，很多人会出现打包打不进去，因为你配置打包插件时没有使用子模块的相对路径，默认会使用当前模块的地址，</h6><h6 id="3-web模块和使用的模块都引入一次，打包能成功，使用也没问题，但是你不觉得很冗余吗，不够优雅。"><a href="#3-web模块和使用的模块都引入一次，打包能成功，使用也没问题，但是你不觉得很冗余吗，不够优雅。" class="headerlink" title="3. web模块和使用的模块都引入一次，打包能成功，使用也没问题，但是你不觉得很冗余吗，不够优雅。"></a>3. web模块和使用的模块都引入一次，打包能成功，使用也没问题，但是你不觉得很冗余吗，不够优雅。</h6><h6 id="最佳的方式：只在使用的模块引入jar包，在web层的pom-xml中配置子模块的jar包资源路径就行了"><a href="#最佳的方式：只在使用的模块引入jar包，在web层的pom-xml中配置子模块的jar包资源路径就行了" class="headerlink" title="最佳的方式：只在使用的模块引入jar包，在web层的pom.xml中配置子模块的jar包资源路径就行了"></a>最佳的方式：只在使用的模块引入jar包，在web层的pom.xml中配置子模块的jar包资源路径就行了</h6></blockquote><h4 id="方式二，-打jar包："><a href="#方式二，-打jar包：" class="headerlink" title="方式二， 打jar包："></a>方式二， 打jar包：</h4><h5 id="1-在用到此jar包的模块引入，同方式一的第一点类似。"><a href="#1-在用到此jar包的模块引入，同方式一的第一点类似。" class="headerlink" title="1. 在用到此jar包的模块引入，同方式一的第一点类似。"></a>1. 在用到此jar包的模块引入，同方式一的第一点类似。</h5><h5 id="2-修改web模块的打包方式为-jar"><a href="#2-修改web模块的打包方式为-jar" class="headerlink" title="2. 修改web模块的打包方式为 jar"></a>2. 修改<code>web</code>模块的打包方式为 <code>jar</code></h5><h5 id="3-为打包插件配置configuration属性"><a href="#3-为打包插件配置configuration属性" class="headerlink" title="3. 为打包插件配置configuration属性"></a>3. 为打包插件配置<code>configuration</code>属性</h5><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line"> <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">executable</span>&gt;</span>true<span class="tag">&lt;/<span class="name">executable</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">includeSystemScope</span>&gt;</span>true<span class="tag">&lt;/<span class="name">includeSystemScope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>web</code>模块完整的<code>pom.xml</code>文件：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>libii-identity<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">packaging</span>&gt;</span>jar<span class="tag">&lt;/<span class="name">packaging</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- SpringBoot 项目热部署 依赖--&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-devtools<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">optional</span>&gt;</span>true<span class="tag">&lt;/<span class="name">optional</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>common<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>compile<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>backend<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>compile<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!--3. 引入外部jar包 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.libii.sso<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>junit-4.12.jar<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>system<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">systemPath</span>&gt;</span>$&#123;basedir&#125;/lib/junit-4.12.jar<span class="tag">&lt;/<span class="name">systemPath</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">finalName</span>&gt;</span>identity<span class="tag">&lt;/<span class="name">finalName</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">           <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">executable</span>&gt;</span>true<span class="tag">&lt;/<span class="name">executable</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">includeSystemScope</span>&gt;</span>true<span class="tag">&lt;/<span class="name">includeSystemScope</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>参考博客：<a href="https://blog.csdn.net/J080624/article/details/81505937" target="_blank" rel="external">https://blog.csdn.net/J080624/article/details/81505937</a></p>]]></content>
    
    <summary type="html">
    
      &lt;h4 id=&quot;项目环境：&quot;&gt;&lt;a href=&quot;#项目环境：&quot; class=&quot;headerlink&quot; title=&quot;项目环境：&quot;&gt;&lt;/a&gt;项目环境：&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;spring-boot&lt;/li&gt;
&lt;li&gt;maven多模块项目&lt;/li&gt;
&lt;li&gt;需要引入的外部jar包&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;目录结构图如下：&quot;&gt;&lt;a href=&quot;#目录结构图如下：&quot; class=&quot;headerlink&quot; title=&quot;目录结构图如下：&quot;&gt;&lt;/a&gt;目录结构图如下：&lt;/h4&gt;&lt;p&gt;&lt;img src=&quot;https://img-blog.csdnimg.cn/20200221205222477.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0OTk3OTA2,size_16,color_FFFFFF,t_70&quot; alt=&quot;目录结构图&quot;&gt;&lt;br&gt;
    
    </summary>
    
    
      <category term="springboot" scheme="https://janche.github.io/tags/springboot/"/>
    
      <category term="引入外部依赖" scheme="https://janche.github.io/tags/%E5%BC%95%E5%85%A5%E5%A4%96%E9%83%A8%E4%BE%9D%E8%B5%96/"/>
    
  </entry>
  
  <entry>
    <title>UTC、GMT、时间戳之间的关系</title>
    <link href="https://janche.github.io/2020/03/08/UTC%E3%80%81GMT%E3%80%81%E6%97%B6%E9%97%B4%E6%88%B3%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB/"/>
    <id>https://janche.github.io/2020/03/08/UTC、GMT、时间戳之间的关系/</id>
    <published>2020-03-08T14:08:10.000Z</published>
    <updated>2020-03-08T14:10:50.166Z</updated>
    
    <content type="html"><![CDATA[<h3 id="时区"><a href="#时区" class="headerlink" title="时区"></a>时区</h3><p>世界时区的划分以本初子午线为标准，向东12个时区，向西12个时区，子午线所在区为0时区，东十二区和西十二区重合，所以一共是24个时区。</p><h3 id="GMT和UTC"><a href="#GMT和UTC" class="headerlink" title="GMT和UTC"></a>GMT和UTC</h3><p><strong>GMT：</strong> 即格林威治时间（Greenwich Mean Time），也是0时区的标准时间。指太阳横穿格林威治子午线（本初子午线）时的时间。但由于地球自转不均匀不规则，导致GMT不精确，现在已经不再作为世界标准时间使用。<br><strong>UTC：</strong> 即协调世界时间（Coordinated Universal Time）。UTC是以原子时秒长为基础，在时刻上尽量接近于GMT的一种时间计量系统。UTC现在作为世界标准时间使用。</p><p>所以，<code>UTC</code>与<code>GMT</code>基本上等同，误差不超过0.9秒。<br><a id="more"></a></p><h3 id="时间戳"><a href="#时间戳" class="headerlink" title="时间戳"></a>时间戳</h3><p><strong>UNIX时间戳</strong>：是从UTC时间1970年1月1日起到现在的秒数，不考虑闰秒，一天有86400秒，它是和时区无关的，无论在地球上的那个角落，同一时刻，UNIX时间戳都是一样的，计算机的本地时间就是根据  Unix时间戳 + 时区差 转换而来的。</p><h3 id="本地时间"><a href="#本地时间" class="headerlink" title="本地时间"></a>本地时间</h3><p><strong>本地时间 = UTC + 时区差</strong><br>时区差：东为正，西为负。在此，把东八区时区差记为 +0800， UTC 是标准时间参照，GMT（格林威治时间）、CST（北京时间）、PST（太平洋时间）等等是具体的时区，兑换如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">GMT: UTC +0    =    GMT: GMT +0</span><br><span class="line">CST: UTC +8    =    CST: GMT +8</span><br><span class="line">PST: UTC -8    =    PST: GMT -8</span><br></pre></td></tr></table></figure></p><p><strong>总结</strong>：</p><ol><li>涉及到多个时区的转换，统一使用unix时间戳存储或交互，或者使用带有时区信息的字符串。</li><li>尽量在上层的代码层面修改时区配置，不要修改系统或软件的配置，防止其他程序因为修改受到影响。</li></ol>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;时区&quot;&gt;&lt;a href=&quot;#时区&quot; class=&quot;headerlink&quot; title=&quot;时区&quot;&gt;&lt;/a&gt;时区&lt;/h3&gt;&lt;p&gt;世界时区的划分以本初子午线为标准，向东12个时区，向西12个时区，子午线所在区为0时区，东十二区和西十二区重合，所以一共是24个时区。&lt;/p&gt;
&lt;h3 id=&quot;GMT和UTC&quot;&gt;&lt;a href=&quot;#GMT和UTC&quot; class=&quot;headerlink&quot; title=&quot;GMT和UTC&quot;&gt;&lt;/a&gt;GMT和UTC&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;GMT：&lt;/strong&gt; 即格林威治时间（Greenwich Mean Time），也是0时区的标准时间。指太阳横穿格林威治子午线（本初子午线）时的时间。但由于地球自转不均匀不规则，导致GMT不精确，现在已经不再作为世界标准时间使用。&lt;br&gt;&lt;strong&gt;UTC：&lt;/strong&gt; 即协调世界时间（Coordinated Universal Time）。UTC是以原子时秒长为基础，在时刻上尽量接近于GMT的一种时间计量系统。UTC现在作为世界标准时间使用。&lt;/p&gt;
&lt;p&gt;所以，&lt;code&gt;UTC&lt;/code&gt;与&lt;code&gt;GMT&lt;/code&gt;基本上等同，误差不超过0.9秒。&lt;br&gt;
    
    </summary>
    
    
      <category term="时区" scheme="https://janche.github.io/tags/%E6%97%B6%E5%8C%BA/"/>
    
  </entry>
  
  <entry>
    <title>Java实现文件的压缩和在浏览器端下载</title>
    <link href="https://janche.github.io/2020/03/08/Java%E5%AE%9E%E7%8E%B0%E6%96%87%E4%BB%B6%E7%9A%84%E5%8E%8B%E7%BC%A9%E5%92%8C%E5%9C%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E7%AB%AF%E4%B8%8B%E8%BD%BD/"/>
    <id>https://janche.github.io/2020/03/08/Java实现文件的压缩和在浏览器端下载/</id>
    <published>2020-03-08T14:05:14.000Z</published>
    <updated>2020-03-08T14:06:21.643Z</updated>
    
    <content type="html"><![CDATA[<h3 id="压缩为zip文件"><a href="#压缩为zip文件" class="headerlink" title="压缩为zip文件"></a>压缩为zip文件</h3><h4 id="1-通过java程序输出文件"><a href="#1-通过java程序输出文件" class="headerlink" title="1. 通过java程序输出文件"></a>1. 通过java程序输出文件</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 功能:压缩多个文件成一个zip文件</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> srcfile：源文件列表</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> zipfile：压缩后的文件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">zipFiles</span><span class="params">(File[] srcfile, File zipfile)</span> </span>&#123;</span><br><span class="line"><span class="keyword">byte</span>[] buf = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line"><span class="comment">//ZipOutputStream类：完成文件或文件夹的压缩</span></span><br><span class="line">ZipOutputStream out = <span class="keyword">new</span> ZipOutputStream(<span class="keyword">new</span> FileOutputStream(zipfile));</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; srcfile.length; i++) &#123;</span><br><span class="line">FileInputStream in = <span class="keyword">new</span> FileInputStream(srcfile[i]);</span><br><span class="line"><span class="comment">// 给列表中的文件单独命名</span></span><br><span class="line">out.putNextEntry(<span class="keyword">new</span> ZipEntry(srcfile[i].getName()));</span><br><span class="line"><span class="keyword">int</span> len;</span><br><span class="line"><span class="keyword">while</span> ((len = in.read(buf)) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">out.write(buf, <span class="number">0</span>, len);</span><br><span class="line">&#125;</span><br><span class="line">out.closeEntry();</span><br><span class="line">in.close();</span><br><span class="line">&#125;</span><br><span class="line">out.close();</span><br><span class="line">System.out.println(<span class="string">"压缩完成."</span>);</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">e.printStackTrace();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><a id="more"></a><h4 id="2-1-通过浏览器下载文件-返回文件流-方式一"><a href="#2-1-通过浏览器下载文件-返回文件流-方式一" class="headerlink" title="2.1 通过浏览器下载文件(返回文件流) - 方式一"></a>2.1 通过浏览器下载文件(返回文件流) - 方式一</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 功能:压缩多个文件，输出压缩后的zip文件流</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> srcfile：源文件列表</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> zipFileName：压缩后的文件名</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@GetMapping</span>(value = <span class="string">"/downzip"</span>)</span><br><span class="line"><span class="keyword">public</span> ResponseEntity&lt;<span class="keyword">byte</span>[]&gt; zipFiles(File[] srcfile, String zipFileName) &#123;</span><br><span class="line">    <span class="keyword">byte</span>[] buf = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];</span><br><span class="line">    <span class="comment">// 获取输出流</span></span><br><span class="line">    ByteArrayOutputStream bos = <span class="keyword">new</span> ByteArrayOutputStream();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// ZipOutputStream类：完成文件或文件夹的压缩</span></span><br><span class="line">        ZipOutputStream out = <span class="keyword">new</span> ZipOutputStream(bos);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; srcfile.length; i++) &#123;</span><br><span class="line">        <span class="comment">// 此处可用任意其他输入流将FileInputStream取代,outputStream为其他步骤的输出流</span></span><br><span class="line">        <span class="comment">// ByteArrayInputStream in = new ByteArrayInputStream(outputStream.toByteArray());</span></span><br><span class="line">            FileInputStream in = <span class="keyword">new</span> FileInputStream(srcfile[i]);</span><br><span class="line">            <span class="comment">// 给列表中的文件单独命名</span></span><br><span class="line">            out.putNextEntry(<span class="keyword">new</span> ZipEntry(srcfile[i].getName()));</span><br><span class="line">            <span class="keyword">int</span> len;</span><br><span class="line">            <span class="keyword">while</span> ((len = in.read(buf)) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                out.write(buf, <span class="number">0</span>, len);</span><br><span class="line">            &#125;</span><br><span class="line">            out.closeEntry();</span><br><span class="line">            in.close();</span><br><span class="line">        &#125;</span><br><span class="line">        out.close();</span><br><span class="line">        bos.close();</span><br><span class="line">        System.out.println(<span class="string">"压缩完成."</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 设置http响应头</span></span><br><span class="line">    HttpHeaders header = <span class="keyword">new</span> HttpHeaders();</span><br><span class="line">    header.add(<span class="string">"Content-Disposition"</span>, <span class="string">"attachment;filename="</span> + zipFileName + <span class="string">".zip"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> ResponseEntity&lt;<span class="keyword">byte</span>[]&gt;(bos.toByteArray(), header, HttpStatus.CREATED);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-2-通过浏览器下载文件-返回文件流-方式二"><a href="#2-2-通过浏览器下载文件-返回文件流-方式二" class="headerlink" title="2.2 通过浏览器下载文件(返回文件流) - 方式二"></a>2.2 通过浏览器下载文件(返回文件流) - 方式二</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 功能:压缩多个文件，输出压缩后的zip文件流</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> srcfile：源文件列表</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> zipFileName：压缩后的文件名</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> response: Http响应</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@GetMapping</span>(value = <span class="string">"/downzip"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">zipFiles</span><span class="params">(File[] srcfile, String zipFileName, HttpServletResponse response)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">byte</span>[] buf = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];</span><br><span class="line">    <span class="comment">// 获取输出流</span></span><br><span class="line">    BufferedOutputStream bos = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        bos = <span class="keyword">new</span> BufferedOutputStream(response.getOutputStream());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        response.reset(); <span class="comment">// 重点突出</span></span><br><span class="line">        <span class="comment">// 不同类型的文件对应不同的MIME类型</span></span><br><span class="line">        response.setContentType(<span class="string">"application/x-msdownload"</span>);</span><br><span class="line">        response.setCharacterEncoding(<span class="string">"utf-8"</span>);</span><br><span class="line">        response.setHeader(<span class="string">"Content-disposition"</span>, <span class="string">"attachment;filename="</span> + zipFileName + <span class="string">".zip"</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// ZipOutputStream类：完成文件或文件夹的压缩</span></span><br><span class="line">        ZipOutputStream out = <span class="keyword">new</span> ZipOutputStream(bos);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; srcfile.length; i++) &#123;</span><br><span class="line">            FileInputStream in = <span class="keyword">new</span> FileInputStream(srcfile[i]);</span><br><span class="line">            <span class="comment">// 给列表中的文件单独命名</span></span><br><span class="line">            out.putNextEntry(<span class="keyword">new</span> ZipEntry(srcfile[i].getName()));</span><br><span class="line">            <span class="keyword">int</span> len;</span><br><span class="line">            <span class="keyword">while</span> ((len = in.read(buf)) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                out.write(buf, <span class="number">0</span>, len);</span><br><span class="line">            &#125;</span><br><span class="line">            out.closeEntry();</span><br><span class="line">            in.close();</span><br><span class="line">        &#125;</span><br><span class="line">        out.close();</span><br><span class="line">        bos.close();</span><br><span class="line">        System.out.println(<span class="string">"压缩完成."</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>以上两种方式，都是以输出文件流的形式，通过浏览器端进行下载，不同的是，第一种将流直接通过接口返回，第二种也是比较常见的一种，将文件流通过response进行输出，两种方式均可，这里留存记录下。</strong></p></blockquote><h3 id="解压缩zip文件"><a href="#解压缩zip文件" class="headerlink" title="解压缩zip文件"></a>解压缩zip文件</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 功能:解压缩</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> zipfile：需要解压缩的文件</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> descDir：解压后的目标目录</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">unZipFiles</span><span class="params">(File zipfile, String descDir)</span> </span>&#123;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">ZipFile zf = <span class="keyword">new</span> ZipFile(zipfile);</span><br><span class="line"><span class="keyword">for</span> (Enumeration entries = zf.entries(); entries.hasMoreElements();) &#123;</span><br><span class="line">ZipEntry entry = (ZipEntry) entries.nextElement();</span><br><span class="line">String zipEntryName = entry.getName();</span><br><span class="line">InputStream in = zf.getInputStream(entry);</span><br><span class="line">OutputStream out = <span class="keyword">new</span> FileOutputStream(descDir + zipEntryName);</span><br><span class="line"><span class="keyword">byte</span>[] buf1 = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">1024</span>];</span><br><span class="line"><span class="keyword">int</span> len;</span><br><span class="line"><span class="keyword">while</span> ((len = in.read(buf1)) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">out.write(buf1, <span class="number">0</span>, len);</span><br><span class="line">&#125;</span><br><span class="line">in.close();</span><br><span class="line">out.close();</span><br><span class="line">System.out.println(<span class="string">"解压缩完成."</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">e.printStackTrace();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;压缩为zip文件&quot;&gt;&lt;a href=&quot;#压缩为zip文件&quot; class=&quot;headerlink&quot; title=&quot;压缩为zip文件&quot;&gt;&lt;/a&gt;压缩为zip文件&lt;/h3&gt;&lt;h4 id=&quot;1-通过java程序输出文件&quot;&gt;&lt;a href=&quot;#1-通过java程序输出文件&quot; class=&quot;headerlink&quot; title=&quot;1. 通过java程序输出文件&quot;&gt;&lt;/a&gt;1. 通过java程序输出文件&lt;/h4&gt;&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;25&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;26&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;27&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;/**&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt; * 功能:压缩多个文件成一个zip文件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt; * &lt;span class=&quot;doctag&quot;&gt;@param&lt;/span&gt; srcfile：源文件列表&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt; * &lt;span class=&quot;doctag&quot;&gt;@param&lt;/span&gt; zipfile：压缩后的文件&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt; */&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;zipFiles&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(File[] srcfile, File zipfile)&lt;/span&gt; &lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&lt;span class=&quot;keyword&quot;&gt;byte&lt;/span&gt;[] buf = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;byte&lt;/span&gt;[&lt;span class=&quot;number&quot;&gt;1024&lt;/span&gt;];&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&lt;span class=&quot;keyword&quot;&gt;try&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;		&lt;span class=&quot;comment&quot;&gt;//ZipOutputStream类：完成文件或文件夹的压缩&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;		ZipOutputStream out = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; ZipOutputStream(&lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; FileOutputStream(zipfile));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;		&lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; (&lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; i = &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;; i &amp;lt; srcfile.length; i++) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;			FileInputStream in = &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; FileInputStream(srcfile[i]);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;			&lt;span class=&quot;comment&quot;&gt;// 给列表中的文件单独命名&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;			out.putNextEntry(&lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; ZipEntry(srcfile[i].getName()));&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;			&lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; len;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;			&lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt; ((len = in.read(buf)) &amp;gt; &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;				out.write(buf, &lt;span class=&quot;number&quot;&gt;0&lt;/span&gt;, len);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;			&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;			out.closeEntry();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;			in.close();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;		&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;		out.close();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;		System.out.println(&lt;span class=&quot;string&quot;&gt;&quot;压缩完成.&quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&amp;#125; &lt;span class=&quot;keyword&quot;&gt;catch&lt;/span&gt; (Exception e) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;		e.printStackTrace();&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="文件下载" scheme="https://janche.github.io/tags/%E6%96%87%E4%BB%B6%E4%B8%8B%E8%BD%BD/"/>
    
      <category term="zip多文件压缩" scheme="https://janche.github.io/tags/zip%E5%A4%9A%E6%96%87%E4%BB%B6%E5%8E%8B%E7%BC%A9/"/>
    
  </entry>
  
  <entry>
    <title>spring-boot集成mybatis通用mapper和pagehelper</title>
    <link href="https://janche.github.io/2019/10/07/spring-boot%E9%9B%86%E6%88%90mybatis%E9%80%9A%E7%94%A8mapper%E5%92%8Cpagehelper/"/>
    <id>https://janche.github.io/2019/10/07/spring-boot集成mybatis通用mapper和pagehelper/</id>
    <published>2019-10-07T11:23:06.000Z</published>
    <updated>2019-10-07T11:24:48.175Z</updated>
    
    <content type="html"><![CDATA[<h3 id="1-项目所需依赖"><a href="#1-项目所需依赖" class="headerlink" title="1. 项目所需依赖"></a>1. 项目所需依赖</h3><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- mybatis 依赖 --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">&lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;</span><br><span class="line">&lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;</span><br><span class="line">&lt;version&gt;2.0.1&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- mybatis pagehelper 分页插件 --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">&lt;groupId&gt;com.github.pagehelper&lt;/groupId&gt;</span><br><span class="line">&lt;artifactId&gt;pagehelper-spring-boot-starter&lt;/artifactId&gt;</span><br><span class="line">&lt;version&gt;1.2.10&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- mybatis 通用mapper --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">&lt;groupId&gt;tk.mybatis&lt;/groupId&gt;</span><br><span class="line">&lt;artifactId&gt;mapper-spring-boot-starter&lt;/artifactId&gt;</span><br><span class="line">&lt;version&gt;2.1.5&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><a id="more"></a><h3 id="2-yml文件中的配置"><a href="#2-yml文件中的配置" class="headerlink" title="2.  yml文件中的配置"></a>2.  <code>yml</code>文件中的配置</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">###  Pagehelper  ###</span><br><span class="line">pagehelper:</span><br><span class="line">  helperDialect: mysql</span><br><span class="line">  reasonable: <span class="keyword">true</span></span><br><span class="line">  supportMethodsArguments: <span class="keyword">true</span></span><br><span class="line">  params: count=countSql</span><br><span class="line">  </span><br><span class="line">###   Mybatis Config  ###</span><br><span class="line">mybatis:</span><br><span class="line">  typeAliasesPackage: com.example.janche.**.domain   # 实体类的包路径</span><br><span class="line">  mapperLocations: classpath:mapper<span class="comment">/**/*.xml                # mapper 的文件路径</span></span><br><span class="line"><span class="comment">  </span></span><br><span class="line"><span class="comment">###   通用 Mapper    ###</span></span><br><span class="line"><span class="comment">mapper:</span></span><br><span class="line"><span class="comment">  IDENTITY: mysql</span></span><br><span class="line"><span class="comment">  notEmpty: false</span></span><br><span class="line"><span class="comment">  mappers:                                                           # 此处可以配置多个通用mapper，因为很多时候，一个是无法满足需求的，后文会介绍到</span></span><br><span class="line"><span class="comment">    - com.example.janche.common.core.Mapper</span></span><br><span class="line"><span class="comment">    - com.example.janche.common.core.TkMapper</span></span><br></pre></td></tr></table></figure><h3 id="3-继承通用-mapper"><a href="#3-继承通用-mapper" class="headerlink" title="3. 继承通用 mapper"></a>3. 继承通用 <code>mapper</code></h3><p>新建两个通用 <code>Mapper</code>的接口 ，注意此Mapper的路径需要同UserMapper等业务Mapper的位置区分开。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.example.janche.common.core;</span><br><span class="line"><span class="keyword">import</span> tk.mybatis.mapper.common.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Mapper</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span></span></span><br><span class="line"><span class="class">        <span class="title">BaseMapper</span>&lt;<span class="title">T</span>&gt;,</span></span><br><span class="line"><span class="class">        <span class="title">MySqlMapper</span>&lt;<span class="title">T</span>&gt;,</span></span><br><span class="line"><span class="class">        <span class="title">IdsMapper</span>&lt;<span class="title">T</span>&gt;,</span></span><br><span class="line"><span class="class">        <span class="title">ConditionMapper</span>&lt;<span class="title">T</span>&gt;,</span></span><br><span class="line"><span class="class">        <span class="title">ExampleMapper</span>&lt;<span class="title">T</span>&gt;</span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.example.janche.common.core;</span><br><span class="line"><span class="keyword">import</span> tk.mybatis.mapper.common.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">TkMapper</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span></span></span><br><span class="line"><span class="class">        <span class="title">BaseMapper</span>&lt;<span class="title">T</span>&gt;,</span></span><br><span class="line"><span class="class">        <span class="title">MySqlMapper</span>&lt;<span class="title">T</span>&gt;,</span></span><br><span class="line"><span class="class">        <span class="title">ConditionMapper</span>&lt;<span class="title">T</span>&gt;,</span></span><br><span class="line"><span class="class">        <span class="title">ExampleMapper</span>&lt;<span class="title">T</span>&gt;</span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>上面两个mapper中，第一个多继承了IdsMapper，其余都是一样的，那为什么需要TkMapper呢，因为继承了IdsMapper的业务Mapper，其实体类中必须且只能有一个主键ID，联合主键或者没有主键都不行，否则启动会报错。</strong></p><h3 id="4-通用Mapper的使用"><a href="#4-通用Mapper的使用" class="headerlink" title="4. 通用Mapper的使用"></a>4. 通用Mapper的使用</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.example.janche.user.dao;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.example.janche.common.core.Mapper;</span><br><span class="line"><span class="keyword">import</span> com.example.janche.user.domain.User;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserMapper</span> <span class="keyword">extends</span> <span class="title">Mapper</span>&lt;<span class="title">User</span>&gt; </span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.example.janche.user.dao;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.example.janche.common.core.TkMapper;</span><br><span class="line"><span class="keyword">import</span> com.example.janche.user.domain.UserAndRole;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserAndRoleMapper</span> <span class="keyword">extends</span> <span class="title">TkMapper</span>&lt;<span class="title">UserAndRole</span>&gt; </span>&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-PageHelper的使用"><a href="#4-PageHelper的使用" class="headerlink" title="4. PageHelper的使用"></a>4. PageHelper的使用</h3><h4 id="4-1-获取用户列表："><a href="#4-1-获取用户列表：" class="headerlink" title="4.1 获取用户列表："></a>4.1 获取用户列表：</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserController 层接口：</span></span><br><span class="line"><span class="meta">@GetMapping</span>(<span class="string">"/list"</span>)</span><br><span class="line"><span class="function"><span class="keyword">public</span> RestResult <span class="title">list</span><span class="params">(@ApiParam(value = <span class="string">"分页信息"</span>)</span> PageParam pageParam,</span></span><br><span class="line"><span class="function">                       @<span class="title">ApiParam</span><span class="params">(value = <span class="string">"筛选条件"</span>)</span> String query) </span>&#123;</span><br><span class="line">    List&lt;User&gt; list = userService.list(pageParam, query);</span><br><span class="line">    PageInfo pageInfo = <span class="keyword">new</span> PageInfo(list);</span><br><span class="line">    <span class="keyword">return</span> ResultGenerator.genSuccessResult(pageInfo);</span><br><span class="line">&#125;</span><br><span class="line">----------------------------------------------------------------------------</span><br><span class="line"><span class="comment">// UserServiceImpl 实现类：</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> UserMapper userMapper; </span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;User&gt; <span class="title">list</span><span class="params">(PageParam pageParam, String query)</span> </span>&#123;</span><br><span class="line">     Example example = <span class="keyword">new</span> Example(User.class);</span><br><span class="line">     example.or().andLike(<span class="string">"username"</span>, <span class="string">"%"</span>+query+<span class="string">"%"</span>);</span><br><span class="line">     example.or().andLike(<span class="string">"actualName"</span>, <span class="string">"%"</span>+query+<span class="string">"%"</span>);</span><br><span class="line">     example.or().andLike(<span class="string">"sex"</span>, <span class="string">"%"</span>+query+<span class="string">"%"</span>);</span><br><span class="line">     </span><br><span class="line"> <span class="comment">// PageHelper 只须在查询语句真正执行前调用即可完成分页</span></span><br><span class="line">     PageHelper.startPage(pageParam.getPage(), pageParam.getSize(), pageParam.getOrderBy());</span><br><span class="line">     <span class="keyword">return</span> userMapper.selectByExample(example);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p><strong>到此，整个接口的调用，不用写一句SQL代码，只要是单表操作，均可通过pagehelper+通用mapper的形式实现。</strong></p><h4 id="4-2-批量删除用户"><a href="#4-2-批量删除用户" class="headerlink" title="4.2 批量删除用户"></a>4.2 批量删除用户</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// UserServiceImpl 实现类：</span></span><br><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> UserMapper userMapper;  <span class="comment">// 继承自Mapper</span></span><br><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> UserAndRoleMapper userAndRoleMapper; <span class="comment">// 继承自TkMapper</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 批量删除用户</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> ids</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">deleteUser</span><span class="params">(String ids)</span> </span>&#123;</span><br><span class="line">    List&lt;String&gt; Ids = Arrays.stream(ids.split(<span class="string">","</span>)).collect(Collectors.toList());</span><br><span class="line">    <span class="comment">// 删除用户和角色的关联</span></span><br><span class="line">    Example example = <span class="keyword">new</span> Example(UserAndRole.class);</span><br><span class="line">    example.and().andIn(<span class="string">"userId"</span>, Ids);</span><br><span class="line">    userAndRoleMapper.deleteByExample(example);</span><br><span class="line">    <span class="comment">// 删除用户</span></span><br><span class="line">    userMapper.deleteByIds(ids);</span><br><span class="line">&#125;</span><br><span class="line">------------------------------------------------------------------------------------</span><br><span class="line"><span class="comment">// UserAndRole 实体类</span></span><br><span class="line"><span class="meta">@Table</span>(name = <span class="string">"user_role"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserAndRole</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Column</span>(name = <span class="string">"role_id"</span>)</span><br><span class="line">    <span class="keyword">private</span> Long roleId;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Column</span>(name = <span class="string">"user_id"</span>)</span><br><span class="line">    <span class="keyword">private</span> Long userId;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>上面的例子使用到了TkMapper，因为UserAndRole实体类中没有唯一主键ID，它是联合主键，IdsMapper中会根据主键Ids批量删除，因此UserAndRoleMapple不能继承 继承过IdsMapper的 Mapper。</strong></p><blockquote><p><strong>一套整合完善的springboot2脚手架工程，源码地址如下：<a href="https://github.com/Janche/springboot-security-project" target="_blank" rel="external">GitHub项目地址</a></strong></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;1-项目所需依赖&quot;&gt;&lt;a href=&quot;#1-项目所需依赖&quot; class=&quot;headerlink&quot; title=&quot;1. 项目所需依赖&quot;&gt;&lt;/a&gt;1. 项目所需依赖&lt;/h3&gt;&lt;figure class=&quot;highlight&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;!-- mybatis 依赖 --&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&amp;lt;groupId&amp;gt;org.mybatis.spring.boot&amp;lt;/groupId&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&amp;lt;artifactId&amp;gt;mybatis-spring-boot-starter&amp;lt;/artifactId&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&amp;lt;version&amp;gt;2.0.1&amp;lt;/version&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;!-- mybatis pagehelper 分页插件 --&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;groupId&amp;gt;com.github.pagehelper&amp;lt;/groupId&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&amp;lt;artifactId&amp;gt;pagehelper-spring-boot-starter&amp;lt;/artifactId&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&amp;lt;version&amp;gt;1.2.10&amp;lt;/version&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;!-- mybatis 通用mapper --&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;groupId&amp;gt;tk.mybatis&amp;lt;/groupId&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&amp;lt;artifactId&amp;gt;mapper-spring-boot-starter&amp;lt;/artifactId&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&amp;lt;version&amp;gt;2.1.5&amp;lt;/version&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="springboot" scheme="https://janche.github.io/tags/springboot/"/>
    
      <category term="mybatis" scheme="https://janche.github.io/tags/mybatis/"/>
    
      <category term="pagehelper" scheme="https://janche.github.io/tags/pagehelper/"/>
    
  </entry>
  
  <entry>
    <title>基于Spring-Security-OAuth2的SSO单点登录（客户端）</title>
    <link href="https://janche.github.io/2019/10/07/%E5%9F%BA%E4%BA%8ESpring-Security-OAuth2%E7%9A%84SSO%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95%EF%BC%88%E5%AE%A2%E6%88%B7%E7%AB%AF%EF%BC%89/"/>
    <id>https://janche.github.io/2019/10/07/基于Spring-Security-OAuth2的SSO单点登录（客户端）/</id>
    <published>2019-10-07T11:21:10.000Z</published>
    <updated>2019-10-07T11:25:10.574Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>单点登录（服务端）：<a href="https://blog.csdn.net/qq_34997906/article/details/97007709" target="_blank" rel="external">https://blog.csdn.net/qq_34997906/article/details/97007709</a></p></blockquote><h3 id="1-缘起"><a href="#1-缘起" class="headerlink" title="1. 缘起"></a>1. 缘起</h3><p>为什么要把客户端单独拿出来写呢 ？<br>博主也参考了网上很多写单点登录的，但基本上都是大同小异，在客户端的自身权限校验 和 单点退出 均未做处理，显然并不满足实际的业务开发。</p><h3 id="2-核心流程"><a href="#2-核心流程" class="headerlink" title="2. 核心流程"></a>2. 核心流程</h3><blockquote><p>客户端登录：用户访问客户端，客户端 security 发现此请求的用户未登录，于是将请求重定向到服务端认证，服务端检测到此请求的用户未登录，则将此请求跳转到服务端提供的登录页面(前后端分离则是前端登录地址，否则为服务端内置的登录页面)，登录成功后，服务端将系统的权限信息（为了减轻服务端的访问压力）和用户的特有标志（如用户名，记录此用户的登录状态）存入redis，然后服务端会跳回到用户第一次访问客户端的页面。</p></blockquote><blockquote><p>客户端URL的拦截：每次请求到来时，客户端都去Redis中去取认证中心存入的权限信息和用户特有的登录标志，权限信息只是为了匹配此登录用户是否有权利访问此接口，用户的特有标志则是为了检测该用户是否在其他客户端退出了，如若没有取到，则重定向到服务端的登录页面。</p></blockquote><a id="more"></a><h3 id="3-所需依赖"><a href="#3-所需依赖" class="headerlink" title="3. 所需依赖"></a>3. 所需依赖</h3><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&lt;!--  集成 SSO 依赖  --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.security.oauth.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-security-oauth2-autoconfigure&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.1.3.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line">&lt;!--  redis 所需 依赖  --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.1.4.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><h3 id="4-配置介绍"><a href="#4-配置介绍" class="headerlink" title="4. 配置介绍"></a>4. 配置介绍</h3><h4 id="4-1-security-核心配置"><a href="#4-1-security-核心配置" class="headerlink" title="4.1 security 核心配置"></a>4.1 security 核心配置</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableOAuth</span>2Sso</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ClientWebsecurityConfigurer</span> <span class="keyword">extends</span> <span class="title">WebSecurityConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"urlFilterInvocationSecurityMetadataSource"</span>)</span><br><span class="line">    UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"urlAccessDecisionManager"</span>)</span><br><span class="line">    AccessDecisionManager urlAccessDecisionManager;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"securityAccessDeniedHandler"</span>)</span><br><span class="line">    <span class="keyword">private</span> AccessDeniedHandler securityAccessDeniedHandler;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"securityAuthenticationEntryPoint"</span>)</span><br><span class="line">    <span class="keyword">private</span> AuthenticationEntryPoint securityAuthenticationEntryPoint;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;auth-server&#125;"</span>)</span><br><span class="line">    <span class="keyword">public</span> String auth_server;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> RestTemplate <span class="title">restTemplate</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">     <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 放行静态资源</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(WebSecurity web)</span> </span>&#123;</span><br><span class="line">        web.ignoring().antMatchers(</span><br><span class="line">                <span class="string">"/css/**"</span>,</span><br><span class="line">                 <span class="string">"/js/**"</span>,</span><br><span class="line">                 <span class="string">"/favicon.ico"</span>,</span><br><span class="line">                  <span class="string">"/static/**"</span>,</span><br><span class="line">                  <span class="string">"/error"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        http</span><br><span class="line">                .authorizeRequests()</span><br><span class="line">                .antMatchers(<span class="string">"/"</span>, <span class="string">"/login"</span>).permitAll()</span><br><span class="line">                .anyRequest().authenticated()</span><br><span class="line">                .withObjectPostProcessor(urlObjectPostProcessor());</span><br><span class="line"></span><br><span class="line">        http</span><br><span class="line">                .exceptionHandling()</span><br><span class="line">                .authenticationEntryPoint(securityAuthenticationEntryPoint)</span><br><span class="line">                .accessDeniedHandler(securityAccessDeniedHandler);</span><br><span class="line"></span><br><span class="line">        http.</span><br><span class="line">                logout()</span><br><span class="line">                .logoutSuccessUrl(auth_server + <span class="string">"/logout"</span>)</span><br><span class="line">                .deleteCookies(<span class="string">"JSESSIONID"</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 不加会导致退出 不支持GET方式</span></span><br><span class="line">        http.csrf().disable();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ObjectPostProcessor <span class="title">urlObjectPostProcessor</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ObjectPostProcessor&lt;FilterSecurityInterceptor&gt;() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> &lt;O extends FilterSecurityInterceptor&gt; <span class="function">O <span class="title">postProcess</span><span class="params">(O o)</span> </span>&#123;</span><br><span class="line">                o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);</span><br><span class="line">                o.setAccessDecisionManager(urlAccessDecisionManager);</span><br><span class="line">                <span class="keyword">return</span> o;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>配置说明：</strong><br><code>.withObjectPostProcessor(urlObjectPostProcessor());</code> 此配置表示启用了<code>spring-security</code>的自定义校验，要实现URL的自定义校验，核心就是<code>urlFilterInvocationSecurityMetadataSource</code>,<code>urlAccessDecisionManager</code>这两个类，第一个类主要功能是 拿到 访问 此URL所需要的<code>GrantedAuthority</code>（即 需要哪些角色），第二个类主要功能是比较用户有的<code>GrantedAuthority</code>（用户拥有的角色）是否包含此URL需要的<code>GrantedAuthority</code>（角色组），只要有一个匹配上则允许访问，没有匹配上则表示没有权限。</p></blockquote><h4 id="4-2-自定义-FilterInvocationSecurityMetadataSource-的配置"><a href="#4-2-自定义-FilterInvocationSecurityMetadataSource-的配置" class="headerlink" title="4.2 自定义 FilterInvocationSecurityMetadataSource 的配置"></a>4.2 自定义 FilterInvocationSecurityMetadataSource 的配置</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> lirong</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@ClassName</span>: UrlFilterInvocationSecurityMetadataSource</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span>: 获取访问此URL所需要的角色集和</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2019-07-10 14:36</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span>(<span class="string">"urlFilterInvocationSecurityMetadataSource"</span>)</span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UrlFilterInvocationSecurityMetadataSource</span> <span class="keyword">implements</span> <span class="title">FilterInvocationSecurityMetadataSource</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Collection&lt;ConfigAttribute&gt; <span class="title">getAttributes</span><span class="params">(Object o)</span> <span class="keyword">throws</span> IllegalArgumentException </span>&#123;</span><br><span class="line"></span><br><span class="line">        HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取Redis中用户的登录标志 判断此用户有没有在其他客户端退出</span></span><br><span class="line">        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span><br><span class="line">        String username = (String) authentication.getPrincipal();</span><br><span class="line">        String isLogin = (String) redisTemplate.opsForValue().get(Constant.REDIS_PERM_KEY_PREFIX + username);</span><br><span class="line">        <span class="keyword">if</span>(StringUtils.isEmpty(isLogin))&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> AccountExpiredException(<span class="string">"用户已在其他客户端退出"</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取此URL需要的角色集合</span></span><br><span class="line">        List&lt;Map&lt;String, String[]&gt;&gt; menuMap = (List&lt;Map&lt;String, String[]&gt;&gt;) redisTemplate.opsForValue().get(Constant.REDIS_PERM_KEY_PREFIX);</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">null</span> != menuMap) &#123;</span><br><span class="line">            <span class="keyword">for</span> (Map&lt;String, String[]&gt; map : menuMap) &#123;</span><br><span class="line">                <span class="keyword">for</span> (String url : map.keySet()) &#123;</span><br><span class="line">                    String[] split = url.split(<span class="string">":"</span>);</span><br><span class="line">                    AntPathRequestMatcher antPathMatcher = <span class="keyword">new</span> AntPathRequestMatcher(split[<span class="number">0</span>], split[<span class="number">1</span>]);</span><br><span class="line">                    <span class="keyword">if</span>(antPathMatcher.matches(request))&#123;</span><br><span class="line">                        <span class="keyword">return</span> SecurityConfig.createList(map.get(url));</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 没有匹配上的资源，都是登录访问</span></span><br><span class="line">        <span class="keyword">return</span> SecurityConfig.createList(<span class="string">"ROLE_LOGIN"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Collection&lt;ConfigAttribute&gt; <span class="title">getAllConfigAttributes</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">supports</span><span class="params">(Class&lt;?&gt; aClass)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>为什么返回<code>ROLE_LOGIN</code> ？</strong><br><code>ROLE_LOGIN</code>，见名知意，只需要登录即可访问，最后返回只是为了给系统没有纳入权限表的URL加一层校验，当然，你也可以直接返回null，这样没有匹配上的URL访问将不受security的访问限制。</p></blockquote><h4 id="4-3-自定义-AccessDecisionManager的配置"><a href="#4-3-自定义-AccessDecisionManager的配置" class="headerlink" title="4.3 自定义 AccessDecisionManager的配置"></a>4.3 自定义 AccessDecisionManager的配置</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span>(<span class="string">"urlAccessDecisionManager"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UrlAccessDecisionManager</span> <span class="keyword">implements</span> <span class="title">AccessDecisionManager</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">decide</span><span class="params">(Authentication auth, Object o, Collection&lt;ConfigAttribute&gt; collection)</span> <span class="keyword">throws</span> AccessDeniedException, AuthenticationException </span>&#123;</span><br><span class="line">  </span><br><span class="line">        Collection&lt;? extends GrantedAuthority&gt; authorities = auth.getAuthorities();</span><br><span class="line">        Iterator&lt;ConfigAttribute&gt; iterator = collection.iterator();</span><br><span class="line">        <span class="keyword">while</span> (iterator.hasNext()) &#123;</span><br><span class="line">            ConfigAttribute ca = iterator.next();</span><br><span class="line">            <span class="comment">//当前请求需要的权限</span></span><br><span class="line">            String needRole = ca.getAttribute();</span><br><span class="line">            <span class="keyword">if</span> (<span class="string">"ROLE_LOGIN"</span>.equals(needRole)) &#123;</span><br><span class="line">                <span class="keyword">if</span> (auth <span class="keyword">instanceof</span> AnonymousAuthenticationToken) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> BadCredentialsException(<span class="string">"用户未登录"</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//当前用户所具有的权限</span></span><br><span class="line">            <span class="keyword">for</span> (GrantedAuthority authority : authorities) &#123;</span><br><span class="line">                <span class="keyword">if</span> (authority.getAuthority().equals(needRole)) &#123;</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> AccessDeniedException(<span class="string">"权限不足!"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">supports</span><span class="params">(ConfigAttribute configAttribute)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">supports</span><span class="params">(Class&lt;?&gt; aClass)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="4-4-用户未登录时的处理"><a href="#4-4-用户未登录时的处理" class="headerlink" title="4.4 用户未登录时的处理"></a>4.4 用户未登录时的处理</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 用户未登录时的处理</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> lirong</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2019-8-8 17:37:27</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span>(<span class="string">"securityAuthenticationEntryPoint"</span>)</span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SecurityAuthenticationEntryPoint</span> <span class="keyword">implements</span> <span class="title">AuthenticationEntryPoint</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Value</span>(<span class="string">"$&#123;auth-server&#125;"</span>)</span><br><span class="line"><span class="keyword">public</span> String auth_server;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">commence</span><span class="params">(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">log.info(<span class="string">"尚未登录:"</span> + authException.getMessage());</span><br><span class="line">response.sendRedirect(request.getContextPath() + <span class="string">"/login"</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>配置说明</strong><br>当在其他客户端退出清掉redis中数据时，此处会产生循环重定向无法跳转到登录页面的问题，我这边的处理是，当前端因为循环重定向拿不到响应时，就直接前端跳转到登录页面，重新登录，不知大家有没有更好的方式。</p></blockquote><h4 id="4-5-用户没有权限时的处理"><a href="#4-5-用户没有权限时的处理" class="headerlink" title="4.5 用户没有权限时的处理"></a>4.5 用户没有权限时的处理</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 用户访问没有权限资源的处理</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> lirong</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Component</span>(<span class="string">"securityAccessDeniedHandler"</span>)</span><br><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SecurityAccessDeniedHandler</span> <span class="keyword">implements</span> <span class="title">AccessDeniedHandler</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)</span></span>&#123;</span><br><span class="line">log.info(request.getRequestURL()+<span class="string">"没有权限"</span>);</span><br><span class="line">ResponseUtils.renderJson(request, response, ResultCode.LIMITED_AUTHORITY, <span class="keyword">null</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>ResponseUtils封装的是一些返回的JSON信息，包含跨域的请求头等。</strong></p></blockquote><h3 id="5-yml中-客户端的配置"><a href="#5-yml中-客户端的配置" class="headerlink" title="5. yml中 客户端的配置"></a>5. yml中 客户端的配置</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">auth-server: http:<span class="comment">//192.168.1.201:9999  // 认证中心的地址</span></span><br><span class="line">server:</span><br><span class="line">  port: <span class="number">8086</span></span><br><span class="line">  servlet:</span><br><span class="line">    session:</span><br><span class="line">      cookie:</span><br><span class="line">        name: UISESSION</span><br><span class="line"></span><br><span class="line">security:</span><br><span class="line">  oauth2:</span><br><span class="line">    client:</span><br><span class="line">      client-id: janche</span><br><span class="line">      client-secret: <span class="number">123456</span></span><br><span class="line">      user-authorization-uri: $&#123;auth-server&#125;/oauth/authorize</span><br><span class="line">      access-token-uri: $&#123;auth-server&#125;/oauth/token</span><br><span class="line">    resource:</span><br><span class="line">      jwt:</span><br><span class="line">        key-uri: $&#123;auth-server&#125;/oauth/token_key</span><br><span class="line">      userInfoUri: $&#123;auth-server&#125;/user/oauth/sso</span><br><span class="line">      token-info-uri: $&#123;auth-server&#125;/oauth/check_token</span><br><span class="line"></span><br><span class="line">spring:</span><br><span class="line">  #redis</span><br><span class="line">  redis:</span><br><span class="line">    database: <span class="number">0</span></span><br><span class="line">    # Redis服务器地址</span><br><span class="line">    host: <span class="number">192.168</span>.1.201</span><br><span class="line">    port: <span class="number">6379</span></span><br><span class="line">    password:</span><br><span class="line">    timeout: <span class="number">5000</span>ms</span><br><span class="line"></span><br><span class="line">    jedis:</span><br><span class="line">      pool:</span><br><span class="line">        # 连接池中的最大连接数</span><br><span class="line">        max-active: <span class="number">8</span></span><br><span class="line">        # 连接池中的最大空闲连接</span><br><span class="line">        max-idle: <span class="number">8</span></span><br><span class="line">        min-idle: <span class="number">0</span></span><br><span class="line">        max-wait: -<span class="number">1</span>ms</span><br></pre></td></tr></table></figure><h3 id="6-Controller"><a href="#6-Controller" class="headerlink" title="6. Controller"></a>6. Controller</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RestTemplate restTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Value</span>(<span class="string">"$&#123;auth-server&#125;"</span>)</span><br><span class="line">    <span class="keyword">public</span> String auth_server;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/normal"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">normal</span><span class="params">( )</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"normal permission test success !!!"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/medium"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">medium</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"mediumpermission test success !!!"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/admin"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">admin</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"admin permission test success !!!"</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/user"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> RestResult <span class="title">getLoginUser</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        String url = auth_server + <span class="string">"/user/oauth/sso"</span>;</span><br><span class="line">        String tokenValue = SecurityUtils.getJwtToken();</span><br><span class="line"></span><br><span class="line">        HttpHeaders headers = <span class="keyword">new</span> HttpHeaders();</span><br><span class="line">        headers.setContentType(MediaType.APPLICATION_JSON);</span><br><span class="line">        headers.set(<span class="string">"Authorization"</span>, <span class="string">"Bearer "</span> + tokenValue);</span><br><span class="line"></span><br><span class="line">        HttpEntity&lt;String&gt; entity = <span class="keyword">new</span> HttpEntity&lt;&gt;(headers);</span><br><span class="line">        SsoUser user = restTemplate.postForObject(url, entity, SsoUser.class);</span><br><span class="line">        <span class="keyword">return</span> ResultGenerator.genSuccessResult(user);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><strong>关于获取登录用户信息</strong><br>因为是<code>OAuth</code>客户端访问服务端，所以一定得带上服务端给颁发的<code>access_token</code>才能在服务端拿到用户数据，否则服务端无法识别，将标识此次请求为未登录，关于<code>yml</code> 中 <code>userInfoUri</code> 的配置，我也有点疑惑，官方文档也没有给出咋使用。</p></blockquote><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><p><strong>单点登录测试：</strong><br><img src="https://img-blog.csdnimg.cn/20190912170719382.gif" alt="单点登录测试"><br><strong>单点退出测试：</strong><br><img src="https://img-blog.csdnimg.cn/20190912170931389.gif" alt="单点退出"></p><p>项目源码：<a href="https://github.com/Janche/sso-oauth2-server" target="_blank" rel="external">单点登录服务端</a>  、<a href="https://github.com/Janche/sso-oauth2-client" target="_blank" rel="external">单点登录客户端</a></p><h3 id="参考博客"><a href="#参考博客" class="headerlink" title="参考博客"></a>参考博客</h3><p><a href="https://www.baeldung.com/sso-spring-security-oauth2" target="_blank" rel="external">https://www.baeldung.com/sso-spring-security-oauth2</a><br><a href="https://www.linzepeng.com/2018/10/31/sso-note1/" target="_blank" rel="external">https://www.linzepeng.com/2018/10/31/sso-note1/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;单点登录（服务端）：&lt;a href=&quot;https://blog.csdn.net/qq_34997906/article/details/97007709&quot;&gt;https://blog.csdn.net/qq_34997906/article/details/97007709&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;1-缘起&quot;&gt;&lt;a href=&quot;#1-缘起&quot; class=&quot;headerlink&quot; title=&quot;1. 缘起&quot;&gt;&lt;/a&gt;1. 缘起&lt;/h3&gt;&lt;p&gt;为什么要把客户端单独拿出来写呢 ？&lt;br&gt;博主也参考了网上很多写单点登录的，但基本上都是大同小异，在客户端的自身权限校验 和 单点退出 均未做处理，显然并不满足实际的业务开发。&lt;/p&gt;
&lt;h3 id=&quot;2-核心流程&quot;&gt;&lt;a href=&quot;#2-核心流程&quot; class=&quot;headerlink&quot; title=&quot;2. 核心流程&quot;&gt;&lt;/a&gt;2. 核心流程&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;客户端登录：用户访问客户端，客户端 security 发现此请求的用户未登录，于是将请求重定向到服务端认证，服务端检测到此请求的用户未登录，则将此请求跳转到服务端提供的登录页面(前后端分离则是前端登录地址，否则为服务端内置的登录页面)，登录成功后，服务端将系统的权限信息（为了减轻服务端的访问压力）和用户的特有标志（如用户名，记录此用户的登录状态）存入redis，然后服务端会跳回到用户第一次访问客户端的页面。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;客户端URL的拦截：每次请求到来时，客户端都去Redis中去取认证中心存入的权限信息和用户特有的登录标志，权限信息只是为了匹配此登录用户是否有权利访问此接口，用户的特有标志则是为了检测该用户是否在其他客户端退出了，如若没有取到，则重定向到服务端的登录页面。&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
      <category term="spring-security" scheme="https://janche.github.io/tags/spring-security/"/>
    
      <category term="OAuth2" scheme="https://janche.github.io/tags/OAuth2/"/>
    
      <category term="SSO" scheme="https://janche.github.io/tags/SSO/"/>
    
  </entry>
  
  <entry>
    <title>基于Spring Security + OAuth2的SSO单点登录（服务端）</title>
    <link href="https://janche.github.io/2019/10/07/%E5%9F%BA%E4%BA%8ESpring-Security-OAuth2%E7%9A%84SSO%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95%EF%BC%88%E6%9C%8D%E5%8A%A1%E7%AB%AF%EF%BC%89/"/>
    <id>https://janche.github.io/2019/10/07/基于Spring-Security-OAuth2的SSO单点登录（服务端）/</id>
    <published>2019-10-07T11:17:50.000Z</published>
    <updated>2019-10-07T11:25:22.090Z</updated>
    
    <content type="html"><![CDATA[<h3 id="相关技术"><a href="#相关技术" class="headerlink" title="相关技术"></a>相关技术</h3><blockquote><ul><li><h4 id="spring-security：-用于安全控制的权限框架"><a href="#spring-security：-用于安全控制的权限框架" class="headerlink" title="spring security： 用于安全控制的权限框架"></a>spring security： 用于安全控制的权限框架</h4></li><li><h4 id="OAuth2：-用于第三方登录认证授权的协议"><a href="#OAuth2：-用于第三方登录认证授权的协议" class="headerlink" title="OAuth2： 用于第三方登录认证授权的协议"></a>OAuth2： 用于第三方登录认证授权的协议</h4></li><li><h4 id="JWT：客户端和服务端通信的数据载体"><a href="#JWT：客户端和服务端通信的数据载体" class="headerlink" title="JWT：客户端和服务端通信的数据载体"></a>JWT：客户端和服务端通信的数据载体</h4></li></ul></blockquote><h3 id="传统登录"><a href="#传统登录" class="headerlink" title="传统登录"></a>传统登录</h3><p>登录web系统后将用户信息保存在session中，sessionId写入浏览器的cookie中，每次访问系统，浏览器自动携带此cookie，服务端根据此sessionId取到相应的session，若为空则表示登录已失效，不为空则表示用户已登录，不需要用户再次输入用户名密码。</p><h3 id="单点登录"><a href="#单点登录" class="headerlink" title="单点登录"></a>单点登录</h3><p>单点登录是一种多站点共享登录访问授权机制，访问用户只需要在一个站点登录就可以访问其它站点需要登录访问的资源(url)。用户在任意一个站点注销登录，则其它站点的登录状态也被注销。简而言之就是：一处登录，处处登录。一处注销，处处注销。<br>spring-security + OAuth2 完美解决了完全跨域的问题。<br><a id="more"></a><br><strong>单点登录时序图</strong><br><img src="https://img-blog.csdnimg.cn/20190723172934479.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0OTk3OTA2,size_16,color_FFFFFF,t_70" alt="单点登录认证流程"></p><blockquote><p><strong>看完上面的时序图，大家应该明白，使用OAuth2整合的单点登录本质上还是利用cookie + session的方式，虽然客户端和（服务端）认证中心交互采用的是JWT的方式，但浏览器和客户端还是采用cookie+session的方式。</strong></p></blockquote><h3 id="整合SSO认证中心"><a href="#整合SSO认证中心" class="headerlink" title="整合SSO认证中心"></a>整合SSO认证中心</h3><h4 id="1-引入核心依赖"><a href="#1-引入核心依赖" class="headerlink" title="1. 引入核心依赖"></a>1. 引入核心依赖</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">   &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.security.oauth.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-security-oauth2-autoconfigure&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.1.3.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><h4 id="2-Security的核心配置文件"><a href="#2-Security的核心配置文件" class="headerlink" title="2. Security的核心配置文件"></a>2. Security的核心配置文件</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableWebSecurity</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WebSecurityConfig</span> <span class="keyword">extends</span> <span class="title">WebSecurityConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"securityAuthenticationProvider"</span>)</span><br><span class="line">    <span class="keyword">private</span> AuthenticationProvider securityAuthenticationProvider;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"userLoginSuccessHandler"</span>)</span><br><span class="line">    <span class="keyword">private</span> AuthenticationSuccessHandler userLoginSuccessHandler;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"securityAuthenticationFailureHandler"</span>)</span><br><span class="line">    <span class="keyword">private</span> AuthenticationFailureHandler securityAuthenticationFailureHandler;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"securityLogoutSuccessHandler"</span>)</span><br><span class="line">    <span class="keyword">private</span> LogoutSuccessHandler securityLogoutSuccessHandler;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"securityAccessDeniedHandler"</span>)</span><br><span class="line">    <span class="keyword">private</span> AccessDeniedHandler securityAccessDeniedHandler;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"securityAuthenticationEntryPoint"</span>)</span><br><span class="line">    <span class="keyword">private</span> AuthenticationEntryPoint securityAuthenticationEntryPoint;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"urlFilterInvocationSecurityMetadataSource"</span>)</span><br><span class="line">    UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"urlAccessDecisionManager"</span>)</span><br><span class="line">    AccessDecisionManager urlAccessDecisionManager;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 访问静态资源</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(WebSecurity web)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        web.ignoring().antMatchers(</span><br><span class="line">                <span class="string">"/css/**"</span>,</span><br><span class="line">                <span class="string">"/js/**"</span>,</span><br><span class="line">                <span class="string">"/images/**"</span>,</span><br><span class="line">                <span class="string">"/fonts/**"</span>,</span><br><span class="line">                <span class="string">"/favicon.ico"</span>,</span><br><span class="line">                <span class="string">"/static/**"</span>,</span><br><span class="line">                <span class="string">"/resources/**"</span>,<span class="string">"/error"</span>,<span class="string">"/status/*"</span>, <span class="string">"/swagger-ui.html"</span>, <span class="string">"/v2/**"</span>, <span class="string">"/webjars/**"</span>, <span class="string">"/swagger-resources/**"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthenticationManagerBuilder auth)</span> </span>&#123;</span><br><span class="line">        auth.authenticationProvider(securityAuthenticationProvider);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line"></span><br><span class="line">        http</span><br><span class="line">            .authorizeRequests()</span><br><span class="line">                .anyRequest()</span><br><span class="line">                .authenticated()</span><br><span class="line">                .withObjectPostProcessor(urlObjectPostProcessor())</span><br><span class="line">            .and()</span><br><span class="line">                .formLogin()</span><br><span class="line">                .loginPage(<span class="string">"/login"</span>) <span class="comment">//自定义登录页面</span></span><br><span class="line">                .loginProcessingUrl(<span class="string">"/login"</span>)</span><br><span class="line">                .usernameParameter(<span class="string">"username"</span>)</span><br><span class="line">                .passwordParameter(<span class="string">"password"</span>)</span><br><span class="line">                .permitAll()</span><br><span class="line">                .failureHandler(securityAuthenticationFailureHandler)</span><br><span class="line">                .successHandler(userLoginSuccessHandler)</span><br><span class="line">            .and()</span><br><span class="line">                .exceptionHandling()</span><br><span class="line">                .authenticationEntryPoint(securityAuthenticationEntryPoint)</span><br><span class="line">                .accessDeniedHandler(securityAccessDeniedHandler)</span><br><span class="line">            .and()</span><br><span class="line">                .logout()</span><br><span class="line">                .deleteCookies(<span class="string">"SESSION"</span>)</span><br><span class="line">                .logoutUrl(<span class="string">"/logout"</span>)</span><br><span class="line">                .logoutSuccessHandler(securityLogoutSuccessHandler)</span><br><span class="line">                .permitAll()</span><br><span class="line">            .and()</span><br><span class="line">            <span class="comment">// 关闭csrf，还可开放退出的GET请求方式，否则只有POST请求方式</span></span><br><span class="line">                .csrf().disable();</span><br><span class="line"></span><br><span class="line">        http</span><br><span class="line">                .sessionManagement()</span><br><span class="line">                <span class="comment">// 无效session跳转</span></span><br><span class="line">                .invalidSessionUrl(<span class="string">"/login"</span>)</span><br><span class="line">                .maximumSessions(<span class="number">1</span>)</span><br><span class="line">                <span class="comment">// session过期跳转</span></span><br><span class="line">                .expiredUrl(<span class="string">"/login"</span>)</span><br><span class="line">                .sessionRegistry(sessionRegistry());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 解决session失效后 sessionRegistry中session没有同步失效的问题</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> HttpSessionEventPublisher <span class="title">httpSessionEventPublisher</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> HttpSessionEventPublisher();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> SessionRegistry <span class="title">sessionRegistry</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> SessionRegistryImpl();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> ObjectPostProcessor <span class="title">urlObjectPostProcessor</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ObjectPostProcessor&lt;FilterSecurityInterceptor&gt;() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> &lt;O extends FilterSecurityInterceptor&gt; <span class="function">O <span class="title">postProcess</span><span class="params">(O o)</span> </span>&#123;</span><br><span class="line">                o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);</span><br><span class="line">                o.setAccessDecisionManager(urlAccessDecisionManager);</span><br><span class="line">                <span class="keyword">return</span> o;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> AuthenticationManager <span class="title">authenticationManagerBean</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">super</span>.authenticationManagerBean();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 设置加密方式</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> PasswordEncoder <span class="title">passwordEncoder</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> BCryptPasswordEncoder();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3-配置基于OAuth2-的授权服务"><a href="#3-配置基于OAuth2-的授权服务" class="headerlink" title="3. 配置基于OAuth2 的授权服务"></a>3. 配置基于OAuth2 的授权服务</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> lirong</span></span><br><span class="line"><span class="comment"> * Date 2019-3-18 09:04:36</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">OAuth2ServerConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 注册资源服务器，开放给SSO客户端访问的资源</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Configuration</span></span><br><span class="line">    <span class="meta">@EnableResourceServer</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ResourceServerConfiguration</span> <span class="keyword">extends</span> <span class="title">ResourceServerConfigurerAdapter</span> </span>&#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String RESOURCE_ID = <span class="string">"libii-sso-server"</span>;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(ResourceServerSecurityConfigurer resources)</span> </span>&#123;</span><br><span class="line">            <span class="comment">// 如果关闭 stateless，则 accessToken 使用时的 session id 会被记录，后续请求不携带 accessToken 也可以正常响应</span></span><br><span class="line">            resources.resourceId(RESOURCE_ID).stateless(<span class="keyword">false</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 为oauth2单独创建角色，这些角色只具有访问受限资源的权限，可解决token失效的问题</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> http</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">            http</span><br><span class="line">                    <span class="comment">// 获取登录用户的 session</span></span><br><span class="line">                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)</span><br><span class="line">                    .and()</span><br><span class="line">                    <span class="comment">// 资源服务器拦截的路径 注意此路径不要拦截主过滤器放行的URL</span></span><br><span class="line">                    .requestMatchers().antMatchers(<span class="string">"/user/**"</span>);</span><br><span class="line">            http</span><br><span class="line">                    .authorizeRequests()</span><br><span class="line">                    <span class="comment">// 配置资源服务器已拦截的路径才有效</span></span><br><span class="line">                    .antMatchers(<span class="string">"/user/**"</span>).authenticated();</span><br><span class="line">            <span class="comment">// .access(" #oauth2.hasScope('select') or hasAnyRole('ROLE_超级管理员', 'ROLE_设备管理员')");</span></span><br><span class="line"></span><br><span class="line">            http</span><br><span class="line">                    .exceptionHandling().accessDeniedHandler(<span class="keyword">new</span> OAuth2AccessDeniedHandler())</span><br><span class="line">                    .and()</span><br><span class="line">                    .authorizeRequests()</span><br><span class="line">                    .anyRequest()</span><br><span class="line">                    .authenticated();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Configuration</span></span><br><span class="line">    <span class="meta">@EnableAuthorizationServer</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">AuthorizationServerConfiguration</span> <span class="keyword">extends</span> <span class="title">AuthorizationServerConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Autowired</span></span><br><span class="line">        AuthenticationManager authenticationManager;</span><br><span class="line">        <span class="meta">@Autowired</span></span><br><span class="line">        <span class="keyword">private</span> DataSource dataSource;</span><br><span class="line">        <span class="meta">@Autowired</span></span><br><span class="line">        SecurityUserService userDetailsService;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// @Bean</span></span><br><span class="line">        <span class="comment">// public TokenStore tokenStore() &#123;</span></span><br><span class="line">        <span class="comment">//     return new JdbcTokenStore(dataSource);</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> TokenStore <span class="title">jwtTokenStore</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> JwtTokenStore(jwtAccessTokenConverter());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> JwtAccessTokenConverter <span class="title">jwtAccessTokenConverter</span><span class="params">()</span></span>&#123;</span><br><span class="line">            JwtAccessTokenConverter converter = <span class="keyword">new</span> JwtAccessTokenConverter();</span><br><span class="line">            converter.setSigningKey(<span class="string">"libii-sso-server"</span>);</span><br><span class="line">            <span class="keyword">return</span> converter;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 密码加密</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> PasswordEncoder <span class="title">passwordEncoder</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> BCryptPasswordEncoder();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> AuthorizationCodeServices <span class="title">authorizationCodeServices</span><span class="params">(DataSource dataSource)</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> JdbcAuthorizationCodeServices(dataSource);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(ClientDetailsServiceConfigurer clients)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">            <span class="comment">// 1. 数据库的方式</span></span><br><span class="line">            clients.jdbc(dataSource);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 2. 内存的方式</span></span><br><span class="line">            <span class="comment">// 定义了两个客户端应用的通行证</span></span><br><span class="line">            <span class="comment">// clients.inMemory()</span></span><br><span class="line">            <span class="comment">//         .withClient("moregame")</span></span><br><span class="line">            <span class="comment">//         .secret(new BCryptPasswordEncoder().encode("123456"))</span></span><br><span class="line">            <span class="comment">//         .authorizedGrantTypes("authorization_code", "refresh_token")</span></span><br><span class="line">            <span class="comment">//         .redirectUris("http://www.site2.com/login")</span></span><br><span class="line">            <span class="comment">//         .scopes("all")</span></span><br><span class="line">            <span class="comment">//         .accessTokenValiditySeconds(3600)</span></span><br><span class="line">            <span class="comment">//         .autoApprove(true)</span></span><br><span class="line">            <span class="comment">//</span></span><br><span class="line">            <span class="comment">//         .and()</span></span><br><span class="line">            <span class="comment">//         .withClient("sheep1")</span></span><br><span class="line">            <span class="comment">//         .secret(new BCryptPasswordEncoder().encode("123456"))</span></span><br><span class="line">            <span class="comment">//         .authorizedGrantTypes("authorization_code", "refresh_token")</span></span><br><span class="line">            <span class="comment">//         .redirectUris("http://www.site1.com/login")</span></span><br><span class="line">            <span class="comment">//         .scopes("all")</span></span><br><span class="line">            <span class="comment">//         .accessTokenValiditySeconds(3600)</span></span><br><span class="line">            <span class="comment">//         .autoApprove(true);</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 声明授权和token的端点以及token的服务的一些配置信息，</span></span><br><span class="line"><span class="comment">         * 比如采用什么存储方式、token的有效期等</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> endpoints</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthorizationServerEndpointsConfigurer endpoints)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">            endpoints</span><br><span class="line">                    .tokenStore(jwtTokenStore())</span><br><span class="line">                    .accessTokenConverter(jwtAccessTokenConverter())</span><br><span class="line">                    .authenticationManager(authenticationManager)</span><br><span class="line">                    .userDetailsService(userDetailsService);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 声明安全约束，哪些允许访问，哪些不允许访问</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> security</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthorizationServerSecurityConfigurer security)</span> </span>&#123;</span><br><span class="line">            <span class="comment">//允许表单认证</span></span><br><span class="line">            security.allowFormAuthenticationForClients();</span><br><span class="line">            security.passwordEncoder(passwordEncoder());</span><br><span class="line">            <span class="comment">// 对于CheckEndpoint控制器[框架自带的校验]的/oauth/check端点允许所有客户端发送器请求而不会被Spring-security拦截</span></span><br><span class="line">            security.tokenKeyAccess(<span class="string">"isAuthenticated()"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><h4 id="配置说明："><a href="#配置说明：" class="headerlink" title="配置说明："></a>配置说明：</h4><h6 id="1-此处把授权服务器和资源服务器一起配置了，可分开配置，如若不配置资源服务器，则在客户端无法通过获取的accessToken和服务端交互（登录和登出除外，-EnableOAuth2Sso注解做了自动处理），当然如果你的项目登录、登出够用了，也可不配置。"><a href="#1-此处把授权服务器和资源服务器一起配置了，可分开配置，如若不配置资源服务器，则在客户端无法通过获取的accessToken和服务端交互（登录和登出除外，-EnableOAuth2Sso注解做了自动处理），当然如果你的项目登录、登出够用了，也可不配置。" class="headerlink" title="1. 此处把授权服务器和资源服务器一起配置了，可分开配置，如若不配置资源服务器，则在客户端无法通过获取的accessToken和服务端交互（登录和登出除外，@EnableOAuth2Sso注解做了自动处理），当然如果你的项目登录、登出够用了，也可不配置。"></a>1. 此处把授权服务器和资源服务器一起配置了，可分开配置，如若不配置资源服务器，则在客户端无法通过获取的accessToken和服务端交互（登录和登出除外，@EnableOAuth2Sso注解做了自动处理），当然如果你的项目登录、登出够用了，也可不配置。</h6><h6 id="2-本文采用的是数据库的方式存储OAuth的客户端信息，内存的方式也在文中提供了，采用JWT的方式实现TokenStore，熟悉OAuth2-的朋友应该知道，有4种方式来配置tokenStore，详情请移步这里tokenStore详解，-虽然我们也使用数据库来存储客户端的配置信息，但是因为使用的jwtTokenStore，access-token等信息都蕴藏在JWT里面，所以数据库只需要一张oauth-client-details表就行了，不需要存储授权码和access-token等信息。"><a href="#2-本文采用的是数据库的方式存储OAuth的客户端信息，内存的方式也在文中提供了，采用JWT的方式实现TokenStore，熟悉OAuth2-的朋友应该知道，有4种方式来配置tokenStore，详情请移步这里tokenStore详解，-虽然我们也使用数据库来存储客户端的配置信息，但是因为使用的jwtTokenStore，access-token等信息都蕴藏在JWT里面，所以数据库只需要一张oauth-client-details表就行了，不需要存储授权码和access-token等信息。" class="headerlink" title="2. 本文采用的是数据库的方式存储OAuth的客户端信息，内存的方式也在文中提供了，采用JWT的方式实现TokenStore，熟悉OAuth2 的朋友应该知道，有4种方式来配置tokenStore，详情请移步这里tokenStore详解， 虽然我们也使用数据库来存储客户端的配置信息，但是因为使用的jwtTokenStore，access_token等信息都蕴藏在JWT里面，所以数据库只需要一张oauth_client_details表就行了，不需要存储授权码和access_token等信息。"></a>2. 本文采用的是数据库的方式存储OAuth的客户端信息，内存的方式也在文中提供了，采用JWT的方式实现TokenStore，熟悉OAuth2 的朋友应该知道，有4种方式来配置tokenStore，详情请移步这里<a href="https://juejin.im/post/5a45aa44f265da43133d770e" target="_blank" rel="external">tokenStore详解</a>， 虽然我们也使用数据库来存储客户端的配置信息，但是因为使用的jwtTokenStore，access_token等信息都蕴藏在JWT里面，所以数据库只需要一张oauth_client_details表就行了，不需要存储授权码和access_token等信息。</h6><h6 id="3-引入userDetailsService，是为了将OAuth2签发的access-token和系统的用户绑定起来，这样，此token就具有了系统用户所具有的访问权限。（有人说，这样绑定后，只要系统用户的session没有失效，token就会自动刷新，不会过期，经测试，token还是会过期，但应该还是有某种其他的联系，有兴趣的小伙伴可以深度研究）"><a href="#3-引入userDetailsService，是为了将OAuth2签发的access-token和系统的用户绑定起来，这样，此token就具有了系统用户所具有的访问权限。（有人说，这样绑定后，只要系统用户的session没有失效，token就会自动刷新，不会过期，经测试，token还是会过期，但应该还是有某种其他的联系，有兴趣的小伙伴可以深度研究）" class="headerlink" title="3. 引入userDetailsService，是为了将OAuth2签发的access_token和系统的用户绑定起来，这样，此token就具有了系统用户所具有的访问权限。（有人说，这样绑定后，只要系统用户的session没有失效，token就会自动刷新，不会过期，经测试，token还是会过期，但应该还是有某种其他的联系，有兴趣的小伙伴可以深度研究）"></a>3. 引入userDetailsService，是为了将OAuth2签发的access_token和系统的用户绑定起来，这样，此token就具有了系统用户所具有的访问权限。（有人说，这样绑定后，只要系统用户的session没有失效，token就会自动刷新，不会过期，经测试，token还是会过期，但应该还是有某种其他的联系，有兴趣的小伙伴可以深度研究）</h6></blockquote><h4 id="4-单点退出"><a href="#4-单点退出" class="headerlink" title="4. 单点退出"></a>4. 单点退出</h4><p><strong>原理：</strong> 用户在某个客户端执行退出操作后，通知认证中心和其他客户端，使他们也触发用户退出的相应操作，这就是单点退出。<br><strong>遇到的问题：</strong> <code>@EnableOAuth2Sso</code>只帮我们实现了通知认证中心退出的操作，此时浏览器和各个客户端建立的<code>session</code>依旧有效，那怎样才能通知客户端呢？。<br><strong>解决方案：</strong> 熟悉<code>spring-security</code>的朋友都知道，<code>security</code>鉴权时会拦截每一个访问的URL，判断用户是否登录，以及用户是否有此URL访问的权限，基于此，我们可在此过程中插入对用户是否在其他客户端退出的判断。在用户登录时，先到认证中心授权认证，登录成功的同时，认证中心将用户具有的权限信息（以用户名为key，权限信息作为value）存入到客户端能够访问的redis中，用户退出时，由认证中心去删除redis该用户的权限信息即可。用户访问客户端期间，一旦客户端的security取不到<code>redis</code>中用户的权限信息了，即表示用户已在其他客户端退出了。<br>详情可参考我的 <a href="https://blog.csdn.net/qq_34997906/article/details/100014815" target="_blank" rel="external">单点登录客户端</a> 这篇博客。</p><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><p>单点登录：<br><img src="https://img-blog.csdnimg.cn/20190912190005274.gif" alt="单点登录"><br>单点退出：<br><img src="https://img-blog.csdnimg.cn/20190912190026587.gif" alt="单点退出"><br>欢迎大家留言讨论<br>Github源码：<a href="https://github.com/Janche/sso-oauth2-server" target="_blank" rel="external">单点登录服务端</a>  、<a href="https://github.com/Janche/sso-oauth2-client" target="_blank" rel="external">单点登录客户端</a></p>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;相关技术&quot;&gt;&lt;a href=&quot;#相关技术&quot; class=&quot;headerlink&quot; title=&quot;相关技术&quot;&gt;&lt;/a&gt;相关技术&lt;/h3&gt;&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;h4 id=&quot;spring-security：-用于安全控制的权限框架&quot;&gt;&lt;a href=&quot;#spring-security：-用于安全控制的权限框架&quot; class=&quot;headerlink&quot; title=&quot;spring security： 用于安全控制的权限框架&quot;&gt;&lt;/a&gt;spring security： 用于安全控制的权限框架&lt;/h4&gt;&lt;/li&gt;
&lt;li&gt;&lt;h4 id=&quot;OAuth2：-用于第三方登录认证授权的协议&quot;&gt;&lt;a href=&quot;#OAuth2：-用于第三方登录认证授权的协议&quot; class=&quot;headerlink&quot; title=&quot;OAuth2： 用于第三方登录认证授权的协议&quot;&gt;&lt;/a&gt;OAuth2： 用于第三方登录认证授权的协议&lt;/h4&gt;&lt;/li&gt;
&lt;li&gt;&lt;h4 id=&quot;JWT：客户端和服务端通信的数据载体&quot;&gt;&lt;a href=&quot;#JWT：客户端和服务端通信的数据载体&quot; class=&quot;headerlink&quot; title=&quot;JWT：客户端和服务端通信的数据载体&quot;&gt;&lt;/a&gt;JWT：客户端和服务端通信的数据载体&lt;/h4&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;传统登录&quot;&gt;&lt;a href=&quot;#传统登录&quot; class=&quot;headerlink&quot; title=&quot;传统登录&quot;&gt;&lt;/a&gt;传统登录&lt;/h3&gt;&lt;p&gt;登录web系统后将用户信息保存在session中，sessionId写入浏览器的cookie中，每次访问系统，浏览器自动携带此cookie，服务端根据此sessionId取到相应的session，若为空则表示登录已失效，不为空则表示用户已登录，不需要用户再次输入用户名密码。&lt;/p&gt;
&lt;h3 id=&quot;单点登录&quot;&gt;&lt;a href=&quot;#单点登录&quot; class=&quot;headerlink&quot; title=&quot;单点登录&quot;&gt;&lt;/a&gt;单点登录&lt;/h3&gt;&lt;p&gt;单点登录是一种多站点共享登录访问授权机制，访问用户只需要在一个站点登录就可以访问其它站点需要登录访问的资源(url)。用户在任意一个站点注销登录，则其它站点的登录状态也被注销。简而言之就是：一处登录，处处登录。一处注销，处处注销。&lt;br&gt;spring-security + OAuth2 完美解决了完全跨域的问题。&lt;br&gt;
    
    </summary>
    
    
      <category term="spring-security" scheme="https://janche.github.io/tags/spring-security/"/>
    
      <category term="OAuth2" scheme="https://janche.github.io/tags/OAuth2/"/>
    
      <category term="SSO" scheme="https://janche.github.io/tags/SSO/"/>
    
  </entry>
  
  <entry>
    <title>Spring Security + JWT 完成RBAC动态授权</title>
    <link href="https://janche.github.io/2019/07/24/Spring-Security-JWT-%E5%AE%8C%E6%88%90RBAC%E5%8A%A8%E6%80%81%E6%8E%88%E6%9D%83/"/>
    <id>https://janche.github.io/2019/07/24/Spring-Security-JWT-完成RBAC动态授权/</id>
    <published>2019-07-24T11:49:19.000Z</published>
    <updated>2019-07-24T11:51:06.590Z</updated>
    
    <content type="html"><![CDATA[<p>此篇文章为spring security系列的第一篇，着重讲解如何通过spring security完成企业级项目的权限控制，以及采用Redis的方式控制JWT的失效。</p><h3 id="1-什么是RBAC"><a href="#1-什么是RBAC" class="headerlink" title="1. 什么是RBAC"></a>1. 什么是RBAC</h3><p>RBAC（Role-Based Access Control ）基于角色的权限控制，权限与角色相关联，用户通过成为适当角色的成员而得到这些角色的权限。</p><h3 id="2-JWT-和-Spring-Security"><a href="#2-JWT-和-Spring-Security" class="headerlink" title="2. JWT 和 Spring Security"></a>2. JWT 和 Spring Security</h3><p>spring security 授权主要分为两种，一种是security内部负责维护登录用户的session，一种则是采用JWT的方式，不管理session。关于JWT 和 Security的详细资料请小伙伴们自行查阅（相关网址推荐：<a href="http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html）" target="_blank" rel="external">http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html）</a><br>此处就不在赘述，好了下面开始正文吧。</p><a id="more"></a><h3 id="3-1-导入依赖"><a href="#3-1-导入依赖" class="headerlink" title="3.1 导入依赖"></a>3.1 导入依赖</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- spring security 和 jwt --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;jjwt&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;0.9.0&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><h3 id="3-security-核心配置类：WebSecurityConfig"><a href="#3-security-核心配置类：WebSecurityConfig" class="headerlink" title="3. security 核心配置类：WebSecurityConfig"></a>3. security 核心配置类：WebSecurityConfig</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br></pre></td><td class="code"><pre><span class="line">@Configuration</span><br><span class="line">@EnableWebSecurity</span><br><span class="line">@EnableConfigurationProperties(CustomConfig.class)</span><br><span class="line">public class WebSecurityConfig extends WebSecurityConfigurerAdapter &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private CustomConfig customConfig;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private SecurityUserService securityUserService;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    @Qualifier(&quot;securityAccessDeniedHandler&quot;)</span><br><span class="line">    private AccessDeniedHandler securityAccessDeniedHandler;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private JwtAuthenticationFilter jwtAuthenticationFilter;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected void configure(AuthenticationManagerBuilder auth) throws Exception &#123;</span><br><span class="line">        auth.userDetailsService(securityUserService).passwordEncoder(passwordEncoder());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected void configure(HttpSecurity http) throws Exception &#123;</span><br><span class="line"></span><br><span class="line">        http.cors()</span><br><span class="line">                // 关闭 CSRF</span><br><span class="line">                .and().csrf().disable()</span><br><span class="line">                // 登录行为由自己实现，参考 LoginController#login</span><br><span class="line">                .formLogin().disable()</span><br><span class="line">                .httpBasic().disable()</span><br><span class="line"></span><br><span class="line">                // 认证请求</span><br><span class="line">                .authorizeRequests()</span><br><span class="line">                // 所有请求都需要登录访问</span><br><span class="line">                .anyRequest()</span><br><span class="line">                .authenticated()</span><br><span class="line">                // RBAC 动态 url 认证</span><br><span class="line">                .anyRequest()</span><br><span class="line">                .access(&quot;@rbacAuthorityService.hasPermission(request,authentication)&quot;)</span><br><span class="line"></span><br><span class="line">                // 登出行为由自己实现，参考 LoginController#logout</span><br><span class="line">                .and().logout().disable()</span><br><span class="line">                // 异常处理</span><br><span class="line">                .exceptionHandling().accessDeniedHandler(securityAccessDeniedHandler);</span><br><span class="line"></span><br><span class="line">        // Session 管理</span><br><span class="line">        http.sessionManagement()</span><br><span class="line">                // 因为使用了JWT，所以这里不管理Session</span><br><span class="line">                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);</span><br><span class="line"></span><br><span class="line">        // 添加自定义 JWT 过滤器</span><br><span class="line">        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 放行所有不需要登录就可以访问的请求，参见 AuthController</span><br><span class="line">     * 也可以在 &#123;@link #configure(HttpSecurity)&#125; 中配置</span><br><span class="line">     * &#123;@code http.authorizeRequests().antMatchers(&quot;/api/auth/**&quot;).permitAll()&#125;</span><br><span class="line">     */</span><br><span class="line">    @Override</span><br><span class="line">    public void configure(WebSecurity web) &#123;</span><br><span class="line">        WebSecurity and = web.ignoring().and();</span><br><span class="line"></span><br><span class="line">        // 忽略 GET</span><br><span class="line">        customConfig.getIgnores().getGet().forEach(url -&gt; and.ignoring().antMatchers(HttpMethod.GET, url));</span><br><span class="line"></span><br><span class="line">        // 忽略 POST</span><br><span class="line">        customConfig.getIgnores().getPost().forEach(url -&gt; and.ignoring().antMatchers(HttpMethod.POST, url));</span><br><span class="line"></span><br><span class="line">        // 忽略 DELETE</span><br><span class="line">        customConfig.getIgnores().getDelete().forEach(url -&gt; and.ignoring().antMatchers(HttpMethod.DELETE, url));</span><br><span class="line"></span><br><span class="line">        // 忽略 PUT</span><br><span class="line">        customConfig.getIgnores().getPut().forEach(url -&gt; and.ignoring().antMatchers(HttpMethod.PUT, url));</span><br><span class="line"></span><br><span class="line">        // 忽略 HEAD</span><br><span class="line">        customConfig.getIgnores().getHead().forEach(url -&gt; and.ignoring().antMatchers(HttpMethod.HEAD, url));</span><br><span class="line"></span><br><span class="line">        // 忽略 PATCH</span><br><span class="line">        customConfig.getIgnores().getPatch().forEach(url -&gt; and.ignoring().antMatchers(HttpMethod.PATCH, url));</span><br><span class="line"></span><br><span class="line">        // 忽略 OPTIONS</span><br><span class="line">        customConfig.getIgnores().getOptions().forEach(url -&gt; and.ignoring().antMatchers(HttpMethod.OPTIONS, url));</span><br><span class="line"></span><br><span class="line">        // 忽略 TRACE</span><br><span class="line">        customConfig.getIgnores().getTrace().forEach(url -&gt; and.ignoring().antMatchers(HttpMethod.TRACE, url));</span><br><span class="line"></span><br><span class="line">        // 按照请求格式忽略</span><br><span class="line">        customConfig.getIgnores().getPattern().forEach(url -&gt; and.ignoring().antMatchers(url));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Bean</span><br><span class="line">    @Override</span><br><span class="line">    public AuthenticationManager authenticationManagerBean() throws Exception &#123;</span><br><span class="line">        return super.authenticationManagerBean();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 设置加密方式</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    @Bean</span><br><span class="line">    public PasswordEncoder passwordEncoder()&#123;</span><br><span class="line">        return new BCryptPasswordEncoder();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>1. <code>AuthenticationManagerBuilder auth</code>主要设置了security的加密方式，（<code>BCryptPasswordEncoder</code>也是目前比较流行安全的一种加密方式，它比MD5效率更高），<code>userDetailsService</code>则负责对用户名、密码的校验和授权。</strong></p><p><strong>2. <code>HttpSecurity http</code> 主要是对security核心过滤器链的配置，可配置登录、登出及异常等处理器，因为我们采用的是JWT的方式，因此禁用了security提供的登录和登出，配置了JWT的过滤器，以及RBAC校验的方式。</strong></p><p><strong>3. <code>WebSecurity web</code>主要负责配置一些security放行的路径，文中通过customConfig读取在配置文件中设置的放行的URL。</strong></p><h3 id="4-配置JWT-的认证过滤器JwtAuthenticationFilter"><a href="#4-配置JWT-的认证过滤器JwtAuthenticationFilter" class="headerlink" title="4. 配置JWT 的认证过滤器JwtAuthenticationFilter"></a>4. 配置JWT 的认证过滤器<code>JwtAuthenticationFilter</code></h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @ClassName: JwtAuthenticationFilter</span><br><span class="line"> * @Description: Jwt 认证过滤器</span><br><span class="line"> * @date 2019-07-12 9:50</span><br><span class="line"> */</span><br><span class="line">@Component</span><br><span class="line">@Slf4j</span><br><span class="line">public class JwtAuthenticationFilter extends OncePerRequestFilter &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private SecurityUserService userDetailsService;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private JwtUtil jwtUtil;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private CustomConfig customConfig;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private IApplicationConfig applicationConfig;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException &#123;</span><br><span class="line"></span><br><span class="line">        // 是否为放行的请求</span><br><span class="line">        if (checkIgnores(request)) &#123;</span><br><span class="line">            chain.doFilter(request, response);</span><br><span class="line">            return;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        String jwt = jwtUtil.getJwtFromRequest(request);</span><br><span class="line"></span><br><span class="line">        if (StrUtil.isNotBlank(jwt)) &#123;</span><br><span class="line">            try &#123;</span><br><span class="line">                String username = jwtUtil.getUsernameFromJWT(jwt, false);</span><br><span class="line"></span><br><span class="line">                UserDetails userDetails = userDetailsService.loadUserByUsername(username);</span><br><span class="line">                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());</span><br><span class="line">                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));</span><br><span class="line"></span><br><span class="line">                SecurityContextHolder.getContext().setAuthentication(authentication);</span><br><span class="line"></span><br><span class="line">                chain.doFilter(request, response);</span><br><span class="line">            &#125; catch (CustomException e) &#123;</span><br><span class="line">                ResponseUtils.renderJson(request, response, e, applicationConfig.getOrigins());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            ResponseUtils.renderJson(request, response, ResultCode.UNAUTHORIZED, null, applicationConfig.getOrigins());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 请求是否不需要进行权限拦截</span><br><span class="line">     * @param request 当前请求</span><br><span class="line">     * @return true - 忽略，false - 不忽略</span><br><span class="line">     */</span><br><span class="line">    private boolean checkIgnores(HttpServletRequest request) &#123;</span><br><span class="line">        String method = request.getMethod();</span><br><span class="line"></span><br><span class="line">        HttpMethod httpMethod = HttpMethod.resolve(method);</span><br><span class="line">        if (ObjectUtil.isNull(httpMethod)) &#123;</span><br><span class="line">            httpMethod = HttpMethod.GET;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Set&lt;String&gt; ignores = Sets.newHashSet();</span><br><span class="line"></span><br><span class="line">        switch (httpMethod) &#123;</span><br><span class="line">            case GET:</span><br><span class="line">                ignores.addAll(customConfig.getIgnores()</span><br><span class="line">                        .getGet());</span><br><span class="line">                break;</span><br><span class="line">            case PUT:</span><br><span class="line">                ignores.addAll(customConfig.getIgnores()</span><br><span class="line">                        .getPut());</span><br><span class="line">                break;</span><br><span class="line">            case HEAD:</span><br><span class="line">                ignores.addAll(customConfig.getIgnores()</span><br><span class="line">                        .getHead());</span><br><span class="line">                break;</span><br><span class="line">            case POST:</span><br><span class="line">                ignores.addAll(customConfig.getIgnores()</span><br><span class="line">                        .getPost());</span><br><span class="line">                break;</span><br><span class="line">            case PATCH:</span><br><span class="line">                ignores.addAll(customConfig.getIgnores()</span><br><span class="line">                        .getPatch());</span><br><span class="line">                break;</span><br><span class="line">            case TRACE:</span><br><span class="line">                ignores.addAll(customConfig.getIgnores()</span><br><span class="line">                        .getTrace());</span><br><span class="line">                break;</span><br><span class="line">            case DELETE:</span><br><span class="line">                ignores.addAll(customConfig.getIgnores()</span><br><span class="line">                        .getDelete());</span><br><span class="line">                break;</span><br><span class="line">            case OPTIONS:</span><br><span class="line">                ignores.addAll(customConfig.getIgnores()</span><br><span class="line">                        .getOptions());</span><br><span class="line">                break;</span><br><span class="line">            default:</span><br><span class="line">                break;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        ignores.addAll(customConfig.getIgnores()</span><br><span class="line">                .getPattern());</span><br><span class="line"></span><br><span class="line">        if (CollUtil.isNotEmpty(ignores)) &#123;</span><br><span class="line">            for (String ignore : ignores) &#123;</span><br><span class="line">                AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method);</span><br><span class="line">                if (matcher.matches(request)) &#123;</span><br><span class="line">                    return true;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return false;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>此过滤器会拦截访问系统的所有的请求，因此需要放行所有被忽略的URL，包括登录和登出，并将校验通过的JWT的用户信息封装为authentication。</strong></p><h3 id="5-RBAC权限匹配器"><a href="#5-RBAC权限匹配器" class="headerlink" title="5. RBAC权限匹配器"></a>5. RBAC权限匹配器</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @ClassName: JwtAuthenticationFilter</span><br><span class="line"> * @Description: Jwt 认证过滤器</span><br><span class="line"> * @date 2019-07-12 9:50</span><br><span class="line"> */</span><br><span class="line">@Slf4j</span><br><span class="line">@Component</span><br><span class="line">public class RbacAuthorityService &#123;</span><br><span class="line">    @Autowired</span><br><span class="line">    private SecurityUserService userDetails;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private RequestMappingHandlerMapping mapping;</span><br><span class="line"></span><br><span class="line">    public boolean hasPermission(HttpServletRequest request, Authentication authentication) &#123;</span><br><span class="line">        checkRequest(request);</span><br><span class="line"></span><br><span class="line">        Object userInfo = authentication.getPrincipal();</span><br><span class="line">        boolean hasPermission = false;</span><br><span class="line"></span><br><span class="line">        if (userInfo instanceof UserDetails) &#123;</span><br><span class="line">            SecurityUser principal = (SecurityUser) userInfo;</span><br><span class="line">            SecurityUser userDTO = (SecurityUser) this.userDetails.loadUserByUsername(principal.getUsername());</span><br><span class="line"></span><br><span class="line">            //获取资源，前后端分离，所以过滤页面权限，只保留按钮权限</span><br><span class="line">            List&lt;MenuRight&gt; btnPerms = userDTO.getMenus().stream()</span><br><span class="line">                    // 过滤页面权限</span><br><span class="line">                    .filter(menuRight -&gt; menuRight.getGrades() &gt;= 3)</span><br><span class="line">                    // 过滤 URL 为空</span><br><span class="line">                    .filter(menuRight -&gt; StrUtil.isNotBlank(menuRight.getUrl()))</span><br><span class="line">                    // 过滤 METHOD 为空</span><br><span class="line">                    .collect(Collectors.toList());</span><br><span class="line">            for (MenuRight btnPerm : btnPerms) &#123;</span><br><span class="line">                AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(btnPerm.getUrl(), btnPerm.getMethod());</span><br><span class="line">                if (antPathMatcher.matches(request)) &#123;</span><br><span class="line">                    hasPermission = true;</span><br><span class="line">                    break;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            return hasPermission;</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            return false;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 校验请求是否存在</span><br><span class="line">     *</span><br><span class="line">     * @param request 请求</span><br><span class="line">     */</span><br><span class="line">    private void checkRequest(HttpServletRequest request) &#123;</span><br><span class="line">        // 获取当前 request 的方法</span><br><span class="line">        String currentMethod = request.getMethod();</span><br><span class="line">        Multimap&lt;String, String&gt; urlMapping = allUrlMapping();</span><br><span class="line"></span><br><span class="line">        for (String uri : urlMapping.keySet()) &#123;</span><br><span class="line">            // 通过 AntPathRequestMatcher 匹配 url</span><br><span class="line">            // 可以通过 2 种方式创建 AntPathRequestMatcher</span><br><span class="line">            // 1：new AntPathRequestMatcher(uri,method) 这种方式可以直接判断方法是否匹配，因为这里我们把 方法不匹配 自定义抛出，所以，我们使用第2种方式创建</span><br><span class="line">            // 2：new AntPathRequestMatcher(uri) 这种方式不校验请求方法，只校验请求路径</span><br><span class="line">            AntPathRequestMatcher antPathMatcher = new AntPathRequestMatcher(uri);</span><br><span class="line">            if (antPathMatcher.matches(request)) &#123;</span><br><span class="line">                if (!urlMapping.get(uri)</span><br><span class="line">                        .contains(currentMethod)) &#123;</span><br><span class="line">                    throw new CustomException(ResultCode.HTTP_BAD_METHOD);</span><br><span class="line">                &#125; else &#123;</span><br><span class="line">                    return;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        throw new CustomException(ResultCode.REQUEST_NOT_FOUND);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 获取 所有URL Mapping，返回格式为&#123;&quot;/test&quot;:[&quot;GET&quot;,&quot;POST&quot;],&quot;/sys&quot;:[&quot;GET&quot;,&quot;DELETE&quot;]&#125;</span><br><span class="line">     *</span><br><span class="line">     * @return &#123;@link ArrayListMultimap&#125; 格式的 URL Mapping</span><br><span class="line">     */</span><br><span class="line">    private Multimap&lt;String, String&gt; allUrlMapping() &#123;</span><br><span class="line">        Multimap&lt;String, String&gt; urlMapping = ArrayListMultimap.create();</span><br><span class="line"></span><br><span class="line">        // 获取url与类和方法的对应信息</span><br><span class="line">        Map&lt;RequestMappingInfo, HandlerMethod&gt; handlerMethods = mapping.getHandlerMethods();</span><br><span class="line"></span><br><span class="line">        handlerMethods.forEach((k, v) -&gt; &#123;</span><br><span class="line">            // 获取当前 key 下的获取所有URL</span><br><span class="line">            Set&lt;String&gt; url = k.getPatternsCondition()</span><br><span class="line">                    .getPatterns();</span><br><span class="line">            RequestMethodsRequestCondition method = k.getMethodsCondition();</span><br><span class="line"></span><br><span class="line">            // 为每个URL添加所有的请求方法</span><br><span class="line">            url.forEach(s -&gt; urlMapping.putAll(s, method.getMethods()</span><br><span class="line">                    .stream()</span><br><span class="line">                    .map(Enum::toString)</span><br><span class="line">                    .collect(Collectors.toList())));</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        return urlMapping;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>此方法看似很多，实则只做了一件事，就是把页面请求的URL和用户拥有的所有权限资源(URL)进行匹配。</strong></p><h3 id="6-UserDetailsService查询数据库用户信息"><a href="#6-UserDetailsService查询数据库用户信息" class="headerlink" title="6. UserDetailsService查询数据库用户信息"></a>6. <code>UserDetailsService</code>查询数据库用户信息</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @Date 2019-7-14 22:46:54</span><br><span class="line"> * @Desc 从数据库查询用户数据</span><br><span class="line"> */</span><br><span class="line">@Component(&quot;securityUserService&quot;)</span><br><span class="line">public class SecurityUserService implements UserDetailsService &#123;</span><br><span class="line">    @Resource</span><br><span class="line">    private UserMapper userMapper;</span><br><span class="line">    @Resource</span><br><span class="line">    private RoleMapper roleMapper;</span><br><span class="line">    @Resource</span><br><span class="line">    private MenuRightMapper menuRightMapper;</span><br><span class="line">    @Override</span><br><span class="line">    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException &#123;</span><br><span class="line"></span><br><span class="line">        UserDTO userDTO = userMapper.getRolesByUsername(username);</span><br><span class="line">        // 默认用户ID为1的为管理员</span><br><span class="line">        if (null != userDTO)&#123;</span><br><span class="line">            if(1L == userDTO.getId()) &#123;</span><br><span class="line">                this.getAdminPermission(userDTO);</span><br><span class="line">            &#125;</span><br><span class="line">            SecurityUser securityUser = new SecurityUser(LoginUserDTO.user2LoginUserDTO(userDTO));</span><br><span class="line">            return securityUser;</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            throw new UsernameNotFoundException(username + &quot; 用户不存在!&quot;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 为管理员赋所有权限</span><br><span class="line">     * @param userDTO</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    private UserDTO getAdminPermission(UserDTO userDTO) &#123;</span><br><span class="line">        List&lt;Role&gt; roles = roleMapper.selectAll();</span><br><span class="line">        List&lt;MenuRight&gt; menuRights = menuRightMapper.selectAll();</span><br><span class="line">        userDTO.setRoles(roles);</span><br><span class="line">        userDTO.setMenus(menuRights);</span><br><span class="line">        return userDTO;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-JWT的刷新和登录用户的注销"><a href="#7-JWT的刷新和登录用户的注销" class="headerlink" title="7. JWT的刷新和登录用户的注销"></a>7. JWT的刷新和登录用户的注销</h3><blockquote><p><strong>众所周知，JWT是无状态的，服务端无法通过解析JWT知道用户是否提前注销，因此借助了Redis的过期机制，来达到通知用户退出的目的。创建JWT时，会将生成的JWT以用户名为前缀存入Redis，退出时，清除Redis中此用户名的JWT，每次访问时解析JWT并判断Redis中是否还存在此用户名的JWT，若不存在，则表示此用户已注销。</strong><br><strong>JWT的续签，此处采用的是refresh_token的形式，及登录的时候创建两个JWT，一个token，一个refresh_token，refresh_token的过期时间设置比较长，token失效后，前端可调用refresh_token的接口来刷新来达到续签的目的。</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * jwt工具类</span><br><span class="line"> * @author  daiyp</span><br><span class="line"> * @date 2018-9-26</span><br><span class="line"> */</span><br><span class="line">@EnableConfigurationProperties(JwtConfig.class)</span><br><span class="line">@Configuration</span><br><span class="line">@Slf4j</span><br><span class="line">public class JwtUtil &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private JwtConfig jwtConfig;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 创建JWT</span><br><span class="line">     *</span><br><span class="line">     * @param authentication 用户认证信息</span><br><span class="line">     * @param rememberMe     记住我</span><br><span class="line">     * @return JWT</span><br><span class="line">     */</span><br><span class="line">    public String createJWT(Authentication authentication, Boolean rememberMe, Boolean isRefresh) &#123;</span><br><span class="line">        SecurityUser user = (SecurityUser) authentication.getPrincipal();</span><br><span class="line">        return createJWT(isRefresh, rememberMe, user.getId(), user.getUsername(), user.getRoles(), user.getMenus(), user.getAuthorities());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 创建JWT</span><br><span class="line">     *</span><br><span class="line">     * @param id          用户id</span><br><span class="line">     * @param subject     用户名</span><br><span class="line">     * @param roles       用户角色</span><br><span class="line">     * @param authorities 用户权限</span><br><span class="line">     * @return JWT</span><br><span class="line">     */</span><br><span class="line">    public String createJWT(Boolean isRefresh,</span><br><span class="line">                            Boolean rememberMe,</span><br><span class="line">                            Long id,</span><br><span class="line">                            String subject,</span><br><span class="line">                            List&lt;Role&gt; roles,</span><br><span class="line">                            List&lt;MenuRight&gt; menus,</span><br><span class="line">                            Collection&lt;? extends GrantedAuthority&gt; authorities) &#123;</span><br><span class="line">        Date now = new Date();</span><br><span class="line">        JwtBuilder builder = Jwts.builder()</span><br><span class="line">                .setId(id.toString())</span><br><span class="line">                .setSubject(subject)</span><br><span class="line">                .setIssuedAt(now)</span><br><span class="line">                .signWith(SignatureAlgorithm.HS256, jwtConfig.getSecret())</span><br><span class="line">                .claim(&quot;roles&quot;, roles)</span><br><span class="line">                // .claim(&quot;perms&quot;, menus)</span><br><span class="line">                .claim(&quot;authorities&quot;, authorities);</span><br><span class="line"></span><br><span class="line">        // 设置过期时间</span><br><span class="line">        Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl();</span><br><span class="line">        String redisKey;</span><br><span class="line">        if (isRefresh)&#123;</span><br><span class="line">            ttl *= 3;</span><br><span class="line">            redisKey = Constant.REDIS_JWT_REFRESH_TOKEN_KEY_PREFIX + subject;</span><br><span class="line">        &#125;else&#123;</span><br><span class="line">            redisKey = Constant.REDIS_JWT_TOKEN_KEY_PREFIX + subject;</span><br><span class="line">        &#125;</span><br><span class="line">        if (ttl &gt; 0) &#123;</span><br><span class="line">            builder.setExpiration(DateUtil.offsetMillisecond(now, ttl.intValue()));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        String jwt = builder.compact();</span><br><span class="line">        // 将生成的JWT保存至Redis</span><br><span class="line">        redisTemplate.opsForValue().set(redisKey, jwt, ttl, TimeUnit.MILLISECONDS);</span><br><span class="line">        return jwt;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 解析JWT</span><br><span class="line">     *</span><br><span class="line">     * @param jwt JWT</span><br><span class="line">     * @return &#123;@link Claims&#125;</span><br><span class="line">     */</span><br><span class="line">    public Claims parseJWT(String jwt, Boolean isRefresh) &#123;</span><br><span class="line">        try &#123;</span><br><span class="line">            Claims claims = Jwts.parser()</span><br><span class="line">                    .setSigningKey(jwtConfig.getSecret())</span><br><span class="line">                    .parseClaimsJws(jwt)</span><br><span class="line">                    .getBody();</span><br><span class="line"></span><br><span class="line">            String username = claims.getSubject();</span><br><span class="line">            String redisKey = (isRefresh ? Constant.REDIS_JWT_REFRESH_TOKEN_KEY_PREFIX : Constant.REDIS_JWT_TOKEN_KEY_PREFIX)</span><br><span class="line">                    + username;</span><br><span class="line"></span><br><span class="line">            // 校验redis中的JWT是否存在</span><br><span class="line">            Long expire = redisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS);</span><br><span class="line">            if (Objects.isNull(expire) || expire &lt;= 0) &#123;</span><br><span class="line">                throw new CustomException(ResultCode.TOKEN_EXPIRED);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            // 校验redis中的JWT是否与当前的一致，不一致则代表用户已注销/用户在不同设备登录，均代表JWT已过期</span><br><span class="line">            String redisToken = (String) redisTemplate.opsForValue().get(redisKey);</span><br><span class="line">            if (!StrUtil.equals(jwt, redisToken)) &#123;</span><br><span class="line">                throw new CustomException(ResultCode.TOKEN_OUT_OF_CTRL);</span><br><span class="line">            &#125;</span><br><span class="line">            return claims;</span><br><span class="line">        &#125; catch (ExpiredJwtException e) &#123;</span><br><span class="line">            log.error(&quot;Token 已过期&quot;);</span><br><span class="line">            throw new CustomException(ResultCode.TOKEN_EXPIRED);</span><br><span class="line">        &#125; catch (UnsupportedJwtException e) &#123;</span><br><span class="line">            log.error(&quot;不支持的 Token&quot;);</span><br><span class="line">            throw new CustomException(ResultCode.TOKEN_PARSE_ERROR);</span><br><span class="line">        &#125; catch (MalformedJwtException e) &#123;</span><br><span class="line">            log.error(&quot;Token 无效&quot;);</span><br><span class="line">            throw new CustomException(ResultCode.TOKEN_PARSE_ERROR);</span><br><span class="line">        &#125; catch (IllegalArgumentException e) &#123;</span><br><span class="line">            log.error(&quot;Token 参数不存在&quot;);</span><br><span class="line">            throw new CustomException(ResultCode.TOKEN_PARSE_ERROR);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 设置JWT过期</span><br><span class="line">     *</span><br><span class="line">     * @param request 请求</span><br><span class="line">     */</span><br><span class="line">    public void invalidateJWT(HttpServletRequest request) &#123;</span><br><span class="line">        String jwt = getJwtFromRequest(request);</span><br><span class="line">        String username = getUsernameFromJWT(jwt, false);</span><br><span class="line">        // 从redis中清除JWT</span><br><span class="line">        redisTemplate.delete(Constant.REDIS_JWT_REFRESH_TOKEN_KEY_PREFIX + username);</span><br><span class="line">        redisTemplate.delete(Constant.REDIS_JWT_TOKEN_KEY_PREFIX + username);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 从 request 的 header 中获取 JWT</span><br><span class="line">     *</span><br><span class="line">     * @param request 请求</span><br><span class="line">     * @return JWT</span><br><span class="line">     */</span><br><span class="line">    public String getJwtFromRequest(HttpServletRequest request) &#123;</span><br><span class="line">        String bearerToken = request.getHeader(&quot;Authorization&quot;);</span><br><span class="line">        if (StrUtil.isNotBlank(bearerToken) &amp;&amp; bearerToken.startsWith(&quot;Bearer &quot;)) &#123;</span><br><span class="line">            return bearerToken.substring(7);</span><br><span class="line">        &#125;</span><br><span class="line">        return null;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 根据 jwt 获取用户名</span><br><span class="line">     *</span><br><span class="line">     * @param jwt JWT</span><br><span class="line">     * @return 用户名</span><br><span class="line">     */</span><br><span class="line">    public String getUsernameFromJWT(String jwt, Boolean isRefresh) &#123;</span><br><span class="line">        Claims claims = parseJWT(jwt, isRefresh);</span><br><span class="line">        return claims.getSubject();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public Map&lt;String, String&gt; refreshJWT(String token) &#123;</span><br><span class="line">        Claims claims = parseJWT(token, true);</span><br><span class="line">        // 获取签发时间</span><br><span class="line">        Date lastTime = claims.getExpiration();</span><br><span class="line">        // 1. 判断refreshToken是否过期</span><br><span class="line">        if (!new Date().before(lastTime))&#123;</span><br><span class="line">            throw new CustomException(ResultCode.TOKEN_EXPIRED);</span><br><span class="line">        &#125;</span><br><span class="line">        // 2. 在redis中删除之前的token和refreshToken</span><br><span class="line">        String username = claims.getSubject();</span><br><span class="line">        // redisTemplate.delete(Constant.REDIS_JWT_REFRESH_TOKEN_KEY_PREFIX + username);</span><br><span class="line">        // redisTemplate.delete(Constant.REDIS_JWT_TOKEN_KEY_PREFIX + username);</span><br><span class="line">        // 3. 创建新的token和refreshToken并存入redis</span><br><span class="line">        String jwtToken = createJWT(false, false, Long.parseLong(claims.getId()), username,</span><br><span class="line">                (List&lt;Role&gt;) claims.get(&quot;roles&quot;), null, (Collection&lt;? extends GrantedAuthority&gt;) claims.get(&quot;authorities&quot;));</span><br><span class="line">        String refreshJwtToken = createJWT(true, false, Long.parseLong(claims.getId()), username,</span><br><span class="line">                (List&lt;Role&gt;) claims.get(&quot;roles&quot;), null, (Collection&lt;? extends GrantedAuthority&gt;) claims.get(&quot;authorities&quot;));</span><br><span class="line">        Map&lt;String, String&gt; map = new HashMap&lt;&gt;();</span><br><span class="line">        map.put(&quot;token&quot;, jwtToken);</span><br><span class="line">        map.put(&quot;refreshToken&quot;, refreshJwtToken);</span><br><span class="line">        return map;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     *</span><br><span class="line">     * 功能：生成 jwt token&lt;br/&gt;</span><br><span class="line">     * @param name实例名</span><br><span class="line">     * @param param需要保存的参数</span><br><span class="line">     * @param secret秘钥</span><br><span class="line">     * @param expirationtime过期时间(5分钟 5*60*1000)</span><br><span class="line">     * @return</span><br><span class="line">     *</span><br><span class="line">     */</span><br><span class="line">    public static String sign(String name, Map&lt;String,Object&gt; param, String secret, Long expirationtime)&#123;</span><br><span class="line">        String JWT = Jwts.builder()</span><br><span class="line">                .setClaims(param)</span><br><span class="line">                .setSubject(name)</span><br><span class="line">                .setExpiration(new Date(System.currentTimeMillis() + expirationtime))</span><br><span class="line">                .signWith(SignatureAlgorithm.HS256,secret)</span><br><span class="line">                .compact();</span><br><span class="line">        return JWT;</span><br><span class="line">    &#125;</span><br><span class="line">    /**</span><br><span class="line">     *</span><br><span class="line">     * 功能：解密 jwt&lt;br/&gt;</span><br><span class="line">     * @param JWTtoken字符串</span><br><span class="line">     * @param secret秘钥</span><br><span class="line">     * @return</span><br><span class="line">     * @exception</span><br><span class="line">     *</span><br><span class="line">     */</span><br><span class="line">    public static Claims verify(String JWT, String secret)&#123;</span><br><span class="line">        Claims claims = Jwts.parser()</span><br><span class="line">                .setSigningKey(secret)</span><br><span class="line">                .parseClaimsJws(JWT)</span><br><span class="line">                .getBody();</span><br><span class="line">        return claims;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public static Object getValueFromToken(String jwt,String key, String secret)&#123;</span><br><span class="line">        return verify(jwt, secret).get(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p></blockquote><p><strong>登录和登出的方法</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @ClassName: LoginController</span><br><span class="line"> * @Description: 登录Controller</span><br><span class="line"> * @date 2019-07-12 9:31</span><br><span class="line"> */</span><br><span class="line">@Slf4j</span><br><span class="line">@RestController</span><br><span class="line">@RequestMapping(&quot;/&quot;)</span><br><span class="line">public class LoginController &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private AuthenticationManager authenticationManager;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private JwtUtil jwtUtil;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 登录</span><br><span class="line">     */</span><br><span class="line">    @PostMapping(&quot;/login&quot;)</span><br><span class="line">    public RestResult login(@RequestParam String username,</span><br><span class="line">                            @RequestParam String password,</span><br><span class="line">                            @RequestParam(required = false, defaultValue = &quot;false&quot;) Boolean rememberMe,</span><br><span class="line">                            HttpServletRequest request,</span><br><span class="line">                            HttpServletResponse response) &#123;</span><br><span class="line">        Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));</span><br><span class="line"></span><br><span class="line">        SecurityContextHolder.getContext()</span><br><span class="line">                .setAuthentication(authentication);</span><br><span class="line"></span><br><span class="line">        String jwt = jwtUtil.createJWT(authentication, rememberMe, false);</span><br><span class="line">        String jwt_refresh = jwtUtil.createJWT(authentication, rememberMe, true);</span><br><span class="line">        Map&lt;String, String&gt; map = new HashMap&lt;&gt;();</span><br><span class="line">        map.put(&quot;token&quot;, jwt);</span><br><span class="line">        map.put(&quot;refreshToken&quot;, jwt_refresh);</span><br><span class="line"></span><br><span class="line">        CookieUtils.setCookie(response, &quot;localhost&quot;, jwt);</span><br><span class="line">        return ResultGenerator.genSuccessResult().setMessage(&quot;登录成功&quot;).setData(map);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 退出</span><br><span class="line">     * @param request</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    @PostMapping(&quot;/logout&quot;)</span><br><span class="line">    public RestResult logout(HttpServletRequest request) &#123;</span><br><span class="line">        try &#123;</span><br><span class="line">            // 设置JWT过期</span><br><span class="line">            jwtUtil.invalidateJWT(request);</span><br><span class="line">        &#125; catch (CustomException e) &#123;</span><br><span class="line">            throw new CustomException(ResultCode.UNAUTHORIZED);</span><br><span class="line">        &#125;</span><br><span class="line">        return ResultGenerator.genSuccessResult().setMessage(&quot;退出成功&quot;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 刷新过期的token</span><br><span class="line">     * @param refreshToken</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    @PostMapping(&quot;/refresh/token&quot;)</span><br><span class="line">    public RestResult refreshToken(String refreshToken) &#123;</span><br><span class="line">        Map&lt;String, String&gt; map;</span><br><span class="line">        try &#123;</span><br><span class="line">            // 刷新</span><br><span class="line">            map = jwtUtil.refreshJWT(refreshToken);</span><br><span class="line">        &#125; catch (CustomException e) &#123;</span><br><span class="line">            throw new CustomException(ResultCode.TOKEN_EXPIRED);</span><br><span class="line">        &#125;</span><br><span class="line">        return ResultGenerator.genSuccessResult().setMessage(&quot;token刷新成功&quot;).setData(map);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-效果测试"><a href="#7-效果测试" class="headerlink" title="7. 效果测试"></a>7. 效果测试</h3><h3 id="8-数据库和源码"><a href="#8-数据库和源码" class="headerlink" title="8. 数据库和源码"></a>8. 数据库和源码</h3><p><strong>上面只是项目的部分核心代码，完整代码和数据库已托管到Github上，请访问<a href="https://github.com/Janche/spring-security-rbac-jwt" target="_blank" rel="external">源码链接</a>自行下载，觉得有用的话，记得star哦，有什么问题欢迎大家通过issues或者邮件进行交流。</strong></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;此篇文章为spring security系列的第一篇，着重讲解如何通过spring security完成企业级项目的权限控制，以及采用Redis的方式控制JWT的失效。&lt;/p&gt;
&lt;h3 id=&quot;1-什么是RBAC&quot;&gt;&lt;a href=&quot;#1-什么是RBAC&quot; class=&quot;headerlink&quot; title=&quot;1. 什么是RBAC&quot;&gt;&lt;/a&gt;1. 什么是RBAC&lt;/h3&gt;&lt;p&gt;RBAC（Role-Based Access Control ）基于角色的权限控制，权限与角色相关联，用户通过成为适当角色的成员而得到这些角色的权限。&lt;/p&gt;
&lt;h3 id=&quot;2-JWT-和-Spring-Security&quot;&gt;&lt;a href=&quot;#2-JWT-和-Spring-Security&quot; class=&quot;headerlink&quot; title=&quot;2. JWT 和 Spring Security&quot;&gt;&lt;/a&gt;2. JWT 和 Spring Security&lt;/h3&gt;&lt;p&gt;spring security 授权主要分为两种，一种是security内部负责维护登录用户的session，一种则是采用JWT的方式，不管理session。关于JWT 和 Security的详细资料请小伙伴们自行查阅（相关网址推荐：&lt;a href=&quot;http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html）&quot;&gt;http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html）&lt;/a&gt;&lt;br&gt;此处就不在赘述，好了下面开始正文吧。&lt;/p&gt;
    
    </summary>
    
    
      <category term="spring-security" scheme="https://janche.github.io/tags/spring-security/"/>
    
      <category term="OAuth2" scheme="https://janche.github.io/tags/OAuth2/"/>
    
      <category term="jwt" scheme="https://janche.github.io/tags/jwt/"/>
    
  </entry>
  
  <entry>
    <title>Hexo(yilia)+Github实现相册和音频功能</title>
    <link href="https://janche.github.io/2019/06/16/Hexo-Github%E5%AE%9E%E7%8E%B0%E7%9B%B8%E5%86%8C%E5%92%8C%E9%9F%B3%E9%A2%91%E5%8A%9F%E8%83%BD/"/>
    <id>https://janche.github.io/2019/06/16/Hexo-Github实现相册和音频功能/</id>
    <published>2019-06-16T15:07:56.000Z</published>
    <updated>2019-06-16T15:22:15.711Z</updated>
    
    <content type="html"><![CDATA[<p><strong>效果图，请先点这里：<a href="https://janche.github.io/photos/">https://janche.github.io/photos/</a></strong></p><h3 id="1-需要准备的资料"><a href="#1-需要准备的资料" class="headerlink" title="1. 需要准备的资料"></a>1. 需要准备的资料</h3><blockquote><p><strong>1. 本文为<code>hexo-theme-yilia</code>主题，其他hexo主题请另行百度</strong><br><strong>2. GitHub上新建一个仓库存储照片（此仓库的作用除了储存还负责更新hexo博客引用的图片链接地址），为了少走弯路，请直接fork原作者的仓库(<a href="https://github.com/lawlite19/Blog-Back-Up.git)，若下载速度太慢，可选择我的备用地址(https://github.com/Janche/Blog-Photo.git)" target="_blank" rel="external">https://github.com/lawlite19/Blog-Back-Up.git)，若下载速度太慢，可选择我的备用地址(https://github.com/Janche/Blog-Photo.git)</a></strong><br><strong>3. Python环境（安装Python3，并配置环境变量，对照片的处理是通过Python命令来处理的）</strong><br><strong>4. 在你的hexo博客的<code>source</code>文件夹下(<code>注意不是yilia主题下的source</code>)，新建一个<code>photos</code>文件夹,用于存放照片相关的文件， 也可通过命令 <code>hexo new page photos</code>创建</strong><br><a id="more"></a></p></blockquote><h3 id="2-更改储存照片仓库-Blog-Photo-Blog-Back-Up-中的URL"><a href="#2-更改储存照片仓库-Blog-Photo-Blog-Back-Up-中的URL" class="headerlink" title="2. 更改储存照片仓库(Blog-Photo/Blog-Back-Up)中的URL"></a>2. 更改储存照片仓库(<code>Blog-Photo</code>/<code>Blog-Back-Up</code>)中的URL</h3><h4 id="2-1-仓库目录说明："><a href="#2-1-仓库目录说明：" class="headerlink" title="2.1 仓库目录说明："></a>2.1 仓库目录说明：</h4><p><img src="https://img-blog.csdnimg.cn/20190616211830463.png" alt="仓库目录"><br><strong>2.1.1 修改<code>ins.js</code></strong><br><img src="https://img-blog.csdnimg.cn/20190616213538210.png" alt="修改ins.js"><br>红框中的地址应改为你GitHub存储照片仓库图片的地址(<code>注意一定是点击download后地址栏的url</code>)，如图b<br>图a：<img src="https://img-blog.csdnimg.cn/20190616214440938.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0OTk3OTA2,size_16,color_FFFFFF,t_70" alt="图1"><br>图b:<br><img src="https://img-blog.csdnimg.cn/20190616220832849.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0OTk3OTA2,size_16,color_FFFFFF,t_70" alt="图2"></p><p><strong>2.1.3 修改tool.py文件</strong><br><img src="https://img-blog.csdnimg.cn/20190616213043110.png" alt="修改url"><br>将红框处的地址替换为hexo博客刚新建的photos地址，这样每次上传图片后将自动更新到hexo博客中。</p><p><strong>2.1.4 照片上传步骤</strong></p><blockquote><p><strong>1. 将<code>blog_photos_copy</code>目录下的5个文件拷贝到hexo博客刚新建的<code>photos</code>目录下，注意，只有第一次配置时需要执行这一步，后续新增照片均不需要。</strong><br><strong>2. 新加的照片按照<code>YYYY-MM-dd_照片文字描述.jpg</code>格式添加到<code>Blog-Photo</code>的<code>photos</code>目录</strong><br><strong>3. 运行Python命令：<code>python tool.py</code>，期间可能需要下载部分python的依赖，根据错误，百度下载就好了。</strong> </p></blockquote><h3 id="3-配置hexo博客"><a href="#3-配置hexo博客" class="headerlink" title="3. 配置hexo博客"></a>3. 配置hexo博客</h3><h4 id="3-1-修改yilia主题的-config-yml"><a href="#3-1-修改yilia主题的-config-yml" class="headerlink" title="3.1 修改yilia主题的_config.yml"></a>3.1 修改<code>yilia</code>主题的<code>_config.yml</code></h4><p><img src="https://img-blog.csdnimg.cn/20190616225106606.png" alt="图片3"></p><h4 id="3-2-将empty-png图片也放于之前的source目录下"><a href="#3-2-将empty-png图片也放于之前的source目录下" class="headerlink" title="3.2 将empty.png图片也放于之前的source目录下"></a>3.2 将empty.png图片也放于之前的<code>source</code>目录下</h4><p><img src="https://img-blog.csdnimg.cn/20190616231743342.png" alt="empty.png"><br>empty.png下载地址：<a href="https://raw.githubusercontent.com/Janche/janche.github.io/master/assets/img/empty.png" target="_blank" rel="external">https://raw.githubusercontent.com/Janche/janche.github.io/master/assets/img/empty.png</a><br>到这里，终于可以在博客上看到你自己的照片墙啦。</p><h3 id="4-为hexo博客添加音频播放"><a href="#4-为hexo博客添加音频播放" class="headerlink" title="4. 为hexo博客添加音频播放"></a>4. 为hexo博客添加音频播放</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;!--音乐播放插件--&gt;</span><br><span class="line">&lt;div style=&quot;margin-top:30px;&quot;&gt;                                                       </span><br><span class="line">  &lt;iframe frameborder=&quot;no&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; width=&quot;330&quot; height=&quot;86&quot; src=&quot;//music.163.com/outchain/player?type=2&amp;id=5232465&amp;auto=1&amp;height=66&quot;&gt;&lt;/iframe&gt;</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><p><strong>你没有看错，就只有这三行代码，你想在那篇文章加音乐，直接添加这三行就行了，<code>src</code>后的链接为歌曲的外链地址，很多歌曲都因为版权而无法生成外链地址。 </strong></p><p>参考文章：</p><ol><li><a href="https://blog.csdn.net/u013082989/article/details/70162293" target="_blank" rel="external">https://blog.csdn.net/u013082989/article/details/70162293</a></li><li><a href="https://blog.csdn.net/wardseptember/article/details/82780684" target="_blank" rel="external">https://blog.csdn.net/wardseptember/article/details/82780684</a></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;strong&gt;效果图，请先点这里：&lt;a href=&quot;https://janche.github.io/photos/&quot;&gt;https://janche.github.io/photos/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;1-需要准备的资料&quot;&gt;&lt;a href=&quot;#1-需要准备的资料&quot; class=&quot;headerlink&quot; title=&quot;1. 需要准备的资料&quot;&gt;&lt;/a&gt;1. 需要准备的资料&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;1. 本文为&lt;code&gt;hexo-theme-yilia&lt;/code&gt;主题，其他hexo主题请另行百度&lt;/strong&gt;&lt;br&gt;&lt;strong&gt;2. GitHub上新建一个仓库存储照片（此仓库的作用除了储存还负责更新hexo博客引用的图片链接地址），为了少走弯路，请直接fork原作者的仓库(&lt;a href=&quot;https://github.com/lawlite19/Blog-Back-Up.git)，若下载速度太慢，可选择我的备用地址(https://github.com/Janche/Blog-Photo.git)&quot;&gt;https://github.com/lawlite19/Blog-Back-Up.git)，若下载速度太慢，可选择我的备用地址(https://github.com/Janche/Blog-Photo.git)&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;strong&gt;3. Python环境（安装Python3，并配置环境变量，对照片的处理是通过Python命令来处理的）&lt;/strong&gt;&lt;br&gt;&lt;strong&gt;4. 在你的hexo博客的&lt;code&gt;source&lt;/code&gt;文件夹下(&lt;code&gt;注意不是yilia主题下的source&lt;/code&gt;)，新建一个&lt;code&gt;photos&lt;/code&gt;文件夹,用于存放照片相关的文件， 也可通过命令 &lt;code&gt;hexo new page photos&lt;/code&gt;创建&lt;/strong&gt;&lt;br&gt;
    
    </summary>
    
    
      <category term="hexo" scheme="https://janche.github.io/tags/hexo/"/>
    
  </entry>
  
  <entry>
    <title>Runtime 调用Process.waitfor导致的阻塞问题</title>
    <link href="https://janche.github.io/2019/06/16/Runtime-%E8%B0%83%E7%94%A8Process-waitfor%E5%AF%BC%E8%87%B4%E7%9A%84%E9%98%BB%E5%A1%9E%E9%97%AE%E9%A2%98/"/>
    <id>https://janche.github.io/2019/06/16/Runtime-调用Process-waitfor导致的阻塞问题/</id>
    <published>2019-06-16T01:34:20.000Z</published>
    <updated>2019-06-16T15:09:37.974Z</updated>
    
    <content type="html"><![CDATA[<h3 id="1-关于Runtime类的小知识"><a href="#1-关于Runtime类的小知识" class="headerlink" title="1. 关于Runtime类的小知识"></a>1. 关于Runtime类的小知识</h3><blockquote><p><strong>1. Runtime.getRuntime()可以取得当前JVM的运行时环境，这也是在Java中唯一一个得到运行时环境的方法</strong><br><strong>2. Runtime中的exit方法是退出JVM</strong></p></blockquote><h3 id="2-Runtime的几个重要的重载方法"><a href="#2-Runtime的几个重要的重载方法" class="headerlink" title="2. Runtime的几个重要的重载方法"></a>2. Runtime的几个重要的重载方法</h3><a id="more"></a><table><thead><tr><th>方法名</th><th>作用</th></tr></thead><tbody><tr><td>exec(String command);</td><td>在单独的进程中执行指定的字符串命令。</td></tr><tr><td>exec(String command, String[] envp)</td><td>在指定环境的单独进程中执行指定的字符串命令。</td></tr><tr><td>exec(String[] cmdarray, String[] envp, File dir)</td><td>在指定环境和工作目录的独立进程中执行指定的命令和变量</td></tr><tr><td>exec(String command, String[] envp, File dir)</td><td>在有指定环境和工作目录的独立进程中执行指定的字符串命令。</td></tr></tbody></table><p>Runtime类的重要的方法还有很多，简单列举几个</p><blockquote><p>1.exit(int status)：终止当前正在运行的 Java 虚拟机<br>2.freeMemory()： 返回 Java 虚拟机中的空闲内存量。<br>3.load(String filename)： 加载作为动态库的指定文件名。<br>4.loadLibrary(String libname)： 加载具有指定库名的动态库。</p></blockquote><h3 id="3-Runtime的使用方式"><a href="#3-Runtime的使用方式" class="headerlink" title="3. Runtime的使用方式"></a>3. Runtime的使用方式</h3><p><strong>3.1 错误的使用exitValue()</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public static void main(String[] args) throws IOException &#123;</span><br><span class="line">      String command = &quot;ping www.baidu.com&quot;;</span><br><span class="line">      Process process = Runtime.getRuntime().exec(command);</span><br><span class="line">      int i = process.exitValue();</span><br><span class="line">      System.out.println(&quot;字进程退出值：&quot;+i);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></p><p><strong>输出：</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Exception in thread &quot;main&quot; java.lang.IllegalThreadStateException: process has not exited</span><br><span class="line">at java.lang.ProcessImpl.exitValue(ProcessImpl.java:443)</span><br><span class="line">at com.lirong.think.runtime.ProcessUtils.main(ProcessUtils.java:26)</span><br></pre></td></tr></table></figure></p><p><strong>原因：</strong></p><blockquote><p>exitValue()方法是非阻塞的，在调用这个方法时cmd命令并没有返回所以引起异常。阻塞形式的方法是waitFor，它会一直等待外部命令执行完毕，然后返回执行的结果。</p></blockquote><p><strong>修改后的版本：</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">public static void main(String[] args) throws IOException &#123;</span><br><span class="line">      String command = &quot;javac&quot;;</span><br><span class="line">      Process process = Runtime.getRuntime().exec(command);</span><br><span class="line">      process.waitFor();</span><br><span class="line">      process.destroy();</span><br><span class="line">      int i = process.exitValue();</span><br><span class="line">      System.out.println(&quot;字进程退出值：&quot;+i);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></p><p><strong>此版本已然可以正常运行，但当主线程和子线程有很多交互的时候还是会出问题，会出现卡死的情况。</strong></p><h4 id="4-卡死原因"><a href="#4-卡死原因" class="headerlink" title="4. 卡死原因"></a>4. 卡死原因</h4><blockquote><p><strong>主进程中调用Runtime.exec会创建一个子进程，用于执行cmd命令。子进程创建后会和主进程分别独立运行。<br> 因为主进程需要等待脚本执行完成，然后对命令返回值或输出进行处理，所以这里主进程调用Process.waitfor等待子进程完成。<br> 运行此cmd命令可以知道：子进程执行过程就是打印信息。主进程中可以通过Process.getInputStream和Process.getErrorStream获取并处理。<br> 这时候子进程不断向主进程发生数据，而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后，子进程不能继续写数据，然后也会挂起。<br>这样子进程等待主进程读取数据，主进程等待子进程结束，两个进程相互等待，最终导致死锁。</strong></p></blockquote><h4 id="5-解决方案"><a href="#5-解决方案" class="headerlink" title="5. 解决方案"></a>5. 解决方案</h4><p>不断的读取消耗缓冲区的数据，以至子进程不会挂起，下面是具体代码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @desc CMD命令测试</span><br><span class="line"> * @date 2019/06/13 20:50</span><br><span class="line"> */</span><br><span class="line">@Slf4j</span><br><span class="line">public class ProcessUtils &#123;</span><br><span class="line">    public static void main(String[] args) throws IOException, InterruptedException &#123;</span><br><span class="line">        String command = &quot;ping www.baidu.com&quot;;</span><br><span class="line">        Process process = Runtime.getRuntime().exec(command);</span><br><span class="line">        readStreamInfo(process.getInputStream(), process.getErrorStream());</span><br><span class="line">        int exit = process.waitFor();</span><br><span class="line">        process.destroy();</span><br><span class="line">        if (exit == 0) &#123;</span><br><span class="line">            log.debug(&quot;子进程正常完成&quot;);</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            log.debug(&quot;子进程异常结束&quot;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 读取RunTime.exec运行子进程的输入流 和 异常流</span><br><span class="line">     * @param inputStreams 输入流</span><br><span class="line">     */</span><br><span class="line">    public static void readStreamInfo(InputStream... inputStreams)&#123;</span><br><span class="line">        ExecutorService executorService = Executors.newFixedThreadPool(inputStreams.length);</span><br><span class="line">        for (InputStream in : inputStreams) &#123;</span><br><span class="line">            executorService.execute(new MyThread (in));</span><br><span class="line">        &#125;</span><br><span class="line">        executorService.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @desc</span><br><span class="line"> * @date 2019/06/13 21:25</span><br><span class="line"> */</span><br><span class="line">@Slf4j</span><br><span class="line">public class MyThread implements Runnable &#123;</span><br><span class="line"></span><br><span class="line">    private InputStream in;</span><br><span class="line">    public MyThread(InputStream in)&#123;</span><br><span class="line">        this.in = in;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public void run() &#123;</span><br><span class="line">        try&#123;</span><br><span class="line">            BufferedReader br = new BufferedReader(new InputStreamReader(in, &quot;GBK&quot;));</span><br><span class="line">            String line = null;</span><br><span class="line">            while((line = br.readLine())!=null)&#123;</span><br><span class="line">                log.debug(&quot; inputStream: &quot; + line);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;catch (IOException e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;finally &#123;</span><br><span class="line">            try &#123;</span><br><span class="line">                in.close();</span><br><span class="line">            &#125; catch (IOException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>写到这里大家以为都结束了哇，并没有，哈哈哈，真实的生成环境总能给你带来很多神奇的问题，Runtime不仅可以直接调用CMD执行命令，还可以调用其他.exe程序执行命令。所以纵使你读取了缓冲区的数据，你的程序依然可能会被卡死，因为有可能你的缓冲区根本就没有数据，而是你的.exe程序卡主了。嗯，所以为你以防万一，你还需要设置超时。</p><h3 id="6-Runtime最优雅的调用方式"><a href="#6-Runtime最优雅的调用方式" class="headerlink" title="6. Runtime最优雅的调用方式"></a>6. Runtime最优雅的调用方式</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @desc</span><br><span class="line"> * @date 2019/06/13 20:50</span><br><span class="line"> */</span><br><span class="line">@Slf4j</span><br><span class="line">public class ProcessUtils &#123;</span><br><span class="line"></span><br><span class="line">   /**</span><br><span class="line">     * @param timeout 超时时长</span><br><span class="line">     * @param fileDir 所运行程序路径</span><br><span class="line">     * @param command 程序所要执行的命令</span><br><span class="line">     * 运行一个外部命令，返回状态.若超过指定的超时时间，抛出TimeoutException</span><br><span class="line">     */</span><br><span class="line">    public static int executeProcess(final long timeout, File fileDir, final String[] command)</span><br><span class="line">            throws IOException, InterruptedException, TimeoutException &#123;</span><br><span class="line">        Process process = Runtime.getRuntime().exec(command, null, fileDir);</span><br><span class="line">        Worker worker = new Worker(process);</span><br><span class="line">        worker.start();</span><br><span class="line">        try &#123;</span><br><span class="line">            worker.join(timeout);</span><br><span class="line">            if (worker.exit != null)&#123;</span><br><span class="line">                return worker.exit;</span><br><span class="line">            &#125; else&#123;</span><br><span class="line">                throw new TimeoutException();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; catch (InterruptedException ex) &#123;</span><br><span class="line">            worker.interrupt();</span><br><span class="line">            Thread.currentThread().interrupt();</span><br><span class="line">            throw ex;</span><br><span class="line">        &#125;</span><br><span class="line">        finally &#123;</span><br><span class="line">            process.destroy();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    private static class Worker extends Thread &#123;</span><br><span class="line">        private final Process process;</span><br><span class="line">        private Integer exit;</span><br><span class="line"></span><br><span class="line">        private Worker(Process process) &#123;</span><br><span class="line">            this.process = process;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void run() &#123;</span><br><span class="line">            InputStream errorStream = null;</span><br><span class="line">            InputStream inputStream = null;</span><br><span class="line">            try &#123;</span><br><span class="line">                errorStream = process.getErrorStream();</span><br><span class="line">                inputStream = process.getInputStream();</span><br><span class="line">                readStreamInfo(errorStream, inputStream);</span><br><span class="line">                exit = process.waitFor();</span><br><span class="line">                process.destroy();</span><br><span class="line">                if (exit == 0) &#123;</span><br><span class="line">                    log.debug(&quot;子进程正常完成&quot;);</span><br><span class="line">                &#125; else &#123;</span><br><span class="line">                    log.debug(&quot;子进程异常结束&quot;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; catch (InterruptedException ignore) &#123;</span><br><span class="line">                return;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 读取RunTime.exec运行子进程的输入流 和 异常流</span><br><span class="line">     * @param inputStreams 输入流</span><br><span class="line">     */</span><br><span class="line">    public static void readStreamInfo(InputStream... inputStreams)&#123;</span><br><span class="line">        ExecutorService executorService = Executors.newFixedThreadPool(inputStreams.length);</span><br><span class="line">        for (InputStream in : inputStreams) &#123;</span><br><span class="line">            executorService.execute(new MyThread(in));</span><br><span class="line">        &#125;</span><br><span class="line">        executorService.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;1-关于Runtime类的小知识&quot;&gt;&lt;a href=&quot;#1-关于Runtime类的小知识&quot; class=&quot;headerlink&quot; title=&quot;1. 关于Runtime类的小知识&quot;&gt;&lt;/a&gt;1. 关于Runtime类的小知识&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;1. Runtime.getRuntime()可以取得当前JVM的运行时环境，这也是在Java中唯一一个得到运行时环境的方法&lt;/strong&gt;&lt;br&gt;&lt;strong&gt;2. Runtime中的exit方法是退出JVM&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-Runtime的几个重要的重载方法&quot;&gt;&lt;a href=&quot;#2-Runtime的几个重要的重载方法&quot; class=&quot;headerlink&quot; title=&quot;2. Runtime的几个重要的重载方法&quot;&gt;&lt;/a&gt;2. Runtime的几个重要的重载方法&lt;/h3&gt;
    
    </summary>
    
    
      <category term="Runtime" scheme="https://janche.github.io/tags/Runtime/"/>
    
  </entry>
  
  <entry>
    <title>为什么要使用serialVersionUID</title>
    <link href="https://janche.github.io/2019/05/19/%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E4%BD%BF%E7%94%A8serialVersionUID/"/>
    <id>https://janche.github.io/2019/05/19/为什么要使用serialVersionUID/</id>
    <published>2019-05-19T09:53:52.000Z</published>
    <updated>2019-05-19T12:25:19.892Z</updated>
    
    <content type="html"><![CDATA[<h3 id="1-序列化是什么"><a href="#1-序列化是什么" class="headerlink" title="1. 序列化是什么"></a>1. 序列化是什么</h3><ul><li><strong>把对象转换为字节序列的过程称为对象的序列化 。</strong></li><li><strong>把字节序列恢复为对象的过程称为对象的反序列化。</strong></li></ul><h3 id="2-Java中如何使用序列化"><a href="#2-Java中如何使用序列化" class="headerlink" title="2. Java中如何使用序列化"></a>2. Java中如何使用序列化</h3><p>只需要让对象实现 <code>Serializable</code>接口即可，如下：<br><a id="more"></a><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @desc 序列化类</span><br><span class="line"> * @date 2019/05/18 12:04</span><br><span class="line"> */</span><br><span class="line">public class Person implements Serializable &#123;</span><br><span class="line"></span><br><span class="line">    private static final long serialVersionUID = 1L;</span><br><span class="line"></span><br><span class="line">    private int id;</span><br><span class="line">    private String name;</span><br><span class="line"></span><br><span class="line">    public Person(int id, String name) &#123;</span><br><span class="line">        this.id = id;</span><br><span class="line">        this.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    @Override</span><br><span class="line">    public String toString() &#123;</span><br><span class="line">        return &quot;Person: &quot; +</span><br><span class="line">                &quot;, id: &quot;+ id +</span><br><span class="line">                &quot;, name: &quot; + name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>让我们来看看<code>Serializable</code> 接口都帮我们做了什么工作？</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">If a serializable class does not explicitly declare a serialVersionUID,</span><br><span class="line">  then the serialization runtime will calculate a default </span><br><span class="line">  serialVersionUID value for that class based on various aspects of the class, </span><br><span class="line"> as described in the Java(TM) Object Serialization Specification.</span><br></pre></td></tr></table></figure><p>大致意思就是：如果没有为Class显示的声明<code>serialVersionUID</code>的话，运行时JVM将默认为Class根据Class的各个字段属性生成一个<code>serialVersionUID</code>，具体生成方式就不做研究了，大致应该是根据各字段的hash值凑成的吧。</p><h3 id="3-测试代码"><a href="#3-测试代码" class="headerlink" title="3. 测试代码"></a>3. 测试代码</h3><p><strong>3.1 序列化测试类：</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @desc 序列化测试类</span><br><span class="line"> * @since 2019/05/18 12:09</span><br><span class="line"> */</span><br><span class="line">public class SerialTest &#123;</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) throws IOException &#123;</span><br><span class="line">        Person person = new Person(1234, &quot;Janche&quot;);</span><br><span class="line">        System.out.println(&quot;Person Serial&quot; + person);</span><br><span class="line">        FileOutputStream fos = new FileOutputStream(&quot;Person.txt&quot;);</span><br><span class="line">        ObjectOutputStream oos = new ObjectOutputStream(fos);</span><br><span class="line">        oos.writeObject(person);</span><br><span class="line">        oos.flush();</span><br><span class="line">        oos.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p><strong>3.2 反序列化测试类</strong><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * @author lirong</span><br><span class="line"> * @desc 反序列化测试类</span><br><span class="line"> * @date 2019/05/18 12:16</span><br><span class="line"> */</span><br><span class="line">public class DeserialTest &#123;</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) throws IOException, ClassNotFoundException &#123;</span><br><span class="line">        FileInputStream fis = new FileInputStream(&quot;Person.txt&quot;);</span><br><span class="line">        ObjectInputStream ois = new ObjectInputStream(fis);</span><br><span class="line">        Person person = (Person) ois.readObject();</span><br><span class="line">        ois.close();</span><br><span class="line">        System.out.println(&quot;Person Deserial&quot; + person);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><blockquote><h4 id="4-测试条件"><a href="#4-测试条件" class="headerlink" title="4. 测试条件"></a>4. 测试条件</h4><p>a. 不实现<code>Serializable</code>接口<br>b. 实现<code>Serializable</code>接口<br>c. 未自定义<code>serialVersionUID</code>字段<br>d. 自定义<code>serialVersionUID</code>字段<br>e. 反序列化前增加字段<br>f. 反序列化前减少字段<br>g. 反序列化前修改字段的类型<br><strong>说明：反序列化前 表示 在序列化操作完成之后到反序列化开始之前这段时间，更改<code>Person</code>类的字段</strong></p></blockquote><blockquote><h5 id="5-测试结果"><a href="#5-测试结果" class="headerlink" title="5. 测试结果"></a>5. 测试结果</h5><p>1.只要有a出现,就会导致<code>java.io.NotSerializableException</code>异常，即无法完成序列化。<br>2.当条件为 b+c  或者 b+d 时，序列化和反序列化均正常执行。<br>3.当条件为 b+c+e 或者 b+c+f 时，反序列化时将抛出<code>InvalidClassException</code>异常。<br>4.当条件为 b+d+e 或者 b+d+f 时，序列化和反序列一切正常，增加字段时，反序列化出来该字段为缺省值，减少字段时，反序列化出来也将没有此字段。<br>5.当条件包含 g 时，反序列化也将抛出<code>InvalidClassException</code>异常。</p></blockquote><blockquote><h5 id="6-测试总结"><a href="#6-测试总结" class="headerlink" title="6. 测试总结"></a>6. 测试总结</h5><p><strong>1. 尽量为每一个需要序列化的类自定义<code>serialVersionUID</code>值，这样可避免因为Class中字段的修改而带来的反序列化异常。</strong><br><strong>2. 尽量不要修改序列化类中的字段类型，如果一定要修改，请重新序列化后再反序列化。</strong><br><strong>3. 如果要为序列化的类中忽略某个字段，可添加<code>transient</code>关键字</strong></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;1-序列化是什么&quot;&gt;&lt;a href=&quot;#1-序列化是什么&quot; class=&quot;headerlink&quot; title=&quot;1. 序列化是什么&quot;&gt;&lt;/a&gt;1. 序列化是什么&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;把对象转换为字节序列的过程称为对象的序列化 。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把字节序列恢复为对象的过程称为对象的反序列化。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-Java中如何使用序列化&quot;&gt;&lt;a href=&quot;#2-Java中如何使用序列化&quot; class=&quot;headerlink&quot; title=&quot;2. Java中如何使用序列化&quot;&gt;&lt;/a&gt;2. Java中如何使用序列化&lt;/h3&gt;&lt;p&gt;只需要让对象实现 &lt;code&gt;Serializable&lt;/code&gt;接口即可，如下：&lt;br&gt;
    
    </summary>
    
    
      <category term="序列化" scheme="https://janche.github.io/tags/%E5%BA%8F%E5%88%97%E5%8C%96/"/>
    
  </entry>
  
</feed>
