Spring security OAuth2.0简介
在开始之前,特别强调一下OAuth和SSO的区别:
OAuth主要用来解决系统和系统间的身份认证问题,比如A、B、C三个独立部署的微服务系统均以REST对外提供服务,现在为A系统增加一些限制,只让B系统调用,而禁止C系统调用;
SSO主要用来帮助用户减少在相通的平台重复输入用户名和密码的登陆操作,例如:用户U在A系统上输入用户名密码之后完成登陆操作,在同一个浏览器的另一个页面打开B系统时可以直接进入系统而不用再次输入用户名密码点击登陆;
Spring security oauth2.0建立在Spring security的基础之上,而Spring security基于Filter chain实现,所以可以认为:Spring security oauth2.0是在原本的Spring security filter chain上增加或者替换了部分filter以实现client authorization;
1、Spring security主要流程说明
上图中可能存在不准确的地方,纯粹为了便于理解
这里重点说明一下以下四个filter:
- SecurityContextPersistenceFilter:
- 将securityContext放入session,或者从session中读取已经存在的securityContext,
- 将securityContext放入ThreadLocal中
- UsernamePasswordAuthenticationFilter :
- 通过AuthenticationManager对传入的用户名密码进行校验
- 将校验结果放入ThreadLocal中的securityContext
- ExceptionTranslationFilter:
- 捕获后续filter抛出的异常,根据异常类型分别处理
- FilterSecurityInterceptor:
- 对经过校验的用户进行鉴权,判断用户是否具备进行当前操作的权限
2、OAuth server配置及说明
2.1、配置
- 因为使用XML进行配置,需要引入对应的namespace
<?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:security="http://www.springframework.org/schema/security"
xmlns:oauth2="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
</beans>
引入security和oauth2对应的xsd文件
- 配置user authentication manager以及spring security user
<security:authentication-manager alias="userAuthenticationManager">
<security:authentication-provider>
<security:user-service>
<security:user name="admin" password="admin" authorities="IS_AUTHENTICATED_FULLY"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
- 简单起见,用户信息就不放在数据库了
- 这里定义的userAuthenticationManager用来做用户校验,验证用户输入的用户名密码是否正确、是否具备访问权限
- 定义一个admin用户,具备IS_AUTHENTICATED_FULLY权限
- 配置Spring security过滤器链
<security:http auto-config="true" authentication-manager-ref="userAuthenticationManager">
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
</security:http>
- 拦截所以请求,所有url均需要IS_AUTHENTICATED_FULLY权限才能访问
- 自动配置登陆界面/login,这个默认链接不会被拦截,否则就进入死循环了
- 引用刚刚定义的认证管理器进行认证管理
到这里,正常的Spring security已经可以正常工作了,输入任何链接都将跳转到/login登陆界面,登陆后跳转到之前输入的url。接下来配置OAuth:
- 配置客户端信息
<oauth2:client-details-service id="clientDetailsService">
<!--
测试:curl -XPOST http://localhost:8080/oauth/token -H 'Content-Type: application/x-www-form-urlencoded' -d 'client_id=client-1&client_secret=client&grant_type=client_credentials'
-->
<oauth2:client client-id="client-1" secret="client" scope="read" authorized-grant-types="client_credentials" authorities="IS_AUTHENTICATED_FULLY"/>
<!--
测试:在浏览器中输入:http://localhost:8080/oauth/authorize?client_id=client-2&response_type=code&state=BWNvjZ
在authorization_code模式下,authorities将使用user的值,而非client
-->
<oauth2:client client-id="client-2" secret="client" scope="read" authorized-grant-types="authorization_code" redirect-uri="http://localhost:8082"/>
<!--
测试:在浏览器中输入:http://localhost:8080/oauth/authorize?client_id=client-3&response_type=code&state=BWNvjZ
在authorization_code模式下,authorities将使用user的值,而非client
-->
<oauth2:client client-id="client-3" secret="client" scope="read" authorized-grant-types="authorization_code" redirect-uri="http://localhost:8083/app" autoapprove="true"/>
<!--
测试:在浏览器中输入:http://localhost:8080/oauth/authorize?client_id=client-4&response_type=token&state=BWNvjZ
在implicit模式下,authorities将使用user的值,而非client
-->
<oauth2:client client-id="client-4" secret="client" scope="read" authorized-grant-types="implicit" redirect-uri="http://localhost:8084/app"/>
<!--
测试:curl -XPOST http://localhost:8080/oauth/token -H 'Content-Type: application/x-www-form-urlencoded' -d 'grant_type=password&username=admin&password=admin&client_id=client-5&client_secret=client'
在password模式下,authorities将使用user的值,而非client
-->
<oauth2:client client-id="client-5" secret="client" scope="read" authorized-grant-types="password"/>
</oauth2:client-details-service>
- 这里配置五个client,验证OAuth2.0对应的各种不同情况
- 除client_credentials模式需要配置authorities外,其他模式都不需要配置authorities,因为其他默认都需要输入用户名密码,将获得user的authorities
- 配置client authentication manager
<security:authentication-manager id="clientAuthenticationManager">
<security:authentication-provider user-service-ref="clientDetailsUserDetailsService"/>
</security:authentication-manager>
- 这里定义的client authentication manager与user authentication manager区别开,是用来验证client及其权限的
- 验证client authentication manager引用clientDetailsService进行客户端验证
- 配置token services用来颁发token
<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore"/>
<bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore" ref="tokenStore"/>
<property name="supportRefreshToken" value="true"/>
<property name="clientDetailsService" ref="clientDetailsService"/>
</bean>
- 根据OAuth2.0中的认证流程,认证服务器需要为经过认证的client颁发token以验明正身
- 颁发的token通过tokenStore保存,后续验证token的合法性
- 为tokenServices提供clientDetailsService信息,可以在clientDetailsService中配置对应的有效期、是否允许刷新token等
- 配置authorization server
<oauth2:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices" user-approval-handler-ref="approvalHandler" check-token-enabled="true">
<oauth2:authorization-code/>
<oauth2:implicit/>
<oauth2:refresh-token/>
<oauth2:client-credentials/>
<oauth2:password/>
</oauth2:authorization-server>
- authorization server提供OAuth所需的endpoint:
- /oauth/confirm_access:默认的授权审核URL,org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint
- /oauth/authorize:默认的鉴权URL,org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint
- /oauth/token:默认获取token的URL,org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
- /oauth/check_token:默认验证token有效性的URL,org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint
- 默认情况下不会开启,需要设置
check-token-enabled="true"
- 默认的user approval handler不支持autoapprove,这里定义为TokenStoreUserApprovalHandler
- 开启authorization-code模式、implicit模式、client-credentials模式、password模式,支持refresh-token
- 配置OAuth spring security过滤器链,与普通spring security过滤器链类似,不同之处在于UsernamePasswordAuthenticationFilter来进行身份过滤,转而使用ClientCredentialsTokenEndpointFilter
<bean id="auth2AuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/>
<!-- 用于client的security:http-basic认证 -->
<bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="typeName" value="Basic"/>
</bean>
<!-- oauth客户端验证过滤器,ClientCredentialsTokenEndpointFilter默认的RequestMatcherPath为/oauth/token -->
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<property name="authenticationManager" ref="clientAuthenticationManager"/>
</bean>
<!-- 校验异常处理类 -->
<bean id="auth2AccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/>
<!-- oauth token权限过滤:校验客户端权限 -->
<security:http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager" entry-point-ref="auth2AuthenticationEntryPoint">
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
<!-- 启用client basic认证:client在使用oauth2:rest-template获取token时,默认使用此认证方式 -->
<security:http-basic entry-point-ref="clientAuthenticationEntryPoint"/>
<!-- 启用client url验证 -->
<security:custom-filter ref="clientCredentialsTokenEndpointFilter" after="BASIC_AUTH_FILTER"/>
<security:access-denied-handler ref="auth2AccessDeniedHandler"/>
</security:http>
- 与上面的security:http相比,一个认证user,一个认证client
- 默认所有的请求都进行user认证,因为user的pattern匹配所有的URL,因此需要将
security:http pattern="/oauth/token"
放到user对应的http前面
- 配置/oauth/check_token过滤器链
<security:http pattern="/oauth/check_token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager" entry-point-ref="auth2AuthenticationEntryPoint">
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
<security:access-denied-handler ref="auth2AccessDeniedHandler"/>
<!-- 在filterChain中增加BasicAuthenticationFilter -->
<security:http-basic/>
</security:http>
与上面的OAuth过滤器链类似,只不过这里使用的是BasicAuthenticationFilter,上面使用的是OAuth2AuthenticationEntryPoint
到这里,验证服务器基本上配置完成了,接下来可以进行简单的测试~
2.2、测试
- 测试链接已经在xml中给出,可以使用postman或者curl进行测试
- authorization_code模式使用浏览器进行测试,在chrome中可以查看Network中request和response跳转过程
3、Resource server配置及说明
- 配置resource server
<bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.RemoteTokenServices">
<property name="clientId" value="client-1"/>
<property name="clientSecret" value="client"/>
<property name="checkTokenEndpointUrl" value="http://localhost:8080/oauth/check_token"/>
</bean>
<oauth2:resource-server id="client-1" token-services-ref="tokenServices"/>
- oauth2:resource-server是一个filter,对应OAuth2AuthenticationProcessingFilter,用来验证访问resource的client是否合法
- 对于resource与OAuth server分离的情况,使用RemoteTokenServices来验证token简单又方便,当然也可以使用更高效的JWT方式
- 通过RemoteTokenServices验证client提供的token是否合法,resource需要访问OAuth server的/oauth/check_token链接,此链接受OAuth server,RemoteTokenServices需要提供resource server的凭据,也就是clientId和clientSecret
- 配置Spring security过滤器链,保护resource server的资源
<security:http entry-point-ref="oAuth2AuthenticationEntryPoint">
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
<security:custom-filter ref="client-1" before="PRE_AUTH_FILTER"/>
<security:access-denied-handler ref="auth2AccessDeniedHandler"/>
</security:http>
<security:authentication-manager/>
- 所有资源访问均需要IS_AUTHENTICATED_FULLY权限
- 自定义resource server过滤器用来验证client
4、Client配置及说明
client作为一个单独的系统,可能有自己的用户权限体系,也可能是使用SSO共享OAuth server的用户权限体系
- 配置client
<oauth2:resource id="oauth" type="client_credentials" client-id="client-1" client-secret="client" access-token-uri="http://localhost:8080/oauth/token"/>
<oauth2:rest-template id="restTemplate" resource="oauth"/>
- 使用restTemplate访问resource server时,会检查client本地是否存在access token,如果不存在则会在正确请求链接前访问access-token-uri获得token