死磕 OAuth2,教練我要學全套的!

昨天松哥和小夥伴們說了 OAuth2 中的授權碼模式,我從頭到尾寫了一個非常詳細的案例,來和小夥伴們分享授權碼模式的使用。

有小夥伴表示為什麼沒有另外三種授權模式的演示代碼?要學就學全套的!這不,松哥趕緊把另外三個授權模式的代碼整出來,供小夥伴們參考。

今天的案例,我就不從頭開始寫了,我們就在上篇文章代碼的基礎上修改就行了,如果小夥伴們還沒看過本系列前面幾篇文章,建議一定先看下,否則本文可能看不懂:

  • 做微服務繞不過的 OAuth2,松哥也來和大家扯一扯
  • 這個案例寫出來,還怕跟面試官扯不明白 OAuth2 登錄流程?

接下來松哥直接上代碼,各種授權模式的流程我就不再重複介紹了,大家可以參考本系列第一篇文章。

對了,文末依然可以下載本文源代碼。

1.簡化模式

要支持簡化模式,其實很簡單。

首先,我們在授權服務器中,增加如下配置表示支持簡化模式:

<code>@Override 

public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("javaboy")
            .secret(new BCryptPasswordEncoder().encode("123"))
            .resourceIds("res1")
            .authorizedGrantTypes("refresh_token","implicit")
            .scopes("all")
            .redirectUris("http://localhost:8082/index.html");
}/<code>

注意,我們只需要在 authorizedGrantTypes 中增加 implicit 表示支持簡化模式即可。

配置完成後,重啟 auth-server。

接下來我們配置資源服務器。因為簡化模式沒有服務端,我們只能通過 js 來請求資源服務器上的數據,所以資源服務器需要支持跨域,我們修改如下兩個地方使之支持跨域:

<code>@RestController
@CrossOrigin(value = "*")
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
    @GetMapping("/admin/hello")
    public String admin() {
        return "admin";
    }
}/<code>

首先在 Controller 上添加 @CrossOrigin 註解使之支持跨域,然後配置 Spring Security 使之支持跨域:

<code>@Override
public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("admin")
            .anyRequest().authenticated()
            .and()

            .cors();
}/<code>

配置完成後,重啟 user-server。

接下來我們來配置第三方應用:

首先我們修改 index.html 頁面:

<code>
你好,江南一點雨!





/<code>

還是之前的超鏈接不變,但是我們將 response_type 的值修改為 token,表示直接返回授權碼,其他參數不變。

這樣,當用戶登錄成功之後,會自動重定向到 http://localhost:8082/index.html 頁面,並且添加了一個錨點參數,類似下面這樣:

<code>http://localhost:8082/index.html#access_token=9fda1800-3b57-4d32-ad01-05ff700d44cc&token_type=bearer&expires_in=1940
/<code>

所以接下來,我們就在 js 中提取出 # 後面的參數,並進一步解析出 access_token 的值。

拿著 access_token 的值,我們去發送一個 Ajax 請求,將 access_token 放在請求頭中,請求成功後,將請求到的數據放在 div 中。

這就是我們說的簡化模式。

配置完成後,啟動 client-app,訪問 http://localhost:8082/index.html 頁面進行測試,用戶授權之後,會自動重定向到該頁面,顯示效果如下:

死磕 OAuth2,教練我要學全套的!

完整代碼可以在文末下載。

2.密碼模式

密碼模式,需要用戶直接在第三方應用上輸入用戶名密碼登錄,我們來看下。

「注意,接下來的代碼是在上篇文章授權碼模式的基礎上改造。」

首先對 auth-server 進行改造,使之支持 password 模式:

<code>@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("javaboy")
            .secret(new BCryptPasswordEncoder().encode("123"))
            .resourceIds("res1")
            .authorizedGrantTypes("password","refresh_token")
            .scopes("all")
            .redirectUris("http://localhost:8082/index.html");
}/<code>

這裡其他地方都不變,主要是在 authorizedGrantTypes 中增加了 password 模式。

由於使用了 password 模式之後,用戶要進行登錄,所以我們需要配置一個 AuthenticationManager,還是在 AuthorizationServer 類中,具體配置如下:

<code>@Autowired
AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints
            .authenticationManager(authenticationManager)

            .tokenServices(tokenServices());
}/<code>

注意,在授權碼模式中,我們配置的 AuthorizationCodeServices 現在不需要了,取而代之的是 authenticationManager。

那麼這個 authenticationManager 實例從哪裡來呢?這需要我們在 Spring Security 的配置中提供,這松哥在之前的 Spring Security 系列教程中說過多次,我就不再贅述,這裡直接上代碼,在 SecurityConfig 中添加如下代碼:

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

配置完成後,重啟 auth-server。

接下來配置 client-app,首先我們添加登錄功能,修改 index.html ,如下:

<code>
你好,江南一點雨!



/<code>

這一個簡單的登錄功能沒啥好說的。

我們來看登錄接口:

<code>@PostMapping("/login")
public String login(String username, String password,Model model) {
    MultiValueMap<string> map = new LinkedMultiValueMap<>();
    map.add("username", username);
    map.add("password", password);

    map.add("client_secret", "123");
    map.add("client_id", "javaboy");
    map.add("grant_type", "password");
    Map<string> resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
    String access_token = resp.get("access_token");
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "Bearer " + access_token);
    HttpEntity<object> httpEntity = new HttpEntity<>(headers);
    ResponseEntity<string> entity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, httpEntity, String.class);
    model.addAttribute("msg", entity.getBody());
    return "index";
}/<string>/<object>/<string>/<string>/<code>

在登錄接口中,當收到一個用戶名密碼之後,我們通過 RestTemplate 發送一個 POST 請求,注意 post 請求中,grant_type 參數的值為 password,通過這個請求,我們可以獲取 auth-server 返回的 access_token,格式如下:

<code>{access_token=02e3a1e1-925f-4d2c-baac-42d76703cae4, token_type=bearer, refresh_token=836d4b75-fe53-4e41-9df1-2aad6dd80a5d, expires_in=7199, scope=all}/<code>

可以看到,返回的 token 數據和前面的類似,不再贅述。

我們提取出 access_token 之後,接下來去請求資源服務器,並將訪問到的數據放在 model 中。

OK,配置完成後,啟動 client-app,訪問 http://localhost:8082/index.html 頁面進行測試。授權完成後,我們在項目首頁可以看到如下內容:

死磕 OAuth2,教練我要學全套的!

完整代碼可以在文末下載。

3.客戶端模式

客戶端模式適用於沒有前端頁面的應用,所以我這裡用一個單元測試來個大家演示。

「注意,接下來的代碼是在上篇文章授權碼模式的基礎上改造。」

首先修改 auth-server ,使之支持客戶端模式:

<code>@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("javaboy")
            .secret(new BCryptPasswordEncoder().encode("123"))
            .resourceIds("res1")
            .authorizedGrantTypes("client_credentials","refresh_token")
            .scopes("all")
            .redirectUris("http://localhost:8082/index.html");
}/<code>

這裡其他地方都不變,主要是在 authorizedGrantTypes 中增加了 client_credentials 模式。

配置完成後,重啟 auth-server。

接下來,在 client-app 中,通過單元測試,我們來寫一段測試代碼:

<code>@Autowired
RestTemplate restTemplate;
@Test
void contextLoads() {

    MultiValueMap<string> map = new LinkedMultiValueMap<>();
    map.add("client_id", "javaboy");
    map.add("client_secret", "123");
    map.add("grant_type", "client_credentials");
    Map<string> resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
    String access_token = resp.get("access_token");
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "Bearer " + access_token);
    HttpEntity<object> httpEntity = new HttpEntity<>(headers);
    ResponseEntity<string> entity = restTemplate.exchange("http://localhost:8081/hello", HttpMethod.GET, httpEntity, String.class);
    System.out.println(entity.getBody());
}/<string>/<object>/<string>/<string>/<code>

這段代碼跟前面的都差不多,就是請求參數不一樣而已,參數 grant_type 的值為 client_credentials。其他都一樣,我就不再贅述了。

這段單元測試,執行完成後,就打印出 hello,我就不再截圖了。

完整代碼可以在文末下載。

4.刷新 token

接下來松哥要講的,是四種授權模式共有的功能。

以授權碼模式為例,當我們啟動 auth-server 之後,在 IntelliJ IDEA 中,我們可以看到項目暴露出來的接口:

死磕 OAuth2,教練我要學全套的!

那麼這些接口都是幹嘛用的呢?我們通過如下一張表格來理解下:

端點 含義 /oauth/authorize 這個是授權的端點 /oauth/token 這個是用來獲取令牌的端點 /oauth/confirm_access 用戶確認授權提交的端點(就是 auth-server 詢問用戶是否授權那個頁面的提交地址) /oauth/error 授權出錯的端點 /oauth/check_token 校驗 access_token 的端點 /oauth/token_key 提供公鑰的端點

一目瞭然。這幾個端點大部分我們都用過了,沒用過的在未來也會用到,到時候再詳細和小夥伴們解釋。

/oauth/token 端點除了頒發令牌,還可以用來刷新令牌,在我們獲取令牌的時候,除了 access_token 之外,還有一個 refresh_token,這個 refresh_token 就是用來刷新令牌用的。

我用 postman 來做一個簡單的刷新令牌請求:

死磕 OAuth2,教練我要學全套的!

注意,刷新的時候需要攜帶上 refresh_token 參數,刷新完成之後,之前舊的 access_token 就會失效。

4.其他

通過上面三個案例,再結合上篇文章,松哥通過四個完完整整的代碼,向家展示了 OAuth2 四種授權模式的基本用法。

這四個完整的案例,大家都可以直接從 github 上下載:

死磕 OAuth2,教練我要學全套的!

好了,先說這麼多,如果有收穫,一定記得點個在看鼓勵下松哥~

案例地址:https://github.com/lenve/oauth2-samples

關注微信公眾號「江南一點雨」,回覆 「666」 免費下載松哥手敲 「274 頁的 Spring Boot系列教程」


分享到:


相關文章: