Spring Cloud進階之路 | 八:授權服務(Spring Cloud Oauth2)


​轉載請注明作者及出處:

作者:銀河架構師

原文鏈接:https://www.cnblogs.com/luas/p/12201382.html

 

oauth2簡介

OAuth(開放授權)是一個開放標准,允許用戶授權第三方應用訪問他們存儲在另外的服務提供者上的信息,而不需要將用戶名和密碼提供給第三方應用或分享他們數據的所有內容。

OAuth 2 定義了四種 Grant Type,授權碼模式(authorization code)、簡化模式(implicit)、密碼模式(Password)、客戶端模式(client credentials),每一種都有適用的應用場景。

運行流程如下所示:

至於每種模式如何運行,需要什么參數,本文不再贅述,大家可自行查閱相關資料,做到爛熟於心。

 

微服務安全策略

在傳統單體Web應用架構中,身份認證從來都不是問題,通過Spring Security或者Shiro,配合session,可以很方便的解決身份認證和鑒權的問題。

隨着不斷擴大的業務需求,在傳統單體架構捉襟見肘的時候,分布式架構應運而生,分布式架構強調的是服務化以及服務的分散化,大大地提升了系統的可靠性和響應速度。

可是,技術並沒有因此而停滯不前,微服務架構又出現了。微服務架構更強調服務的專業化、精細化,更加強調單一職責、輕量級通信(HTTP)、獨立性並且進程隔離。

隨着應用架構的改變,身份認證的方式也在發生變化,為了適應架構的變化、需求的變化,身份認證與鑒權方案也需要不斷的變革。

拋開鑒權不談,先說一下身份認證。David Borsos 在倫敦的微服務大會上提出的四種方案,單點登錄(SSO)、分布式session、客戶端 Token、客戶端 Token 與 API 網關結合。其中,后兩者頗受開發者歡迎,也很適合微服務架構。然而,無論后兩者中的哪一種方案,均需一個獨立的授權服務器配合,才能發放token,進而協助資源服務器進行身份認證。

 

搭建授權服務器


基於Spring Cloud Oauth2框架搭建基本的授權服務。

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion><parent>
        <groupId>com.luas.cloud</groupId>
        <artifactId>java-boot-parent-2.1</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../../java-boot-parent-2.1</relativePath>
    </parent><groupId>com.luas.xmall</groupId>
    <artifactId>xmall-auth</artifactId>
    <version>1.0.0-SNAPSHOT</version><name>xmall-auth</name>
    <description>xmall authorization center</description><properties>
    </properties><dependencies>
        <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><!-- nacos cloud -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency><dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency><dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies><build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build></project>

 

動態配置

同前文所述,bootstrap.yml中配置nacos發現地址、配置中心地址,實現服務注冊及配置遠程獲取、動態刷新。后續此類配置均相同,不再贅述。

spring:
  application:
    name: xmall-auth
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
      discovery:
        server-addr: 127.0.0.1:8848

 

AuthorizationServer

AuthorizationServer主要配置有三大類,安全配置、端點配置、客戶端配置

安全配置主要針對授權服務器端點的訪問策略、認證策略、加密方式等進行配置。

端點配置主要配置授權服務器的token存儲方式、token轉換、端點增強、端點自定義、token授權、token生成等進行配置。

客戶端配置主要配置接入的客戶端相關信息,如授權類型、授權范圍、秘鑰等內容。

package com.luas.xmall.auth.configuration;
​
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.builders.JdbcClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
​
import java.util.ArrayList;
import java.util.List;
​
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
​
    @Autowired
    private PasswordEncoder passwordEncoder;
​
    @Autowired
    private UserDetailsService userDetailsService;
​
    @Autowired
    private AuthenticationManager authenticationManager;
​
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.
                allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
        ;
    }
​
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(new InMemoryTokenStore())
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    }
​
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        String client_secret = passwordEncoder.encode("123456");
​
        clients
                .inMemory()
                // admin,授權碼認證、密碼認證、客戶端認證、簡單認證、刷新token
                .withClient("admin")
                .secret(client_secret)
                .resourceIds("xmall-auth", "xmall-product")
                .scopes("server", "select")
                .authorizedGrantTypes("authorization_code", "password", "refresh_token", "client_credentials", "implicit")
                .redirectUris("http://www.baidu.com")
​
                .and()
                // client_1,密碼認證、刷新token
                .withClient("client_1")
                .secret(client_secret)
                .resourceIds("xmall-auth", "xmall-product")
                .scopes("server", "select")
                .authorizedGrantTypes("password", "refresh_token")
​
                .and()
                // client_2,客戶端認證、刷新token
                .withClient("client_2")
                .secret(client_secret)
                .resourceIds("xmall-auth", "xmall-product")
                .scopes("server", "select")
                .authorizedGrantTypes("client_credentials", "refresh_token");
    }
​
}

ResourceServer

主要負責針對資源服務器的安全訪問策略進行相關配置。

為什么授權服務器還需要進行資源服務器相關配置?

舉一個最簡單的例子。比如github,第三方接入之后,發起請求,用戶跳轉到github進行登錄並授權,這時,接入方可以得到授權,但是,並不知道用戶的具體信息,需要帶着授權去詢問github,當前用戶的相關信息。

github進行相關身份認證之后,將可授權的用戶信息返回給接入方。這個獲取當前用戶相關信息的接口端點,是為資源。既然是資源,就要受資源服務器管轄,進而就需要進行資源服務器配置。

package com.luas.xmall.auth.configuration;
​
import org.springframework.context.annotation.Configuration;
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;
​
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("xmall-auth").stateless(true);
    }
​
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers()
                .antMatchers("/oauth/user")
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
    }
}

注意,用戶相關信息的接口端點必須由ResourceServer安全策略攔截。

 

SpringSecurity配置

老套路了,毋庸置疑需要配置。可前兩個都進行了SpringSecurity相關配置,為何還需要單獨針對SpringSecurity再進行配置呢?

答案之一就在於授權模式中的授權碼模式,后續會出相關文章,詳細說明這個問題。此外,還需配置UserDetailsService、PasswordEncoder等內容。

package com.luas.xmall.auth.configuration;
​
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
​
import java.util.ArrayList;
import java.util.List;
​
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
​
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers()
                .anyRequest()
                .and()
                .formLogin()
                .and()
                .csrf().disable();
    }
​
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
​
    @Bean(name = "userDetailsService")
    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return createUserDetailsService();
    }
​
    @Override
    protected UserDetailsService userDetailsService() {
        return createUserDetailsService();
    }
​
    private UserDetailsService createUserDetailsService() {
        String password = passwordEncoder().encode("123456");
​
        List<UserDetails> users = new ArrayList<>();
​
        UserDetails user_admin = User.withUsername("admin").password(password).authorities("ADMIN", "USER").build();
        UserDetails user_1 = User.withUsername("user_1").password(password).authorities("ADMIN", "USER").build();
        UserDetails user_2 = User.withUsername("user_2").password(password).authorities("USER").build();
​
        users.add(user_admin);
        users.add(user_1);
        users.add(user_2);
​
        return new InMemoryUserDetailsManager(users);
    }
​
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

 

用戶信息端點

為接入方提供用戶信息查詢端點,查詢當前認證用戶的相關信息。

package com.luas.xmall.auth.controller;
​
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
import java.security.Principal;
​
@RestController
@RequestMapping("/oauth")
public class UserController {
​
    @RequestMapping("/user")
    public Principal user(Principal user) {
        return user;
    }
​
}

 

啟動

端口配置為7777,啟動服務。

 

授權


本文使用Postman工具,模擬rest請求。

訪問http://localhost:7777/oauth/token,輸入一眾參數,諸如client_id、client_secret、grant_type等,點擊Send,即出現access_token、refresh_token等信息。

用戶信息

拿着上一步獲取的授權,訪問http://localhost:7777/oauth/user,查詢當前用戶的相關信息。

注意,要添加授權token,本文采用header方式(header方式添加授權token,即在HttpHeader中添加header Authentication,內容為Bearer+空格+token),如下所示:

授權服務器搭建成功!

 

 

源碼


github

https://github.com/liuminglei/SpringCloudLearning/tree/master/08

gitee

https://gitee.com/xbd521/SpringCloudLearning/tree/master/08

 

 

微信搜索【銀河架構師】,發現更多精彩內容。

技術資料領取方法:關注公眾號,回復微服務,領取微服務相關電子書;回復MK精講,領取MK精講系列電子書;回復JAVA 進階,領取JAVA進階知識相關電子書;回復JAVA面試,領取JAVA面試相關電子書,回復JAVA WEB領取JAVA WEB相關電子書。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM