Spring Security Oauth2 的配置


使用oauth2保護你的應用,可以分為簡易的分為三個步驟

  • 配置資源服務器
  • 配置認證服務器
  • 配置spring security

前兩點是oauth2的主體內容,但前面我已經描述過了,spring security oauth2是建立在spring security基礎之上的,所以有一些體系是公用的。

oauth2根據使用場景不同,分成了4種模式

  • 授權碼模式(authorization code)
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

本文重點講解接口對接中常使用的密碼模式(以下簡稱password模式)和客戶端模式(以下簡稱client模式)。授權碼模式使用到了回調地址,是最為復雜的方式,通常網站中經常出現的微博,qq第三方登錄,都會采用這個形式。簡化模式不常用。

 

配置資源服務器和授權服務器

由於是兩個oauth2的核心配置,我們放到一個配置類中。 
為了方便下載代碼直接運行,我這里將客戶端信息放到了內存中,生產中可以配置到數據庫中。token的存儲一般選擇使用redis,一是性能比較好,二是自動過期的機制,符合token的特性。

 1 @Configuration  2 public class OAuth2ServerConfig {  3 
 4     private static final String RESOURCE_ID = "wymlib";  5     //資源配置服務器
 6  @Configuration  7  @EnableResourceServer  8     @Order(110)  9     protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { 10 
11  @Override 12         public void configure(ResourceServerSecurityConfigurer resources) { 13             resources.resourceId(RESOURCE_ID).stateless(true); 14  } 15 
16  @Override 17         public void configure(HttpSecurity http) throws Exception { 18  http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) 19  .and() 20  .requestMatchers().anyRequest() 21  .and() 22  .anonymous() 23  .and() 24  .authorizeRequests() 25                 .antMatchers("/api/v1/**").authenticated();//配置訪問控制,必須認證過后才可以訪問
26  } 27  } 28 
29     //認證服務器
30  @Configuration 31  @EnableAuthorizationServer 32     @Order(99) 33     protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { 34 
35  @Autowired 36  AuthenticationManager authenticationManager; 37 // @Autowired 38 // RedisConnectionFactory redisConnectionFactory;
39         
40         @Value("${config.oauth2.clientID}") 41  String clientID; 42 
43         @Value("${config.oauth2.clientSecret}") 44  String clientSecret; 45 
46         @Value("${config.oauth2.accessTokenValiditySeconds}") 47         int accessTokenValiditySeconds; 48 
49  @Override 50         public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 51  clients.inMemory().withClient(clientID) 52  .resourceIds(RESOURCE_ID) 53                     .authorizedGrantTypes("client_credentials", "refresh_token") 54                     .scopes("select") 55                     .authorities("client") 56  .secret(clientSecret) 57  .accessTokenValiditySeconds(accessTokenValiditySeconds); 58  } 59 
60         /*@Override 61  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 62  endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory)) 63  .authenticationManager(authenticationManager); 64  }*/
65 
66  @Override 67         public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { 68             //允許表單認證
69  oauthServer.allowFormAuthenticationForClients(); 70  } 71 
72  } 73 
74 }

 

簡單說下spring security oauth2的認證思路。

  • client模式,沒有用戶的概念,直接與認證服務器交互,用配置中的客戶端信息去申請accessToken,客戶端有自己的client_id,client_secret對應於用戶的username,password,而客戶端也擁有自己的authorities,當采取client模式認證時,對應的權限也就是客戶端自己的authorities。

  • password模式,自己本身有一套用戶體系,在認證時需要帶上自己的用戶名和密碼,以及客戶端的client_id,client_secret。此時,accessToken所包含的權限是用戶本身的權限,而不是客戶端的權限。

我對於兩種模式的理解便是,如果你的系統已經有了一套用戶體系,每個用戶也有了一定的權限,可以采用password模式;如果僅僅是接口的對接,不考慮用戶,則可以使用client模式。

配置spring security

在spring security的版本迭代中,產生了多種配置方式,建造者模式,適配器模式等等設計模式的使用,spring security內部的認證flow也是錯綜復雜,在我一開始學習ss也產生了不少困惑,總結了一下配置經驗:使用了springboot之后,spring security其實是有不少自動配置的,我們可以僅僅修改自己需要的那一部分,並且遵循一個原則,直接覆蓋最需要的那一部分。這一說法比較抽象,舉個例子。比如配置內存中的用戶認證器。有兩種配置方式

 1 planA:  2 
 3 @Bean  4 protected UserDetailsService userDetailsService(){  5     InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();  6     manager.createUser(User.withUsername("user_1").password("123456").authorities("USER").build());  7     manager.createUser(User.withUsername("user_2").password("123456").authorities("USER").build());  8     return manager;  9 } 10 planB: 11 
