前言
关于 OAuth2.0的认证体系,翻阅了好多资料,RCF 文档太多,看了一半就看不下去了,毕竟全英文的文档看起来,是有一点让我烦躁,但也对 OAuth2.0的认证流程有了一个基本的概念,之前用 SpringSecurity 做了一个基于 RBAC 的权限管理系统的基础配置,所以对 SpringSecurity 算是比较了解了,于是 OAuth2.0的实现,也想用 SpringSecurity 的来做,心想应该比较简单,然而...事实上,我反反复复,拿起又放弃,放弃又拿起,来来回回折腾了3个多月,才真正的掌握了这个 OAuth2.0插件(OAuth2.0不是一个独立的框架,只是 SpringSecurity 的一个插件而已)。
官网的 Demo 配置,是基于 JavaConfig 的配置方式,以前都用 XML 的,没接触过 JavaConfig,所以又绕了一圈,把 JavaConfig 方式的所有框架(Spring、SpringMVC、Mybatis、SpringSecurity、Web.xml)基本配置方式都走了一圈, 确实,全代码配置是很酷,很清爽,说实话,今后我也会逐渐往这方面走,因为这个方式比较有代码感,哈哈,但是现在还不行,因为有很多插件啊、特殊的配置方式啊,我都还不清楚要怎么配置,处于安全考虑,还是老老实实的用 XML 的比较好。
额外插播一则我团队的招聘广告:
阿里巴巴 - 淘系技术部招聘:https://www.cnblogs.com/wuxinzhe/p/11258226.html
项目的说明
网上有很多,SpringSecurityOAuth2.0的配置文章,但是每个文章,都是将认证服务器和资源服务器写在一起的,并没有将认证与资源分离,也没有讲不同的资源之间如何拆分,然而我们在设计分布式系统的时候,总会以模块化的方式,将不同的资源写成不同的项目,比如,将网站的一个电商系统,专门写成一个项目,把网站中的论坛系统,写成另一个项目,部署的时候,每个项目就可以单独部署,后端系统均以 RESTFull 的方式开放数据接口(RESTFull就是推荐使用 OAuth2.0的方式进行认证管理)。这样的方式来设计程序,最大的优点就是模块之间相互独立,互不干涉,在开发工作当中,可以并行开发,单独维护,同时模块分离出来,今后还可以进行很便利的集群,而不需要修改任何原来的代码,所以对整个项目的扩展性是非常好的,不同的项目之间,可以简单的使用 HttpClient 进行通讯,OAuth2.0五种授权模式当中,有一种授权模式就是为这种资源服务器之间的通讯而设计的。
认证服务器与资源服务器分离的这个配置方式,同时也实现了“统一认证”的模式,只需要在认真服务器上做了认证,拿到了 Token,就可以访问所有授权的资源服务器。
接下来,我们开始搭建认证服务器的配置。
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.0modelVersion> 6 <groupId>ShowingsgroupId> 7 <artifactId>OAuthServerartifactId> 8 <version>1.0-SNAPSHOTversion> 9 <build> 10 <finalName>showingsfinalName> 11 <plugins> 12 13 <plugin> 14 <groupId>org.mybatis.generatorgroupId> 15 <artifactId>mybatis-generator-maven-pluginartifactId> 16 <version>1.3.2version> 17 <configuration> 18 <verbose>trueverbose> 19 <overwrite>trueoverwrite> 20 configuration> 21 plugin> 22 <plugin> 23 <groupId>org.apache.maven.pluginsgroupId> 24 <artifactId>maven-compiler-pluginartifactId> 25 <configuration> 26 <source>1.7source> 27 <target>1.7target> 28 configuration> 29 plugin> 30 plugins> 31 build> 32 <properties> 33 <security.version>4.2.2.RELEASEsecurity.version> 34 <spring.version>4.3.7.RELEASEspring.version> 35 <security.oauth.version>2.0.7.RELEASEsecurity.oauth.version> 36 properties> 37 <dependencies> 38 39 <dependency> 40 <groupId>org.springframeworkgroupId> 41 <artifactId>spring-coreartifactId> 42 <version>${spring.version}version> 43 dependency> 44 45 <dependency> 46 <groupId>org.springframeworkgroupId> 47 <artifactId>spring-webartifactId> 48 <version>${spring.version}version> 49 dependency> 50 51 <dependency> 52 <groupId>org.springframeworkgroupId> 53 <artifactId>spring-oxmartifactId> 54 <version>${spring.version}version> 55 dependency> 56 57 <dependency> 58 <groupId>org.springframeworkgroupId> 59 <artifactId>spring-txartifactId> 60 <version>${spring.version}version> 61 dependency> 62 63 <dependency> 64 <groupId>org.springframeworkgroupId> 65 <artifactId>spring-webmvcartifactId> 66 <version>${spring.version}version> 67 dependency> 68 69 <dependency> 70 <groupId>org.springframeworkgroupId> 71 <artifactId>spring-aopartifactId> 72 <version>${spring.version}version> 73 dependency> 74 75 <dependency> 76 <groupId>org.springframeworkgroupId> 77 <artifactId>spring-context-supportartifactId> 78 <version>${spring.version}version> 79 80 <exclusions> 81 <exclusion> 82 <groupId>commons-logginggroupId> 83 <artifactId>commons-loggingartifactId> 84 exclusion> 85 exclusions> 86 dependency> 87 88 <dependency> 89 <groupId>org.springframeworkgroupId> 90 <artifactId>spring-expressionartifactId> 91 <version>${spring.version}version> 92 dependency> 93 94 <dependency> 95 <groupId>javax.validationgroupId> 96 <artifactId>validation-apiartifactId> 97 <version>2.0.0.Alpha2version> 98 dependency> 99 100 <dependency> 101 <groupId>org.hibernategroupId> 102 <artifactId>hibernate-validatorartifactId> 103 <version>6.0.0.Alpha2version> 104 dependency> 105 106 <dependency> 107 <groupId>com.mchangegroupId> 108 <artifactId>c3p0artifactId> 109 <version>0.9.5.1version> 110 dependency> 111 112 <dependency> 113 <groupId>org.mybatisgroupId> 114 <artifactId>mybatisartifactId> 115 <version>3.3.0version> 116 dependency> 117 118 <dependency> 119 <groupId>com.github.pagehelpergroupId> 120 <artifactId>pagehelperartifactId> 121 <version>4.1.6version> 122 dependency> 123 124 <dependency> 125 <groupId>com.github.jsqlparsergroupId> 126 <artifactId>jsqlparserartifactId> 127 <version>0.9.6version> 128 dependency> 129 130 <dependency> 131 <groupId>org.mybatisgroupId> 132 <artifactId>mybatis-springartifactId> 133 <version>1.2.3version> 134 dependency> 135 136 137 <dependency> 138 <groupId>mysqlgroupId> 139 <artifactId>mysql-connector-javaartifactId> 140 <version>5.1.6version> 141 dependency> 142 <dependency> 143 <groupId>jstlgroupId> 144 <artifactId>jstlartifactId> 145 <version>1.2version> 146 dependency> 147 148 <dependency> 149 <groupId>javax.elgroupId> 150 <artifactId>javax.el-apiartifactId> 151 <version>3.0.1-b04version> 152 dependency> 153 154 155 <dependency> 156 <groupId>org.springframework.securitygroupId> 157 <artifactId>spring-security-coreartifactId> 158 <version>${security.version}version> 159 dependency> 160 <dependency> 161 <groupId>org.springframework.securitygroupId> 162 <artifactId>spring-security-webartifactId> 163 <version>${security.version}version> 164 dependency> 165 <dependency> 166 <groupId>org.springframework.securitygroupId> 167 <artifactId>spring-security-taglibsartifactId> 168 <version>${security.version}version> 169 dependency> 170 <dependency> 171 <groupId>org.springframework.securitygroupId> 172 <artifactId>spring-security-configartifactId> 173 <version>${security.version}version> 174 dependency> 175 176 <dependency> 177 <groupId>org.springframework.security.oauthgroupId> 178 <artifactId>spring-security-oauth2artifactId> 179 <version>${security.oauth.version}version> 180 dependency> 181 182 183 <dependency> 184 <groupId>org.slf4jgroupId> 185 <artifactId>slf4j-apiartifactId> 186 <version>1.7.10version> 187 dependency> 188 <dependency> 189 <groupId>ch.qos.logbackgroupId> 190 <artifactId>logback-classicartifactId> 191 <version>1.1.2version> 192 dependency> 193 <dependency> 194 <groupId>ch.qos.logbackgroupId> 195 <artifactId>logback-coreartifactId> 196 <version>1.1.2version> 197 dependency> 198 199 200 <dependency> 201 <groupId>javax.servletgroupId> 202 <artifactId>javax.servlet-apiartifactId> 203 <version>3.1.0version> 204 dependency> 205 206 207 <dependency> 208 <groupId>org.codehaus.jacksongroupId> 209 <artifactId>jackson-mapper-aslartifactId> 210 <version>1.9.13version> 211 dependency> 212 <dependency> 213 <groupId>com.fasterxml.jackson.coregroupId> 214 <artifactId>jackson-annotationsartifactId> 215 <version>2.6.1version> 216 dependency> 217 <dependency> 218 <groupId>com.fasterxml.jackson.coregroupId> 219 <artifactId>jackson-coreartifactId> 220 <version>2.6.1version> 221 dependency> 222 <dependency> 223 <groupId>com.fasterxml.jackson.coregroupId> 224 <artifactId>jackson-databindartifactId> 225 <version>2.6.1version> 226 dependency> 227 228 229 dependencies> 230 231 project>
Pom 很长,但其实没有多少内容,我们需要自己写的代码,也非常非常非常的少...= =,是不是很开心?嘿嘿...
项目目录结构
Yes,你没看错,目录内容真的很少...Java 的部分,真的就没几个= =
配置文件
首先是 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 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 27 <property name="mapperLocations"> 28 <list> 29 <value>classpath*:/mapper/*.xmlvalue> 30 list> 31 property> 32 bean> 33 34 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"> <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 26 <bean id="mappingJacksonHttpMessageConverter" 27 class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> 28 <property name="supportedMediaTypes"> 29 <list> 30 <value>application/json;charset=UTF-8value> 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 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 18 <plugins> 19 20 <plugin interceptor="com.github.pagehelper.PageHelper"> 21 22 <property name="dialect" value="mysql"/> 23 24 25 26 <property name="offsetAsPageNum" value="true"/> 27 28 29 <property name="rowBoundsWithCount" value="true"/> 30 31 32 <property name="pageSizeZero" value="true"/> 33 34 35 36 <property name="reasonable" value="true"/> 37 38 39 40 41 <property name="params" value="pageNum=pageHelperStart;pageSize=pageHelperRows;"/> 42 43 <property name="supportMethodsArguments" value="false"/> 44 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 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 14 <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> 15 <Encoding>UTF-8Encoding> 16 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-8Encoding> 30 31 <file>${LOG_HOME}/${appName}.logfile> 32 36 <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 37 41 <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.logfileNamePattern> 42 47 <MaxHistory>365MaxHistory> 48 51 <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> 52 <maxFileSize>100MBmaxFileSize> 53 timeBasedFileNamingAndTriggeringPolicy> 54 rollingPolicy> 55 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 71 72 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 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 23 <javaTypeResolver> 24 <property name="forceBigDecimals" value="false"/> 25 javaTypeResolver> 26 27 28 <javaModelGenerator targetPackage="cn.com.showings.entity" 29 targetProject="src/main/java"> 30 31 <property name="enableSubPackages" value="false"/> 32 33 <property name="trimStrings" value="true"/> 34 javaModelGenerator> 35 36 <sqlMapGenerator targetPackage="mapper" 37 targetProject="src/main/resources"> 38 39 <property name="enableSubPackages" value="false"/> 40 sqlMapGenerator> 41 42 <javaClientGenerator type="XMLMAPPER" targetPackage="cn.com.showings.mapper" 43 targetProject="src/main/java"> 44 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 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 62 63 64 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 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 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 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 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 7 <context-param> 8 <param-name>contextConfigLocationparam-name> 9 <param-value>classpath*:config/application-*.xmlparam-value> 10 context-param> 11 <context-param> 12 <param-name>webAppRootKeyparam-name> 13 <param-value>web.rootparam-value> 14 context-param> 15 <listener> 16 <listener-class>org.springframework.web.util.WebAppRootListenerlistener-class> 17 listener> 18 19 <listener> 20 <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class> 21 listener> 22 23 <filter> 24 <filter-name>CharacterEncodingFilterfilter-name> 25 <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class> 26 <init-param> 27 <param-name>encodingparam-name> 28 <param-value>utf-8param-value> 29 init-param> 30 filter> 31 <filter-mapping> 32 <filter-name>CharacterEncodingFilterfilter-name> 33 <url-pattern>/*url-pattern> 34 filter-mapping> 35 <filter> 36 <filter-name>springSecurityFilterChainfilter-name> 37 <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class> 38 filter> 39 <filter-mapping> 40 <filter-name>springSecurityFilterChainfilter-name> 41 <url-pattern>/*url-pattern> 42 filter-mapping> 43 44 <servlet> 45 <servlet-name>dispatcherServletservlet-name> 46 <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class> 47 <init-param> 48 <param-name>contextConfigLocationparam-name> 49 <param-value>classpath*:config/spring-mvc.xmlparam-value> 50 init-param> 51 <load-on-startup>2load-on-startup> 52 servlet> 53 <servlet-mapping> 54 <servlet-name>dispatcherServletservlet-name> 55 <url-pattern>/url-pattern> 56 servlet-mapping> 57 <welcome-file-list> 58 <welcome-file>login.htmlwelcome-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 Approvaltitle> <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 ListgrantedAuthorities = new ArrayList<>(); public MyUserDetail(User user) { this.user = user; initAuthority(); } private void initAuthority() { List 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加密,验证的时候,也是有加密,所以存入数据库的时候,要是没有加密就会验证不通过。
下一篇,我会写这个怎么用,当然有的朋友熟悉的,自然也知道这个怎么用。