<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringSecurity+OAuth2.0怎么玩?

          共 108850字,需瀏覽 218分鐘

           ·

          2021-04-10 10:49

          點擊上方藍色字體,選擇“標星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

            作者 |  夢游的龍貓

          來源 |  urlify.cn/VzeQ7b

          前言

            關(guān)于 OAuth2.0的認證體系,翻閱了好多資料,RCF 文檔太多,看了一半就看不下去了,畢竟全英文的文檔看起來,是有一點讓我煩躁,但也對 OAuth2.0的認證流程有了一個基本的概念,之前用 SpringSecurity 做了一個基于 RBAC 的權(quán)限管理系統(tǒng)的基礎(chǔ)配置,所以對 SpringSecurity 算是比較了解了,于是 OAuth2.0的實現(xiàn),也想用 SpringSecurity 的來做,心想應(yīng)該比較簡單,然而...事實上,我反反復(fù)復(fù),拿起又放棄,放棄又拿起,來來回回折騰了3個多月,才真正的掌握了這個 OAuth2.0插件(OAuth2.0不是一個獨立的框架,只是 SpringSecurity 的一個插件而已)。

            官網(wǎng)的 Demo 配置,是基于 JavaConfig 的配置方式,以前都用 XML 的,沒接觸過 JavaConfig,所以又繞了一圈,把 JavaConfig 方式的所有框架(Spring、SpringMVC、Mybatis、SpringSecurity、Web.xml)基本配置方式都走了一圈, 確實,全代碼配置是很酷,很清爽,說實話,今后我也會逐漸往這方面走,因為這個方式比較有代碼感,哈哈,但是現(xiàn)在還不行,因為有很多插件啊、特殊的配置方式啊,我都還不清楚要怎么配置,處于安全考慮,還是老老實實的用 XML 的比較好。

           

            額外插播一則我團隊的招聘廣告:

            阿里巴巴 - 淘系技術(shù)部招聘:https://www.cnblogs.com/wuxinzhe/p/11258226.html

           

           項目的說明

            網(wǎng)上有很多,SpringSecurityOAuth2.0的配置文章,但是每個文章,都是將認證服務(wù)器和資源服務(wù)器寫在一起的,并沒有將認證與資源分離,也沒有講不同的資源之間如何拆分,然而我們在設(shè)計分布式系統(tǒng)的時候,總會以模塊化的方式,將不同的資源寫成不同的項目,比如,將網(wǎng)站的一個電商系統(tǒng),專門寫成一個項目,把網(wǎng)站中的論壇系統(tǒng),寫成另一個項目,部署的時候,每個項目就可以單獨部署,后端系統(tǒng)均以 RESTFull 的方式開放數(shù)據(jù)接口(RESTFull就是推薦使用 OAuth2.0的方式進行認證管理)。這樣的方式來設(shè)計程序,最大的優(yōu)點就是模塊之間相互獨立,互不干涉,在開發(fā)工作當中,可以并行開發(fā),單獨維護,同時模塊分離出來,今后還可以進行很便利的集群,而不需要修改任何原來的代碼,所以對整個項目的擴展性是非常好的,不同的項目之間,可以簡單的使用 HttpClient 進行通訊,OAuth2.0五種授權(quán)模式當中,有一種授權(quán)模式就是為這種資源服務(wù)器之間的通訊而設(shè)計的。

            認證服務(wù)器與資源服務(wù)器分離的這個配置方式,同時也實現(xiàn)了“統(tǒng)一認證”的模式,只需要在認真服務(wù)器上做了認證,拿到了 Token,就可以訪問所有授權(quán)的資源服務(wù)器。

            接下來,我們開始搭建認證服務(wù)器的配置。

             POM

          項目用到的框架有這幾個:Spring、SpringSecurity、Mybatis

          1 <?xml version="1.0" encoding="UTF-8"?>
            2 <project xmlns="http://maven.apache.org/POM/4.0.0"
            3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            5     <modelVersion>4.0.0</modelVersion>
            6     <groupId>Showings</groupId>
            7     <artifactId>OAuthServer</artifactId>
            8     <version>1.0-SNAPSHOT</version>
            9     <build>
           10         <finalName>showings</finalName>
           11         <plugins>
           12             <!--Mybatis 逆向工程插件-->
           13             <plugin>
           14                 <groupId>org.mybatis.generator</groupId>
           15                 <artifactId>mybatis-generator-maven-plugin</artifactId>
           16                 <version>1.3.2</version>
           17                 <configuration>
           18                     <verbose>true</verbose>
           19                     <overwrite>true</overwrite>
           20                 </configuration>
           21             </plugin>
           22             <plugin>
           23                 <groupId>org.apache.maven.plugins</groupId>
           24                 <artifactId>maven-compiler-plugin</artifactId>
           25                 <configuration>
           26                     <source>1.7</source>
           27                     <target>1.7</target>
           28                 </configuration>
           29             </plugin>
           30         </plugins>
           31     </build>
           32     <properties>
           33         <security.version>4.2.2.RELEASE</security.version>
           34         <spring.version>4.3.7.RELEASE</spring.version>
           35         <security.oauth.version>2.0.7.RELEASE</security.oauth.version>
           36     </properties>
           37     <dependencies>
           38         <!-- SpringFramework Start -->
           39         <dependency>
           40             <groupId>org.springframework</groupId>
           41             <artifactId>spring-core</artifactId>
           42             <version>${spring.version}</version>
           43         </dependency>
           44 
           45         <dependency>
           46             <groupId>org.springframework</groupId>
           47             <artifactId>spring-web</artifactId>
           48             <version>${spring.version}</version>
           49         </dependency>
           50 
           51         <dependency>
           52             <groupId>org.springframework</groupId>
           53             <artifactId>spring-oxm</artifactId>
           54             <version>${spring.version}</version>
           55         </dependency>
           56 
           57         <dependency>
           58             <groupId>org.springframework</groupId>
           59             <artifactId>spring-tx</artifactId>
           60             <version>${spring.version}</version>
           61         </dependency>
           62 
           63         <dependency>
           64             <groupId>org.springframework</groupId>
           65             <artifactId>spring-webmvc</artifactId>
           66             <version>${spring.version}</version>
           67         </dependency>
           68 
           69         <dependency>
           70             <groupId>org.springframework</groupId>
           71             <artifactId>spring-aop</artifactId>
           72             <version>${spring.version}</version>
           73         </dependency>
           74 
           75         <dependency>
           76             <groupId>org.springframework</groupId>
           77             <artifactId>spring-context-support</artifactId>
           78             <version>${spring.version}</version>
           79             <!--排除自帶的日志工具,從而轉(zhuǎn)向使用SLF4J日志-->
           80             <exclusions>
           81                 <exclusion>
           82                     <groupId>commons-logging</groupId>
           83                     <artifactId>commons-logging</artifactId>
           84                 </exclusion>
           85             </exclusions>
           86         </dependency>
           87 
           88         <dependency>
           89             <groupId>org.springframework</groupId>
           90             <artifactId>spring-expression</artifactId>
           91             <version>${spring.version}</version>
           92         </dependency>
           93         <!-- SpringFramework End -->
           94         <dependency>
           95             <groupId>javax.validation</groupId>
           96             <artifactId>validation-api</artifactId>
           97             <version>2.0.0.Alpha2</version>
           98         </dependency>
           99         <!--數(shù)據(jù)有效性驗證框架-->
          100         <dependency>
          101             <groupId>org.hibernate</groupId>
          102             <artifactId>hibernate-validator</artifactId>
          103             <version>6.0.0.Alpha2</version>
          104         </dependency>
          105         <!--c3p0-->
          106         <dependency>
          107             <groupId>com.mchange</groupId>
          108             <artifactId>c3p0</artifactId>
          109             <version>0.9.5.1</version>
          110         </dependency>
          111         <!--Mybatis-->
          112         <dependency>
          113             <groupId>org.mybatis</groupId>
          114             <artifactId>mybatis</artifactId>
          115             <version>3.3.0</version>
          116         </dependency>
          117         <!--Mybatis分頁工具 pageHelper-->
          118         <dependency>
          119             <groupId>com.github.pagehelper</groupId>
          120             <artifactId>pagehelper</artifactId>
          121             <version>4.1.6</version>
          122         </dependency>
          123         <!--分頁搭配SQL解析工具-->
          124         <dependency>
          125             <groupId>com.github.jsqlparser</groupId>
          126             <artifactId>jsqlparser</artifactId>
          127             <version>0.9.6</version>
          128         </dependency>
          129         <!--Mybatis Spring整合-->
          130         <dependency>
          131             <groupId>org.mybatis</groupId>
          132             <artifactId>mybatis-spring</artifactId>
          133             <version>1.2.3</version>
          134         </dependency>
          135 
          136         <!--MySQL Driver-->
          137         <dependency>
          138             <groupId>mysql</groupId>
          139             <artifactId>mysql-connector-java</artifactId>
          140             <version>5.1.6</version>
          141         </dependency>
          142         <dependency>
          143             <groupId>jstl</groupId>
          144             <artifactId>jstl</artifactId>
          145             <version>1.2</version>
          146         </dependency>
          147         <!-- https://mvnrepository.com/artifact/javax.el/javax.el-api -->
          148         <dependency>
          149             <groupId>javax.el</groupId>
          150             <artifactId>javax.el-api</artifactId>
          151             <version>3.0.1-b04</version>
          152         </dependency>
          153 
          154         <!--spring security-->
          155         <dependency>
          156             <groupId>org.springframework.security</groupId>
          157             <artifactId>spring-security-core</artifactId>
          158             <version>${security.version}</version>
          159         </dependency>
          160         <dependency>
          161             <groupId>org.springframework.security</groupId>
          162             <artifactId>spring-security-web</artifactId>
          163             <version>${security.version}</version>
          164         </dependency>
          165         <dependency>
          166             <groupId>org.springframework.security</groupId>
          167             <artifactId>spring-security-taglibs</artifactId>
          168             <version>${security.version}</version>
          169         </dependency>
          170         <dependency>
          171             <groupId>org.springframework.security</groupId>
          172             <artifactId>spring-security-config</artifactId>
          173             <version>${security.version}</version>
          174         </dependency>
          175 
          176         <dependency>
          177             <groupId>org.springframework.security.oauth</groupId>
          178             <artifactId>spring-security-oauth2</artifactId>
          179             <version>${security.oauth.version}</version>
          180         </dependency>
          181 
          182         <!--SLF4J日志 start-->
          183         <dependency>
          184             <groupId>org.slf4j</groupId>
          185             <artifactId>slf4j-api</artifactId>
          186             <version>1.7.10</version>
          187         </dependency>
          188         <dependency>
          189             <groupId>ch.qos.logback</groupId>
          190             <artifactId>logback-classic</artifactId>
          191             <version>1.1.2</version>
          192         </dependency>
          193         <dependency>
          194             <groupId>ch.qos.logback</groupId>
          195             <artifactId>logback-core</artifactId>
          196             <version>1.1.2</version>
          197         </dependency>
          198         <!--SLF4J日志 end-->
          199 
          200         <dependency>
          201             <groupId>javax.servlet</groupId>
          202             <artifactId>javax.servlet-api</artifactId>
          203             <version>3.1.0</version>
          204         </dependency>
          205 
          206         <!--Jackson start-->
          207         <dependency>
          208             <groupId>org.codehaus.jackson</groupId>
          209             <artifactId>jackson-mapper-asl</artifactId>
          210             <version>1.9.13</version>
          211         </dependency>
          212         <dependency>
          213             <groupId>com.fasterxml.jackson.core</groupId>
          214             <artifactId>jackson-annotations</artifactId>
          215             <version>2.6.1</version>
          216         </dependency>
          217         <dependency>
          218             <groupId>com.fasterxml.jackson.core</groupId>
          219             <artifactId>jackson-core</artifactId>
          220             <version>2.6.1</version>
          221         </dependency>
          222         <dependency>
          223             <groupId>com.fasterxml.jackson.core</groupId>
          224             <artifactId>jackson-databind</artifactId>
          225             <version>2.6.1</version>
          226         </dependency>
          227         <!--Jackson end-->
          228 
          229     </dependencies>
          230 
          231 </project>

          Pom

             Pom 很長,但其實沒有多少內(nèi)容,我們需要自己寫的代碼,也非常非常非常的少...= =,是不是很開心?嘿嘿...

             項目目錄結(jié)構(gòu)

            Yes,你沒看錯,目錄內(nèi)容真的很少...Java 的部分,真的就沒幾個= =

             配置文件

          首先是 Dao 的配置文件:

          application-dao.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:context="http://www.springframework.org/schema/context"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


              <!--獲取數(shù)據(jù)庫配置文件-->
              <context:property-placeholder location="classpath:config/db.properties"/>
              <context:component-scan base-package="cn.com.showings.mapper"/>

              <!--設(shè)置數(shù)據(jù)源c3p0-->
              <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
                  <property name="driverClass" value="${jdbc.driver}"/>
                  <property name="jdbcUrl" value="${jdbc.url}"/>
                  <property name="user" value="${jdbc.username}"/>
                  <property name="password" value="${jdbc.password}"/>
                  <property name="maxPoolSize" value="50"/>
                  <property name="minPoolSize" value="2"/>
                  <property name="maxIdleTime" value="60"/>
              </bean>

              <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
                  <property name="configLocation" value="classpath:config/mybatis-config.xml"/>
                  <property name="dataSource" ref="dataSource"/>
                  <!-- 顯式指定Mapper文件位置 -->
                  <property name="mapperLocations">
                      <list>
                          <value>classpath*:/mapper/*.xml</value>
                      </list>
                  </property>
              </bean>

              <!--自動掃描mapper接口,并注入sqlsession-->
              <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
                  <property name="basePackage" value="cn.com.showings.mapper"/>
                  <property name="sqlSessionFactoryBeanName" value="sqlSession"/>
              </bean>

              <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
                  <property name="dataSource" ref="dataSource"/>
              </bean>
          </beans>

            這邊可以看到,我們明明使用的是 Mybatis 的 ORM框架,為啥還要配置一個 jdbcTemplate?其實說來慚愧,本人比較擅長 MyBatis,所以算是強迫癥一定要用這個框架,但是人家 Spring 有自己的 SpringData 的框架,而 SpringSecurityOAuth2.0的插件中,很多內(nèi)容都是用 SpringData 的方式去實現(xiàn)的,我如果要棄用 jdbcTemplate,那我得重寫所有框架內(nèi)涉及的數(shù)據(jù)庫操作,那太累了- -,當然啦,我這么寫肯定不好,因為認證系統(tǒng)本身沒有什么復(fù)雜邏輯和除了框架外的額外操作,所以我這么做,挺浪費資源的(占內(nèi)存),大家可以不要效仿這一塊,用到 MyBatis 的地方只有一個讀取用戶名及密碼的接口,也就是說,為了一個接口,確實沒有必要引入一個框架。等我掌握了 jdbcTemplate 的用法,我也會去掉這個累贅。

            當然~!如果,你的認證系統(tǒng),跟用戶管理系統(tǒng),是合在一起的情況下,那倒是沒啥問題,畢竟用戶管理也是有很多邏輯的,像注冊呀、改密啊、綁定密保啊、修改用戶信息呀,這些什么鬼的。

           接著我們來配置 Service:

          application-service.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:context="http://www.springframework.org/schema/context"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

              <!--掃描service-->
              <context:component-scan base-package="cn.com.showings.service"/>

              <!--注冊統(tǒng)一異常控制-->
              <bean id="exception" class="cn.com.showings.controller.ExceptionController"/>

          </beans>

            沒什么內(nèi)容,看注釋就知道了。

          然后配置 Transaction:

          application-transaction.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans
                 http://www.springframework.org/schema/beans/spring-beans.xsd
                 http://www.springframework.org/schema/tx
                 http://www.springframework.org/schema/tx/spring-tx.xsd"
          >
              <!--事務(wù)管理對象-->
              <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                  <property name="dataSource" ref="dataSource"/>
              </bean>
              <!--注解事務(wù)-->
              <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

          </beans>

            嗯...事實上,可以不用配置,因為根本用不上。

          配置 Spring-mvc:

          spring-mvc.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:context="http://www.springframework.org/schema/context"
                 xmlns:mvc="http://www.springframework.org/schema/mvc"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

              <!--自動掃描控制器-->
              <context:component-scan base-package="cn.com.showings.controller"/>
              <!--控制器映射器和控制器適配器-->
              <mvc:annotation-driven/>
              <mvc:default-servlet-handler/>

              <mvc:resources mapping="/js/**" location="/js/"/>
              <mvc:resources mapping="/css/**" location="/css/"/>
              <mvc:resources mapping="/fonts/**" location="/fonts/"/>


              <!--視圖渲染-->
              <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                  <property name="prefix" value="/WEB-INF/"/>
                  <property name="suffix" value=".jsp"/>
              </bean>

              <!-- rest json related... start -->
              <bean id="mappingJacksonHttpMessageConverter"
                    class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                  <property name="supportedMediaTypes">
                      <list>
                          <value>application/json;charset=UTF-8</value>
                      </list>
                  </property>
              </bean>
              <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
                  <property name="messageConverters">
                      <list>
                          <ref bean="mappingJacksonHttpMessageConverter"/>
                      </list>
                  </property>
              </bean>
              <!-- rest json related... end -->

          </beans>

            這..也沒啥可說的,全世界都這么配置的= = 

          再來配置一個 Mybatis 的分頁插件...其實可以不用配置,因為根本用不到,除非以后有啥擴展的話:

          mybatis-config.xml

          <?xml version="1.0" encoding="UTF-8" ?>
          <!DOCTYPE configuration
                  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
                  "http://mybatis.org/dtd/mybatis-3-config.dtd">

          <configuration>
              <settings>
                  <setting name="logImpl" value="STDOUT_LOGGING"/>
              </settings>
              <!--
              plugins在配置文件中的位置必須符合要求,否則會報錯,順序如下:
              properties?, settings?,
              typeAliases?, typeHandlers?,
              objectFactory?,objectWrapperFactory?,
              plugins?,
              environments?, databaseIdProvider?, mappers?
              -->
              <plugins>
                  <!-- com.github.pagehelper為PageHelper類所在包名 -->
                  <plugin interceptor="com.github.pagehelper.PageHelper">
                      <!-- 4.0.0以后版本可以不設(shè)置該參數(shù) -->
                      <property name="dialect" value="mysql"/>
                      <!-- 該參數(shù)默認為false -->
                      <!-- 設(shè)置為true時,會將RowBounds第一個參數(shù)offset當成pageNum頁碼使用 -->
                      <!-- 和startPage中的pageNum效果一樣-->
                      <property name="offsetAsPageNum" value="true"/>
                      <!-- 該參數(shù)默認為false -->
                      <!-- 設(shè)置為true時,使用RowBounds分頁會進行count查詢 -->
                      <property name="rowBoundsWithCount" value="true"/>
                      <!-- 設(shè)置為true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結(jié)果 -->
                      <!-- (相當于沒有執(zhí)行分頁查詢,但是返回結(jié)果仍然是Page類型)-->
                      <property name="pageSizeZero" value="true"/>
                      <!-- 3.3.0版本可用 - 分頁參數(shù)合理化,默認false禁用 -->
                      <!-- 啟用合理化時,如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最后一頁 -->
                      <!-- 禁用合理化時,如果pageNum<1或pageNum>pages會返回空數(shù)據(jù) -->
                      <property name="reasonable" value="true"/>
                      <!-- 3.5.0版本可用 - 為了支持startPage(Object params)方法 -->
                      <!-- 增加了一個`params`參數(shù)來配置參數(shù)映射,用于從Map或ServletRequest中取值 -->
                      <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默認值 -->
                      <!-- 不理解該含義的前提下,不要隨便復(fù)制該配置 -->
                      <property name="params" value="pageNum=pageHelperStart;pageSize=pageHelperRows;"/>
                      <!-- 支持通過Mapper接口參數(shù)來傳遞分頁參數(shù) -->
                      <property name="supportMethodsArguments" value="false"/>
                      <!-- always總是返回PageInfo類型,check檢查返回類型是否為PageInfo,none返回Page -->
                      <property name="returnPageInfo" value="none"/>
                  </plugin>
              </plugins>
          </configuration>

            這個比較詳細,因為這個比較麻煩。所以內(nèi)容都寫的很多,如果你不配置這個,自然對目前來說,也是可以的。

          再來一個 LogBack 的配置文件,這個配置文件必須放在配置文件的根目錄下,我是使用 IDEA ,maven 的方式搭建項目的,這種配置資源全部都放在 resources 文件夾下,而且文件名字還就得叫這個:

          logback.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <!--
          scan:當此屬性設(shè)置為true時,配置文件如果發(fā)生改變,將會被重新加載,默認值為true
          scanPeriod:設(shè)置監(jiān)測配置文件是否有修改的時間間隔,如果沒有給出時間單位,默認單位是毫秒當scan為true時,此屬性生效。默認的時間間隔為1分鐘。
          debug:當此屬性設(shè)置為true時,將打印出logback內(nèi)部日志信息,實時查看logback運行狀態(tài)。默認值為false
          -->
          <configuration scan="true" scanPeriod="60 seconds" debug="false">
              <!-- 定義日志的根目錄 -->

              <property name="LOG_HOME" value="/Users/wuxinzhe/IdeaProjects/OAuthServer/logs"/>
              <!-- 定義日志文件名稱 -->
              <property name="appName" value="OAuthServer"/>
              <!-- ch.qos.logback.core.ConsoleAppender 表示控制臺輸出 -->
              <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
                  <Encoding>UTF-8</Encoding>
                  <!--
                  日志輸出格式:%d表示日期時間,%thread表示線程名,%-5level:級別從左顯示5個字符寬度
                  %logger{50} 表示logger名字最長50個字符,否則按照句點分割。 %msg:日志消息,%n是換行符
                  -->
                  <layout class="ch.qos.logback.classic.PatternLayout">
                      <pattern>
                          %n[%d{yyyy-MM-dd HH:mm:ss}] [userID:%X{userID}] [%logger{50}] %n[%-5level] %msg %n
                      </pattern>
                  </layout>
              </appender>

              <!-- 滾動記錄文件,先將日志記錄到指定文件,當符合某個條件時,將日志記錄到其他文件 -->
              <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
                  <Encoding>UTF-8</Encoding>
                  <!-- 指定日志文件的名稱 -->
                  <file>${LOG_HOME}/${appName}.log</file>
                  <!--
                  當發(fā)生滾動時,決定 RollingFileAppender 的行為,涉及文件移動和重命名
                  TimeBasedRollingPolicy: 最常用的滾動策略,它根據(jù)時間來制定滾動策略,既負責滾動也負責出發(fā)滾動。
                  -->
                  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                      <!--
                      滾動時產(chǎn)生的文件的存放位置及文件名稱 %d{yyyy-MM-dd}:按天進行日志滾動
                      %i:當文件大小超過maxFileSize時,按照i進行文件滾動
                      -->
                      <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
                      <!--
                      可選節(jié)點,控制保留的歸檔文件的最大數(shù)量,超出數(shù)量就刪除舊文件。假設(shè)設(shè)置每天滾動,
                      且maxHistory是365,則只保存最近365天的文件,刪除之前的舊文件。注意,刪除舊文件是,
                      那些為了歸檔而創(chuàng)建的目錄也會被刪除。
                      -->
                      <MaxHistory>365</MaxHistory>
                      <!--
                      當日志文件超過maxFileSize指定的大小是,根據(jù)上面提到的%i進行日志文件滾動 注意此處配置SizeBasedTriggeringPolicy是無法實現(xiàn)按文件大小進行滾動的,必須配置timeBasedFileNamingAndTriggeringPolicy
                      -->
                      <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                          <maxFileSize>100MB</maxFileSize>
                      </timeBasedFileNamingAndTriggeringPolicy>
                  </rollingPolicy>
                  <!--
                  日志輸出格式:%d表示日期時間,%thread表示線程名,%-5level:級別從左顯示5個字符寬度 %logger{50} 表示logger名字最長50個字符,否則按照句點分割。 %msg:日志消息,%n是換行符
                  -->
                  <layout class="ch.qos.logback.classic.PatternLayout">
                      <pattern>
                          %n[%d{yyyy-MM-dd HH:mm:ss}] [userID:%X{userID}] [%logger{50}] %n[%-5level] %msg %n
                      </pattern>
                  </layout>
              </appender>

              <!--
              logger主要用于存放日志對象,也可以定義日志類型、級別
              name:表示匹配的logger類型前綴,也就是包的前半部分
              level:要記錄的日志級別,包括 TRACE < DEBUG < INFO < WARN < ERROR
              additivity:作用在于children-logger是否使用 rootLogger配置的appender進行輸出,false:表示只用當前l(fā)ogger的appender-ref,true:表示當前l(fā)ogger的appender-ref和rootLogger的appender-ref都有效
              -->

              <!--
              root與logger是父子關(guān)系,沒有特別定義則默認為root,任何一個類只會和一個logger對應(yīng),
              要么是定義的logger,要么是root,判斷的關(guān)鍵在于找到這個logger,然后判斷這個logger的appender和level。
              -->
              <root level="info">
                  <appender-ref ref="stdout"/>
                  <appender-ref ref="appLogAppender"/>
              </root>

          </configuration>

            這個東西我想是必須的吧,畢竟,日志應(yīng)該還是有用的...雖然我整個項目中,都沒有輸出日志的代碼,不過框架還是有日志輸出的需求的。= =

           還剩下最后一個配置文件,這個配置文件是 MyBatis 的逆向工程插件的配置文件,如果你有用的話,就弄一個吧

          generatorConfig.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE generatorConfiguration
                  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
                  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

          <generatorConfiguration>
              <classPathEntry
                      location="/Users/wuxinzhe/.m2/repository/mysql/mysql-connector-java/5.1.6/mysql-connector-java-5.1.6.jar"/>
              <context id="testTables" targetRuntime="MyBatis3">
                  <commentGenerator>
                      <!-- 是否去除自動生成的注釋 true:是 : false:否 -->
                      <property name="suppressAllComments" value="true"/>
                  </commentGenerator>
                  <!--數(shù)據(jù)庫連接的信息:驅(qū)動類、連接地址、用戶名、密碼 -->
                  <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                                  connectionURL="jdbc:mysql://showings.com.cn:3306/oauth"
                                  userId="root"
                                  password="199176">
                  </jdbcConnection>

                  <!-- 默認false,把JDBC DECIMAL 和 NUMERIC 類型解析為 Integer,為 true時把JDBC DECIMAL 和
                      NUMERIC 類型解析為java.math.BigDecimal -->
                  <javaTypeResolver>
                      <property name="forceBigDecimals" value="false"/>
                  </javaTypeResolver>

                  <!-- targetProject:生成PO類的位置 -->
                  <javaModelGenerator targetPackage="cn.com.showings.entity"
                                      targetProject="src/main/java">
                      <!-- enableSubPackages:是否讓schema作為包的后綴 -->
                      <property name="enableSubPackages" value="false"/>
                      <!-- 從數(shù)據(jù)庫返回的值被清理前后的空格 -->
                      <property name="trimStrings" value="true"/>
                  </javaModelGenerator>
                  <!-- targetProject:mapper映射文件生成的位置 -->
                  <sqlMapGenerator targetPackage="mapper"
                                   targetProject="src/main/resources">
                      <!-- enableSubPackages:是否讓schema作為包的后綴 -->
                      <property name="enableSubPackages" value="false"/>
                  </sqlMapGenerator>
                  <!-- targetPackage:mapper接口生成的位置 -->
                  <javaClientGenerator type="XMLMAPPER" targetPackage="cn.com.showings.mapper"
                                       targetProject="src/main/java">
                      <!-- enableSubPackages:是否讓schema作為包的后綴 -->
                      <property name="enableSubPackages" value="false"/>
                  </javaClientGenerator>
                  <!--指定數(shù)據(jù)庫表-->
                  <table tableName="USER_ROLE"
                         enableCountByExample="false"
                         enableUpdateByExample="false"
                         enableDeleteByExample="false"
                         enableSelectByExample="false"
                         selectByExampleQueryId="false"/>
              </context>
          </generatorConfiguration>

             好了,最重要的:

          application-security.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:sec="http://www.springframework.org/schema/security"
                 xmlns:oauth2="http://www.springframework.org/schema/security/oauth2"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans
                 http://www.springframework.org/schema/beans/spring-beans.xsd
                 http://www.springframework.org/schema/security
                 http://www.springframework.org/schema/security/spring-security-4.2.xsd
                 http://www.springframework.org/schema/security/oauth2
                 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"
          >

              <sec:http pattern="/js/**" security="none"/>
              <sec:http pattern="/fonts/**" security="none"/>
              <sec:http pattern="/css/**" security="none"/>

              <!--  /oauth/token 是oauth2登陸驗證請求的url用于獲取access_token  ,默認的生存時間是43200秒,即12小時-->
              <sec:http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="oauth2AuthenticationManager"
                        entry-point-ref="oauth2AuthenticationEntryPoint" use-expressions="false">
                  <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY"/>
                  <sec:anonymous enabled="false"/>
                  <sec:http-basic entry-point-ref="oauth2AuthenticationEntryPoint"/>
                  <sec:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER"/>
                  <sec:access-denied-handler ref="oauth2AccessDeniedHandler"/>
                  <sec:csrf disabled="true"/>
              </sec:http>

              <!--處理訪問成功-->
              <bean id="oauth2AuthenticationEntryPoint"
                    class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/>
              <!--處理訪問拒絕-->
              <bean id="oauth2AccessDeniedHandler"
                    class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/>
              <!--處理認證點-->
              <bean id="oauthUserApprovalHandler"
                    class="org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler"/>
              <!--處理訪問控制-->
              <bean id="oauth2AccessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
                  <constructor-arg>
                      <list>
                          <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter"/>
                          <bean class="org.springframework.security.access.vote.RoleVoter"/>
                          <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
                      </list>
                  </constructor-arg>
              </bean>

              <bean id="clientCredentialsTokenEndpointFilter"
                    class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
                  <property name="authenticationManager" ref="oauth2AuthenticationManager"/>
              </bean>

              <!--可訪問客戶端參數(shù)配置,可轉(zhuǎn)成數(shù)據(jù)庫配置-->
              <oauth2:client-details-service id="clientDetailsService">
                  <oauth2:client client-id="web_client"
                                 authorized-grant-types="password,authorization_code,refresh_token,implicit"
                                 secret="web" scope="read,write"/>
              </oauth2:client-details-service>

              <!--可訪問客戶端參數(shù)配置,數(shù)據(jù)庫管理方案-->
              <!--<bean id="clientDetailsService"-->
              <!--class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService">-->
              <!--<constructor-arg index="0" ref="dataSource"/>-->
              <!--</bean>-->

              <bean id="oauth2ClientDetailsUserService"
                    class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
                  <constructor-arg ref="clientDetailsService"/>
              </bean>

              <sec:authentication-manager id="oauth2AuthenticationManager">
                  <sec:authentication-provider user-service-ref="oauth2ClientDetailsUserService"/>
              </sec:authentication-manager>

              <!--Config token services-->
              <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
                  <constructor-arg index="0" ref="dataSource"/>
              </bean>

              <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
                  <property name="tokenStore" ref="tokenStore"/>
                  <property name="clientDetailsService" ref="clientDetailsService"/>
                  <property name="supportRefreshToken" value="true"/>
              </bean>
              <bean id="jdbcAuthorizationCodeServices"
                    class="org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices">
                  <constructor-arg index="0" ref="dataSource"/>
              </bean>
              <!--oauth2 的server所能支持的請求類型-->
              <oauth2:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
                                           user-approval-handler-ref="oauthUserApprovalHandler"
                                           user-approval-page="oauth_approval"
                                           error-page="oauth_error">
                  <oauth2:authorization-code authorization-code-services-ref="jdbcAuthorizationCodeServices"/>
                  <oauth2:implicit/>
                  <oauth2:refresh-token/>
                  <oauth2:client-credentials/>
                  <oauth2:password/>
              </oauth2:authorization-server>

              <oauth2:resource-server id="webResourceServer" resource-id="web-resource" token-services-ref="tokenServices"/>

              <sec:http disable-url-rewriting="true" use-expressions="false" authentication-manager-ref="authenticationManager">
                  <sec:intercept-url pattern="/oauth/**" access="ROLE_USER"/>
                  <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
                  <sec:form-login authentication-failure-url="login.html?authorization_error=true"
                                  default-target-url="index.html"
                                  login-page="login.html" login-processing-url="login"/>
                  <sec:logout logout-success-url="success.html" logout-url="logout"/>
                  <sec:access-denied-handler error-page="login.html?access_denied=true"/>
                  <sec:anonymous/>
                  <sec:csrf disabled="true"/>
              </sec:http>

              <!-- 驗證的權(quán)限控制 -->
              <sec:authentication-manager id="authenticationManager">
                  <sec:authentication-provider user-service-ref="userServiceImpl">
                      <sec:password-encoder hash="md5"/>
                  </sec:authentication-provider>
              </sec:authentication-manager>

          </beans>

            這個文檔很長,而且我也必須做一個講解,否則就算配置了,估計也不知道怎么用。 

            我們,分段講解:

          1     <sec:http pattern="/js/**" security="none"/>
          2     <sec:http pattern="/fonts/**" security="none"/>
          3     <sec:http pattern="/css/**" security="none"/>

               這個,是告訴框架,這三塊內(nèi)容,不需要權(quán)限驗證,任何人都可以獲取,所以對這三個資源,直接繞過 SpringSecurity 框架。

          <!--  /oauth/token 是oauth2登陸驗證請求的url用于獲取access_token  ,默認的生存時間是43200秒,即12小時-->
              <sec:http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="oauth2AuthenticationManager"
                        entry-point-ref="oauth2AuthenticationEntryPoint" use-expressions="false">
                  <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY"/>
                  <sec:anonymous enabled="false"/>
                  <sec:http-basic entry-point-ref="oauth2AuthenticationEntryPoint"/>
                  <sec:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER"/>
                  <sec:access-denied-handler ref="oauth2AccessDeniedHandler"/>
                  <sec:csrf disabled="true"/>
              </sec:http>

              這部分,是OAuth2.0用于獲取 token 的地址,就是上面寫的"/oauth/token",上面的配置內(nèi)容我挑著講解一下,首先是 “authentication-manager-ref”,這是是認證管理器的指定,意思就是,當訪問這個資源的時候,我們要用特定的,專門為獲取 token 的認證管理器,這么說可能不理解,那有特定的認證管理器,就肯定有普通的認證管理器,普通的認證管理器,就是之前我配置 RBAC 權(quán)限系統(tǒng)的時候,用戶的以用戶名及密碼登錄時的那個驗證用戶密碼的管理器,這個是最普通的,也是必須有的管理器,相對這個管理器來說,OAuth2.0需要一個專門對“/oauth/token”這個資源(要獲取的 token 本身就是一種資源),有一個認證管理器,認證的是 client-id 與對應(yīng)的 client-secret,就是對訪問客戶端的認證,我們對客戶端程序是有限制的,不是每個客戶端程序都能夠訪問我們的接口,而是我們資源服務(wù)器授權(quán)的某個客戶端才能有資格來申請,這么說可能還是不太好理解,我用現(xiàn)實中的例子來說明這個問題:

            我們都在某些小型的網(wǎng)站,尤其是論壇上看到登錄時,可以選擇使用 QQ 登錄,對吧?這也是 OAuth2.0的一個業(yè)務(wù)場景,其實這些小型網(wǎng)站要實現(xiàn)這類功能之前,必須先跟騰訊申請合作,然后騰訊會在它自己的客戶端數(shù)據(jù)表中,創(chuàng)建一個屬于這個小網(wǎng)站的一個 ID,對于騰訊來說,這個小網(wǎng)站就是一個客戶端,他想要獲取騰訊的一些資源,比如用戶昵稱及頭像。那小網(wǎng)站 A 跟騰訊簽訂了合作協(xié)議,騰訊同意它作為一個客戶端來訪問資源(僅僅是說 token 的資源,因為這個針對客戶端的授權(quán)認證,僅僅只是用來申請 token 的),但是騰訊不允許沒有跟他簽訂合作的其他小網(wǎng)站來訪問資源,所以那些沒有申請合作的小網(wǎng)站,沒有騰訊分發(fā)的 ID及密碼,自然就不能發(fā)起訪問申請。而且,OAuth2.0有一個很重要的功能,就是根據(jù)不同的客戶端,可以區(qū)別對待,比如一個比較大一點的中型網(wǎng)站,跟騰訊有付費關(guān)系,就是所謂的哈哈,QQ 會員級別,那他跟其他沒付費的小網(wǎng)站自然不同,騰訊能讓她訪問的資源內(nèi)容的范圍肯定也不同,所以通過不同的 client,自然就可以進行區(qū)別對待,冠冕堂皇收取 QQ 會員費啦~!哈哈

            其他元素,我就不解釋了,都有對應(yīng)的 Bean,對應(yīng) Bean 上都有注釋,主要是沒啥不好理解的,所以就不需要解釋了吧。

          <!--可訪問客戶端參數(shù)配置,可轉(zhuǎn)成數(shù)據(jù)庫配置-->
              <oauth2:client-details-service id="clientDetailsService">
                  <oauth2:client client-id="web_client"
                                 authorized-grant-types="password,authorization_code,refresh_token,implicit"
                                 secret="web" scope="read,write"/>
              </oauth2:client-details-service>

            這就是剛才我說的,客戶端配置,我這邊是完全是寫死的,因為我的網(wǎng)站沒打算建立開放平臺給別人,媽蛋,我的網(wǎng)站要有那個能力,大家都來我這里獲取資源,我還在這里寫代碼作死?當然啦,我在底下有注釋,用于數(shù)據(jù)庫配置的方式,其實也沒啥難的。這邊我們可以看一下,authorized-grant-type 是認證類型,五種認證類型,可以根據(jù)需要,做一些取舍,一般,常用的是 authorization_code,這個是最常用的手法,資源服務(wù)器之間的通訊可以使用implicit,具體這幾種方式怎么用,恩,回頭我再寫一個博客專門講好了。

          <!--oauth2 的server所能支持的請求類型-->
              <oauth2:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
                                           user-approval-handler-ref="oauthUserApprovalHandler"
                                           user-approval-page="oauth_approval"
                                           error-page="oauth_error">
                  <oauth2:authorization-code authorization-code-services-ref="jdbcAuthorizationCodeServices"/>
                  <oauth2:implicit/>
                  <oauth2:refresh-token/>
                  <oauth2:client-credentials/>
                  <oauth2:password/>
              </oauth2:authorization-server>

            這個呢,可以講得也不多,就是那個 user-approval-page,我講一下,我們在使用微信的時候,其實微信也是用的 OAuth2.0,只是沒有讓你輸入用戶密碼,因為你本身就登錄著微信,而微信是 Socket 連接,是收信任的鏈接方式,所以當你點一些小程序或者一些其他基于微信開發(fā)的一些程序的時候,就從來不用輸入用戶密碼,但是一定會有一個提示,就是問你是否要授權(quán)微信登錄,其實就是這樣的一個類似的授權(quán)頁面,如圖:

            

            為啥要有這個頁面,好像多此一舉的感覺呢?很簡單咯,以防萬一,一些惡意網(wǎng)站,在你不知情的情況下,去調(diào)用你的信息。而 error-page 就是當用戶點擊拒絕訪問的時候,跳轉(zhuǎn)的頁面。

          1 <oauth2:resource-server id="webResourceServer" resource-id="web-resource" token-services-ref="tokenServices"/>

            這個是資源 ID,定義資源服務(wù)器的,恩...其實我也不知道這個要不要加入到這里,沒試過,因為這個是必須要加到資源服務(wù)器的配置文件當中的,而認證服務(wù)器要不要我還不清楚,因為我目前還沒有把資源服務(wù)器的內(nèi)容寫起來,反正暫且先寫著,如果不需要,回頭再去掉就行。

          <sec:http disable-url-rewriting="true" use-expressions="false" authentication-manager-ref="authenticationManager">
                  <sec:intercept-url pattern="/oauth/**" access="ROLE_USER"/>
                  <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
                  <sec:form-login authentication-failure-url="login.html?authorization_error=true"
                                  default-target-url="index.html"
                                  login-page="login.html" login-processing-url="login"/>
                  <sec:logout logout-success-url="success.html" logout-url="logout"/>
                  <sec:access-denied-handler error-page="login.html?access_denied=true"/>
                  <sec:anonymous/>
                  <sec:csrf disabled="true"/>
              </sec:http>

              <!-- 驗證的權(quán)限控制 -->
              <sec:authentication-manager id="authenticationManager">
                  <sec:authentication-provider user-service-ref="userServiceImpl">
                      <sec:password-encoder hash="md5"/>
                  </sec:authentication-provider>
              </sec:authentication-manager> 

             這個,就是用戶認證系統(tǒng),不管用RBAC權(quán)限管理設(shè)計,還是 OAuth2.0協(xié)議,都不可避免要輸入用戶密碼,這部分呢就是用來定義這個的。這里有一個坑啊,就是這兩個東西,必須放在最后面,因為,我們的攔截地址是攔截/**及/oauth/**,如果寫在剛才申請 token 的那個/oauth/token 的配置前面,則會被覆蓋,那不管怎么輸入用戶密碼,都不會走用戶驗證管理器,而是都去走剛才上面說到的那個特殊的,驗證client的用戶驗證管理器。那就不管怎樣,都登錄不了了= =。

           

            好了,最后就是 Web.xml的配置了。

          web.xml

          <?xml version="1.0" encoding="UTF-8"?>
          <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
                   version="3.1">
              <!--設(shè)置spring 配置文件的位置-->
              <context-param>
                  <param-name>contextConfigLocation</param-name>
                  <param-value>classpath*:config/application-*.xml</param-value>
              </context-param>
              <context-param>
                  <param-name>webAppRootKey</param-name>
                  <param-value>web.root</param-value>
              </context-param>
              <listener>
                  <listener-class>org.springframework.web.util.WebAppRootListener</listener-class>
              </listener>
              <!--配置spring listener-->
              <listener>
                  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
              </listener>
              <!--解決POST亂碼問題-->
              <filter>
                  <filter-name>CharacterEncodingFilter</filter-name>
                  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
                  <init-param>
                      <param-name>encoding</param-name>
                      <param-value>utf-8</param-value>
                  </init-param>
              </filter>
              <filter-mapping>
                  <filter-name>CharacterEncodingFilter</filter-name>
                  <url-pattern>/*</url-pattern>
              </filter-mapping>
              <filter>
                  <filter-name>springSecurityFilterChain</filter-name>
                  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
              </filter>
              <filter-mapping>
                  <filter-name>springSecurityFilterChain</filter-name>
                  <url-pattern>/*</url-pattern>
              </filter-mapping>
              <!--springMVC前端控制器配置-->
              <servlet>
                  <servlet-name>dispatcherServlet</servlet-name>
                  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
                  <init-param>
                      <param-name>contextConfigLocation</param-name>
                      <param-value>classpath*:config/spring-mvc.xml</param-value>
                  </init-param>
                  <load-on-startup>2</load-on-startup>
              </servlet>
              <servlet-mapping>
                  <servlet-name>dispatcherServlet</servlet-name>
                  <url-pattern>/</url-pattern>
              </servlet-mapping>
              <welcome-file-list>
                  <welcome-file>login.html</welcome-file>
              </welcome-file-list>
          </web-app>

            這個配置很普通,沒有什么復(fù)雜的。恩,關(guān)鍵的地方說一下,就是 welcome-file-list 這邊,我設(shè)置成了 login.html,我是這么考慮的:這個認證系統(tǒng)吧,如果真的用起來,可能會比較頻繁,所以不希望總是要經(jīng)過 spirng 及 spring-mvc 來控制,就做成靜態(tài)頁面好了,這樣可以提高些效率。而且本身登錄也沒有什么復(fù)雜邏輯,用不到 JSP,所以我把登錄頁面做成了靜態(tài)頁面,而且希望項目的默認訪問地址,就是登錄頁面,因為本身這就只是一個認證系統(tǒng)而已,沒有其他的功能。

            但是看一下前面的 Security 配置中,對用戶登錄認證的部分的配置,我配置了登錄失敗的一些錯誤頁面,

           <sec:form-login authentication-failure-url="login.html?authorization_error=true"
                                  default-target-url="index.html"
                                  login-page="login.html" login-processing-url="login"/>
           <sec:access-denied-handler error-page="login.html?access_denied=true"/>

            首先是“l(fā)ogin.html?authorization_error=true”,這個是當認證失敗,就是驗證用戶密碼錯誤的時候,返回的頁面,當然還是要回到登錄頁面,但是帶了URL參數(shù),還有一個就是“l(fā)ogin.html?access_denied=true”,這個是訪問拒絕的頁面,就是當你沒有登錄就想要訪問一些資源的時候,會跳轉(zhuǎn)到這個頁面,其實還是登錄頁面,只是攜帶參數(shù)不同,不過...本身認證系統(tǒng)就沒有什么其他接口,所以這個可能也沒啥用就是了...那反正定義一個,也好。

            在 login.html 頁面呢,因為是靜態(tài)的,不能用 jstl 表達式來獲取 URL 參數(shù)從而顯示不同的提示語,但是可以使用 js 來做這個事兒,回頭我再貼出 login.html 的代碼。   

           幾個頁面

          login.html

          <!DOCTYPE html>
          <html lang="zh">
          <head>
              <meta charset="UTF-8">
              <title>登錄驗證</title>
          </head>
          <link rel="stylesheet" href="css/bootstrap.min.css">
          <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script>
          <script type="text/javascript" src="js/bootstrap.min.js"></script>
          <body>
          <div class="container">
              <div class="row" style="height: 150px"></div>
              <div class="row">
                  <div class="col-xs-4"></div>
                  <div class="col-xs-4">
                      <div class="page-header">
                          <h1 class="text-center">系統(tǒng)登錄</h1>
                      </div>
                      <form action="/login" method="post">
                          <div class="form-group">
                              <div class="input-group">
                                  <span class="input-group-addon">
                                      <span class="glyphicon glyphicon-user"></span>
                                  </span>
                                  <input type="text" class="form-control" placeholder="用戶名" name="username"
                                         aria-describedby="basic-addon1">
                              </div>
                          </div>
                          <div class="form-group">
                              <div class="input-group">
                                  <span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
                                  <input type="password" class="form-control" placeholder="密碼" name="password"
                                         aria-describedby="basic-addon1">
                              </div>
                          </div>
                          <button type="submit" class="btn btn-default pull-right">登錄</button>
                      </form>
                  </div>
                  <div class="col-xs-4"></div>
              </div>
              <div class="row" style="height: 10px">
              </div>
              <div class="row">
                  <div class="col-xs-4"></div>
                  <div class="col-xs-4">
                      <div id="tip" class="alert alert-danger alert-dismissible hidden" role="alert">
                          <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                              <span aria-hidden="true">×</span>
                          </button>
                          <strong>提示!</strong>
                          <span id="tip-message"></span>
                      </div>
                  </div>
                  <div class="col-xs-4"></div>
              </div>
          </div>
          </body>
          <script type="text/javascript">
              function getQueryString(name) {
                  var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)''i');
                  var r = window.location.search.substr(1).match(reg);
                  if (r !== null) {
                      return unescape(r[2]);
                  }
                  return null;
              }
              if (getQueryString('authorization_error') !== null) {
                  $('#tip-message').text('用戶密碼不正確!');
                  $('#tip').removeClass('hidden');
              } else if (getQueryString('access_denied') !== null) {
                  $('#tip-message').text('您還尚未登錄!');
                  $('#tip').removeClass('hidden');
              } else {
                  $('#tip').addClass('hidden');
              }
          </script>
          </html>

          oauth_approval.jsp

          <%@ page contentType="text/html;charset=UTF-8" language="java" %>
          <!DOCTYPE HTML>

          <html>
          <head>
              <title>Oauth Approval</title>
              <link rel="stylesheet" href="../css/bootstrap.min.css">
              <script type="text/javascript" src="../js/jquery-3.2.1.min.js"></script>
              <script type="text/javascript" src="../js/bootstrap.min.js"></script>
          </head>
          <body>
          <div class="container">
              <div class="row">
                  <div class="col-xs-12">
                      <div class="page-header">
                          <h1 class="text-center">你是否授權(quán)"${authorizationRequest.clientId}"訪問你的個人信息?</h1>
                      </div>
                  </div>
              </div>
              <div class="row">
                  <div class="col-xs-4"></div>
                  <div class="col-xs-4">
                      <form id="oauth-form" action="${pageContext.request.contextPath}/oauth/authorize" method="post">
                          <input id="approval" name='user_oauth_approval' type='hidden'/>
                          <div class="btn-group btn-group-justified">
                              <div class="btn-group" role="group">
                                  <button id="access_authorize" class="btn btn-lg btn-primary" type="button">同意授權(quán)</button>
                              </div>
                              <div class="btn-group" role="group">
                                  <button id="access_denied" class="btn btn-lg btn-danger" type="button">拒絕訪問</button>
                              </div>
                          </div>
                      </form>
                  </div>
                  <div class="col-xs-4"></div>
              </div>
          </div>
          <script type="text/javascript">
              var approval = $('#approval');

              $('#access_authorize').on('click'function () {
                  approval.val('true');
                  $('#oauth-form').submit();
              });

              $('#access_denied').on('click'function () {
                  approval.val('false');
                  $('#oauth-form').submit();
              });
          </script>
          </body>
          </html>

          oauth_error.jsp

          <%@ page contentType="text/html;charset=UTF-8" language="java" %>
          <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
          <!DOCTYPE HTML>
          <html>
          <head>
              <title>授權(quán)失敗</title>
          </head>
          <body>

          <h3>
              授權(quán)失敗!
          </h3>

          <div class="alert alert-danger">
              <c:out value="${error.summary}"/>
          </div>

          </body>
          </html>


           幾個類

          ExceptionController.java 


           

          這個是統(tǒng)一異常處理,這邊針對非 Ajax 請求,返回 error 頁面,但是我沒有寫 Error 頁面,懶得寫啦,因為根本用不到,但是該補還是得補上,以后再補好了。這個我就說明一下,免得有朋友看不懂這塊。

          package cn.com.showings.controller;

          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;
          import org.springframework.http.HttpStatus;
          import org.springframework.util.StringUtils;
          import org.springframework.web.servlet.HandlerExceptionResolver;
          import org.springframework.web.servlet.ModelAndView;
          import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;

          /**
           * 知識產(chǎn)權(quán)聲明:本文件自創(chuàng)建起,其內(nèi)容的知識產(chǎn)權(quán)即歸屬于原作者,任何他人不可擅自復(fù)制或模仿.
           * 創(chuàng)建者: wu   創(chuàng)建時間: 16/9/29
           * 類說明: 統(tǒng)一異常處理
           */

          public class ExceptionController implements HandlerExceptionResolver {

              private static Logger logger = LoggerFactory.getLogger(ExceptionController.class);

              public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
                  logger.error(ex.getMessage(), ex);
                  ModelAndView modelAndView = new ModelAndView();
                  if (isAjaxRequest(httpServletRequest)) {
                      modelAndView.setView(new MappingJackson2JsonView());
                  } else {
                      modelAndView.setViewName("error");
                  }
                  modelAndView.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
                  modelAndView.addObject("error_info", ex.getMessage());
                  return modelAndView;
              }

              private boolean isAjaxRequest(HttpServletRequest request) {
                  return "XMLHttpRequest".equals(request.getHeader("X-Requested-With")) || !StringUtils.isEmpty(request.getParameter("jsonp"));
              }
          }

          UserServiceImpl.java


          這個類是實現(xiàn)了 UserService 接口,而 UserService 接口內(nèi)沒有任何內(nèi)容,只是為了留給以后萬一要集成用戶管理系統(tǒng)或者其他用戶操作,故意加的一層接口。UserService 接口繼承了 SpringSecurity 框架中的 UserDetailsService,所以這個類目前只實現(xiàn)了一個方法,就是唯一需要使用 mybatis 讀取數(shù)據(jù)庫的接口= =。。如果后期不打算擴展其他功能,單純就只用來認證,那可去掉 Mybatis,直接jdbcTemplate 來查詢用戶信息。

          package cn.com.showings.service.impl;

          import cn.com.showings.entity.MyUserDetail;
          import cn.com.showings.entity.User;
          import cn.com.showings.mapper.UserMapper;
          import cn.com.showings.service.UserService;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.security.core.userdetails.UserDetails;
          import org.springframework.security.core.userdetails.UsernameNotFoundException;
          import org.springframework.stereotype.Service;

          /**
           * 類說明:用戶服務(wù),管理用戶注冊、讀取用戶信息等用戶相關(guān)操作
           */
          @Service
          public class UserServiceImpl implements UserService {

              @Autowired
              private UserMapper userMapper;

              public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                  User user = userMapper.selectByUsername(username);
                  if (user == null) {
                      throw new UsernameNotFoundException("Not found any user for username[" + username + "]");
                  } else {
                      return new MyUserDetail(user);
                  }
              }
          }

           MyUserDetail.java


          這個類,是用來實現(xiàn)框架中的 UserDetails 接口的,框架中沒有什么實體類這種概念,都是用接口實現(xiàn)的,我們這邊就主要按照標準,組裝一下這個對象的數(shù)據(jù)結(jié)構(gòu)。

          package cn.com.showings.entity;

          import org.springframework.security.core.GrantedAuthority;
          import org.springframework.security.core.authority.SimpleGrantedAuthority;
          import org.springframework.security.core.userdetails.UserDetails;

          import java.util.ArrayList;
          import java.util.Collection;
          import java.util.List;

          /**
           * 類說明:實現(xiàn) UserDetail
           */
          public class MyUserDetail implements UserDetails {

              private static final long serialVersionUID = 3006176344390176165L;

              private User user;

              private static final String ROLE_PREFIX = "ROLE_";


              private List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

              public MyUserDetail(User user) {
                  this.user = user;
                  initAuthority();
              }

              private void initAuthority() {
                  List<Role> roleList = user.getRoleList();
                  for (Role role : roleList) {
                      this.grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + role.getName()));
                  }
              }

              public Collection<? extends GrantedAuthority> getAuthorities() {
                  return this.grantedAuthorities;
              }

              public String getPassword() {
                  return this.user.getPassword();
              }

              public String getUsername() {
                  return this.user.getUsername();
              }

              public boolean isAccountNonExpired() {
                  return true;
              }

              public boolean isAccountNonLocked() {
                  return true;
              }

              public boolean isCredentialsNonExpired() {
                  return true;
              }

              public boolean isEnabled() {
                  return true;
              }

              public User getUser() {
                  return user;
              }

          }

            剩下就是 User 類和 Role 類,以及根據(jù)數(shù)據(jù)庫多對多映射出來的 UserRole 類,都是實體類,沒啥好說的,無非就是根據(jù)數(shù)據(jù)庫映射出來的幾個字段而已。

            OK 啦,認證服務(wù)器的配置就配置完了。資源服務(wù)器呢,其實沒有啥特別的,除了 SpringSecurity 的那個配置文件,其他都跟授權(quán)服務(wù)器一樣,當然資源服務(wù)器要擴展一些自己的功能,肯定還有一些特殊的東西,那反正就權(quán)限認證這塊,我貼出代碼,其他的配置,你們都可以按照授權(quán)服務(wù)器來配置,配置一些比如 dao,spirng,mvc,logback,mybatis,所以,上面那些其實也不是沒用的啦= =

            好了,區(qū)別就在于 security 的配置:

          <?xml version="1.0" encoding="UTF-8"?>
          <beans xmlns="http://www.springframework.org/schema/beans"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:sec="http://www.springframework.org/schema/security"
                 xmlns:oauth2="http://www.springframework.org/schema/security/oauth2"
                 xsi:schemaLocation="http://www.springframework.org/schema/beans
                 http://www.springframework.org/schema/beans/spring-beans.xsd
                 http://www.springframework.org/schema/security
                 http://www.springframework.org/schema/security/spring-security-4.2.xsd
                 http://www.springframework.org/schema/security/oauth2
                 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"
          >

              <sec:http pattern="/**" create-session="never" entry-point-ref="oauth2AuthenticationEntryPoint"
                        access-decision-manager-ref="oauth2AccessDecisionManager" use-expressions="false">
                  <sec:anonymous enabled="false"/>
                  <sec:intercept-url pattern="/**" access="ROLE_USER,SCOPE_READ"/>
                  <sec:custom-filter ref="webResourceServer" before="PRE_AUTH_FILTER"/>
                  <sec:access-denied-handler ref="oauth2AccessDeniedHandler"/>
                  <sec:csrf disabled="true"/>
              </sec:http>

              <!--處理訪問成功-->
              <bean id="oauth2AuthenticationEntryPoint"
                    class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/>
              <!--處理訪問拒絕-->
              <bean id="oauth2AccessDeniedHandler"
                    class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/>
              <!--處理認證點-->
              <bean id="oauthUserApprovalHandler"
                    class="org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler"/>
              <!--處理訪問控制-->
              <bean id="oauth2AccessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
                  <constructor-arg>
                      <list>
                          <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter"/>
                          <bean class="org.springframework.security.access.vote.RoleVoter"/>
                          <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
                      </list>
                  </constructor-arg>
              </bean>


              <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
                  <constructor-arg index="0" ref="dataSource"/>
              </bean>

              <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
                  <property name="tokenStore" ref="tokenStore"/>
                  <property name="supportRefreshToken" value="true"/>
              </bean>

              <oauth2:resource-server id="webResourceServer" resource-id="web-resource" token-services-ref="tokenServices"/>


          </beans>

            這邊的 DataSource要說明一下,這個連接的數(shù)據(jù)庫跟認證服務(wù)器的數(shù)據(jù)庫要是一樣的,所以這個項目如果數(shù)據(jù)庫分庫的話,自然要有多個 dataSource,建議也是分開,認證系統(tǒng)不要跟業(yè)務(wù)系統(tǒng)混在一起,從根本上區(qū)分開每個模塊,數(shù)據(jù)庫也要分開。而這個 dataSource 因為鏈接的跟認證系統(tǒng)是相同的數(shù)據(jù)庫,所以自然就是一個統(tǒng)一認證的模式,只要在認證系統(tǒng)內(nèi)認證過的,獲取的 token 在其他模塊中也會到相同的數(shù)據(jù)庫中去驗證。另外說一下,這個資源服務(wù)器的配置內(nèi)容,有一點亂,可能還可以繼續(xù)精簡一些,資源服務(wù)器我還沒有深入去看,可能這也已經(jīng)是最小配置了。

            對了,數(shù)據(jù)庫不能忘了。

          /*
           Navicat Premium Data Transfer

           Source Server         : Showings
           Source Server Type    : MySQL
           Source Server Version : 50554
           Source Host           : 120.25.99.8
           Source Database       : oauth

           Target Server Type    : MySQL
           Target Server Version : 50554
           File Encoding         : utf-8

           Date: 04/21/2017 09:08:54 AM
          */

          SET NAMES utf8;
          SET FOREIGN_KEY_CHECKS = 0;

          -- ----------------------------
          --  Table structure for `oauth_access_token`
          -- ----------------------------
          DROP TABLE IF EXISTS `oauth_access_token`;
          CREATE TABLE `oauth_access_token` (
            `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
            `token_id` varchar(255) DEFAULT NULL,
            `token` blob,
            `authentication_id` varchar(255) DEFAULT NULL,
            `user_name` varchar(255) DEFAULT NULL,
            `client_id` varchar(255) DEFAULT NULL,
            `authentication` blob,
            `refresh_token` varchar(255) DEFAULT NULL,
            KEY `token_id_index` (`token_id`),
            KEY `authentication_id_index` (`authentication_id`),
            KEY `user_name_index` (`user_name`),
            KEY `client_id_index` (`client_id`),
            KEY `refresh_token_index` (`refresh_token`)
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

          -- ----------------------------
          --  Table structure for `oauth_client_details`
          -- ----------------------------
          DROP TABLE IF EXISTS `oauth_client_details`;
          CREATE TABLE `oauth_client_details` (
            `client_id` varchar(255) NOT NULL,
            `resource_ids` varchar(255) DEFAULT NULL,
            `client_secret` varchar(255) DEFAULT NULL,
            `scope` varchar(255) DEFAULT NULL,
            `authorized_grant_types` varchar(255) DEFAULT NULL,
            `web_server_redirect_uri` varchar(255) DEFAULT NULL,
            `authorities` varchar(255) DEFAULT NULL,
            `access_token_validity` int(11) DEFAULT NULL,
            `refresh_token_validity` int(11) DEFAULT NULL,
            `additional_information` text,
            `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
            `archived` tinyint(1) DEFAULT '0',
            `trusted` tinyint(1) DEFAULT '0',
            `autoapprove` varchar(255) DEFAULT 'false',
            PRIMARY KEY (`client_id`)
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

          -- ----------------------------
          --  Table structure for `oauth_code`
          -- ----------------------------
          DROP TABLE IF EXISTS `oauth_code`;
          CREATE TABLE `oauth_code` (
            `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
            `code` varchar(255) DEFAULT NULL,
            `authentication` blob,
            KEY `code_index` (`code`)
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

          -- ----------------------------
          --  Table structure for `oauth_refresh_token`
          -- ----------------------------
          DROP TABLE IF EXISTS `oauth_refresh_token`;
          CREATE TABLE `oauth_refresh_token` (
            `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
            `token_id` varchar(255) DEFAULT NULL,
            `token` blob,
            `authentication` blob,
            KEY `token_id_index` (`token_id`)
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

          -- ----------------------------
          --  Table structure for `ROLE`
          -- ----------------------------
          DROP TABLE IF EXISTS `ROLE`;
          CREATE TABLE `ROLE` (
            `ID` int(2) NOT NULL AUTO_INCREMENT,
            `NAME` varchar(10) NOT NULL,
            PRIMARY KEY (`ID`)
          ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

          -- ----------------------------
          --  Table structure for `USER`
          -- ----------------------------
          DROP TABLE IF EXISTS `USER`;
          CREATE TABLE `USER` (
            `UID` varchar(255) NOT NULL,
            `CREATE_TIME` datetime DEFAULT NULL,
            `PASSWORD` varchar(255) NOT NULL,
            `USERNAME` varchar(255) NOT NULL,
            `LAST_LOGIN_TIME` datetime DEFAULT NULL,
            PRIMARY KEY (`UID`),
            UNIQUE KEY `guid` (`UID`),
            UNIQUE KEY `username` (`USERNAME`)
          ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

          -- ----------------------------
          --  Table structure for `USER_ROLE`
          -- ----------------------------
          DROP TABLE IF EXISTS `USER_ROLE`;
          CREATE TABLE `USER_ROLE` (
            `ID` int(255) NOT NULL AUTO_INCREMENT,
            `USER_UID` varchar(256) NOT NULL,
            `ROLE_ID` int(1) NOT NULL,
            PRIMARY KEY (`ID`),
            KEY `user_id_index` (`USER_UID`(255))
          ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

          SET FOREIGN_KEY_CHECKS = 1;

            這個。補充說一下,用戶密碼要經(jīng)過 MD5加密,驗證的時候,也是有加密,所以存入數(shù)據(jù)庫的時候,要是沒有加密就會驗證不通過。




          鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布

          ??????

          ??長按上方微信二維碼 2 秒





          感謝點贊支持下哈 

          瀏覽 61
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线免费观看三级成人片 | 男男无码一区二区三区 | 三级网站在线麻豆 | 激情视频小说 | 久久久久久久久久久久久不卡了 |