spring security oauth2 password受權模式

前面的一篇文章講了spring security oauth2的client credentials受權模式,通常用於跟用戶無關的,開放平臺api認證相關的受權場景。本文主要講一下跟用戶相關的受權模式之一password模式。html

回顧四種模式

OAuth 2.0定義了四種受權方式。java

  • 受權碼模式(authorization code)
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)(主要用於api認證,跟用戶無關)

maven

<dependency>
			<groupId>org.springframework.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
複製代碼

配置

security config

支持password模式要配置AuthenticationManagergit

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//須要正常運行的話,須要取消這段註釋,緣由見下面小節
//    @Override
//    public void configure(HttpSecurity http) throws Exception {
//        http.csrf().disable();
//        http.requestMatchers().antMatchers("/oauth/**")
//                .and()
//                .authorizeRequests()
//                .antMatchers("/oauth/**").authenticated();
//    }

    //配置內存模式的用戶
    @Bean
    @Override
    protected UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("demoUser1").password("123456").authorities("USER").build());
        manager.createUser(User.withUsername("demoUser2").password("123456").authorities("USER").build());
        return manager;
    }

    /**
     * 須要配置這個支持password模式
     * support password grant type
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
複製代碼

這個是比client credentials模式新增的配置,主要配置用戶以及authenticationManagergithub

auth server配置

@Configuration
@EnableAuthorizationServer //提供/oauth/authorize,/oauth/token,/oauth/check_token,/oauth/confirm_access,/oauth/error
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
                .tokenKeyAccess("permitAll()") //url:/oauth/token_key,exposes public key for token verification if using JWT tokens
                .checkTokenAccess("isAuthenticated()") //url:/oauth/check_token allow check token
                .allowFormAuthenticationForClients();
    }
    
    /**
     * 注入authenticationManager
     * 來支持 password grant type
     */
    @Autowired
    private AuthenticationManager authenticationManager;

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

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("demoApp")
                .secret("demoAppSecret")
                .authorizedGrantTypes("client_credentials", "password", "refresh_token")
                .scopes("all")
                .resourceIds("oauth2-resource")
                .accessTokenValiditySeconds(1200)
                .refreshTokenValiditySeconds(50000);
}
複製代碼

這裏記得注入authenticationManager來支持password模式web

不然報錯以下spring

➜  ~ curl -i -X POST -d "username=demoUser1&password=123456&grant_type=password&client_id=demoApp&client_secret=demoAppSecret" http://localhost:8080/oauth/token
HTTP/1.1 400
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
X-Application-Context: application
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 03 Dec 2017 07:01:27 GMT
Connection: close

{"error":"unsupported_grant_type","error_description":"Unsupported grant type: password"}
複製代碼

resource server 配置

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

//    /**
//      * 要正常運行,須要反註釋掉這段,具體緣由見下面分析
//     * 這裏設置須要token驗證的url
//     * 這些須要在WebSecurityConfigurerAdapter中排查掉
//     * 不然優先進入WebSecurityConfigurerAdapter,進行的是basic auth或表單認證,而不是token認證
//     * @param http
//     * @throws Exception
//     */
//    @Override
//    public void configure(HttpSecurity http) throws Exception {
//        http.requestMatchers().antMatchers("/api/**")
//                .and()
//                .authorizeRequests()
//                .antMatchers("/api/**").authenticated();
//    }


}
複製代碼

驗證

請求token

curl -i -X POST -d "username=demoUser1&password=123456&grant_type=password&client_id=demoApp&client_secret=demoAppSecret" http://localhost:8080/oauth/token
複製代碼

返回json

HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
X-Application-Context: application
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 03 Dec 2017 04:53:40 GMT

{"access_token":"4cfb16f9-116c-43cf-a8d4-270e824ce5d7","token_type":"bearer","refresh_token":"8e9bfbda-77e5-4d97-b061-4e319de7eb4a","expires_in":1199,"scope":"all"}
複製代碼

攜帶token訪問資源

curl -i http://localhost:8080/api/blog/1\?access_token\=4cfb16f9-116c-43cf-a8d4-270e824ce5d7
複製代碼

或者api

curl -i -H "Accept: application/json" -H "Authorization: Bearer 4cfb16f9-116c-43cf-a8d4-270e824ce5d7" -X GET http://localhost:8080/api/blog/1
複製代碼

返回bash

HTTP/1.1 302
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Set-Cookie: JSESSIONID=F168A54F0F3C3D96A053DB0CFE129FBF; Path=/; HttpOnly
Location: http://localhost:8080/login
Content-Length: 0
Date: Sun, 03 Dec 2017 05:20:19 GMT
複製代碼

出錯緣由見下一小結app

成功返回

HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
X-Application-Context: application
Content-Type: text/plain;charset=UTF-8
Content-Length: 14
Date: Sun, 03 Dec 2017 06:39:24 GMT

this is blog 1
複製代碼

錯誤返回

HTTP/1.1 401
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Cache-Control: no-store
Pragma: no-cache
WWW-Authenticate: Bearer realm="oauth2-resource", error="invalid_token", error_description="Invalid access token: 466ee845-2d08-461b-8f62-8204c47f652"
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 03 Dec 2017 06:39:28 GMT

{"error":"invalid_token","error_description":"Invalid access token: 466ee845-2d08-461b-8f62-8204c47f652"}
複製代碼

WebSecurityConfigurerAdapter與ResourceServerConfigurerAdapter

兩者都有針對http security的配置,他們的默認配置以下

WebSecurityConfigurerAdapter

spring-security-config-4.2.3.RELEASE-sources.jar!/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java

@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
		WebSecurityConfigurer<WebSecurity> {
		//......
protected void configure(HttpSecurity http) throws Exception {
		logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin().and()
			.httpBasic();
	}

	//......
}	
複製代碼

能夠看到WebSecurityConfigurerAdapter的order是100

ResourceServerConfigurerAdapter

spring-security-oauth2-2.0.14.RELEASE-sources.jar!/org/springframework/security/oauth2/config/annotation/web/configuration/ResourceServerConfigurerAdapter.java

public void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests().anyRequest().authenticated();
	}
複製代碼

它的order是SecurityProperties.ACCESS_OVERRIDE_ORDER - 1 spring-boot-autoconfigure-1.5.5.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerProperties.java

/**
	 * The order of the filter chain used to authenticate tokens. Default puts it after
	 * the actuator endpoints and before the default HTTP basic filter chain (catchall).
	 */
	private int filterOrder = SecurityProperties.ACCESS_OVERRIDE_ORDER - 1;
複製代碼

因而可知WebSecurityConfigurerAdapter的攔截要優先於ResourceServerConfigurerAdapter

兩者關係

  • WebSecurityConfigurerAdapter用於保護oauth相關的endpoints,同時主要做用於用戶的登陸(form login,Basic auth)
  • ResourceServerConfigurerAdapter用於保護oauth要開放的資源,同時主要做用於client端以及token的認證(Bearer auth)

所以兩者是分工協做的

  • 在WebSecurityConfigurerAdapter不攔截oauth要開放的資源
@Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.requestMatchers().antMatchers("/oauth/**")
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").authenticated();
    }
複製代碼
  • 在ResourceServerConfigurerAdapter配置須要token驗證的資源
@Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers("/api/**")
                .and()
                .authorizeRequests()
                .antMatchers("/api/**").authenticated();
    }
複製代碼

這樣就大功告成

doc

相關文章
相關標籤/搜索