參考以下兩個文章:
http://www.cnblogs.com/0201zcr/p/5328847.html
http://wwwcomy.iteye.com/blog/2230265
web.xml 注意這里的<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <url-pattern>/*</url-pattern> 而<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <url-pattern>*.action</url-pattern>會導致org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter構造器里面的/oauth/token被security攔截,而spring mvc卻沒有攔截
<!-- SpringSecurity必須的filter start--> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- SpringSecurity必須的filter end--> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContextMvc.xml </param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.action</url-pattern> </servlet-mapping>
<!-- /oauth/token 是oauth2登陸驗證請求的url 用於獲取access_token ,默認的生存時間是43200秒,即12小時--> <http pattern="/oauth/token.action" create-session="stateless" authentication-manager-ref="clientAuthenticationManager"> <intercept-url pattern="/oauth/token.action" access="IS_AUTHENTICATED_FULLY" /> <!-- 可以訪問的角色名稱,如果需要攔截,需要實現UserDetails接口,實現getAuthorities()方法--> <anonymous enabled="false" /> <http-basic entry-point-ref="oauth2AuthenticationEntryPoint" /> <custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" /> <access-denied-handler ref="oauth2AccessDeniedHandler" /> </http>
這個authentication-manager 是OAUTH的,還需要另一個spring security ,如果已經在使用spring security 那么只需要配置這一個.ClientDetailsUserDetailsService 實現了spring security的UserDetailsService,在ClientDetailsUserDetailsService
的loadUserByUsername並不是驗證我們用戶的賬號密碼,驗證用戶的賬號密碼在spring security里面已經自己處理了,這里的loadUserByUsername是驗證我們的客戶端也就是第三方的網址,或者APP,是否有權限訪問我們的接口.例如這里我們的第三方APP用戶名為mobile_1,密碼為secret_1,可以配置多個第三方APP
<!-- 驗證的權限控制 --> <authentication-manager id="clientAuthenticationManager"> <authentication-provider user-service-ref="oauth2ClientDetailsUserService" /> </authentication-manager> <oauth2:client-details-service id="clientDetailsService" > <oauth2:client client-id="mobile_1" authorized-grant-types="password,authorization_code,refresh_token,implicit" secret="secret_1" scope="read,write,trust" authorities="ROLE_CLIENT,ROLE_TRUSTED_CLIENT" resource-ids="mobile-resource" /> </oauth2:client-details-service> <beans:bean id="oauth2ClientDetailsUserService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService"> <beans:constructor-arg ref="clientDetailsService" /> </beans:bean>
spring security 的authentication-manager , daoAuthenticationProvider需要自己實現,這里就不貼出來了
<!-- 權限管理者 --> <authentication-manager alias="myAuthenticationManager"> <!-- 權限提供者 --> <authentication-provider ref="daoAuthenticationProvider" /> </authentication-manager> <beans:bean id="daoAuthenticationProvider" class="com.thesys.common.security.provider.MyDaoAuthenticationProvider"> <beans:property name="userDetailsService" ref="securityService" /> <beans:property name="PasswordEncoder" ref="md5PasswordEncoder" /> </beans:bean>
復制org.springframework.security.oauth2.provider.token.DefaultTokenServices 內容新建類MyTokenService 自己重寫 private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken)這個方法,實現自己的TOKEN生成方式
<!-- for spring oauth2 --> <!--token在服務器存儲的方式 InMemoryTokenStore :保存在內存 ;JdbcTokenStore : 保存在數據庫中 --> <beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore" /> <!--<beans:bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">--> <!--令牌服務的實體--> <beans:bean id="tokenServices" class="com.thesys.common.security.oauth.MyTokenService" > <beans:property name="tokenStore" ref="tokenStore"></beans:property> <beans:property name="supportRefreshToken" value="true"/> <beans:property name="clientDetailsService" ref="clientDetailsService" /> </beans:bean> <!-- 自己重寫的類 --> <!--處理訪問成功--> <beans:bean id="oauth2AuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint" /> <!--處理訪問拒絕--> <beans:bean id="oauth2AccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" /> <!--處理認證點--> <beans:bean id="oauthUserApprovalHandler" class="org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler" /> <!--處理訪問控制--> <beans:bean id="oauth2AccessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased"> <beans:constructor-arg> <beans:list> <beans:bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" /> <beans:bean class="org.springframework.security.access.vote.RoleVoter" /> <beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" /> </beans:list> </beans:constructor-arg> </beans:bean> <!--oauth2 的server所能支持的請求類型--> <oauth2:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices" user-approval-handler-ref="oauthUserApprovalHandler"> <oauth2:authorization-code /> <oauth2:implicit /> <oauth2:refresh-token /> <oauth2:client-credentials /> <oauth2:password /> </oauth2:authorization-server>
這里解決.do .action的攔截問題, <beans:constructor-arg value="/oauth/token.action" /> 把默認的/oauth/token 改成/oauth/token.action 就可以解決.do或者.action 的攔截問題
<beans:bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter"> <beans:property name="authenticationManager" ref="clientAuthenticationManager" /> <beans:constructor-arg value="/oauth/token.action" /> </beans:bean>
<intercept-url pattern="/admin**" access="IS_AUTHENTICATED_FULLY" /> 因為沒有項目沒有角色的設置,只要登錄了就可以訪問,所以不設置角色驗證
<!--指定spring要保護的資源,如果沒有這個,訪問控制的時候會說沒有Authentication object:--> <oauth2:resource-server id="mobileResourceServer" resource-id="mobile-resource" token-services-ref="tokenServices" /> <http pattern="/json**" create-session="never" entry-point-ref="oauth2AuthenticationEntryPoint" access-decision-manager-ref="oauth2AccessDecisionManager"> <anonymous enabled="false" /> <intercept-url pattern="/json**" access="IS_AUTHENTICATED_FULLY" /><!-- --> <custom-filter ref="mobileResourceServer" before="PRE_AUTH_FILTER" /> <access-denied-handler ref="oauth2AccessDeniedHandler" /> </http> <http pattern="/admin**" create-session="never" entry-point-ref="oauth2AuthenticationEntryPoint" access-decision-manager-ref="oauth2AccessDecisionManager"> <anonymous enabled="false" /> <intercept-url pattern="/admin**" access="IS_AUTHENTICATED_FULLY" /> <!-- --> <custom-filter ref="mobileResourceServer" before="PRE_AUTH_FILTER" /> <access-denied-handler ref="oauth2AccessDeniedHandler" /> </http>
因為沒有設置必須為POST 提交,所以無論GET POST 提交以下請求會返回access_token
http://localhost:8028/oauth/token.action?client_id=mobile_1&client_secret=secret_1&grant_type=password&username=test&password=1
- {
- "access_token": "6cd40d26561c4ac89e447dd5214c7033",
- "token_type": "bearer",
- "refresh_token": "459f7555-e733-43c9-8ab8-016b15a61427",
- "expires_in": 43199,
- "scope": "read trust write"
- }
刷新access_token
/oauth/token.action?client_id=&client_secret=&grant_type=refresh_token&refresh_token=459f7555-e733-43c9-8ab8-016b15a61427
{
"access_token": "859f08ec-7552-45b2-bb54-50328e462646",
"token_type": "bearer",
"refresh_token": "26bef8b9-2521-4efd-a81b-1e2873866c8f",
"expires_in": 2591999,
"scope": "read trust write"
}
然后帶着access_token訪問,就可以成功訪問
http://localhost:8028/admin.action?access_token=52d33d7d81ee4a388d79bf00387b1325
沒有access_token訪問的話,會返回(注意看這里是XML方式返回,因為請求頭為空,spring返回默認第一個,如果需要json,那么請在請求頭加上accept:application/json)
http://localhost:8028/admin.action
- <oauth>
- <error_description>An Authentication object was not found in the SecurityContext</error_description>
- <error>unauthorized</error>
- </oauth>
數據庫存儲方式
<!--token在服務器存儲的方式 InMemoryTokenStore :保存在內存 ;JdbcTokenStore : 保存在數據庫中 <beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore" />--> <beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore" >
按照規范建兩個表,因為需要訪問的第三方不多,我只需要持久化access_token所以沒有建立oauth_client_details,oauth_code
Drop table if exists oauth_access_token; create table oauth_access_token ( create_time timestamp default now(), token_id VARCHAR(255), token BLOB, authentication_id VARCHAR(255), user_name VARCHAR(255), client_id VARCHAR(255), authentication BLOB, refresh_token VARCHAR(255) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; Drop table if exists oauth_refresh_token; create table oauth_refresh_token ( create_time timestamp default now(), token_id VARCHAR(255), token BLOB, authentication BLOB ) ENGINE=InnoDB DEFAULT CHARSET=utf8;