Spring Security OAuth2:認證服務器實現


授權碼模式

創建父工程

cloud-oauth2-parent

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wj</groupId>
    <artifactId>cloud-oauth2-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>cloud-oauth2-base</module>
        <module>cloud-oauth2-auth-server</module>
    </modules>
    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <mybatis-plus.version>3.2.0</mybatis-plus.version>
        <druid.version>1.1.12</druid.version>
        <kaptcha.version>2.3.2</kaptcha.version>
        <fastjson.version>1.2.8</fastjson.version>
        <commons-lang.version>2.6</commons-lang.version>
        <commons-collections.version>3.2.2</commons-collections.version>
        <commons-io.version>2.6</commons-io.version>
        <!-- 定義版本號, 子模塊直接引用-->
        <oauth-security.version>1.0-SNAPSHOT</oauth-security.version>
    </properties>


    <!--依賴聲明-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <!--maven不支持多繼承,使用import來依賴管理配置-->
                <scope>import</scope>
            </dependency>
            <!--mybatis-plus啟動器-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!--druid連接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!-- kaptcha 用於圖形驗證碼 -->
            <dependency>
                <groupId>com.github.penggle</groupId>
                <artifactId>kaptcha</artifactId>
                <version>${kaptcha.version}</version>
            </dependency>
            <!-- 工具類依賴 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>

            <dependency>
                <groupId>commons-lang</groupId>
                <artifactId>commons-lang</artifactId>
                <version>${commons-lang.version}</version>
            </dependency>
            <dependency>
                <groupId>commons-collections</groupId>
                <artifactId>commons-collections</artifactId>
                <version>${commons-collections.version}</version>
            </dependency>
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commons-io.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <!--springboot 打包插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.2.4.RELEASE</version>
            </plugin>
        </plugins>
    </build>
</project>

創建子工程

cloud-oauth2-base

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-oauth2-parent</artifactId>
        <groupId>com.wj</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-oauth2-base</artifactId>

    <dependencies>
        <!--類中setter/getter,使用注解-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- 工具類依賴 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>
    </dependencies>
</project>

創建通用返回類:com.wj.base.result.R

@Data
public class R implements Serializable {

    // 響應業務狀態
    private Integer code;

    // 響應消息
    private String message;

    // 響應中的數據
    private Object data;

    public R() {
    }
    public R(Object data) {
        this.code = 200;
        this.message = "OK";
        this.data = data;
    }
    public R(String message, Object data) {
        this.code = 200;
        this.message = message;
        this.data = data;
    }

    public R(Integer code, String message, Object data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static R ok() {
        return new R(null);
    }
    public static R ok(String message) {
        return new R(message, null);
    }
    public static R ok(Object data) {
        return new R(data);
    }
    public static R ok(String message, Object data) {
        return new R(message, data);
    }

    public static R build(Integer code, String message) {
        return new R(code, message, null);
    }

    public static R build(Integer code, String message, Object data) {
        return new R(code, message, data);
    }

    public String toJsonString() {
        return JSON.toJSONString(this);
    }

    /**
     * JSON字符串轉成 R 對象
     */
    public static R format(String json) {
        try {
            return JSON.parseObject(json, R.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

cloud-oauth2-auth-server

基本配置

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-oauth2-parent</artifactId>
        <groupId>com.wj</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-oauth2-auth-server</artifactId>

    <dependencies>
        <dependency>
            <artifactId>cloud-oauth2-base</artifactId>
            <groupId>com.wj</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--spring mvc相關的-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Security、OAuth2 和JWT等 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <!-- 注冊到 Eureka
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        -->

        <!-- redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--mybatis-plus啟動器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.2.4.RELEASE</version>
                <configuration>
                    <mainClass>com.wj.oauth2.AuthServerApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

server:
  port: 8090
  servlet:
    context-path: /auth
spring:
  datasource:
    username: root
    password: 1234
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/study-security?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8

springboot主啟動類com.wj.oauth2.AuthServerApplication:

@SpringBootApplication
public class AuthServerApplication {

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

}
配置類

統一管理Bean配置類: SpringSecurityBean

@Configuration
public class SpringSecurityBean {
    @Bean  //引入PasswordEncoder
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

SpringSecurity配置類:

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 內存方式存儲用戶信息,這里為了方便就不從數據庫中查詢了
        auth.inMemoryAuthentication().withUser("admin")
                .password(passwordEncoder.encode("1234"))
                .authorities("product");
    }
}

認證服務器配置類:AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer//開啟認證服務器功能
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**  配置被允許訪問此認證服務器的客戶端詳情信息
     * 方式1:內存方式管理
     * 方式2:數據庫管理
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 使用內存方式
        clients.inMemory()
                // 客戶端id
                .withClient("wj-pc")
        // 客戶端密碼,要加密,不然一直要求登錄
        .secret(passwordEncoder.encode("wj-secret"))
        // 資源id, 如商品資源
        .resourceIds("product-server")
        // 授權類型, 可同時支持多種授權類型
        .authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
        // 授權范圍標識,哪部分資源可訪問(all是標識,不是代表所有)
        .scopes("all")
        // false 跳轉到授權頁面手動點擊授權,true 不用手動授權,直接響應授權碼,
        .autoApprove(false)
        .redirectUris("http://www.baidu.com/");// 客戶端回調地址
    }
}

配置說明:

withClient:允許訪問此認證服務器的客戶端id , 如:PC、APP、小程序各不同的的客戶端id。

secret:客戶端密碼,要加密存儲,不然獲取不到令牌一直要求登錄,, 而且一定不能被泄露。

authorizedGrantTypes: 授權類型, 可同時支持多種授權類型:可配置:"authorization_code", "password", "implicit","client_credentials","refresh_token"

scopes:授權范圍標識,如指定微服務名稱,則只能訪問指定的微服務。

autoApprove:false 跳轉到授權頁面手動點擊授權,true 不用手動授權,直接響應授權碼

redirectUris 當獲取授權碼后,認證服務器會重定向到這個URI,並且帶着一個授權碼code響應回來

令牌訪問端點

Spring Security 對 OAuth2 默認提供了可直接訪問端點,即URL:

/oauth/authorize:申請授權碼 code, 涉及的類AuthorizationEndpoint

/oauth/token:獲取令牌 token, 涉及的類TokenEndpoint/oauth/check_token:用於資源服務器請求端點來檢查令牌是否有效, 涉及的類CheckTokenEndpoint

/oauth/confirm_access:用戶確認授權提交, 涉及的類WhitelabelApprovalEndpoint

/oauth/error:授權服務錯誤信息, 涉及的類WhitelabelErrorEndpoint

/oauth/token_key:提供公有密匙的端點,使用 JWT 令牌時會使用 , 涉及的類TokenKeyEndpoint

測試

發送請求獲取授權碼code

訪問:localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code

這里的client_id是在AuthorizationServerConfig中配置的。

輸入賬號密碼進行登陸,賬號和密碼:admin/1234 ,是在SpringSecurityConfig中配置的

image-20210308172218218

登陸成功后,選擇Approve,點擊Authorize,這里跳轉到www.baidu.com ,並且后面攜帶了code,這里的code就是授權碼,后面我們就可以通過授權碼來獲取令牌(access_token)

image-20210308172256770

通過授權碼獲取令牌

使用postman測試:http://localhost:8090/auth/oauth/token

image-20210308172928031

這里的username和password是在AuthorizationServerConfig中配置的

設置為post請求,並設置請求體

image-20210308175033878

這里的grant_type是authorization_code,code是上一步獲取的code

發送請求后,獲得了access_token

image-20210308175522928

注意,code只能獲取一次access_token,獲取后就會失效,第二次獲取就會失敗

image-20210308175604772

密碼授權模式

密碼模式(Resource Owner Password Credentials Grant)中,用戶向客戶端提供自己在服務提供商(認證服務器)上的用戶名和密碼,然后客戶端通過用戶提供的用戶名和密碼向服務提供商(認證服務器)獲取令牌。

如果用戶名和密碼遺漏,服務提供商(認證服務器)無法判斷客戶端提交的用戶和密碼是否盜取來的,那意味着令牌就可隨時獲取,數據被丟失。

所以密碼授權模式適用於產品都是企業內部的,用戶名密碼共享不要緊。如果是第三方這種不太適合。也適用手機APP提交用戶名密碼。

配置密碼模式

修改SpringSecurityConfig配置類:

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 內存方式存儲用戶信息
        auth.inMemoryAuthentication().withUser("admin")
                .password(passwordEncoder.encode("1234"))
                .authorities("product");
    }

    /**
     * password密碼模式需要使用此認證管理器
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

修改AuthorizationServerConfig類:

@Configuration
@EnableAuthorizationServer//開啟認證服務器功能
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    /**  配置被允許訪問此認證服務器的客戶端詳情信息
     * 方式1:內存方式管理
     * 方式2:數據庫管理
     * localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 使用內存方式
        clients.inMemory()
                // 客戶端id
                .withClient("wj-pc")
        // 客戶端密碼,要加密,不然一直要求登錄, 獲取不到令牌, 而且一定不能被泄露
        .secret(passwordEncoder.encode("wj-secret"))
        // 資源id, 如商品資源
        .resourceIds("product-server")
        // 授權類型, 可同時支持多種授權類型
        .authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
        // 授權范圍標識,哪部分資源可訪問(all是標識,不是代表所有)
        .scopes("all")
        // false 跳轉到授權頁面手動點擊授權,true 不用手動授權,直接響應授權碼,
        .autoApprove(false)
        .redirectUris("http://www.baidu.com/");// 客戶端回調地址
    }

    /**
     * 重寫父類的方法
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //密碼模式需要設置此認證管理器
        endpoints.authenticationManager(authenticationManager);
    }
}

測試使用

使用postman測試:

訪問:http://localhost:8090/auth/oauth/token

image-20210309092223859

填上請求參數:

image-20210309092340231

發送請求即可獲取到access_token:

image-20210309092419351

簡化模式

不通過第三方應用程序的服務器,直接在瀏覽器中向認證服務器申請令牌,不需要先獲取授權碼。直接可以一次請求就可得到令牌,在redirect_uri指定的回調地址中傳遞令牌( access_token )。該模式適合直接運行在瀏覽器上的應用,不用后端支持(例如 Javascript 應用)

注意:只需要客戶端id,客戶端密碼都不需要。

瀏覽器直接訪問:localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=token

先登錄:

image-20210309092818515

登陸成功並授權后,直接跳轉到指定頁面,並在uri中返回了access_token

image-20210309092920599

注意

  • 簡化模式不允許按照 OAuth2 規范發布刷新令牌(refresh token)。這種行為是有必要的,它要求在使用運行在瀏覽器中的程序時,用戶必須在場,這樣可以在任何需要的時候,給第三方應用授權。

  • 當使用簡化模式時,第三方應用始終需要通過重定向URI來注冊,這樣能確保不會將token傳給不需要驗證的客戶端。如果不這樣做,一些心懷不軌的用戶可能先注冊一個應用,然后試圖讓其他的應用來頂替,接收這個 token,這樣可能導致災難性的結果

客戶端授權模式

客戶端模式(Client Credentials Grant)指客戶端以自己的名義,而不是以用戶的名義,向服務提供商(認證服務器)進行認證。嚴格地說,客戶端模式並不屬於OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求服務提供商(認證服務器)提供服務,其實不存在授權問題。

  • 客戶端向認證服務器進行身份認證,並要求一個訪問令牌。

  • 認證服務器確認無誤后,向客戶端提供訪問令牌

測試

post測試:http://localhost:8090/auth/oauth/token

image-20210309093435745

grant_type等於client_credentials

image-20210309093543715

發送請求返回了access_token,注意響應結果沒有刷新令牌的

image-20210309093620031


免責聲明!

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



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