OAuth2保護微服務 案例實戰

OAuth2保護微服務 案例實戰

前言

不得不吐槽一下,一些經典書籍的案例 真的已經過時了,首先依賴包不適用,案例不正確,代碼欠缺,而且很多關鍵位置的代碼並沒有貼出來,這對於許多不僅僅是看書,也想動手敲一敲案例的學習者來說很不友好!!一度都想跳過這一章,記錄使用使用OAuth2+SpringSecurity+SpringCloud時遇到的一系列坑。

參照《Spring微服務實戰》

1. 依賴包問題

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

注意起步依賴 spring-cloud-starter···,原書中的spring-cloud-···真的讓我吐了,一直懷疑可能是因爲我版本的問題,查了很久之後發現,確實是包的問題。

工程結構如圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-O9df0GMr-1591115573053)(C:\Users\a\AppData\Roaming\Typora\typora-user-images\1591104088075.png)]

2.服務引導類Application

@SpringBootApplication
@RestController
@EnableResourceServer
@EnableAuthorizationServer //OAuth2服務
public class OAuth2Application {
    @GetMapping(value = "/auth/user",produces = "application/json")
    public Map<String,Object> user(OAuth2Authentication user){
        Map<String,Object> userInfo=new HashMap<>();
        userInfo.put("user",user.getUserAuthentication().getPrincipal());
        userInfo.put("authorities", AuthorityUtils.authorityListToSet(user.getUserAuthentication().getAuthorities()));
        return userInfo;
    }


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

OAuth2服務的引導類,除了通過註解聲明該服務是OAuth2服務外,添加了一個 /user 路徑映射,當有客戶端服務訪問由OAuth2保護的服務時會調用這個端點。(說直白點就是,客戶端服務拿着授權令牌Authorization時,對這個Authorization進行解析,回返回對應的user和authorities)。

此類在原書中仍然是個坑,在指向驗證服務時,原書裏的userInfoUri:/auth/user,而原書裏的啓動類卻是RequestMapping("/user"),說是會映射到/auth/user路徑上去,然而我在試驗時,並不是這樣,而是要通過/user纔可以,這不是給自己添麻煩嗎?! (我猜測本書中可能有某個地方設置一個類似路徑prefix這樣的東西,因爲其實所有的請求路徑都是 ip+port/auth/···· 然而 它實際的編碼裏 並沒有添加/auth )

3.配置類OAuth2

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory() //指定爲內存存儲 jdbc存儲或內存存儲
                .withClient("orgFeign") //註冊的應用程序名稱
                .secret("thisissecret") //密鑰
                .authorizedGrantTypes(  //授權類型
                        "password",
                        "client_credentials"
                ).scopes("webclient","mobileclient"); //作用域
        //定義驗證服務註冊的客戶端應用程序
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
//                .tokenKeyAccess("permitAll()")  // auth/oauth/token_key是公開的
//                .checkTokenAccess("permitAll()") // auth/oauth/check_token是公開的
//                .allowFormAuthenticationForClients() //表單驗證(申請令牌)
                .passwordEncoder(NoOpPasswordEncoder.getInstance());
    }

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

該類定義了哪些應用程序可以訪問由OAuth2保護的服務,重點關注覆寫的兩個configure方法。

第一個configure中,定義了哪些客戶端註冊到OAuth2中,withClient()和secret()是客戶端應用程序獲取OAuth2訪問令牌的名稱和密鑰,authorizedGrantTypes()爲授權類型列表 這裏指定爲密碼類型,scopes爲作用域,爲自定義的內容 可以用來做細粒度的授權規則。

第二個configure中,有三行註釋掉的代碼,其實這是我出現401時參照別人的解決方案,但實際無濟於事,我401的時候加了還是401,我解決正常運行後註釋掉也不影響它照常工作。

4. 配置類Security

與上一個配置類不同,上一個是配置OAuth2相關的,這個配置類是配置SpringSecurity的用戶id 密碼和角色。與單獨使用SpringSecurity進行權限控制資源保護的基本一樣。

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    @Bean
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance())
                .withUser("jam").password("123456").roles("USER")
                .and()
                .withUser("yang").password("123456").roles("USER","ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .anonymous().disable()
                .authorizeRequests()
                .antMatchers("/oauth/token").permitAll();
    }
}

同樣重點也是兩個configure方法,本文的例子中爲了方便選擇了一個棄用的密碼編碼器NoOpPasswordEncoder,官方已不推薦使用,第一個configure方法採用基於內存的方式註冊了兩個用戶jam和yang;第二個configure方法纔是解決了我之前出現401的關鍵步驟之一!!!原書中並沒有提到需放通 /oauth/token 這一路徑,實屬有點坑人。

以上就可以嘗試起通過OAuth2服務來獲取令牌以及驗證授權。

5. Postman測試

在這裏插入圖片描述

首先 是這裏的路徑問題 原書中是 ip:port/auth/oauth/token 同理,用加上auth之後仍然是不行的,加上前面Security配置類上我們放通的是 /oauth/token(只需要放通,背後映射做的處理由OAtuh2幫我們完成),所以正確的路徑如圖,需要選擇Authorization的Type爲Basic Auth,右邊有兩個參數要填寫,這裏的Username和Password分別對應OAuth2配置類的withClient()和secret()

在這裏插入圖片描述

除了Authorization之外,還需要寫入對應的參數,這裏需要在body裏選擇表單數據 form-data,然後分別寫grant_type授權類型,scope作用域,username和password(註冊的用戶的用戶名密碼),前兩個參數參見 3.OAtuh2配置類,用戶名密碼是Security配置類上註冊的。這裏有兩個 註釋掉的參數 client_id 和client_secret 這是一些博客上解決訪問/auth/token報401的方案,但是我這裏行不通,所以我把它注掉了。

結果可以看到 access_token和token_type 此爲OAuth2頒發的令牌,而我們可以拿着這個令牌去OAuth2服務器驗證權限,還記得2.的引導類中的那個地址映射嗎?可以在HTTP頭部攜帶這個令牌去驗證。

在這裏插入圖片描述

至此,授權令牌的頒發與驗證就已經完成了。

6. 配置被保護的服務

// 本文以一個 OrgnizationApplication-9001爲例

6.1 yaml與引導類

server:
  port: 9001

security:
  oauth2:
    resource:
      userInfoUri: http://localhost:4396/auth/user
@SpringBootApplication
@EnableResourceServer  //註解爲受OAuth2保護的資源
@EnableEurekaClient
public class OrganizationApplication9001 {
    public static void main(String[] args){
        SpringApplication.run(OrganizationApplication9001.class,args);
    }
}

@EnableResourceServer 會強制執行一個Filter,會對所有到達改服務的請求執行Filter檢查傳入的HTTP首部是否存在OAuth2訪問令牌,然後通過回調yaml中的uri來查看令牌是否有效。

6.2 測試的Controller

@RestController
public class OrganizationController {
    @GetMapping("/organization")
    public String getMsg(){
        return "This is organization  9001";
    }
}

6.2 定義訪問規則

@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers(HttpMethod.GET,"/organization")
                .hasRole("ADMIN")
                .anyRequest()
                .authenticated();
    }
}

這裏定義了 /organization 資源路徑的規則,需要ADMIN角色,(本文中的兩個用戶jam爲user角色,而yang爲user和admin角色),所以可以通過分別向OAuth獲取這兩個用戶的access_token 然後訪問這個服務 加以驗證。

6.3 訪問被保護的服務

如圖,這個截圖例子測試的是jam用戶也即user角色的結果,會發現403拒絕訪問,達到了預期的效果,yang用戶即admin角色可以自行測試,是可以正常放通進行訪問的。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章