使用Spring Security OAuth 實現OAuth 2.0 授權

liuyatao發表於2018-01-12

OAuth 2.0 簡介

OAuth 2.0是一種工業級的授權協議。OAuth 2.0是從建立於2006年的OAuth 1.0繼承而來的。OAuth 2.0致力於幫助開發者簡化授權併為web應用、桌面應用、移動應用、嵌入式應用提供具體的授權流程。

OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 supersedes the work done on the original OAuth protocol created in 2006. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices.

OAuth 2.0的四個角色

為了方便理解,以常用的使用微信登入為例

  • Resource Owner

    資源擁有者,對應微信的每個使用者微信上設定的個人資訊是屬於每個使用者的,不屬於騰訊。

  • Resource Server

    資源伺服器,一般就是使用者資料的一些操作(增刪改查)的REST API,比如微信的獲取使用者基本資訊的介面。

  • Client Application

    第三方客戶端,對比微信中就是各種微信公眾號開發的應用,第三方應用經過認證伺服器授權後即可訪問資源伺服器的REST API來獲取使用者的頭像、性別、地區等基本資訊。

  • Authorization Server

    認證伺服器,驗證第三方客戶端是否合法。如果合法就給客戶端頒佈token,第三方通過token來呼叫資源伺服器的API。

四種授權方式(Grant Type)

  • anthorization_code

    授權碼型別,適用於Web Server Application。模式為:客戶端先呼叫/oauth/authorize/進到使用者授權介面,使用者授權後返回code,客戶端然後根據code和appSecret獲取access token

  • implicit 簡化型別,相對於授權碼型別少了授權碼獲取的步驟。客戶端應用授權後認證伺服器會直接將access token放在客戶端的url。客戶端解析url獲取token。這種方式其實是不太安全的,可以通過https安全通道縮短access token的有效時間來較少風險。

  • password

    密碼型別,客戶端應用通過使用者的username和password獲access token。適用於資源伺服器、認證伺服器與客戶端具有完全的信任關係,因為要將使用者要將使用者的使用者名稱密碼直接傳送給客戶端應用,客戶端應用通過使用者傳送過來的使用者名稱密碼獲取token,然後訪問資源伺服器資源。比如支付寶就可以直接用淘寶使用者名稱和密碼登入,因為它們屬於同一家公司,彼此充分信任

  • client_credentials

    客戶端型別,是不需要使用者參與的一種方式,用於不同服務之間的對接。比如自己開發的應用程式要呼叫簡訊驗證碼服務商的服務,呼叫地圖服務商的服務、呼叫手機訊息推送服務商的服務。當需要呼叫服務是可以直接使用服務商給的appIDappSecret來獲取token,得到token之後就可以直接呼叫服務。

其他概念

  • scope:訪問資源伺服器的哪些作用域。
  • refresh token:當access token 過期後,可以通過refresh token重新獲取access token。

實現

有的時候資源伺服器和認證伺服器是兩個不同的應用,有的時候資源伺服器和認證伺服器在通一個應用中,不同之處在於資源伺服器是否需要檢查token的有效性,前者需要檢查,後者不需要。這裡實現後者。

Application的安全配置

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .and().csrf().disable()
                .authorizeRequests().anyRequest().authenticated();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("lyt").password("lyt").authorities("ROLE_USER")
                .and().withUser("admin").password("admin").authorities("ROLE_ADMIN");
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
複製程式碼

認證伺服器配置

@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfiguration  extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("client")
                .scopes("read","write")
                .secret("secret")
                .authorizedGrantTypes("authorization_code","password","implicit","client_credentials");}

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
       endpoints.authenticationManager(authenticationManager);
    }

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;
}

複製程式碼

資源伺服器配置

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableResourceServer
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.antMatcher("/oauth2/api/**").authorizeRequests()
			.antMatchers(HttpMethod.GET, "/read/**").access("#oauth2.hasScope('read')")
			.antMatchers(HttpMethod.POST, "/write/**").access("#oauth2.hasScope('write')")
			.antMatchers(HttpMethod.PUT, "/write/**").access("#oauth2.hasScope('write')")
			.antMatchers(HttpMethod.DELETE, "/write/**").access("#oauth2.hasScope('write')");
	}

}

複製程式碼

資源伺服器filter-order設定

需要在application.yml中將filter-order設定成3,具體原因參考 連結

防止cookie衝突

為了避免認證伺服器的cookie和客戶端的cookie衝突,出現錯誤,最好修改cookie name 或者設定contextPath

測試

postman中提供OAuth 2.0的認證方式,可以獲取到token之後再把認證加入http請求中,即可請求資源伺服器的REST API

  • 客戶端資訊

輸入圖片說明
客戶端資訊

  • 授權

輸入圖片說明
授權

  • 獲取的token

輸入圖片說明
獲取的token

  • 訪問資源伺服器API

輸入圖片說明
訪問資源伺服器API

最後

測試程式碼github地址。有興趣可以關注微信公眾賬號獲取最新推送文章。

輸入圖片說明
歡迎關注微信公眾賬號

相關文章