spring cloud oauth2微服務認證授權

springcloud oauth 官方頁面 https://spring.io/projects/spring-security-oauth#learn

oauth2官網 https://oauth.net/2/

OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This specification and its extensions are being developed within the IETF OAuth Working Group.

OAuth 2.0是用於授權的行業標準協議。 OAuth 2.0着眼於簡化客戶端開發人員,同時爲Web應用程序,桌面應用程序,移動電話和客廳設備提供特定的授權流程。 該規範及其擴展名正在IETF OAuth工作組內開發。

首先來了解下Oatuh2中的幾個名字,方便下文的闡述。

  1. Third-party application: 第三方應用
  2. Resource Owner: 資源持有者,一般就是用戶自身
  3. Authorization server: 認證服務器
  4. Resource server: 資源服務器,即具體資源的存儲方。與認證服務器是不同的邏輯節點,但是在物理上,雙方是可以在一起的
  5. User Agent: 用戶代理,一般就是指的瀏覽器
  6. Http Service: 服務提供者,也就是持有Resource Server的存在方。可以理解爲類似QQ,或者微信這樣具備用戶信息的服務者。

Oauth2的作用就是讓第三方應用在用戶(資源持有者)授權的情況下,通過認證服務器的認證,從而安全的在資源服務器上獲得對應的用戶資源的流程指導。

oauth2常用的四種模式

 1.密碼模式(Resource Owner Password Credentials Grant)

 

  •  第一步:用戶訪問用頁面時,輸入第三方認證所需要的信息(QQ/微信賬號密碼)
  •  第二步:應用頁面那種這個信息去認證服務器授權
  •  第三步:認證服務器授權通過,拿到token,訪問真正的資源頁面

優點:不需要多次請求轉發,額外開銷,同時可以獲取更多的用戶信息。(都拿到賬號密碼了)

缺點:侷限性,認證服務器和應用方必須有超高的信賴

應用場景:自家公司搭建的認證服務器

 

2.客戶端憑證模式(Client Credentials Grant)

  •  第一步:用戶訪問應用客戶端
  •  第二步:通過客戶端定義的驗證方法,拿到token,無需授權
  •  第三步:訪問資源服務器A
  •  第四步:拿到一次token就可以暢通無阻的訪問其他的資源頁面。

這是一種最簡單的模式,只要client請求,我們就將AccessToken發送給它。這種模式是最方便但最不安全的模式。因此這就要求我們對client完全的信任,而client本身也是安全的。

因此這種模式一般用來提供給我們完全信任的服務器端服務。在這個過程中不需要用戶的參與。

3.授權碼授權模式(Authorization code Grant)

  • 第一步:用戶攜帶client_id redirect_uri response_type 訪問應用客戶端
  • 第二步:客戶端返回登錄認證頁面 
  • 第三步:輸入用戶名密碼登錄  
  • 第四步:客戶端驗證碼密碼正確 賬號密碼準確性 和權限 驗證通過 重定向到請求參數配置的重定向地址 並且攜帶code
  • 第五步:用戶攜帶code grant_type redirect_uri client_id client_secret (這裏client ID和secret 使用表單認證)
  • 第六步:客戶端驗證成功  通過返回accss_token refresh 信息
  • 第七步:拿到token 且token 沒失效 就可以暢通無阻的訪問其他的資源頁面。

4.隱式授權模式(Implicit Grant)

 授權碼默認簡化版本

  • 第一步:用戶攜帶client_id redirect_uri response_type 訪問應用客戶端
  • 第二步:客戶端返回登錄認證頁面 
  • 第三步:輸入用戶名密碼登錄  
  • 第四步:客戶端驗證碼密碼正確 賬號密碼準確性 和權限 驗證通過 重定向到請求參數配置的重定向地址 並且攜帶refresh_token和access_token等信息
  • 第五步:拿到token 且token 沒失效 就可以暢通無阻的訪問其他的資源頁面。

 

理論到此結束

這裏創建3個服務

第一個 eureka 註冊中心 

第二個 auth-service  認證服務

第三個 resource-service 資源服務器

使用的springcloud 版本 爲 Hoxton.SR4 

auth-service服務開始 

認證服務整體如圖

pom依賴配置
        <!-- Eureka 服務發現與註冊客戶端依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <!-- actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

AuthorizationServerConfig
/**
 * 認證配置
 *
 * @author admin
 * @date 2020-06-17 17:06:30
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

   private final AuthenticationManager authenticationManager;

   private final ClientDetailsService clientDetailsService;

   private final UserDetailsService userDetailsService;

   @Autowired
   public AuthorizationServerConfig(ClientDetailsService clientDetailsService, AuthenticationManager authenticationManager, UserDetailsService userDetailsService) {
      this.clientDetailsService = clientDetailsService;
      this.authenticationManager = authenticationManager;
      this.userDetailsService = userDetailsService;
   }

   @Override
   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
      //用來配置令牌端點(Token Endpoint)的安全與權限訪問。
      security
            .tokenKeyAccess("permitAll()")
            .checkTokenAccess("permitAll()")
            .accessDeniedHandler(new AccessDeniedHandlerImpl())
            //允許表單傳入 client_id client_secret進行認證
            .allowFormAuthenticationForClients()
      ;
   }

   /**
    * 認證配置
    * @param endpoints endpoints
    * @throws Exception endpoints
    */
   @Override
   public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      //用來配置授權以及令牌(Token)的訪問端點和令牌服務(比如:配置令牌的簽名與存儲方式)
      endpoints
            .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
            .authenticationManager(this.authenticationManager)
            //token 存儲
            .tokenStore(tokenStore())
            .userDetailsService(userDetailsService)
      ;
   }

   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      //內存中配置客戶端信息
      //用來配置客戶端詳情信息,一般使用數據庫來存儲或讀取應用配置的詳情信息
      clients.inMemory().withClient("user")
            .accessTokenValiditySeconds(30 * 60)
            .refreshTokenValiditySeconds(30 * 60)
            .authorizedGrantTypes("authorization_code", "refresh_token", "client_credentials", "password","implicit")
            .scopes("all", "read", "write")
            // // secret密碼配置從 Spring Security 5.0開始必須以 {加密方式}+加密後的密碼 這種格式填寫
            //        /*
            //         *   當前版本5新增支持加密方式:
            //         *   bcrypt - BCryptPasswordEncoder (Also used for encoding)
            //         *   ldap - LdapShaPasswordEncoder
            //         *   MD4 - Md4PasswordEncoder
            //         *   MD5 - new MessageDigestPasswordEncoder("MD5")
            //         *   noop - NoOpPasswordEncoder
            //         *   pbkdf2 - Pbkdf2PasswordEncoder
            //         *   scrypt - SCryptPasswordEncoder
            //         *   SHA-1 - new MessageDigestPasswordEncoder("SHA-1")
            //         *   SHA-256 - new MessageDigestPasswordEncoder("SHA-256")
            //         *   sha256 - StandardPasswordEncoder
            //         */
            .secret("{noop}style")
            .redirectUris("https://www.baidu.com")
      ;
   }

   /**
    * Persistence interface for OAuth2 tokens.
    *
    * @return TokenStore
    */
   @Bean
   public TokenStore tokenStore() {
      return new InMemoryTokenStore();
   }

}
SecurityConfig
package com.style.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * Security 配置
 *
 * @author admin
 * @date 2020-06-18 15:23:04
 */
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS).permitAll()
            .antMatchers("/oauth/**").permitAll()
            .anyRequest().authenticated()
            .and()
            //basic 彈窗登錄
            //.httpBasic()
            //表單登錄
            .formLogin()
            .and()
            .csrf().disable()
      ;
   }

   @Bean
   public UserDetailsService userDetails() {
      UserDetails admin = User.withUsername("admin")
            //,默認BCryptPasswordEncoder 更多實現 org.springframework.security.crypto.password.PasswordEncoder
            //可查看該接口的實現
            // password  Spring Security 5.0開始必須以 {加密方式}+加密後的密碼 這種格式填寫
            .password("{bcrypt}$2a$10$ZlFDDZMkZ9P7Yb4BsZ50ZueNzn7yM3GTJD97M5cJMWDu4oKr1Lsuq")
            .roles("ADMIN", "USER")
            .build();
      UserDetails user = User.withUsername("user")
            .password("{bcrypt}" + new BCryptPasswordEncoder().encode("123456"))
            .roles("USER")
            .build();
      //內存用戶管理器
      InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
      userDetailsManager.createUser(admin);
      userDetailsManager.createUser(user);
      return userDetailsManager;
   }

   @Bean
   @Override
   protected AuthenticationManager authenticationManager() throws Exception {
      return super.authenticationManager();
   }
}

AuthServerApplication 啓動類

/**
 * 認證服務
 *
 * @author admin
 */
@SpringBootApplication
@EnableDiscoveryClient
public class AuthServerApplication {

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

}

 

resource-service 

整體目錄結構

pom依賴配置

        <!-- Eureka 服務發現與註冊客戶端依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <!-- actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
ResourceServerConfig
package com.style.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

/**
 * 資源服務配置
 *
 * @author admin
 * @date 2020-06-18 16:59:28
 */
@EnableResourceServer
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

   private static final String RESOURCE_ID = "resource";

   @Override
   public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
      resources.resourceId(RESOURCE_ID);
      resources.tokenServices(tokenService());
   }

   @Override
   public void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
            .antMatchers("/oauth/**").permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .csrf()
            .disable()
            .formLogin()
      ;
   }

   @Bean
   public RemoteTokenServices tokenService() {
      //遠程token服務 即爲認證的服務器的check_token地址
      RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
      remoteTokenServices.setClientId("user");
      remoteTokenServices.setClientSecret("style");
      // 20110 端口爲auth-service 的端口
      remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:20110/oauth/check_token");
      return remoteTokenServices;
   }

   /**
    * Persistence interface for OAuth2 tokens.
    *
    * @return TokenStore
    */
   @Bean
   public TokenStore tokenStore() {
      return new InMemoryTokenStore();
   }
}
SecurityConfig
package com.style.auth.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * Security配置
 *
 * @author admin
 * @date 2020-06-18 15:23:04
 */
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/oauth/**").permitAll()
            .anyRequest().authenticated()
            .and()
            //表單登錄
            .formLogin()
      ;
   }
}

ResourceController

@RestController
public class ResourceController {

   @Value("${server.port}")
   private Integer serverPort;

   @GetMapping("/port")
   public String getPort() {
      return "server port is :" + serverPort;
   }
}
ResourceServerApplication 啓動類

啓動 eureka

啓動 auth-service

啓動 resource-service

四個模式按個測試下

1.密碼模式

postman 訪問 

http://localhost:20110/oauth/token?client_id=user&client_secret=style&grant_type=password&username=user&password=123456

2.客戶端憑證模式

http://localhost:20110/oauth/token?client_id=user&client_secret=style&grant_type=client_credentials&scope=read

3.授權碼模式

瀏覽器請求

獲取code 

 http://localhost:20110/oauth/authorize?response_type=code&redirect_uri=https://www.baidu.com&client_id=user&scop=all

跳轉登錄界面

輸入賬號密碼進行登錄

重定向 到 https://www.baidu.com/?code=Zs97m8  並且攜帶了code碼

下一步根據 code 進行獲取token

請求地址 

http://localhost:20110/oauth/token?code=Zs97m8&grant_type=authorization_code&redirect_uri=https://www.baidu.com&scope=all&client_id=user&client_secret=style

 

4.簡化模式

http://localhost:20110/oauth/authorize?response_type=token&client_id=user&redirect_uri=https://www.baidu.com

首先重定向到登錄頁面 

登錄成功 重定向到設置的重定向 地址中

改地址已經存在了 access_token 

https://www.baidu.com/#access_token=eb96d104-cec7-4d2e-9da6-6fd8f2851446&token_type=bearer&expires_in=1031&scope=all%20read%20write

 

刷新token 接口 

http://localhost:20110/oauth/token?client_id=user&client_secret=style&grant_type=refresh_token&refresh_token=819e651a-b704-4bce-9147-532c5fc06ada

驗證下 resource_serivice 

該接口爲resource_service中定義的一個接口 位於ResourceController類中 

第一次訪問沒有攜帶token 進行訪問 提示錯誤 

第二次 

 

亦或者 

請求頭裏添加 Authorization value爲 bearer{空格}+token

源碼見 https://github.com/passliang/panda

參考知乎文章https://zhuanlan.zhihu.com/p/84670338

https://www.cnblogs.com/Innocent-of-Dabber/p/11009811.html

 

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