天天看點

oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!

需要先說一下,松哥最近寫的教程,都是成系列的,有一些重複的東西寫來寫去就沒意思了,是以每一篇文章都預設大家已經懂了前面的内容了,是以下文有任何看不懂的地方,建議一定先看下相關系列:

「Spring Security 系列:」

  1. 挖一個大坑,Spring Security 開搞!
  2. 松哥手把手帶你入門 Spring Security,别再問密碼怎麼解密了
  3. 手把手教你定制 Spring Security 中的表單登入
  4. Spring Security 做前後端分離,咱就别做頁面跳轉了!統統 JSON 互動
  5. Spring Security 中的授權操作原來這麼簡單
  6. Spring Security 如何将使用者資料存入資料庫?
  7. Spring Security+Spring Data Jpa 強強聯手,安全管理隻有更簡單!

「OAuth2 系列:」

  1. 做微服務繞不過的 OAuth2,松哥也來和大家扯一扯
  2. 這個案例寫出來,還怕跟面試官扯不明白 OAuth2 登入流程?
  3. 死磕 OAuth2,教練我要學全套的!
  4. OAuth2 令牌還能存入 Redis ?越玩越溜!
  5. 想讓 OAuth2 和 JWT 在一起愉快玩耍?請看松哥的表演
  6. 和大家分享一點微服務架構中的安全管理思路

好了,開始今天的正文。

單點登入是我們在分布式系統中很常見的一個需求。

分布式系統由多個不同的子系統組成,而我們在使用系統的時候,隻需要登入一次即可,這樣其他系統都認為使用者已經登入了,不用再去登入。前面和小夥伴們分享了 OAuth2+JWT 的登入方式,這種無狀态登入實際上天然的滿足單點登入的需求,可以參考:想讓 OAuth2 和 JWT 在一起愉快玩耍?請看松哥的表演。

當然大家也都知道,無狀态登入也是有弊端的。

是以今天松哥想和大家說一說 Spring Boot+OAuth2 做單點登入,利用 @EnableOAuth2Sso 注解快速實作單點登入功能。

松哥依然建議大家在閱讀本文時,先看看本系列前面的文章,這有助于更好的了解本文。

1.項目建立

前面的案例中,松哥一直都把授權伺服器和資源伺服器分開建立,今天這個案例,為了省事,我就把授權伺服器和資源伺服器搭建在一起(不過相信大家看了前面的文章,應該也能自己把這兩個伺服器拆分開)。

是以,今天我們一共需要三個服務:

項目 端口 描述 auth-server 1111 授權伺服器+資源伺服器 client1 1112 子系統1 client2 1113 子系統2

auth-server 用來扮演授權伺服器+資源伺服器的角色,client1 和 client2 則分别扮演子系統的角色,将來等 client1 登入成功之後,我們也就能通路 client2 了,這樣就能看出來單點登入的效果。

我們建立一個名為 oauth2-sso 的 Maven 項目作為父工程即可。

2.統一認證中心

接下來我們來搭建統一認證中心。

首先我們建立一個名為 auth-server 的 module,建立時添加如下依賴:

oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!

項目建立成功之後,這個子產品由于要扮演授權伺服器+資源伺服器的角色,是以我們先在這個項目的啟動類上添加 @EnableResourceServer 注解,表示這是一個資源伺服器:

@[email protected] class AuthServerApplication {    public static void main(String[] args) {        SpringApplication.run(AuthServerApplication.class, args);    }}
           

接下來我們進行授權伺服器的配置,由于資源伺服器和授權伺服器合并在一起,是以授權伺服器的配置要省事很多:

@[email protected] class AuthServerConfig extends AuthorizationServerConfigurerAdapter {    @Autowired    PasswordEncoder passwordEncoder;    @Override    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {        clients.inMemory()                .withClient("javaboy")                .secret(passwordEncoder.encode("123"))                .autoApprove(true)                .redirectUris("http://localhost:1112/login", "http://localhost:1113/login")                .scopes("user")                .accessTokenValiditySeconds(7200)                .authorizedGrantTypes("authorization_code");    }}
           

這裡我們隻需要簡單配置一下用戶端的資訊即可,這裡的配置很簡單,前面的文章也講過了,大家要是不懂,可以參考本系列前面的文章:這個案例寫出來,還怕跟面試官扯不明白 OAuth2 登入流程?。

當然這裡為了簡便,用戶端的資訊配置是基于記憶體的,如果大家想将用戶端資訊存入資料庫中,也是可以的,參考:OAuth2 令牌還能存入 Redis ?越玩越溜!

接下來我們再來配置 Spring Security:

@[email protected](1)public class SecurityConfig extends WebSecurityConfigurerAdapter {    @Bean    PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    @Override    public void configure(WebSecurity web) throws Exception {        web.ignoring().antMatchers("/login.html", "/css/**", "/js/**", "/images/**");    }    @Override    protected void configure(HttpSecurity http) throws Exception {        http.requestMatchers()                .antMatchers("/login")                .antMatchers("/oauth/authorize")                .and()                .authorizeRequests().anyRequest().authenticated()                .and()                .formLogin()                .loginPage("/login.html")                .loginProcessingUrl("/login")                .permitAll()                .and()                .csrf().disable();    }    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        auth.inMemoryAuthentication()                .withUser("sang")                .password(passwordEncoder().encode("123"))                .roles("admin");    }}
           

關于 Spring Security 的配置,如果小夥伴們不懂,可以看看松哥最近正在連載的 Spring Security 系列。

我這裡來大緻捋一下:

  1. 首先提供一個 BCryptPasswordEncoder 的執行個體,用來做密碼加解密用。
  2. 由于我自定義了登入頁面,是以在 WebSecurity 中對這些靜态資源方形。
  3. HttpSecurity 中,我們對認證相關的端點放行,同時配置一下登入頁面和登入接口。
  4. AuthenticationManagerBuilder 中提供一個基于記憶體的使用者(小夥伴們可以根據 Spring Security 系列第 7 篇文章自行調整為從資料庫加載)。
  5. 另外還有一個比較關鍵的地方,因為資源伺服器和授權伺服器在一起,是以我們需要一個 @Order 注解來提升 Spring Security 配置的優先級。

SecurityConfig 和 AuthServerConfig 都是授權伺服器需要提供的東西(如果小夥伴們想将授權伺服器和資源伺服器拆分,請留意這句話),接下來,我們還需要提供一個暴露使用者資訊的接口(如果将授權伺服器和資源伺服器分開,這個接口将由資源伺服器提供):

@RestControllerpublic class UserController {    @GetMapping("/user")    public Principal getCurrentUser(Principal principal) {        return principal;    }}
           

最後,我們在 application.properties 中配置一下項目端口:

server.port=1111
           

另外,松哥自己提前準備了一個登入頁面,如下:

oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!

将登入頁面相關的 html、css、js 等拷貝到 resources/static 目錄下:

oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!

這個頁面很簡單,就是一個登入表單而已,我把核心部分列出來:

        使用者名                    

        密碼                    

                    登入                        

注意一下 action 送出位址不要寫錯即可。

「文末可以下載下傳源碼。」

如此之後,我們的統一認證登入平台就算是 OK 了。

3.用戶端建立

接下來我們來建立一個用戶端項目,建立一個名為 client1 的 Spring Boot 項目,添加如下依賴:

oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!

項目建立成功之後,我們來配置一下 Spring Security:

@[email protected] class SecurityConfig extends WebSecurityConfigurerAdapter {    @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests().anyRequest().authenticated().and().csrf().disable();    }}
           

這段配置很簡單,就是說我們 client1 中所有的接口都需要認證之後才能通路,另外添加一個 @EnableOAuth2Sso 注解來開啟單點登入功能。

接下來我們在 client1 中再來提供一個測試接口:

@RestControllerpublic class HelloController {    @GetMapping("/hello")    public String hello() {        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();        return authentication.getName() + Arrays.toString(authentication.getAuthorities().toArray());    }}
           

這個測試接口傳回目前登入使用者的姓名和角色資訊。

接下來我們需要在 client1 的 application.properties 中配置 oauth2 的相關資訊:

security.oauth2.client.client-secret=123security.oauth2.client.client-id=javaboysecurity.oauth2.client.user-authorization-uri=http://localhost:1111/oauth/authorizesecurity.oauth2.client.access-token-uri=http://localhost:1111/oauth/tokensecurity.oauth2.resource.user-info-uri=http://localhost:1111/userserver.port=1112server.servlet.session.cookie.name=s1
           

這裡的配置也比較熟悉,我們來看一下:

  1. client-secret 是用戶端密碼。
  2. client-id 是用戶端 id。
  3. user-authorization-uri 是使用者授權的端點。
  4. access-token-uri 是擷取令牌的端點。
  5. user-info-uri 是擷取使用者資訊的接口(從資源伺服器上擷取)。
  6. 最後再配置一下端口,然後給 cookie 取一個名字。

如此之後,我們的 client1 就算是配置完成了。

按照相同的方式,我們再來配置一個 client2,client2 和 client1 一模一樣,就是 cookie 的名字不同(随意取,不相同即可)。

4.測試

接下來,我們分别啟動 auth-server、client1 和 client2,首先我們嘗試去方式 client1 中的 hello 接口,這個時候會自動跳轉到統一認證中心:

oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!

然後輸入使用者名密碼進行登入。

登入成功之後,會自動跳轉回 client1 的 hello 接口,如下:

oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!

此時我們再去通路 client2 ,發現也不用登入了,直接就可以通路:

oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!

OK,如此之後,我們的單點登入就成功了。

5.流程解析

最後,我再來和小夥伴們把上面代碼的一個執行流程捋一捋:

  1. 首先我們去通路 client1 的 /hello 接口,但是這個接口是需要登入才能通路的,是以我們的請求被攔截下來,攔截下來之後,系統會給我們重定向到 client1 的 /login 接口,這是讓我們去登入。
oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!
  1. 當我們去通路 client1 的登入接口時,由于我們配置了 @EnableOAuth2Sso 注解,這個操作會再次被攔截下來,單點登入攔截器會根據我們在 application.properties 中的配置,自動發起請求去擷取授權碼:
oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!
  1. 在第二步發送的請求是請求 auth-server 服務上的東西,這次請求當然也避免不了要先登入,是以再次重定向到 auth-server 的登入頁面,也就是大家看到的統一認證中心。
  2. 在統一認真中心我們完成登入功能,登入完成之後,會繼續執行第二步的請求,這個時候就可以成功擷取到授權碼了。
oauth2 單點登入_Spring Boot+OAuth2,一個注解搞定單點登入!
  1. 擷取到授權碼之後,這個時候會重定向到我們 client1 的 login 頁面,但是實際上我們的 client1 其實是沒有登入頁面的,是以這個操作依然會被攔截,此時攔截到的位址包含有授權碼,拿着授權碼,在 OAuth2ClientAuthenticationProcessingFilter 類中向 auth-server 發起請求,就能拿到 access_token 了(參考:這個案例寫出來,還怕跟面試官扯不明白 OAuth2 登入流程?)。
  2. 在第五步拿到 access_token 之後,接下來在向我們配置的 user-info-uri 位址發送請求,擷取登入使用者資訊,拿到使用者資訊之後,在 client1 上自己再走一遍 Spring Security 登入流程,這就 OK 了。

OK,本文和小夥伴們聊了一些 SpringBoot +OAuth2 單點登入的問題,完整案例下載下傳位址:https://github.com/lenve/oauth2-samples

如果小夥伴們覺得有用的話,記得點個在看鼓勵下松哥。