12 @Configuration 13 @EnableWebSecurity 14 public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 15 
16  @Override 17     protected void configure(AuthenticationManagerBuilder auth) throws Exception { 18  auth.inMemoryAuthentication() 19                 .withUser("user_1").password("123456").authorities("USER") 20  .and() 21                 .withUser("user_2").password("123456").authorities("USER"); 22  } 23 
24  @Bean 25  @Override 26    public AuthenticationManager authenticationManagerBean() throws Exception { 27        AuthenticationManager manager = super.authenticationManagerBean(); 28         return manager; 29  } 30 }

 

你最終都能得到配置在內存中的兩個用戶,前者是直接替換掉了容器中的UserDetailsService,這么做比較直觀;后者是替換了AuthenticationManager,當然你還會在SecurityConfiguration 復寫其他配置,這么配置最終會由一個委托者去認證。如果你熟悉spring security,會知道AuthenticationManager和AuthenticationProvider以及UserDetailsService的關系,他們都是頂級的接口,實現類之間錯綜復雜的聚合關系…配置方式千差萬別,但理解清楚認證流程,知道各個實現類對應的職責才是掌握spring security的關鍵。

下面給出我最終的配置:

 1 @Configuration  2 @EnableWebSecurity  3 public class SecurityConfiguration extends WebSecurityConfigurerAdapter {  4 
 5  @Bean  6  @Override  7     protected UserDetailsService userDetailsService(){  8         InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();  9         manager.createUser(User.withUsername("user_1").password("123456").authorities("USER").build()); 10         manager.createUser(User.withUsername("user_2").password("123456").authorities("USER").build()); 11         return manager; 12  } 13 
14  @Override 15     protected void configure(HttpSecurity http) throws Exception { 16         // @formatter:off
17  http 18  .requestMatchers().anyRequest() 19  .and() 20  .authorizeRequests() 21                 .antMatchers("/oauth/*").permitAll(); 22         // @formatter:on
23  } 24 }

 

重點就是配置了一個UserDetailsService,和ClientDetailsService一樣,為了方便運行,使用內存中的用戶,實際項目中,一般使用的是數據庫保存用戶,具體的實現類可以使用JdbcDaoImpl或者JdbcUserDetailsManager。

 

xml配置:

 1 <!-- OAuth2 URL: /oauth/token 的處理與配置 一般使用時這里不需要修改, 直接使用即可 -->
 2     <sec:http pattern="/oauth/token" create-session="stateless"
 3         authentication-manager-ref="oauth2AuthenticationManager"
 4         entry-point-ref="oauth2AuthenticationEntryPoint" use-expressions="false">
 5         <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
 6         <sec:anonymous enabled="false" />
 7         <sec:http-basic entry-point-ref="oauth2AuthenticationEntryPoint" />
 8         <sec:custom-filter ref="clientCredentialsTokenEndpointFilter"
 9             before="BASIC_AUTH_FILTER" />
 10         <sec:access-denied-handler ref="oauth2AccessDeniedHandler" />
 11         <!-- <csrf disabled="true"/> -->
 12     </sec:http>
 13     
 14     
 15     
 16     <sec:authentication-manager id="oauth2AuthenticationManager">
 17         <sec:authentication-provider  18             user-service-ref="oauth2ClientDetailsUserService" />
 19     </sec:authentication-manager>
 20     
 21     
 22         <beans:bean id="oauth2ClientDetailsUserService"
 23         class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
 24         <beans:constructor-arg ref="clientDetailsService" />
 25         </beans:bean>
 26         
 27         <!-- 管理 ClientDetails -->
 28         <beans:bean id="clientDetailsService"
 29             class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService">
 30             <beans:constructor-arg index="0" ref="dataSource" />
 31         </beans:bean>
 32         
 33     <beans:bean id="oauth2AuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint" />
 34         
 35     <!-- 處理grant_type=client_credentials 的邏輯 只從請求中獲取client_id與client_secret -->
 36     <beans:bean id="clientCredentialsTokenEndpointFilter"
 37         class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
 38         <beans:property name="authenticationManager" ref="oauth2AuthenticationManager" />
 39     </beans:bean>
 40 
 41     
 42     
 43 
 44 攔截api/v1:  45 
 46     <sec:http pattern="/api/v1/**" create-session="never"
 47         entry-point-ref="oauth2AuthenticationEntryPoint"
 48         access-decision-manager-ref="oauth2AccessDecisionManager"
 49         use-expressions="false">
 50         <sec:anonymous enabled="false" />
 51         <sec:intercept-url pattern="/api/v1/**"
 52             access="IS_AUTHENTICATED_ANONYMOUSLY" />
 53         <sec:custom-filter ref="unityResourceServer" before="PRE_AUTH_FILTER" />
 54         <sec:access-denied-handler ref="oauth2AccessDeniedHandler" />
 55     </sec:http>
 56     
 57     <!-- 擴展Spring Security 默認的 AccessDecisionManager 添加對OAuth中 scope 的檢查與校驗 -->
 58     <beans:bean id="oauth2AccessDecisionManager"
 59         class="org.springframework.security.access.vote.UnanimousBased">
 60         <beans:constructor-arg>
 61             <beans:list>
 62                 <beans:bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
 63                 <beans:bean class="org.springframework.security.access.vote.RoleVoter" />
 64                 <beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
 65             </beans:list>
 66         </beans:constructor-arg>
 67     </beans:bean>    
 68     
 69     <!-- 每一個資源(resource)的定義, resource-id必須唯一, OauthClientDetails中的resourceIds屬性的值由此來的,  70         允許一個Client有多個resource-id, 由逗號(,)分隔 每一個定義會在Security Flow中添加一個位於 PRE_AUTH_FILTER  71         之前的Filter -->
 72     <!--unity resource server filter -->
 73     <oauth2:resource-server id="unityResourceServer"
 74         resource-id="unity-resource" token-services-ref="tokenServices" />
 75 
 76     <beans:bean id="tokenServices"
 77         class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
 78         <beans:property name="tokenStore" ref="tokenStore" />
 79         <beans:property name="clientDetailsService" ref="clientDetailsService" />
 80         <beans:property name="supportRefreshToken" value="true" />
 81         <beans:property name="accessTokenValiditySeconds"    value="30" />
 82     </beans:bean>
 83     
 84     <!--Config token services -->
 85     <!--<beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore"/> -->
 86     <beans:bean id="tokenStore"
 87         class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
 88         <beans:constructor-arg index="0" ref="dataSource" />
 89     </beans:bean>
 90 
 91 
 92 
 93 <!-- Security OAuth Flow的核心配置 每一個配置對應一類具體的grant_type 可根據需求刪除或禁用, 如: <oauth2:implicit  94         disabled="true"/> 默認支持OAuth2提供的5類grant_type, 若不需要任何一類, 將其配置注釋掉(或刪掉)即可. 若需要自定義  95         authorization url, 在 <oauth2:authorization-server > 配置中添加authorization-endpoint-url,如:  96         authorization-endpoint-url="/oauth2/authorization" 若需要自定義 token url, 在 <oauth2:authorization-server  97         > 配置中添加token-endpoint-url配置, 如:token-endpoint-url="/oauth2/my_token" -->
 98     <oauth2:authorization-server  99         client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
100         user-approval-handler-ref="oauthUserApprovalHandler"
101         user-approval-page="oauth_approval" error-page="oauth_error">
102         <oauth2:authorization-code authorization-code-services-ref="jdbcAuthorizationCodeServices" />
103         <oauth2:implicit />
104         <oauth2:refresh-token />
105         <oauth2:client-credentials />
106         <oauth2:password />
107     </oauth2:authorization-server>
108 
109     
110     <beans:bean id="oauthUserApprovalHandler"
111         class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">
112         <beans:property name="tokenStore" ref="tokenStore" />
113         <beans:property name="clientDetailsService" ref="clientDetailsService" />
114         <beans:property name="requestFactory" ref="oAuth2RequestFactory" />
115         <!-- <beans:property name="oauthService" ref="oauthService"/> -->
116     </beans:bean>
117 
118     <!-- 管理 Authorization code -->
119     <beans:bean id="jdbcAuthorizationCodeServices"
120         class="org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices">
121         <beans:constructor-arg index="0" ref="dataSource" />
122     </beans:bean>
123 
124     
125     
126 請求token的url: 127 http://192.9.8.144/ymlib/oauth/token?client_id=aa086e67c36342ed9ab6519247f5b68b&client_secret=mQDUMa03Rdy5vcMWjYHJmitkWJi3Rosr&grant_type=client_credentials

 

參考資料:

從零開始的Spring Security Oauth2(一)

從零開始的Spring Security Oauth2(二)

從零開始的Spring Security Oauth2(三)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM