Oauth2認證模式之授權碼模式實現


Oauth2認證模式之授權碼模式(authorization code)

本示例實現了Oauth2之授權碼模式,授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的后台服務器,與"服務提供商"的認證服務器進行互動。

閱讀本示例之前,你需要先有以下兩點基礎:

  • 需要對spring security有一定的配置使用經驗,用戶認證這一塊,spring security oauth2建立在spring security的基礎之上
  • oauth2開放授權標准基礎,可以穩步到OAuth2 詳解,瀏覽下授權碼模式,理解下基本概念

概述

實現 oauth2,可以簡易的分為三個步驟

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

代碼實現

1.pom.xml添加maven依賴

    <dependencies>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

2.配置資源服務器

public class ResourceServerConfig {

    private static final String RESOURCE_ID = "account";

    @Configuration
    @EnableResourceServer()
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(RESOURCE_ID).stateless(true);
        }

        @Override
        public void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity
                    .requestMatchers()
                    // 保險起見,防止被主過濾器鏈路攔截
                    .antMatchers("/account/**").and()
                    .authorizeRequests().anyRequest().authenticated()
                    .and()
                    .authorizeRequests()
                    .antMatchers("/account/info/**").access("#oauth2.hasScope('get_user_info')")
                    .antMatchers("/account/child/**").access("#oauth2.hasScope('get_childlist')");
        }
    }
}

3.配置認證服務器

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client1")
                .resourceIds(RESOURCE_ID)
                .authorizedGrantTypes("authorization_code", "refresh_token", "implicit")
                .authorities("ROLE_CLIENT")
                .scopes("get_user_info", "get_childlist")
                .secret("secret")
                .redirectUris("http://localhost:8081/client/account/redirect")
                .autoApprove(true)
                .autoApprove("get_user_info");
    }

4.配置spring security

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 創建兩個內存用戶
        manager.createUser(User.withUsername("admin").password("123456").authorities("USER").build());
        manager.createUser(User.withUsername("lin").password("123456").authorities("USER").build());
        return manager;
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 密碼生成器(默認為bcrypt模式)
     *
     * @return
     */
//    @Bean
//    PasswordEncoder passwordEncoder() {
//        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
//    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity.
                requestMatchers()
                // 必須登錄過的用戶才可以進行 oauth2 的授權碼申請
                .antMatchers("/", "/home", "/login", "/oauth/authorize")
                .and()
                .authorizeRequests()
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/login")
                .and()
                .httpBasic()
                .disable()
                .exceptionHandling()
                .accessDeniedPage("/login?authorization_error=true")
                .and()
                .csrf()
                .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
                .disable();
    }
}

使用介紹

  • 找到AuthResServerApplication.java運行server服務,默認端口:8080
  • 找到ClientApplication.java運行client客戶端,端口:8081

1.嘗試直接訪問用戶信息

http://localhost:8080/account/info/testAccount1/

返回未授權錯誤

<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>

2.嘗試獲取授權碼

http://localhost:8080/oauth/authorize?client_id=client1&response_type=code&redirect_uri=http://localhost:8081/client/account/redirect

結果被主過濾器攔截,302 跳轉到登錄頁,因為 /oauth/authorize 端點是受保護的端點,必須登錄的用戶才能申請 code。

3.輸入用戶名和密碼

輸入用戶名和密碼 admin 123456
如上用戶名密碼是交給 SpringSecurity 的主過濾器用來認證的

4.登錄成功后,真正進行授權碼的申請

oauth/authorize 認證成功,會根據 redirect_uri 執行 302 重定向,並且帶上生成的 code,注意重定向到的是 8001 端口,這個時候已經是另外一個應用了。

localhost:8081/client/account/redirect?code=xxxx
代碼中封裝了一個 http 請求,使得 client1 使用 restTemplate 向 server 發送 token 的申請,當然是使用 code 來申請的,並最終成功獲取到 access_token

{
access_token: "59a25558-f714-4ca8-aa87-c36f93c120bf",
token_type: "bearer",
refresh_token: "92436849-7ef7-4923-8270-5a2c9b464556",
expires_in: 43199,
scope: "get_user_info get_childlist"
}

5.攜帶 access_token 訪問account信息

http://localhost:8080/account/info/testAccount1?access_token=59a25558-f714-4ca8-aa87-c36f93c120bf

6.正常返回信息

{
name: "testAccount1",
nickName: "測試用戶1",
remark: "備注1",
childAccount: [
{
name: "testChild1_0",
nickName: "測試子用戶1_0",
remark: "0",
childAccount: null
},
{
name: "testChild1_1",
nickName: "測試子用戶1_1",
remark: "1",
childAccount: null
},
{
name: "testChild1_2",
nickName: "測試子用戶1_2",
remark: "2",
childAccount: null
},
{
name: "testChild1_3",
nickName: "測試子用戶1_3",
remark: "3",
childAccount: null
},
{
name: "testChild1_4",
nickName: "測試子用戶1_4",
remark: "4",
childAccount: null
},
{
name: "testChild1_5",
nickName: "測試子用戶1_5",
remark: "5",
childAccount: null
},
{
name: "testChild1_6",
nickName: "測試子用戶1_6",
remark: "6",
childAccount: null
},
{
name: "testChild1_7",
nickName: "測試子用戶1_7",
remark: "7",
childAccount: null
},
{
name: "testChild1_8",
nickName: "測試子用戶1_8",
remark: "8",
childAccount: null
},
{
name: "testChild1_9",
nickName: "測試子用戶1_9",
remark: "9",
childAccount: null
}
]
}

資料


免責聲明!

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



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