使用 spring-security-oauth2 體驗 OAuth 2.0 的四種授權模式

2022-07-10 21:00:41

背景

一直對OAuth 2.0的四種授權模式比較好奇,瞭解的僅限網上的資料,沒有使用程式碼體驗過,這次使用spring-security-oauth2來體驗這四種模式的整個過程。

相關程式碼

pom檔案

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.0.16.RELEASE</version>
            <exclusions>
                <exclusion>
                    <artifactId>spring-core</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-context</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-beans</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-security-core</artifactId>
                    <groupId>org.springframework.security</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>26.0-jre</version>
        </dependency>
    </dependencies>
設定類

@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfigurerAdapter extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient("clientUser")
                .secret("{bcrypt}" + new BCryptPasswordEncoder().encode("123456"))
                .authorizedGrantTypes("authorization_code", "implicit", "password", "client_credentials");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        UserDetails userDetails = User.withUsername("username")
                .password("{bcrypt}" + new BCryptPasswordEncoder().encode("password"))
                .roles("123")
                .build();
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(userDetails);
        daoAuthenticationProvider.setUserDetailsService(inMemoryUserDetailsManager);
        AuthenticationManager authenticationManager = new ProviderManager(
                Lists.<AuthenticationProvider>newArrayList(daoAuthenticationProvider));
        endpoints.authenticationManager(authenticationManager);
    }
}

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(
            SecurityProperties properties) {
        SecurityProperties.User user = properties.getUser();
        List<String> roles = user.getRoles();
        return new InMemoryUserDetailsManager(User.withUsername("user")
                .password("{bcrypt}" + new BCryptPasswordEncoder().encode("123456"))
                .roles(StringUtils.toStringArray(roles)).build());
    }
}
啟動類

@SpringBootApplication(
        exclude = UserDetailsServiceAutoConfiguration.class
        // excludeName = "org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration"
)
public class SpringSecurityStudyApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityStudyApplication.class, args);
    }
}

授權碼模式

第一步 存取GET /oauth/authorize

相關程式碼在org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint
org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint

請求引數和返回結果如下:

返回結果在瀏覽器上展示的話,是讓使用者來勾選是否同意授權的一個頁面,還有返回結果的_csrf的值要作為第二步的引數。

curl如下:

curl --location --request GET 'http://127.0.0.1:8090/oauth/authorize?response_type=code&client_id=clientUser&redirect_uri=https://www.baidu.com/&scope=scope' \
--header 'Authorization: Basic dXNlcjoxMjM0NTY=' \
--header 'Cookie: JSESSIONID=AB254815273DB81F1F3BAF74E94DAAB6'

第二步 存取POST /oauth/authorize

相關程式碼在org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint

crul如下:

curl --location --request POST 'http://127.0.0.1:8090/oauth/authorize?user_oauth_approval=true&scope.scope=true&_csrf=a95516db-6ce2-4033-9b81-1060b6c4d829' \
--header 'Cookie: JSESSIONID=73E846796ACB7818E09B93AC4CFD320D'

_csrf 要使用第一步返回的結果,在返回頭的Location裡可以得到授權碼

第一個引數必須要有,因為:

<input name="user_oauth_approval" value="true" type="hidden"/>

@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model, SessionStatus sessionStatus, Principal principal) {
}

public static final String USER_OAUTH_APPROVAL = "user_oauth_approval";

第二個引數是使用者是否同意授權

第三步 存取POST /oauth/token

相關程式碼在org.springframework.security.oauth2.provider.endpoint.TokenEndpoint

code 使用第二步的返回結果

crul如下:

curl --location --request POST 'http://127.0.0.1:8090/oauth/token' \
--header 'Authorization: Basic Y2xpZW50VXNlcjoxMjM0NTY=' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: JSESSIONID=5D41BF01BC875BDF266D3C2178537F21' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=1pakV1' \
--data-urlencode 'redirect_uri=https://www.baidu.com/' \
--data-urlencode 'client_id=clientUser' \
--data-urlencode 'scope=scope'

簡化模式

第一步 存取GET /oauth/authorize

crul如下:

curl --location --request GET 'http://127.0.0.1:8090/oauth/authorize?response_type=token&client_id=clientUser&redirect_uri=https://www.baidu.com/&scope=scope' \
--header 'Authorization: Basic dXNlcjoxMjM0NTY=' \
--header 'Cookie: JSESSIONID=6AD429F6CF30C10C0E9F1A35EC78A790'

第二步 存取POST /oauth/authorize


crul如下:

curl --location --request POST 'http://127.0.0.1:8090/oauth/authorize?user_oauth_approval=true&scope.scope=true&_csrf=1ba6be5e-845f-47f2-9680-db613adc47c7' \
--header 'Cookie: JSESSIONID=6AD429F6CF30C10C0E9F1A35EC78A790'

密碼模式

直接存取POST /oauth/token

curl如下:

curl --location --request POST 'http://127.0.0.1:8090/oauth/token' \
--header 'Authorization: Basic Y2xpZW50VXNlcjoxMjM0NTY=' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: JSESSIONID=7E149951AB7D3C03E31E21450754DAAE' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=username' \
--data-urlencode 'scope=scope' \
--data-urlencode 'password=password'

使用者端模式

直接存取POST /oauth/token

curl如下:

curl --location --request POST 'http://127.0.0.1:8090/oauth/token' \
--header 'Authorization: Basic Y2xpZW50VXNlcjoxMjM0NTY=' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: JSESSIONID=7E149951AB7D3C03E31E21450754DAAE' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=scope'

參考

理解OAuth 2.0 - 阮一峰