使用 OAuth 2.0 認證的的好處是顯然易見的。你只須要用同一個帳號密碼,就能在各個網站進行訪問,而免去了在每一個網站都進行註冊的繁瑣過程。 本文將介紹 OAuth 2.0 的原理,並基於 Spring Security 和 GitHub 帳號,來演示 OAuth 2.0 的認證的過程。css
什麼是 OAuth 2.0
OAuth 2.0 的規範能夠參考 : RFC 6749html
OAuth 是一個開放標準,容許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。目前,OAuth 的最新版本爲 2.0前端
OAuth 容許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據。每個令牌受權一個特定的網站(例如,視頻編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth 容許用戶受權第三方網站訪問他們存儲在另外的服務提供者上的信息,而不須要分享他們的訪問許可或他們數據的全部內容。java
OAuth 2.0 的核心概念
OAuth 2.0 主要有4類角色:git
- resource owner:資源全部者,指終端的「用戶」(user)
- resource server:資源服務器,即服務提供商存放受保護資源。訪問這些資源,須要得到訪問令牌(access token)。它與認證服務器,能夠是同一臺服務器,也能夠是不一樣的服務器。若是,咱們訪問新浪博客網站,那麼若是使用新浪博客的帳號來登陸新浪博客網站,那麼新浪博客的資源和新浪博客的認證都是同一家,能夠認爲是同一個服務器。若是,咱們是新浪博客帳號去登陸了知乎,那麼顯然知乎的資源和新浪的認證不是一個服務器。
- client:客戶端,表明向受保護資源進行資源請求的第三方應用程序。
- authorization server: 受權服務器, 在驗證資源全部者並得到受權成功後,將發放訪問令牌給客戶端。
OAuth 2.0 的認證流程
認證流程以下:github
1web 2spring 3api 4安全 5 6 7 8 9 10 11 12 13 14 15 16 17 |
+--------+ +---------------+ | |--(A)- Authorization Request ->| Resource | | | | Owner | | |<-(B)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(C)-- Authorization Grant -->| Authorization | | Client | | Server | | |<-(D)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(E)----- Access Token ------>| Resource | | | | Server | | |<-(F)--- Protected Resource ---| | +--------+ +---------------+ |
- (A)用戶打開客戶端之後,客戶端請求資源全部者(用戶)的受權。
- (B)用戶贊成給予客戶端受權。
- (C)客戶端使用上一步得到的受權,向認證服務器申請訪問令牌。
- (D)認證服務器對客戶端進行認證之後,確認無誤,贊成發放訪問令牌。
- (E)客戶端使用訪問令牌,向資源服務器申請獲取資源。
- (F)資源服務器確認令牌無誤,贊成向客戶端開放資源。
其中,用戶受權有四種模式:
- 受權碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
實踐 OAuth 2.0
Talk is cheap!下面將演示代碼。 本例子將經過 Gradle、Spring Boot、Spring Security、 Thymeleaf、等技術來實現一個client 以及 resource server,並 經過 GitHub來給咱們的應用受權。
依賴
本項目基於Gralde 來管理依賴,讀者能夠自行改爲 Maven 的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 該依賴對於編譯發行是必須的 compile( 'org.springframework.boot:spring-boot-starter-web' ) // 添加 Thymeleaf 的依賴 compile( 'org.springframework.boot:spring-boot-starter-thymeleaf' ) // 添加 Spring Security 依賴 compile( 'org.springframework.boot:spring-boot-starter-security' ) // 添加 Thymeleaf Spring Security 依賴,與 Thymeleaf 版本一致都是 3.x compile( 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE' ) // 添加 Spring Security OAuth2 依賴 compile( 'org.springframework.security.oauth:spring-security-oauth2:2.1.0.RELEASE' ) // 該依賴對於編譯測試是必須的,默認包含編譯產品依賴和編譯時依 testCompile( 'org.springframework.boot:spring-boot-starter-test' ) // 添加 Spring Security Test 依賴 testCompile( 'org.springframework.security:spring-security-test:4.2.2.RELEASE' ) |
配置
項目的核心配置以下:
1 2 3 4 5 6 7 8 |
github.client.clientId=ad2abbc19b6c5f0ed117 github.client.clientSecret=26db88a4dfc34cebaf196e68761c1294ac4ce265 github.client.accessTokenUri=https: //github.com/login/oauth/access_token github.client.userAuthorizationUri=https: //github.com/login/oauth/authorize github.client.clientAuthenticationScheme=form github.client.tokenName=oauth_token github.client.authenticationScheme=query github.resource.userInfoUri=https: //api.github.com/user |
包括了做爲一個client 所須要大部分參數。其中 clientId 、 clientSecret 是在 GitHub 註冊一個應用時生成的。若是讀者不想註冊應用,則能夠直接用上面的配置便可。 若是要註冊,則文章最後有註冊流程。
項目安全的配置
安全配置上須要加上@EnableWebSecurity
、 @EnableOAuth2Client
註解,來啓用 Web 安全認證機制,並代表這是一個OAuth 2.0 客戶端 。 @EnableGlobalMethodSecurity
註明,項目採用了基於方法的安全設置 :
1 2 3 4 |
@EnableWebSecurity @EnableOAuth2Client // 啓用 OAuth 2.0 客戶端 @EnableGlobalMethodSecurity (prePostEnabled = true ) // 啓用方法安全設置 public class SecurityConfig extends WebSecurityConfigurerAdapter { |
使用 Spring Security,咱們須要繼承 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
並重寫如下 configure 方法:
1 2 3 4 5 6 7 8 9 10 11 12 |
@Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(ssoFilter(), BasicAuthenticationFilter. class ) .antMatcher( "/**" ) .authorizeRequests() .antMatchers( "/" , "/index" , "/403" , "/css/**" , "/js/**" , "/fonts/**" ).permitAll() // 不設限制,都容許訪問 .anyRequest() .authenticated() .and().logout().logoutSuccessUrl( "/" ).permitAll() .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) ; } |
上面的配置是設置了一些過過濾策略,除了靜態資源以及不須要受權的頁面,咱們容許訪問,其餘的資源,都是須要受權訪問。
其中,咱們也設置了一個過濾器 ssoFilter,用於在 BasicAuthenticationFilter 以前進行攔截。若是攔截道的是/login
,就是訪問認證服務器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
private Filter ssoFilter() { OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter( "/login" ); OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(github(), oauth2ClientContext); githubFilter.setRestTemplate(githubTemplate); UserInfoTokenServices tokenServices = new UserInfoTokenServices(githubResource().getUserInfoUri(), github().getClientId()); tokenServices.setRestTemplate(githubTemplate); githubFilter.setTokenServices(tokenServices); return githubFilter; } @Bean public FilterRegistrationBean oauth2ClientFilterRegistration( OAuth2ClientContextFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(filter); registration.setOrder(- 100 ); return registration; } @Bean @ConfigurationProperties ( "github.client" ) public AuthorizationCodeResourceDetails github() { return new AuthorizationCodeResourceDetails(); } @Bean @ConfigurationProperties ( "github.resource" ) public ResourceServerProperties githubResource() { return new ResourceServerProperties(); } |
資源服務器
咱們寫了兩個控制器來提供相應的資源。
MainController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Controller public class MainController { @GetMapping ( "/" ) public String root() { return "redirect:/index" ; } @GetMapping ( "/index" ) public String index(Principal principal, Model model) { if (principal == null ){ return "index" ; } System.out.println(principal.toString()); model.addAttribute( "principal" , principal); return "index" ; } @GetMapping ( "/403" ) public String accesssDenied() { return "403" ; } } |
在index 頁面,將如認證成功,將會顯示一些認證信息。
UserController.java 是用來模擬用戶管理的相關資源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@RestController @RequestMapping ( "/" ) public class UserController { /** * 查詢所用用戶 * @return */ @GetMapping ( "/users" ) @PreAuthorize ( "hasAuthority('ROLE_USER')" ) // 指定角色權限才能操做方法 public ModelAndView list(Model model) { List<User> list = new ArrayList<>(); // 當前所在頁面數據列表 list.add( new User( "waylau" , 29 )); list.add( new User( "老衛" , 30 )); model.addAttribute( "title" , "用戶管理" ); model.addAttribute( "userList" , list); return new ModelAndView( "users/list" , "userModel" , model); } } |
前端頁面
頁面,我主要是採用 Thymeleaf 以及Bootstrap 來編寫的。
首頁用於現實用戶的基本信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
< body > < div class = "container" > < div class = "mt-3" > < h2 >Hello Spring Security</ h2 > </ div > < div sec:authorize = "isAuthenticated()" th:if = "${principal}" th:object = "${principal}" > < p >已有用戶登陸</ p > < p >登陸的用戶爲: < span sec:authentication = "name" ></ span ></ p > < p >用戶權限爲: < span th:text = "*{userAuthentication.authorities}" ></ span ></ p > < p >用戶頭像爲: < img alt = "" class = "avatar width-full rounded-2" height = "230" th:src = "*{userAuthentication.details.avatar_url}" width = "230" ></ p > </ div > < div sec:authorize = "isAnonymous()" > < p >未有用戶登陸</ p > </ div > </ div > </ body > |
用戶管理界面顯示用戶的列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
< body > < div class = "container" > < div class = "mt-3" > < h2 th:text = "${userModel.title}" >Welcome to waylau.com</ h2 > </ div > < table class = "table table-hover" > < thead > < tr > < td >Age</ td > < td >Name</ td > < td sec:authorize = "hasRole('ADMIN')" >Operation</ td > </ tr > </ thead > < tbody > < tr th:if = "${userModel.userList.size()} eq 0" > < td colspan = "3" >沒有用戶信息!!</ td > </ tr > < tr th:each = "user : ${userModel.userList}" > < td th:text = "${user.age}" >11</ td > < td th:text = "${user.name}" >waylau</ a ></ td > < td sec:authorize = "hasRole('ADMIN')" > < div > 我是管理員 </ div > </ td > </ tr > </ tbody > </ table > </ body > |
運行效果
這個是沒有受權訪問首頁:
![](http://static.javashuo.com/static/loading.gif)
當咱們點擊登陸,會重定向到 GitHub,登陸界面並進行受權:
![](http://static.javashuo.com/static/loading.gif)
這個是受權後的首頁:
![](http://static.javashuo.com/static/loading.gif)
受權後就可以進入用戶管理界面:
![](http://static.javashuo.com/static/loading.gif)
註冊GitHub 應用
若是須要註冊,請看下面的流程,來生成 Client ID 和 Client Secret
訪問https://github.com/settings/applications/new
註冊應用,生成 客戶端 id 和 密碼。好比:
1 2 |
Client ID :ad2abbc19b6c5f0ed117 Client Secret :26db88a4dfc34cebaf196e68761c1294ac4ce265 |
![](http://static.javashuo.com/static/loading.gif)
客戶端 id 和 密碼寫入程序配置便可。
源碼