前言
關於 OAuth2.0的認證體系,翻閱了好多資料,RCF 文檔太多,看了一半就看不下去了,畢竟全英文的文檔看起來,是有一點讓我煩躁,但也對 OAuth2.0的認證流程有了一個基本的概念,以前用 SpringSecurity 作了一個基於 RBAC 的權限管理系統的基礎配置,因此對 SpringSecurity 算是比較瞭解了,因而 OAuth2.0的實現,也想用 SpringSecurity 的來作,心想應該比較簡單,然而...事實上,我反反覆覆,拿起又放棄,放棄又拿起,來來回回折騰了3個多月,才真正的掌握了這個 OAuth2.0插件(OAuth2.0不是一個獨立的框架,只是 SpringSecurity 的一個插件而已)。javascript
官網的 Demo 配置,是基於 JavaConfig 的配置方式,之前都用 XML 的,沒接觸過 JavaConfig,因此又繞了一圈,把 JavaConfig 方式的全部框架(Spring、SpringMVC、Mybatis、SpringSecurity、Web.xml)基本配置方式都走了一圈, 確實,全代碼配置是很酷,很清爽,說實話,從此我也會逐漸往這方面走,由於這個方式比較有代碼感,哈哈,可是如今還不行,由於有不少插件啊、特殊的配置方式啊,我都還不清楚要怎麼配置,處於安全考慮,仍是老老實實的用 XML 的比較好。css
額外插播一則我團隊的招聘廣告:html
阿里巴巴 - 淘系技術部招聘:http://www.javashuo.com/article/p-beijdcwl-gr.html前端
網上有不少,SpringSecurityOAuth2.0的配置文章,可是每一個文章,都是將認證服務器和資源服務器寫在一塊兒的,並無將認證與資源分離,也沒有講不一樣的資源之間如何拆分,然而咱們在設計分佈式系統的時候,總會以模塊化的方式,將不一樣的資源寫成不一樣的項目,好比,將網站的一個電商系統,專門寫成一個項目,把網站中的論壇系統,寫成另外一個項目,部署的時候,每一個項目就能夠單獨部署,後端系統均以 RESTFull 的方式開放數據接口(RESTFull就是推薦使用 OAuth2.0的方式進行認證管理)。這樣的方式來設計程序,最大的優勢就是模塊之間相互獨立,互不干涉,在開發工做當中,能夠並行開發,單獨維護,同時模塊分離出來,從此還能夠進行很便利的集羣,而不須要修改任何原來的代碼,因此對整個項目的擴展性是很是好的,不一樣的項目之間,能夠簡單的使用 HttpClient 進行通信,OAuth2.0五種受權模式當中,有一種受權模式就是爲這種資源服務器之間的通信而設計的。java
認證服務器與資源服務器分離的這個配置方式,同時也實現了「統一認證」的模式,只須要在認真服務器上作了認證,拿到了 Token,就能夠訪問全部受權的資源服務器。mysql
接下來,咱們開始搭建認證服務器的配置。jquery
項目用到的框架有這幾個:Spring、SpringSecurity、Mybatisgit
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 <!--排除自帶的日誌工具,從而轉向使用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 <!--數據有效性驗證框架--> 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 很長,但其實沒有多少內容,咱們須要本身寫的代碼,也很是很是很是的少...= =,是否是很開心?嘿嘿...github
Yes,你沒看錯,目錄內容真的不多...Java 的部分,真的就沒幾個= =web
首先是 Dao 的配置文件:
application-dao.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 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"> 6 7 8 <!--獲取數據庫配置文件--> 9 <context:property-placeholder location="classpath:config/db.properties"/> 10 <context:component-scan base-package="cn.com.showings.mapper"/> 11 12 <!--設置數據源c3p0--> 13 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> 14 <property name="driverClass" value="${jdbc.driver}"/> 15 <property name="jdbcUrl" value="${jdbc.url}"/> 16 <property name="user" value="${jdbc.username}"/> 17 <property name="password" value="${jdbc.password}"/> 18 <property name="maxPoolSize" value="50"/> 19 <property name="minPoolSize" value="2"/> 20 <property name="maxIdleTime" value="60"/> 21 </bean> 22 23 <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean"> 24 <property name="configLocation" value="classpath:config/mybatis-config.xml"/> 25 <property name="dataSource" ref="dataSource"/> 26 <!-- 顯式指定Mapper文件位置 --> 27 <property name="mapperLocations"> 28 <list> 29 <value>classpath*:/mapper/*.xml</value> 30 </list> 31 </property> 32 </bean> 33 34 <!--自動掃描mapper接口,並注入sqlsession--> 35 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 36 <property name="basePackage" value="cn.com.showings.mapper"/> 37 <property name="sqlSessionFactoryBeanName" value="sqlSession"/> 38 </bean> 39 40 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 41 <property name="dataSource" ref="dataSource"/> 42 </bean> 43 </beans>這邊能夠看到,咱們明明使用的是 Mybatis 的 ORM框架,爲啥還要配置一個 jdbcTemplate?其實說來慚愧,本人比較擅長 MyBatis,因此算是強迫症必定要用這個框架,可是人家 Spring 有本身的 SpringData 的框架,而 SpringSecurityOAuth2.0的插件中,不少內容都是用 SpringData 的方式去實現的,我若是要棄用 jdbcTemplate,那我得重寫全部框架內涉及的數據庫操做,那太累了- -,固然啦,我這麼寫確定很差,由於認證系統自己沒有什麼複雜邏輯和除了框架外的額外操做,因此我這麼作,挺浪費資源的(佔內存),你們能夠不要效仿這一塊,用到 MyBatis 的地方只有一個讀取用戶名及密碼的接口,也就是說,爲了一個接口,確實沒有必要引入一個框架。等我掌握了 jdbcTemplate 的用法,我也會去掉這個累贅。
固然~!若是,你的認證系統,跟用戶管理系統,是合在一塊兒的狀況下,那卻是沒啥問題,畢竟用戶管理也是有不少邏輯的,像註冊呀、改密啊、綁定密保啊、修改用戶信息呀,這些什麼鬼的。
接着咱們來配置 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"/> <!--註冊統一異常控制--> <bean id="exception" class="cn.com.showings.controller.ExceptionController"/> </beans>沒什麼內容,看註釋就知道了。
而後配置 Transaction:
application-transaction.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans.xsd 6 http://www.springframework.org/schema/tx 7 http://www.springframework.org/schema/tx/spring-tx.xsd"> 8 <!--事務管理對象--> 9 <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 10 <property name="dataSource" ref="dataSource"/> 11 </bean> 12 <!--註解事務--> 13 <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/> 14 15 </beans>嗯...事實上,能夠不用配置,由於根本用不上。
配置 Spring-mvc:
spring-mvc.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:mvc="http://www.springframework.org/schema/mvc" 6 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"> 7 8 <!--自動掃描控制器--> 9 <context:component-scan base-package="cn.com.showings.controller"/> 10 <!--控制器映射器和控制器適配器--> 11 <mvc:annotation-driven/> 12 <mvc:default-servlet-handler/> 13 14 <mvc:resources mapping="/js/**" location="/js/"/> 15 <mvc:resources mapping="/css/**" location="/css/"/> 16 <mvc:resources mapping="/fonts/**" location="/fonts/"/> 17 18 19 <!--視圖渲染--> 20 <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 21 <property name="prefix" value="/WEB-INF/"/> 22 <property name="suffix" value=".jsp"/> 23 </bean> 24 25 <!-- rest json related... start --> 26 <bean id="mappingJacksonHttpMessageConverter" 27 class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> 28 <property name="supportedMediaTypes"> 29 <list> 30 <value>application/json;charset=UTF-8</value> 31 </list> 32 </property> 33 </bean> 34 <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> 35 <property name="messageConverters"> 36 <list> 37 <ref bean="mappingJacksonHttpMessageConverter"/> 38 </list> 39 </property> 40 </bean> 41 <!-- rest json related... end --> 42 43 </beans>
這..也沒啥可說的,全世界都這麼配置的= =
再來配置一個 Mybatis 的分頁插件...其實能夠不用配置,由於根本用不到,除非之後有啥擴展的話:
mybatis-config.xml
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 6 <configuration> 7 <settings> 8 <setting name="logImpl" value="STDOUT_LOGGING"/> 9 </settings> 10 <!-- 11 plugins在配置文件中的位置必須符合要求,不然會報錯,順序以下: 12 properties?, settings?, 13 typeAliases?, typeHandlers?, 14 objectFactory?,objectWrapperFactory?, 15 plugins?, 16 environments?, databaseIdProvider?, mappers? 17 --> 18 <plugins> 19 <!-- com.github.pagehelper爲PageHelper類所在包名 --> 20 <plugin interceptor="com.github.pagehelper.PageHelper"> 21 <!-- 4.0.0之後版本能夠不設置該參數 --> 22 <property name="dialect" value="mysql"/> 23 <!-- 該參數默認爲false --> 24 <!-- 設置爲true時,會將RowBounds第一個參數offset當成pageNum頁碼使用 --> 25 <!-- 和startPage中的pageNum效果同樣--> 26 <property name="offsetAsPageNum" value="true"/> 27 <!-- 該參數默認爲false --> 28 <!-- 設置爲true時,使用RowBounds分頁會進行count查詢 --> 29 <property name="rowBoundsWithCount" value="true"/> 30 <!-- 設置爲true時,若是pageSize=0或者RowBounds.limit = 0就會查詢出所有的結果 --> 31 <!-- (至關於沒有執行分頁查詢,可是返回結果仍然是Page類型)--> 32 <property name="pageSizeZero" value="true"/> 33 <!-- 3.3.0版本可用 - 分頁參數合理化,默認false禁用 --> 34 <!-- 啓用合理化時,若是pageNum<1會查詢第一頁,若是pageNum>pages會查詢最後一頁 --> 35 <!-- 禁用合理化時,若是pageNum<1或pageNum>pages會返回空數據 --> 36 <property name="reasonable" value="true"/> 37 <!-- 3.5.0版本可用 - 爲了支持startPage(Object params)方法 --> 38 <!-- 增長了一個`params`參數來配置參數映射,用於從Map或ServletRequest中取值 --> 39 <!-- 能夠配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默認值 --> 40 <!-- 不理解該含義的前提下,不要隨便複製該配置 --> 41 <property name="params" value="pageNum=pageHelperStart;pageSize=pageHelperRows;"/> 42 <!-- 支持經過Mapper接口參數來傳遞分頁參數 --> 43 <property name="supportMethodsArguments" value="false"/> 44 <!-- always老是返回PageInfo類型,check檢查返回類型是否爲PageInfo,none返回Page --> 45 <property name="returnPageInfo" value="none"/> 46 </plugin> 47 </plugins> 48 </configuration>這個比較詳細,由於這個比較麻煩。因此內容都寫的不少,若是你不配置這個,天然對目前來講,也是能夠的。
再來一個 LogBack 的配置文件,這個配置文件必須放在配置文件的根目錄下,我是使用 IDEA ,maven 的方式搭建項目的,這種配置資源所有都放在 resources 文件夾下,並且文件名字還就得叫這個:
logback.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!-- 3 scan:當此屬性設置爲true時,配置文件若是發生改變,將會被從新加載,默認值爲true。 4 scanPeriod:設置監測配置文件是否有修改的時間間隔,若是沒有給出時間單位,默認單位是毫秒當scan爲true時,此屬性生效。默認的時間間隔爲1分鐘。 5 debug:當此屬性設置爲true時,將打印出logback內部日誌信息,實時查看logback運行狀態。默認值爲false。 6 --> 7 <configuration scan="true" scanPeriod="60 seconds" debug="false"> 8 <!-- 定義日誌的根目錄 --> 9 10 <property name="LOG_HOME" value="/Users/wuxinzhe/IdeaProjects/OAuthServer/logs"/> 11 <!-- 定義日誌文件名稱 --> 12 <property name="appName" value="OAuthServer"/> 13 <!-- ch.qos.logback.core.ConsoleAppender 表示控制檯輸出 --> 14 <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> 15 <Encoding>UTF-8</Encoding> 16 <!-- 17 日誌輸出格式:%d表示日期時間,%thread表示線程名,%-5level:級別從左顯示5個字符寬度 18 %logger{50} 表示logger名字最長50個字符,不然按照句點分割。 %msg:日誌消息,%n是換行符 19 --> 20 <layout class="ch.qos.logback.classic.PatternLayout"> 21 <pattern> 22 %n[%d{yyyy-MM-dd HH:mm:ss}] [userID:%X{userID}] [%logger{50}] %n[%-5level] %msg %n 23 </pattern> 24 </layout> 25 </appender> 26 27 <!-- 滾動記錄文件,先將日誌記錄到指定文件,當符合某個條件時,將日誌記錄到其餘文件 --> 28 <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender"> 29 <Encoding>UTF-8</Encoding> 30 <!-- 指定日誌文件的名稱 --> 31 <file>${LOG_HOME}/${appName}.log</file> 32 <!-- 33 當發生滾動時,決定 RollingFileAppender 的行爲,涉及文件移動和重命名 34 TimeBasedRollingPolicy: 最經常使用的滾動策略,它根據時間來制定滾動策略,既負責滾動也負責出發滾動。 35 --> 36 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 37 <!-- 38 滾動時產生的文件的存放位置及文件名稱 %d{yyyy-MM-dd}:按天進行日誌滾動 39 %i:當文件大小超過maxFileSize時,按照i進行文件滾動 40 --> 41 <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern> 42 <!-- 43 可選節點,控制保留的歸檔文件的最大數量,超出數量就刪除舊文件。假設設置天天滾動, 44 且maxHistory是365,則只保存最近365天的文件,刪除以前的舊文件。注意,刪除舊文件是, 45 那些爲了歸檔而建立的目錄也會被刪除。 46 --> 47 <MaxHistory>365</MaxHistory> 48 <!-- 49 當日志文件超過maxFileSize指定的大小是,根據上面提到的%i進行日誌文件滾動 注意此處配置SizeBasedTriggeringPolicy是沒法實現按文件大小進行滾動的,必須配置timeBasedFileNamingAndTriggeringPolicy 50 --> 51 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 52 <maxFileSize>100MB</maxFileSize> 53 </timeBasedFileNamingAndTriggeringPolicy> 54 </rollingPolicy> 55 <!-- 56 日誌輸出格式:%d表示日期時間,%thread表示線程名,%-5level:級別從左顯示5個字符寬度 %logger{50} 表示logger名字最長50個字符,不然按照句點分割。 %msg:日誌消息,%n是換行符 57 --> 58 <layout class="ch.qos.logback.classic.PatternLayout"> 59 <pattern> 60 %n[%d{yyyy-MM-dd HH:mm:ss}] [userID:%X{userID}] [%logger{50}] %n[%-5level] %msg %n 61 </pattern> 62 </layout> 63 </appender> 64 65 <!-- 66 logger主要用於存放日誌對象,也能夠定義日誌類型、級別 67 name:表示匹配的logger類型前綴,也就是包的前半部分 68 level:要記錄的日誌級別,包括 TRACE < DEBUG < INFO < WARN < ERROR 69 additivity:做用在於children-logger是否使用 rootLogger配置的appender進行輸出,false:表示只用當前logger的appender-ref,true:表示當前logger的appender-ref和rootLogger的appender-ref都有效 70 --> 71 72 <!-- 73 root與logger是父子關係,沒有特別定義則默認爲root,任何一個類只會和一個logger對應, 74 要麼是定義的logger,要麼是root,判斷的關鍵在於找到這個logger,而後判斷這個logger的appender和level。 75 --> 76 <root level="info"> 77 <appender-ref ref="stdout"/> 78 <appender-ref ref="appLogAppender"/> 79 </root> 80 81 </configuration>這個東西我想是必須的吧,畢竟,日誌應該仍是有用的...雖然我整個項目中,都沒有輸出日誌的代碼,不過框架仍是有日誌輸出的需求的。= =
還剩下最後一個配置文件,這個配置文件是 MyBatis 的逆向工程插件的配置文件,若是你有用的話,就弄一個吧
generatorConfig.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE generatorConfiguration 3 PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" 4 "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> 5 6 <generatorConfiguration> 7 <classPathEntry 8 location="/Users/wuxinzhe/.m2/repository/mysql/mysql-connector-java/5.1.6/mysql-connector-java-5.1.6.jar"/> 9 <context id="testTables" targetRuntime="MyBatis3"> 10 <commentGenerator> 11 <!-- 是否去除自動生成的註釋 true:是 : false:否 --> 12 <property name="suppressAllComments" value="true"/> 13 </commentGenerator> 14 <!--數據庫鏈接的信息:驅動類、鏈接地址、用戶名、密碼 --> 15 <jdbcConnection driverClass="com.mysql.jdbc.Driver" 16 connectionURL="jdbc:mysql://showings.com.cn:3306/oauth" 17 userId="root" 18 password="199176"> 19 </jdbcConnection> 20 21 <!-- 默認false,把JDBC DECIMAL 和 NUMERIC 類型解析爲 Integer,爲 true時把JDBC DECIMAL 和 22 NUMERIC 類型解析爲java.math.BigDecimal --> 23 <javaTypeResolver> 24 <property name="forceBigDecimals" value="false"/> 25 </javaTypeResolver> 26 27 <!-- targetProject:生成PO類的位置 --> 28 <javaModelGenerator targetPackage="cn.com.showings.entity" 29 targetProject="src/main/java"> 30 <!-- enableSubPackages:是否讓schema做爲包的後綴 --> 31 <property name="enableSubPackages" value="false"/> 32 <!-- 從數據庫返回的值被清理先後的空格 --> 33 <property name="trimStrings" value="true"/> 34 </javaModelGenerator> 35 <!-- targetProject:mapper映射文件生成的位置 --> 36 <sqlMapGenerator targetPackage="mapper" 37 targetProject="src/main/resources"> 38 <!-- enableSubPackages:是否讓schema做爲包的後綴 --> 39 <property name="enableSubPackages" value="false"/> 40 </sqlMapGenerator> 41 <!-- targetPackage:mapper接口生成的位置 --> 42 <javaClientGenerator type="XMLMAPPER" targetPackage="cn.com.showings.mapper" 43 targetProject="src/main/java"> 44 <!-- enableSubPackages:是否讓schema做爲包的後綴 --> 45 <property name="enableSubPackages" value="false"/> 46 </javaClientGenerator> 47 <!--指定數據庫表--> 48 <table tableName="USER_ROLE" 49 enableCountByExample="false" 50 enableUpdateByExample="false" 51 enableDeleteByExample="false" 52 enableSelectByExample="false" 53 selectByExampleQueryId="false"/> 54 </context> 55 </generatorConfiguration>好了,最重要的:
application-security.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:sec="http://www.springframework.org/schema/security" 5 xmlns:oauth2="http://www.springframework.org/schema/security/oauth2" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans.xsd 8 http://www.springframework.org/schema/security 9 http://www.springframework.org/schema/security/spring-security-4.2.xsd 10 http://www.springframework.org/schema/security/oauth2 11 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> 12 13 <sec:http pattern="/js/**" security="none"/> 14 <sec:http pattern="/fonts/**" security="none"/> 15 <sec:http pattern="/css/**" security="none"/> 16 17 <!-- /oauth/token 是oauth2登錄驗證請求的url用於獲取access_token ,默認的生存時間是43200秒,即12小時--> 18 <sec:http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="oauth2AuthenticationManager" 19 entry-point-ref="oauth2AuthenticationEntryPoint" use-expressions="false"> 20 <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY"/> 21 <sec:anonymous enabled="false"/> 22 <sec:http-basic entry-point-ref="oauth2AuthenticationEntryPoint"/> 23 <sec:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER"/> 24 <sec:access-denied-handler ref="oauth2AccessDeniedHandler"/> 25 <sec:csrf disabled="true"/> 26 </sec:http> 27 28 <!--處理訪問成功--> 29 <bean id="oauth2AuthenticationEntryPoint" 30 class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/> 31 <!--處理訪問拒絕--> 32 <bean id="oauth2AccessDeniedHandler" 33 class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/> 34 <!--處理認證點--> 35 <bean id="oauthUserApprovalHandler" 36 class="org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler"/> 37 <!--處理訪問控制--> 38 <bean id="oauth2AccessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased"> 39 <constructor-arg> 40 <list> 41 <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter"/> 42 <bean class="org.springframework.security.access.vote.RoleVoter"/> 43 <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/> 44 </list> 45 </constructor-arg> 46 </bean> 47 48 <bean id="clientCredentialsTokenEndpointFilter" 49 class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter"> 50 <property name="authenticationManager" ref="oauth2AuthenticationManager"/> 51 </bean> 52 53 <!--可訪問客戶端參數配置,可轉成數據庫配置--> 54 <oauth2:client-details-service id="clientDetailsService"> 55 <oauth2:client client-id="web_client" 56 authorized-grant-types="password,authorization_code,refresh_token,implicit" 57 secret="web" scope="read,write"/> 58 </oauth2:client-details-service> 59 60 <!--可訪問客戶端參數配置,數據庫管理方案--> 61 <!--<bean id="clientDetailsService"--> 62 <!--class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService">--> 63 <!--<constructor-arg index="0" ref="dataSource"/>--> 64 <!--</bean>--> 65 66 <bean id="oauth2ClientDetailsUserService" 67 class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService"> 68 <constructor-arg ref="clientDetailsService"/> 69 </bean> 70 71 <sec:authentication-manager id="oauth2AuthenticationManager"> 72 <sec:authentication-provider user-service-ref="oauth2ClientDetailsUserService"/> 73 </sec:authentication-manager> 74 75 <!--Config token services--> 76 <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore"> 77 <constructor-arg index="0" ref="dataSource"/> 78 </bean> 79 80 <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices"> 81 <property name="tokenStore" ref="tokenStore"/> 82 <property name="clientDetailsService" ref="clientDetailsService"/> 83 <property name="supportRefreshToken" value="true"/> 84 </bean> 85 <bean id="jdbcAuthorizationCodeServices" 86 class="org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices"> 87 <constructor-arg index="0" ref="dataSource"/> 88 </bean> 89 <!--oauth2 的server所能支持的請求類型--> 90 <oauth2:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices" 91 user-approval-handler-ref="oauthUserApprovalHandler" 92 user-approval-page="oauth_approval" 93 error-page="oauth_error"> 94 <oauth2:authorization-code authorization-code-services-ref="jdbcAuthorizationCodeServices"/> 95 <oauth2:implicit/> 96 <oauth2:refresh-token/> 97 <oauth2:client-credentials/> 98 <oauth2:password/> 99 </oauth2:authorization-server> 100 101 <oauth2:resource-server id="webResourceServer" resource-id="web-resource" token-services-ref="tokenServices"/> 102 103 <sec:http disable-url-rewriting="true" use-expressions="false" authentication-manager-ref="authenticationManager"> 104 <sec:intercept-url pattern="/oauth/**" access="ROLE_USER"/> 105 <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/> 106 <sec:form-login authentication-failure-url="login.html?authorization_error=true" 107 default-target-url="index.html" 108 login-page="login.html" login-processing-url="login"/> 109 <sec:logout logout-success-url="success.html" logout-url="logout"/> 110 <sec:access-denied-handler error-page="login.html?access_denied=true"/> 111 <sec:anonymous/> 112 <sec:csrf disabled="true"/> 113 </sec:http> 114 115 <!-- 驗證的權限控制 --> 116 <sec:authentication-manager id="authenticationManager"> 117 <sec:authentication-provider user-service-ref="userServiceImpl"> 118 <sec:password-encoder hash="md5"/> 119 </sec:authentication-provider> 120 </sec:authentication-manager> 121 122 </beans>這個文檔很長,並且我也必須作一個講解,不然就算配置了,估計也不知道怎麼用。
咱們,分段講解:
1 <sec:http pattern="/js/**" security="none"/> 2 <sec:http pattern="/fonts/**" security="none"/> 3 <sec:http pattern="/css/**" security="none"/>這個,是告訴框架,這三塊內容,不須要權限驗證,任何人均可以獲取,因此對這三個資源,直接繞過 SpringSecurity 框架。
1 <!-- /oauth/token 是oauth2登錄驗證請求的url用於獲取access_token ,默認的生存時間是43200秒,即12小時--> 2 <sec:http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="oauth2AuthenticationManager" 3 entry-point-ref="oauth2AuthenticationEntryPoint" use-expressions="false"> 4 <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY"/> 5 <sec:anonymous enabled="false"/> 6 <sec:http-basic entry-point-ref="oauth2AuthenticationEntryPoint"/> 7 <sec:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER"/> 8 <sec:access-denied-handler ref="oauth2AccessDeniedHandler"/> 9 <sec:csrf disabled="true"/> 10 </sec:http>
這部分,是OAuth2.0用於獲取 token 的地址,就是上面寫的"/oauth/token",上面的配置內容我挑着講解一下,首先是 「authentication-manager-ref」,這是是認證管理器的指定,意思就是,當訪問這個資源的時候,咱們要用特定的,專門爲獲取 token 的認證管理器,這麼說可能不理解,那有特定的認證管理器,就確定有普通的認證管理器,普通的認證管理器,就是以前我配置 RBAC 權限系統的時候,用戶的以用戶名及密碼登陸時的那個驗證用戶密碼的管理器,這個是最普通的,也是必須有的管理器,相對這個管理器來講,OAuth2.0須要一個專門對「/oauth/token」這個資源(要獲取的 token 自己就是一種資源),有一個認證管理器,認證的是 client-id 與對應的 client-secret,就是對訪問客戶端的認證,咱們對客戶端程序是有限制的,不是每一個客戶端程序都可以訪問咱們的接口,而是咱們資源服務器受權的某個客戶端纔能有資格來申請,這麼說可能仍是不太好理解,我用現實中的例子來講明這個問題:
咱們都在某些小型的網站,尤爲是論壇上看到登陸時,能夠選擇使用 QQ 登陸,對吧?這也是 OAuth2.0的一個業務場景,其實這些小型網站要實現這類功能以前,必須先跟騰訊申請合做,而後騰訊會在它本身的客戶端數據表中,建立一個屬於這個小網站的一個 ID,對於騰訊來講,這個小網站就是一個客戶端,他想要獲取騰訊的一些資源,好比用戶暱稱及頭像。那小網站 A 跟騰訊簽定了合做協議,騰訊贊成它做爲一個客戶端來訪問資源(僅僅是說 token 的資源,由於這個針對客戶端的受權認證,僅僅只是用來申請 token 的),可是騰訊不容許沒有跟他簽定合做的其餘小網站來訪問資源,因此那些沒有申請合做的小網站,沒有騰訊分發的 ID及密碼,天然就不能發起訪問申請。並且,OAuth2.0有一個很重要的功能,就是根據不一樣的客戶端,能夠區別對待,好比一個比較大一點的中型網站,跟騰訊有付費關係,就是所謂的哈哈,QQ 會員級別,那他跟其餘沒付費的小網站天然不一樣,騰訊能讓她訪問的資源內容的範圍確定也不一樣,因此經過不一樣的 client,天然就能夠進行區別對待,堂而皇之收取 QQ 會員費啦~!哈哈
其餘元素,我就不解釋了,都有對應的 Bean,對應 Bean 上都有註釋,主要是沒啥很差理解的,因此就不須要解釋了吧。
<!--可訪問客戶端參數配置,可轉成數據庫配置--> <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>這就是剛纔我說的,客戶端配置,我這邊是徹底是寫死的,由於個人網站沒打算創建開放平臺給別人,媽蛋,個人網站要有那個能力,你們都來我這裏獲取資源,我還在這裏寫代碼做死?固然啦,我在底下有註釋,用於數據庫配置的方式,其實也沒啥難的。這邊咱們能夠看一下,authorized-grant-type 是認證類型,五種認證類型,能夠根據須要,作一些取捨,通常,經常使用的是 authorization_code,這個是最經常使用的手法,資源服務器之間的通信可使用implicit,具體這幾種方式怎麼用,恩,回頭我再寫一個博客專門講好了。
1 <!--oauth2 的server所能支持的請求類型--> 2 <oauth2:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices" 3 user-approval-handler-ref="oauthUserApprovalHandler" 4 user-approval-page="oauth_approval" 5 error-page="oauth_error"> 6 <oauth2:authorization-code authorization-code-services-ref="jdbcAuthorizationCodeServices"/> 7 <oauth2:implicit/> 8 <oauth2:refresh-token/> 9 <oauth2:client-credentials/> 10 <oauth2:password/> 11 </oauth2:authorization-server>這個呢,能夠講得也很少,就是那個 user-approval-page,我講一下,咱們在使用微信的時候,其實微信也是用的 OAuth2.0,只是沒有讓你輸入用戶密碼,由於你自己就登陸着微信,而微信是 Socket 鏈接,是收信任的連接方式,因此當你點一些小程序或者一些其餘基於微信開發的一些程序的時候,就歷來不用輸入用戶密碼,可是必定會有一個提示,就是問你是否要受權微信登陸,其實就是這樣的一個相似的受權頁面,如圖:
爲啥要有這個頁面,好像畫蛇添足的感受呢?很簡單咯,以防萬一,一些惡意網站,在你不知情的狀況下,去調用你的信息。而 error-page 就是當用戶點擊拒絕訪問的時候,跳轉的頁面。
1 <oauth2:resource-server id="webResourceServer" resource-id="web-resource" token-services-ref="tokenServices"/>這個是資源 ID,定義資源服務器的,恩...其實我也不知道這個要不要加入到這裏,沒試過,由於這個是必需要加到資源服務器的配置文件當中的,而認證服務器要不要我還不清楚,由於我目前尚未把資源服務器的內容寫起來,反正暫且先寫着,若是不須要,回頭再去掉就行。
1 <sec:http disable-url-rewriting="true" use-expressions="false" authentication-manager-ref="authenticationManager"> 2 <sec:intercept-url pattern="/oauth/**" access="ROLE_USER"/> 3 <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/> 4 <sec:form-login authentication-failure-url="login.html?authorization_error=true" 5 default-target-url="index.html" 6 login-page="login.html" login-processing-url="login"/> 7 <sec:logout logout-success-url="success.html" logout-url="logout"/> 8 <sec:access-denied-handler error-page="login.html?access_denied=true"/> 9 <sec:anonymous/> 10 <sec:csrf disabled="true"/> 11 </sec:http> 12 13 <!-- 驗證的權限控制 --> 14 <sec:authentication-manager id="authenticationManager"> 15 <sec:authentication-provider user-service-ref="userServiceImpl"> 16 <sec:password-encoder hash="md5"/> 17 </sec:authentication-provider> 18 </sec:authentication-manager>
這個,就是用戶認證系統,無論用RBAC權限管理設計,仍是 OAuth2.0協議,都不可避免要輸入用戶密碼,這部分呢就是用來定義這個的。這裏有一個坑啊,就是這兩個東西,必須放在最後面,由於,咱們的攔截地址是攔截/**及/oauth/**,若是寫在剛纔申請 token 的那個/oauth/token 的配置前面,則會被覆蓋,那無論怎麼輸入用戶密碼,都不會走用戶驗證管理器,而是都去走剛纔上面說到的那個特殊的,驗證client的用戶驗證管理器。那就無論怎樣,都登陸不了了= =。
好了,最後就是 Web.xml的配置了。
web.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 5 version="3.1"> 6 <!--設置spring 配置文件的位置--> 7 <context-param> 8 <param-name>contextConfigLocation</param-name> 9 <param-value>classpath*:config/application-*.xml</param-value> 10 </context-param> 11 <context-param> 12 <param-name>webAppRootKey</param-name> 13 <param-value>web.root</param-value> 14 </context-param> 15 <listener> 16 <listener-class>org.springframework.web.util.WebAppRootListener</listener-class> 17 </listener> 18 <!--配置spring listener--> 19 <listener> 20 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 21 </listener> 22 <!--解決POST亂碼問題--> 23 <filter> 24 <filter-name>CharacterEncodingFilter</filter-name> 25 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 26 <init-param> 27 <param-name>encoding</param-name> 28 <param-value>utf-8</param-value> 29 </init-param> 30 </filter> 31 <filter-mapping> 32 <filter-name>CharacterEncodingFilter</filter-name> 33 <url-pattern>/*</url-pattern> 34 </filter-mapping> 35 <filter> 36 <filter-name>springSecurityFilterChain</filter-name> 37 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 38 </filter> 39 <filter-mapping> 40 <filter-name>springSecurityFilterChain</filter-name> 41 <url-pattern>/*</url-pattern> 42 </filter-mapping> 43 <!--springMVC前端控制器配置--> 44 <servlet> 45 <servlet-name>dispatcherServlet</servlet-name> 46 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 47 <init-param> 48 <param-name>contextConfigLocation</param-name> 49 <param-value>classpath*:config/spring-mvc.xml</param-value> 50 </init-param> 51 <load-on-startup>2</load-on-startup> 52 </servlet> 53 <servlet-mapping> 54 <servlet-name>dispatcherServlet</servlet-name> 55 <url-pattern>/</url-pattern> 56 </servlet-mapping> 57 <welcome-file-list> 58 <welcome-file>login.html</welcome-file> 59 </welcome-file-list> 60 </web-app>
這個配置很普通,沒有什麼複雜的。恩,關鍵的地方說一下,就是 welcome-file-list 這邊,我設置成了 login.html,我是這麼考慮的:這個認證系統吧,若是真的用起來,可能會比較頻繁,因此不但願老是要通過 spirng 及 spring-mvc 來控制,就作成靜態頁面好了,這樣能夠提升些效率。並且自己登陸也沒有什麼複雜邏輯,用不到 JSP,因此我把登陸頁面作成了靜態頁面,並且但願項目的默認訪問地址,就是登陸頁面,由於自己這就只是一個認證系統而已,沒有其餘的功能。
可是看一下前面的 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"/>
首先是「login.html?authorization_error=true」,這個是當認證失敗,就是驗證用戶密碼錯誤的時候,返回的頁面,固然仍是要回到登陸頁面,可是帶了URL參數,還有一個就是「login.html?access_denied=true」,這個是訪問拒絕的頁面,就是當你沒有登陸就想要訪問一些資源的時候,會跳轉到這個頁面,其實仍是登陸頁面,只是攜帶參數不一樣,不過...自己認證系統就沒有什麼其餘接口,因此這個可能也沒啥用就是了...那反正定義一個,也好。
在 login.html 頁面呢,由於是靜態的,不能用 jstl 表達式來獲取 URL 參數從而顯示不一樣的提示語,可是可使用 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">系統登陸</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">你是否受權"${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">贊成受權</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>受權失敗</title> </head> <body> <h3> 受權失敗! </h3> <div class="alert alert-danger"> <c:out value="${error.summary}"/> </div> </body> </html>
ExceptionController.java
這個是統一異常處理,這邊針對非 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; /** * 知識產權聲明:本文件自建立起,其內容的知識產權即歸屬於原做者,任何他人不可擅自複製或模仿. * 建立者: wu 建立時間: 16/9/29 * 類說明: 統一異常處理 */ 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
這個類是實現了 UserService 接口,而 UserService 接口內沒有任何內容,只是爲了留給之後萬一要集成用戶管理系統或者其餘用戶操做,故意加的一層接口。UserService 接口繼承了 SpringSecurity 框架中的 UserDetailsService,因此這個類目前只實現了一個方法,就是惟一須要使用 mybatis 讀取數據庫的接口= =。。若是後期不打算擴展其餘功能,單純就只用來認證,那可去掉 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; /** * 類說明:用戶服務,管理用戶註冊、讀取用戶信息等用戶相關操做 */ @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
這個類,是用來實現框架中的 UserDetails 接口的,框架中沒有什麼實體類這種概念,都是用接口實現的,咱們這邊就主要按照標準,組裝一下這個對象的數據結構。
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; /** * 類說明:實現 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 類,以及根據數據庫多對多映射出來的 UserRole 類,都是實體類,沒啥好說的,無非就是根據數據庫映射出來的幾個字段而已。
OK 啦,認證服務器的配置就配置完了。資源服務器呢,其實沒有啥特別的,除了 SpringSecurity 的那個配置文件,其餘都跟受權服務器同樣,固然資源服務器要擴展一些本身的功能,確定還有一些特殊的東西,那反正就權限認證這塊,我貼出代碼,其餘的配置,大家均可以按照受權服務器來配置,配置一些好比 dao,spirng,mvc,logback,mybatis,因此,上面那些其實也不是沒用的啦= =
好了,區別就在於 security 的配置:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:sec="http://www.springframework.org/schema/security" 5 xmlns:oauth2="http://www.springframework.org/schema/security/oauth2" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans.xsd 8 http://www.springframework.org/schema/security 9 http://www.springframework.org/schema/security/spring-security-4.2.xsd 10 http://www.springframework.org/schema/security/oauth2 11 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd"> 12 13 <sec:http pattern="/**" create-session="never" entry-point-ref="oauth2AuthenticationEntryPoint" 14 access-decision-manager-ref="oauth2AccessDecisionManager" use-expressions="false"> 15 <sec:anonymous enabled="false"/> 16 <sec:intercept-url pattern="/**" access="ROLE_USER,SCOPE_READ"/> 17 <sec:custom-filter ref="webResourceServer" before="PRE_AUTH_FILTER"/> 18 <sec:access-denied-handler ref="oauth2AccessDeniedHandler"/> 19 <sec:csrf disabled="true"/> 20 </sec:http> 21 22 <!--處理訪問成功--> 23 <bean id="oauth2AuthenticationEntryPoint" 24 class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/> 25 <!--處理訪問拒絕--> 26 <bean id="oauth2AccessDeniedHandler" 27 class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/> 28 <!--處理認證點--> 29 <bean id="oauthUserApprovalHandler" 30 class="org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler"/> 31 <!--處理訪問控制--> 32 <bean id="oauth2AccessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased"> 33 <constructor-arg> 34 <list> 35 <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter"/> 36 <bean class="org.springframework.security.access.vote.RoleVoter"/> 37 <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/> 38 </list> 39 </constructor-arg> 40 </bean> 41 42 43 <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore"> 44 <constructor-arg index="0" ref="dataSource"/> 45 </bean> 46 47 <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices"> 48 <property name="tokenStore" ref="tokenStore"/> 49 <property name="supportRefreshToken" value="true"/> 50 </bean> 51 52 <oauth2:resource-server id="webResourceServer" resource-id="web-resource" token-services-ref="tokenServices"/> 53 54 55 </beans>
這邊的 DataSource要說明一下,這個鏈接的數據庫跟認證服務器的數據庫要是同樣的,因此這個項目若是數據庫分庫的話,天然要有多個 dataSource,建議也是分開,認證系統不要跟業務系統混在一塊兒,從根本上區分開每一個模塊,數據庫也要分開。而這個 dataSource 由於連接的跟認證系統是相同的數據庫,因此天然就是一個統一認證的模式,只要在認證系統內認證過的,獲取的 token 在其餘模塊中也會到相同的數據庫中去驗證。另外說一下,這個資源服務器的配置內容,有一點亂,可能還能夠繼續精簡一些,資源服務器我尚未深刻去看,可能這也已是最小配置了。
對了,數據庫不能忘了。
1 /* 2 Navicat Premium Data Transfer 3 4 Source Server : Showings 5 Source Server Type : MySQL 6 Source Server Version : 50554 7 Source Host : 120.25.99.8 8 Source Database : oauth 9 10 Target Server Type : MySQL 11 Target Server Version : 50554 12 File Encoding : utf-8 13 14 Date: 04/21/2017 09:08:54 AM 15 */ 16 17 SET NAMES utf8; 18 SET FOREIGN_KEY_CHECKS = 0; 19 20 -- ---------------------------- 21 -- Table structure for `oauth_access_token` 22 -- ---------------------------- 23 DROP TABLE IF EXISTS `oauth_access_token`; 24 CREATE TABLE `oauth_access_token` ( 25 `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 26 `token_id` varchar(255) DEFAULT NULL, 27 `token` blob, 28 `authentication_id` varchar(255) DEFAULT NULL, 29 `user_name` varchar(255) DEFAULT NULL, 30 `client_id` varchar(255) DEFAULT NULL, 31 `authentication` blob, 32 `refresh_token` varchar(255) DEFAULT NULL, 33 KEY `token_id_index` (`token_id`), 34 KEY `authentication_id_index` (`authentication_id`), 35 KEY `user_name_index` (`user_name`), 36 KEY `client_id_index` (`client_id`), 37 KEY `refresh_token_index` (`refresh_token`) 38 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 39 40 -- ---------------------------- 41 -- Table structure for `oauth_client_details` 42 -- ---------------------------- 43 DROP TABLE IF EXISTS `oauth_client_details`; 44 CREATE TABLE `oauth_client_details` ( 45 `client_id` varchar(255) NOT NULL, 46 `resource_ids` varchar(255) DEFAULT NULL, 47 `client_secret` varchar(255) DEFAULT NULL, 48 `scope` varchar(255) DEFAULT NULL, 49 `authorized_grant_types` varchar(255) DEFAULT NULL, 50 `web_server_redirect_uri` varchar(255) DEFAULT NULL, 51 `authorities` varchar(255) DEFAULT NULL, 52 `access_token_validity` int(11) DEFAULT NULL, 53 `refresh_token_validity` int(11) DEFAULT NULL, 54 `additional_information` text, 55 `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 56 `archived` tinyint(1) DEFAULT '0', 57 `trusted` tinyint(1) DEFAULT '0', 58 `autoapprove` varchar(255) DEFAULT 'false', 59 PRIMARY KEY (`client_id`) 60 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 61 62 -- ---------------------------- 63 -- Table structure for `oauth_code` 64 -- ---------------------------- 65 DROP TABLE IF EXISTS `oauth_code`; 66 CREATE TABLE `oauth_code` ( 67 `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 68 `code` varchar(255) DEFAULT NULL, 69 `authentication` blob, 70 KEY `code_index` (`code`) 71 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 72 73 -- ---------------------------- 74 -- Table structure for `oauth_refresh_token` 75 -- ---------------------------- 76 DROP TABLE IF EXISTS `oauth_refresh_token`; 77 CREATE TABLE `oauth_refresh_token` ( 78 `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 79 `token_id` varchar(255) DEFAULT NULL, 80 `token` blob, 81 `authentication` blob, 82 KEY `token_id_index` (`token_id`) 83 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 84 85 -- ---------------------------- 86 -- Table structure for `ROLE` 87 -- ---------------------------- 88 DROP TABLE IF EXISTS `ROLE`; 89 CREATE TABLE `ROLE` ( 90 `ID` int(2) NOT NULL AUTO_INCREMENT, 91 `NAME` varchar(10) NOT NULL, 92 PRIMARY KEY (`ID`) 93 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 94 95 -- ---------------------------- 96 -- Table structure for `USER` 97 -- ---------------------------- 98 DROP TABLE IF EXISTS `USER`; 99 CREATE TABLE `USER` ( 100 `UID` varchar(255) NOT NULL, 101 `CREATE_TIME` datetime DEFAULT NULL, 102 `PASSWORD` varchar(255) NOT NULL, 103 `USERNAME` varchar(255) NOT NULL, 104 `LAST_LOGIN_TIME` datetime DEFAULT NULL, 105 PRIMARY KEY (`UID`), 106 UNIQUE KEY `guid` (`UID`), 107 UNIQUE KEY `username` (`USERNAME`) 108 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 109 110 -- ---------------------------- 111 -- Table structure for `USER_ROLE` 112 -- ---------------------------- 113 DROP TABLE IF EXISTS `USER_ROLE`; 114 CREATE TABLE `USER_ROLE` ( 115 `ID` int(255) NOT NULL AUTO_INCREMENT, 116 `USER_UID` varchar(256) NOT NULL, 117 `ROLE_ID` int(1) NOT NULL, 118 PRIMARY KEY (`ID`), 119 KEY `user_id_index` (`USER_UID`(255)) 120 ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; 121 122 SET FOREIGN_KEY_CHECKS = 1;
這個。補充說一下,用戶密碼要通過 MD5加密,驗證的時候,也是有加密,因此存入數據庫的時候,要是沒有加密就會驗證不經過。
下一篇,我會寫這個怎麼用,固然有的朋友熟悉的,天然也知道這個怎麼用。