国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Spring Authorization Server入門 (二) Spring Boot整合Spring Authorization Server

這篇具有很好參考價(jià)值的文章主要介紹了Spring Authorization Server入門 (二) Spring Boot整合Spring Authorization Server。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

在新版本的sas(1.2.1)中獲取token更新了授權(quán)碼校驗(yàn)邏輯,只能用form-data傳遞參數(shù),使用url-params會(huì)失敗,原因見(jiàn)issue1451
對(duì)應(yīng)的 commit 在這里: Fix to ensure endpoints distinguish between form and query parameters

前言

文章較長(zhǎng),步驟比較繁瑣,請(qǐng)各位讀者耐心觀看。
上篇文章大概了解了下框架的相關(guān)理論,本篇文章將帶大家一步步構(gòu)建一個(gè)簡(jiǎn)單的認(rèn)證服務(wù)器
開(kāi)始之前先放一下文檔的鏈接:官網(wǎng)文檔

項(xiàng)目環(huán)境要求(當(dāng)前框架版本1.1.0)

  1. Spring Boot版本大于等于3.1.0-RC1
  2. JDK版本大于等于17

認(rèn)證項(xiàng)目搭建

1. 在Idea中或Spring Initializr中創(chuàng)建spring boot項(xiàng)目

  1. Spring Boot版本選擇3.1.0,Java版本選擇17以上,在Dependencies中勾選Spring Authorization Server和spring web依賴,其它看自己需要
    spring-authorization-server,Spring Authorization Server,Spring Security,Spring,spring boot,spring,java

    引入持久層框架(本人用的是MybatisPlus,讀者可自選)

     <dependency>
         <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-boot-starter</artifactId>
         <version>3.5.3.1</version>
     </dependency>
    

    引入webjars和bootstrap,自定義登錄頁(yè)和確認(rèn)頁(yè)面時(shí)使用

    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>webjars-locator-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>bootstrap</artifactId>
        <version>5.2.3</version>
    </dependency>
    

    項(xiàng)目pom.xml示例

    <?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>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.1.0</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>authorization-example</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>authorization-example</name>
        <description>authorization-example</description>
        <properties>
            <java.version>17</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.5.3.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.webjars</groupId>
                <artifactId>webjars-locator-core</artifactId>
            </dependency>
            <dependency>
                <groupId>org.webjars</groupId>
                <artifactId>bootstrap</artifactId>
                <version>5.2.3</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    
  2. 初始化框架自帶數(shù)據(jù)庫(kù)表

    schema位置如圖
    spring-authorization-server,Spring Authorization Server,Spring Security,Spring,spring boot,spring,java

    修改后適配MySQL的SQL如下

    -- 用戶授權(quán)確認(rèn)表
    CREATE TABLE oauth2_authorization_consent
    (
        registered_client_id varchar(100)  NOT NULL,
        principal_name       varchar(200)  NOT NULL,
        authorities          varchar(1000) NOT NULL,
        PRIMARY KEY (registered_client_id, principal_name)
    );
    -- 用戶認(rèn)證信息表
    CREATE TABLE oauth2_authorization
    (
        id                            varchar(100) NOT NULL,
        registered_client_id          varchar(100) NOT NULL,
        principal_name                varchar(200) NOT NULL,
        authorization_grant_type      varchar(100) NOT NULL,
        authorized_scopes             varchar(1000) DEFAULT NULL,
        attributes                    blob          DEFAULT NULL,
        state                         varchar(500)  DEFAULT NULL,
        authorization_code_value      blob          DEFAULT NULL,
        authorization_code_issued_at  DATETIME      DEFAULT NULL,
        authorization_code_expires_at DATETIME      DEFAULT NULL,
        authorization_code_metadata   blob          DEFAULT NULL,
        access_token_value            blob          DEFAULT NULL,
        access_token_issued_at        DATETIME      DEFAULT NULL,
        access_token_expires_at       DATETIME      DEFAULT NULL,
        access_token_metadata         blob          DEFAULT NULL,
        access_token_type             varchar(100)  DEFAULT NULL,
        access_token_scopes           varchar(1000) DEFAULT NULL,
        oidc_id_token_value           blob          DEFAULT NULL,
        oidc_id_token_issued_at       DATETIME      DEFAULT NULL,
        oidc_id_token_expires_at      DATETIME      DEFAULT NULL,
        oidc_id_token_metadata        blob          DEFAULT NULL,
        refresh_token_value           blob          DEFAULT NULL,
        refresh_token_issued_at       DATETIME      DEFAULT NULL,
        refresh_token_expires_at      DATETIME      DEFAULT NULL,
        refresh_token_metadata        blob          DEFAULT NULL,
        user_code_value               blob          DEFAULT NULL,
        user_code_issued_at           DATETIME      DEFAULT NULL,
        user_code_expires_at          DATETIME      DEFAULT NULL,
        user_code_metadata            blob          DEFAULT NULL,
        device_code_value             blob          DEFAULT NULL,
        device_code_issued_at         DATETIME      DEFAULT NULL,
        device_code_expires_at        DATETIME      DEFAULT NULL,
        device_code_metadata          blob          DEFAULT NULL,
        PRIMARY KEY (id)
    );
    -- 客戶端表
    CREATE TABLE oauth2_registered_client
    (
        id                            varchar(100)                            NOT NULL,
        client_id                     varchar(100)                            NOT NULL,
        client_id_issued_at           DATETIME      DEFAULT CURRENT_TIMESTAMP NOT NULL,
        client_secret                 varchar(200)  DEFAULT NULL,
        client_secret_expires_at      DATETIME      DEFAULT NULL,
        client_name                   varchar(200)                            NOT NULL,
        client_authentication_methods varchar(1000)                           NOT NULL,
        authorization_grant_types     varchar(1000)                           NOT NULL,
        redirect_uris                 varchar(1000) DEFAULT NULL,
        post_logout_redirect_uris     varchar(1000) DEFAULT NULL,
        scopes                        varchar(1000)                           NOT NULL,
        client_settings               varchar(2000)                           NOT NULL,
        token_settings                varchar(2000)                           NOT NULL,
        PRIMARY KEY (id)
    );
    

2. 在config包下創(chuàng)建AuthorizationConfig類,并添加配置

配置端點(diǎn)的過(guò)濾器鏈

/**
 * 配置端點(diǎn)的過(guò)濾器鏈
 *
 * @param http spring security核心配置類
 * @return 過(guò)濾器鏈
 * @throws Exception 拋出
 */
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    // 配置默認(rèn)的設(shè)置,忽略認(rèn)證端點(diǎn)的csrf校驗(yàn)
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
        // 開(kāi)啟OpenID Connect 1.0協(xié)議相關(guān)端點(diǎn)
        .oidc(Customizer.withDefaults())
        // 設(shè)置自定義用戶確認(rèn)授權(quán)頁(yè)
        .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI));
    http
        // 當(dāng)未登錄時(shí)訪問(wèn)認(rèn)證端點(diǎn)時(shí)重定向至login頁(yè)面
        .exceptionHandling((exceptions) -> exceptions
            .defaultAuthenticationEntryPointFor(
            new LoginUrlAuthenticationEntryPoint("/login"),
            new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
            )
        )
        // 處理使用access token訪問(wèn)用戶信息端點(diǎn)和客戶端注冊(cè)端點(diǎn)
        .oauth2ResourceServer((resourceServer) -> resourceServer
        .jwt(Customizer.withDefaults()));

    return http.build();
}

配置身份驗(yàn)證過(guò)濾器鏈

/**
 * 配置認(rèn)證相關(guān)的過(guò)濾器鏈
 *
 * @param http spring security核心配置類
 * @return 過(guò)濾器鏈
 * @throws Exception 拋出
 */
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests((authorize) -> authorize
            // 放行靜態(tài)資源
            .requestMatchers("/assets/**", "/webjars/**", "/login").permitAll()
            .anyRequest().authenticated()
        )
        // 指定登錄頁(yè)面
        .formLogin(formLogin ->
            formLogin.loginPage("/login")
        );
    // 添加BearerTokenAuthenticationFilter,將認(rèn)證服務(wù)當(dāng)做一個(gè)資源服務(wù),解析請(qǐng)求頭中的token
    http.oauth2ResourceServer((resourceServer) -> resourceServer
        .jwt(Customizer.withDefaults()));

    return http.build();
}

配置密碼解析器

/**
 * 配置密碼解析器,使用BCrypt的方式對(duì)密碼進(jìn)行加密和驗(yàn)證
 *
 * @return BCryptPasswordEncoder
 */
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

配置客戶端repository

/**
 * 配置客戶端Repository
 *
 * @param jdbcTemplate    db 數(shù)據(jù)源信息
 * @param passwordEncoder 密碼解析器
 * @return 基于數(shù)據(jù)庫(kù)的repository
 */
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
    RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
        // 客戶端id
        .clientId("messaging-client")
        // 客戶端秘鑰,使用密碼解析器加密
        .clientSecret(passwordEncoder.encode("123456"))
        // 客戶端認(rèn)證方式,基于請(qǐng)求頭的認(rèn)證
        .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
        // 配置資源服務(wù)器使用該客戶端獲取授權(quán)時(shí)支持的方式
        .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
        .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
        .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
        // 授權(quán)碼模式回調(diào)地址,oauth2.1已改為精準(zhǔn)匹配,不能只設(shè)置域名,并且屏蔽了localhost
        .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
        // 配置一個(gè)百度的域名回調(diào),稍后使用該回調(diào)獲取code
        .redirectUri("https://www.baidu.com")
        // 該客戶端的授權(quán)范圍,OPENID與PROFILE是IdToken的scope,獲取授權(quán)時(shí)請(qǐng)求OPENID的scope時(shí)認(rèn)證服務(wù)會(huì)返回IdToken
        .scope(OidcScopes.OPENID)
        .scope(OidcScopes.PROFILE)
        // 自定scope
        .scope("message.read")
        .scope("message.write")
        // 客戶端設(shè)置,設(shè)置用戶需要確認(rèn)授權(quán)
        .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
        .build();

    // 基于db存儲(chǔ)客戶端,還有一個(gè)基于內(nèi)存的實(shí)現(xiàn) InMemoryRegisteredClientRepository
    JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);

    // 初始化客戶端
    RegisteredClient repositoryByClientId = registeredClientRepository.findByClientId(registeredClient.getClientId());
    if (repositoryByClientId == null) {
        registeredClientRepository.save(registeredClient);
    }
    // 設(shè)備碼授權(quán)客戶端
    RegisteredClient deviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
        .clientId("device-message-client")
        // 公共客戶端
        .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
        // 設(shè)備碼授權(quán)
        .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
        .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
        // 自定scope
        .scope("message.read")
        .scope("message.write")
        .build();
    RegisteredClient byClientId = registeredClientRepository.findByClientId(deviceClient.getClientId());
    if (byClientId == null) {
        registeredClientRepository.save(deviceClient);
    }
    return registeredClientRepository;
}

客戶端設(shè)置(ClientSettings)說(shuō)明

  1. requireProofKey:當(dāng)使用該客戶端發(fā)起PKCE流程時(shí)必須設(shè)置為true。
  2. requireAuthorizationConsent:當(dāng)設(shè)置為true時(shí)登錄后會(huì)先跳轉(zhuǎn)授權(quán)確認(rèn)頁(yè)面,確認(rèn)后才會(huì)跳轉(zhuǎn)到redirect_uri,為false時(shí)不會(huì)跳轉(zhuǎn)至授權(quán)確認(rèn)頁(yè)面。
  3. jwkSetUrl:設(shè)置客戶端jwks的url。
  4. tokenEndpointAuthenticationSigningAlgorithm:設(shè)置token端點(diǎn)對(duì)驗(yàn)證方法為CLIENT_SECRET_JWT,PRIVATE_KEY_JWT的客戶端進(jìn)行身份驗(yàn)證使用的簽名算法。

token設(shè)置(TokenSettings)說(shuō)明

  1. authorizationCodeTimeToLive:授權(quán)碼(authorization_code)有效時(shí)長(zhǎng)。
  2. accessTokenTimeToLive:access_token有效時(shí)長(zhǎng)。
  3. accessTokenFormat:access_token的格式,SELF_CONTAINED是自包含token(jwt格式),REFERENCE是不透明token,相相當(dāng)于是token元數(shù)據(jù)的一個(gè)id,通過(guò)id找到對(duì)應(yīng)數(shù)據(jù)(自省令牌時(shí)),如下
public final class OAuth2TokenFormat implements Serializable {
    private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;

	/**
	 * Self-contained tokens use a protected, time-limited data structure that contains token metadata
	 * and claims of the user and/or client. JSON Web Token (JWT) is a widely used format.
	 */
	public static final OAuth2TokenFormat SELF_CONTAINED = new OAuth2TokenFormat("self-contained");

	/**
	 * Reference (opaque) tokens are unique identifiers that serve as a reference
	 * to the token metadata and claims of the user and/or client, stored at the provider.
	 */
	public static final OAuth2TokenFormat REFERENCE = new OAuth2TokenFormat("reference");

}
  1. deviceCodeTimeToLive:設(shè)備碼有效時(shí)長(zhǎng)。
  2. reuseRefreshTokens:刷新token時(shí)是否重用refresh token,設(shè)置為true后refresh token不變,false刷新token時(shí)會(huì)重新簽發(fā)一個(gè)refresh token。
  3. refreshTokenTimeToLive:refresh token有效時(shí)長(zhǎng)。
  4. idTokenSignatureAlgorithm:設(shè)置id token的加密算法。

如果數(shù)據(jù)庫(kù)已經(jīng)存在客戶端數(shù)據(jù)或不需要默認(rèn)設(shè)置,則直接注入一個(gè)JdbcRegisteredClientRepository即可


/**
 * 配置客戶端Repository
 *
 * @param jdbcTemplate    db 數(shù)據(jù)源信息
 * @return 基于數(shù)據(jù)庫(kù)的repository
 */
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
    return new JdbcRegisteredClientRepository(jdbcTemplate);
}

配置授權(quán)管理服務(wù)

/**
 * 配置基于db的oauth2的授權(quán)管理服務(wù)
 *
 * @param jdbcTemplate               db數(shù)據(jù)源信息
 * @param registeredClientRepository 上邊注入的客戶端repository
 * @return JdbcOAuth2AuthorizationService
 */
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
    // 基于db的oauth2認(rèn)證服務(wù),還有一個(gè)基于內(nèi)存的服務(wù)InMemoryOAuth2AuthorizationService
    return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}

配置授權(quán)確認(rèn)管理服務(wù)

/**
 * 配置基于db的授權(quán)確認(rèn)管理服務(wù)
 *
 * @param jdbcTemplate               db數(shù)據(jù)源信息
 * @param registeredClientRepository 客戶端repository
 * @return JdbcOAuth2AuthorizationConsentService
 */
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
    // 基于db的授權(quán)確認(rèn)管理服務(wù),還有一個(gè)基于內(nèi)存的服務(wù)實(shí)現(xiàn)InMemoryOAuth2AuthorizationConsentService
    return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}

配置jwk

/**
 * 配置jwk源,使用非對(duì)稱加密,公開(kāi)用于檢索匹配指定選擇器的JWK的方法
 *
 * @return JWKSource
 */
@Bean
public JWKSource<SecurityContext> jwkSource() {
    KeyPair keyPair = generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    RSAKey rsaKey = new RSAKey.Builder(publicKey)
        .privateKey(privateKey)
        .keyID(UUID.randomUUID().toString())
        .build();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
}

/**
 * 生成rsa密鑰對(duì),提供給jwk
 *
 * @return 密鑰對(duì)
 */
private static KeyPair generateRsaKey() {
    KeyPair keyPair;
    try {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        keyPair = keyPairGenerator.generateKeyPair();
    } catch (Exception ex) {
        throw new IllegalStateException(ex);
    }
    return keyPair;
}

配置jwt解析器

/**
 * 配置jwt解析器
 *
 * @param jwkSource jwk源
 * @return JwtDecoder
 */
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
    return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}

配置認(rèn)證服務(wù)器設(shè)置

/**
 * 添加認(rèn)證服務(wù)器配置,設(shè)置jwt簽發(fā)者、默認(rèn)端點(diǎn)請(qǐng)求地址等
 *
 * @return AuthorizationServerSettings
 */
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder().build();
}

配置一個(gè)基于內(nèi)存的默認(rèn)用戶

/**
 * 先暫時(shí)配置一個(gè)基于內(nèi)存的用戶,框架在用戶認(rèn)證時(shí)會(huì)默認(rèn)調(diào)用
 * {@link UserDetailsService#loadUserByUsername(String)} 方法根據(jù)
 * 賬號(hào)查詢用戶信息,一般是重寫該方法實(shí)現(xiàn)自己的邏輯
 *
 * @param passwordEncoder 密碼解析器
 * @return UserDetailsService
 */
@Bean
public UserDetailsService users(PasswordEncoder passwordEncoder) {
    UserDetails user = User.withUsername("admin")
        .password(passwordEncoder.encode("123456"))
        .roles("admin", "normal")
        .authorities("app", "web")
        .build();
    return new InMemoryUserDetailsManager(user);
}

完整的AuthorizationConfig.java如下

package com.example.config;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;

/**
 * 認(rèn)證配置
 * {@link EnableMethodSecurity} 開(kāi)啟全局方法認(rèn)證,啟用JSR250注解支持,啟用注解 {@link Secured} 支持,
 * 在Spring Security 6.0版本中將@Configuration注解從@EnableWebSecurity, @EnableMethodSecurity, @EnableGlobalMethodSecurity
 * 和 @EnableGlobalAuthentication 中移除,使用這些注解需手動(dòng)添加 @Configuration 注解
 * {@link EnableWebSecurity} 注解有兩個(gè)作用:
 * 1. 加載了WebSecurityConfiguration配置類, 配置安全認(rèn)證策略。
 * 2. 加載了AuthenticationConfiguration, 配置了認(rèn)證信息。
 *
 * @author vains
 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class AuthorizationConfig {

    private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";

    /**
     * 配置端點(diǎn)的過(guò)濾器鏈
     *
     * @param http spring security核心配置類
     * @return 過(guò)濾器鏈
     * @throws Exception 拋出
     */
    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        // 配置默認(rèn)的設(shè)置,忽略認(rèn)證端點(diǎn)的csrf校驗(yàn)
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                // 開(kāi)啟OpenID Connect 1.0協(xié)議相關(guān)端點(diǎn)
                .oidc(Customizer.withDefaults())
                // 設(shè)置自定義用戶確認(rèn)授權(quán)頁(yè)
                .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI));
        http
                // 當(dāng)未登錄時(shí)訪問(wèn)認(rèn)證端點(diǎn)時(shí)重定向至login頁(yè)面
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                )
                // 處理使用access token訪問(wèn)用戶信息端點(diǎn)和客戶端注冊(cè)端點(diǎn)
                .oauth2ResourceServer((resourceServer) -> resourceServer
                        .jwt(Customizer.withDefaults()));

        return http.build();
    }

    /**
     * 配置認(rèn)證相關(guān)的過(guò)濾器鏈
     *
     * @param http spring security核心配置類
     * @return 過(guò)濾器鏈
     * @throws Exception 拋出
     */
    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((authorize) -> authorize
                        // 放行靜態(tài)資源
                        .requestMatchers("/assets/**", "/webjars/**", "/login").permitAll()
                        .anyRequest().authenticated()
                )
                // 指定登錄頁(yè)面
                .formLogin(formLogin ->
                        formLogin.loginPage("/login")
                );
        // 添加BearerTokenAuthenticationFilter,將認(rèn)證服務(wù)當(dāng)做一個(gè)資源服務(wù),解析請(qǐng)求頭中的token
        http.oauth2ResourceServer((resourceServer) -> resourceServer
                .jwt(Customizer.withDefaults()));

        return http.build();
    }

    /**
     * 配置密碼解析器,使用BCrypt的方式對(duì)密碼進(jìn)行加密和驗(yàn)證
     *
     * @return BCryptPasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置客戶端Repository
     *
     * @param jdbcTemplate    db 數(shù)據(jù)源信息
     * @param passwordEncoder 密碼解析器
     * @return 基于數(shù)據(jù)庫(kù)的repository
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                // 客戶端id
                .clientId("messaging-client")
                // 客戶端秘鑰,使用密碼解析器加密
                .clientSecret(passwordEncoder.encode("123456"))
                // 客戶端認(rèn)證方式,基于請(qǐng)求頭的認(rèn)證
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                // 配置資源服務(wù)器使用該客戶端獲取授權(quán)時(shí)支持的方式
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                // 授權(quán)碼模式回調(diào)地址,oauth2.1已改為精準(zhǔn)匹配,不能只設(shè)置域名,并且屏蔽了localhost,本機(jī)使用127.0.0.1訪問(wèn)
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
                .redirectUri("https://www.baidu.com")
                // 該客戶端的授權(quán)范圍,OPENID與PROFILE是IdToken的scope,獲取授權(quán)時(shí)請(qǐng)求OPENID的scope時(shí)認(rèn)證服務(wù)會(huì)返回IdToken
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                // 自定scope
                .scope("message.read")
                .scope("message.write")
                // 客戶端設(shè)置,設(shè)置用戶需要確認(rèn)授權(quán)
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();

        // 基于db存儲(chǔ)客戶端,還有一個(gè)基于內(nèi)存的實(shí)現(xiàn) InMemoryRegisteredClientRepository
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);

        // 初始化客戶端
        RegisteredClient repositoryByClientId = registeredClientRepository.findByClientId(registeredClient.getClientId());
        if (repositoryByClientId == null) {
            registeredClientRepository.save(registeredClient);
        }
        // 設(shè)備碼授權(quán)客戶端
        RegisteredClient deviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("device-message-client")
                // 公共客戶端
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                // 設(shè)備碼授權(quán)
                .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                // 自定scope
                .scope("message.read")
                .scope("message.write")
                .build();
        RegisteredClient byClientId = registeredClientRepository.findByClientId(deviceClient.getClientId());
        if (byClientId == null) {
            registeredClientRepository.save(deviceClient);
        }
        return registeredClientRepository;
    }

    /**
     * 配置基于db的oauth2的授權(quán)管理服務(wù)
     *
     * @param jdbcTemplate               db數(shù)據(jù)源信息
     * @param registeredClientRepository 上邊注入的客戶端repository
     * @return JdbcOAuth2AuthorizationService
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        // 基于db的oauth2認(rèn)證服務(wù),還有一個(gè)基于內(nèi)存的服務(wù)實(shí)現(xiàn)InMemoryOAuth2AuthorizationService
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 配置基于db的授權(quán)確認(rèn)管理服務(wù)
     *
     * @param jdbcTemplate               db數(shù)據(jù)源信息
     * @param registeredClientRepository 客戶端repository
     * @return JdbcOAuth2AuthorizationConsentService
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        // 基于db的授權(quán)確認(rèn)管理服務(wù),還有一個(gè)基于內(nèi)存的服務(wù)實(shí)現(xiàn)InMemoryOAuth2AuthorizationConsentService
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 配置jwk源,使用非對(duì)稱加密,公開(kāi)用于檢索匹配指定選擇器的JWK的方法
     *
     * @return JWKSource
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    /**
     * 生成rsa密鑰對(duì),提供給jwk
     *
     * @return 密鑰對(duì)
     */
    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    /**
     * 配置jwt解析器
     *
     * @param jwkSource jwk源
     * @return JwtDecoder
     */
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    /**
     * 添加認(rèn)證服務(wù)器配置,設(shè)置jwt簽發(fā)者、默認(rèn)端點(diǎn)請(qǐng)求地址等
     *
     * @return AuthorizationServerSettings
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

    /**
     * 先暫時(shí)配置一個(gè)基于內(nèi)存的用戶,框架在用戶認(rèn)證時(shí)會(huì)默認(rèn)調(diào)用
     * {@link UserDetailsService#loadUserByUsername(String)} 方法根據(jù)
     * 賬號(hào)查詢用戶信息,一般是重寫該方法實(shí)現(xiàn)自己的邏輯
     *
     * @param passwordEncoder 密碼解析器
     * @return UserDetailsService
     */
    @Bean
    public UserDetailsService users(PasswordEncoder passwordEncoder) {
        UserDetails user = User.withUsername("admin")
                .password(passwordEncoder.encode("123456"))
                .roles("admin", "normal", "unAuthentication")
                .authorities("app", "web", "/test2", "/test3")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

}

注意,配置類中提到的基于內(nèi)存存儲(chǔ)的類禁止用于生產(chǎn)環(huán)境

3. 添加AuthorizationController,將請(qǐng)求轉(zhuǎn)發(fā)至自定義的登錄頁(yè)面和用戶確認(rèn)授權(quán)頁(yè)面

以下代碼摘抄自官方示例

使用thymeleaf框架渲染頁(yè)面

package com.example.controller;

import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 認(rèn)證服務(wù)器相關(guān)自定接口
 *
 * @author vains
 */
@Controller
@RequiredArgsConstructor
public class AuthorizationController {

    private final RegisteredClientRepository registeredClientRepository;

    private final OAuth2AuthorizationConsentService authorizationConsentService;


    @GetMapping("/login")
    public String login() {
        return "login";
    }

    @GetMapping(value = "/oauth2/consent")
    public String consent(Principal principal, Model model,
                          @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
                          @RequestParam(OAuth2ParameterNames.SCOPE) String scope,
                          @RequestParam(OAuth2ParameterNames.STATE) String state,
                          @RequestParam(name = OAuth2ParameterNames.USER_CODE, required = false) String userCode) {

        // Remove scopes that were already approved
        Set<String> scopesToApprove = new HashSet<>();
        Set<String> previouslyApprovedScopes = new HashSet<>();
        RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
        if (registeredClient == null) {
            throw new RuntimeException("客戶端不存在");
        }
        OAuth2AuthorizationConsent currentAuthorizationConsent =
                this.authorizationConsentService.findById(registeredClient.getId(), principal.getName());
        Set<String> authorizedScopes;
        if (currentAuthorizationConsent != null) {
            authorizedScopes = currentAuthorizationConsent.getScopes();
        } else {
            authorizedScopes = Collections.emptySet();
        }
        for (String requestedScope : StringUtils.delimitedListToStringArray(scope, " ")) {
            if (OidcScopes.OPENID.equals(requestedScope)) {
                continue;
            }
            if (authorizedScopes.contains(requestedScope)) {
                previouslyApprovedScopes.add(requestedScope);
            } else {
                scopesToApprove.add(requestedScope);
            }
        }

        model.addAttribute("clientId", clientId);
        model.addAttribute("state", state);
        model.addAttribute("scopes", withDescription(scopesToApprove));
        model.addAttribute("previouslyApprovedScopes", withDescription(previouslyApprovedScopes));
        model.addAttribute("principalName", principal.getName());
        model.addAttribute("userCode", userCode);
        if (StringUtils.hasText(userCode)) {
            model.addAttribute("requestURI", "/oauth2/device_verification");
        } else {
            model.addAttribute("requestURI", "/oauth2/authorize");
        }

        return "consent";
    }

    private static Set<ScopeWithDescription> withDescription(Set<String> scopes) {
        Set<ScopeWithDescription> scopeWithDescriptions = new HashSet<>();
        for (String scope : scopes) {
            scopeWithDescriptions.add(new ScopeWithDescription(scope));

        }
        return scopeWithDescriptions;
    }

    @Data
    public static class ScopeWithDescription {
        private static final String DEFAULT_DESCRIPTION = "UNKNOWN SCOPE - We cannot provide information about this permission, use caution when granting this.";
        private static final Map<String, String> scopeDescriptions = new HashMap<>();
        static {
            scopeDescriptions.put(
                    OidcScopes.PROFILE,
                    "This application will be able to read your profile information."
            );
            scopeDescriptions.put(
                    "message.read",
                    "This application will be able to read your message."
            );
            scopeDescriptions.put(
                    "message.write",
                    "This application will be able to add new messages. It will also be able to edit and delete existing messages."
            );
            scopeDescriptions.put(
                    "other.scope",
                    "This is another scope example of a scope description."
            );
        }

        public final String scope;
        public final String description;

        ScopeWithDescription(String scope) {
            this.scope = scope;
            this.description = scopeDescriptions.getOrDefault(scope, DEFAULT_DESCRIPTION);
        }
    }

}

4. 在application.yml中配置db數(shù)據(jù)源

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/authorization-example?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8
    username: root
    password: root

5. 編寫登錄頁(yè)面和用戶授權(quán)確認(rèn)頁(yè)面

以下代碼摘抄自官方示例

登錄頁(yè)面 login.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Spring Authorization Server sample</title>
    <link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
    <link rel="stylesheet" href="/assets/css/signin.css" th:href="@{/assets/css/signin.css}" />
</head>
<body>
<div class="container">
    <form class="form-signin w-100 m-auto" method="post" th:action="@{/login}">
        <div th:if="${param.error}" class="alert alert-danger" role="alert">
            Invalid username or password.
        </div>
        <div th:if="${param.logout}" class="alert alert-success" role="alert">
            You have been logged out.
        </div>
        <h1 class="h3 mb-3 fw-normal">Please sign in</h1>
        <div class="form-floating">
            <input type="text" id="username" name="username" class="form-control" required autofocus>
            <label for="username">Username</label>
        </div>
        <div class="form-floating">
            <input type="password" id="password" name="password" class="form-control" required>
            <label for="password">Password</label>
        </div>
        <div>
            <button class="w-100 btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
        </div>
    </form>
</div>
</body>
</html>

登錄頁(yè)面css, signin.css

html,
body {
    height: 100%;
}

body {
    display: flex;
    align-items: start;
    padding-top: 100px;
    background-color: #f5f5f5;
}

.form-signin {
    max-width: 330px;
    padding: 15px;
}

.form-signin .form-floating:focus-within {
    z-index: 2;
}

.form-signin input[type="username"] {
    margin-bottom: -1px;
    border-bottom-right-radius: 0;
    border-bottom-left-radius: 0;
}

.form-signin input[type="password"] {
    margin-bottom: 10px;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}

用戶授權(quán)確認(rèn)頁(yè)面consent.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Custom consent page - Consent required</title>
    <link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
    <script>
        function cancelConsent() {
            document.consent_form.reset();
            document.consent_form.submit();
        }
    </script>
</head>
<body>
<div class="container">
    <div class="row py-5">
        <h1 class="text-center text-primary">App permissions</h1>
    </div>
    <div class="row">
        <div class="col text-center">
            <p>
                The application
                <span class="fw-bold text-primary" th:text="${clientId}"></span>
                wants to access your account
                <span class="fw-bold" th:text="${principalName}"></span>
            </p>
        </div>
    </div>
    <div th:if="${userCode}" class="row">
        <div class="col text-center">
            <p class="alert alert-warning">
                You have provided the code
                <span class="fw-bold" th:text="${userCode}"></span>.
                Verify that this code matches what is shown on your device.
            </p>
        </div>
    </div>
    <div class="row pb-3">
        <div class="col text-center">
            <p>
                The following permissions are requested by the above app.<br/>
                Please review these and consent if you approve.
            </p>
        </div>
    </div>
    <div class="row">
        <div class="col text-center">
            <form name="consent_form" method="post" th:action="${requestURI}">
                <input type="hidden" name="client_id" th:value="${clientId}">
                <input type="hidden" name="state" th:value="${state}">
                <input th:if="${userCode}" type="hidden" name="user_code" th:value="${userCode}">

                <div th:each="scope: ${scopes}" class="form-check py-1">
                    <input class="form-check-input"
                           style="float: none"
                           type="checkbox"
                           name="scope"
                           th:value="${scope.scope}"
                           th:id="${scope.scope}">
                    <label class="form-check-label fw-bold px-2" th:for="${scope.scope}" th:text="${scope.scope}"></label>
                    <p class="text-primary" th:text="${scope.description}"></p>
                </div>

                <p th:if="${not #lists.isEmpty(previouslyApprovedScopes)}">
                    You have already granted the following permissions to the above app:
                </p>
                <div th:each="scope: ${previouslyApprovedScopes}" class="form-check py-1">
                    <input class="form-check-input"
                           style="float: none"
                           type="checkbox"
                           th:id="${scope.scope}"
                           disabled
                           checked>
                    <label class="form-check-label fw-bold px-2" th:for="${scope.scope}" th:text="${scope.scope}"></label>
                    <p class="text-primary" th:text="${scope.description}"></p>
                </div>

                <div class="pt-3">
                    <button class="btn btn-primary btn-lg" type="submit" id="submit-consent">
                        Submit Consent
                    </button>
                </div>
                <div class="pt-3">
                    <button class="btn btn-link regular" type="button" id="cancel-consent" onclick="cancelConsent();">
                        Cancel
                    </button>
                </div>
            </form>
        </div>
    </div>
    <div class="row pt-4">
        <div class="col text-center">
            <p>
                <small>
                    Your consent to provide access is required.<br/>
                    If you do not approve, click Cancel, in which case no information will be shared with the app.
                </small>
            </p>
        </div>
    </div>
</div>
</body>
</html>

至此,一個(gè)簡(jiǎn)單的認(rèn)證服務(wù)就搭建成功了。

本來(lái)不想設(shè)置自定義頁(yè)面的,但是不知道是本人的網(wǎng)絡(luò)問(wèn)題,還是默認(rèn)的頁(yè)面里的css相關(guān)cdn無(wú)法訪問(wèn),頁(yè)面加載巨慢還丑,只能從官方示例中拿一下登錄頁(yè)面和用戶授權(quán)確認(rèn)頁(yè)面,css改為從項(xiàng)目的webjars中引入

最后放一下項(xiàng)目結(jié)構(gòu)圖

spring-authorization-server,Spring Authorization Server,Spring Security,Spring,spring boot,spring,java

6. 簡(jiǎn)單測(cè)試

1. 拼接url,訪問(wèn)授權(quán)接口

http://127.0.0.1:8080/oauth2/authorize?client_id=messaging-client&response_type=code&scope=message.read&redirect_uri=https%3A%2F%2Fwww.baidu.com

2. 授權(quán)接口檢測(cè)到未登錄,重定向至登錄頁(yè)面

spring-authorization-server,Spring Authorization Server,Spring Security,Spring,spring boot,spring,java

3. 輸入AuthorizationConfig中配置的賬號(hào)密碼

賬號(hào):admin, 密碼:123456

4. 登錄成功后跳轉(zhuǎn)至授權(quán)確認(rèn)頁(yè)面

登錄成功跳轉(zhuǎn)至第1步的授權(quán)接口,授權(quán)接口檢測(cè)到用戶未確認(rèn)授權(quán),跳轉(zhuǎn)至授權(quán)確認(rèn)頁(yè)面

spring-authorization-server,Spring Authorization Server,Spring Security,Spring,spring boot,spring,java

選擇對(duì)應(yīng)的scope并提交確認(rèn)權(quán)限

5. 提交后重定向至第1步的授權(quán)接口

授權(quán)接口生成code并重定向至第1步請(qǐng)求授權(quán)接口時(shí)攜帶的redirectUri地址,重定向時(shí)攜帶上參數(shù)code和state,我這里省略掉了state參數(shù),重定向之后只會(huì)攜帶code參數(shù);state用來(lái)防止CSRF攻擊,正式請(qǐng)求需生成并攜帶state參數(shù)。

6. 用戶確認(rèn)授權(quán)后攜帶code跳轉(zhuǎn)至redirectUri

一般來(lái)說(shuō)配置的回調(diào)地址都是客戶端的接口,接口在接收到回調(diào)時(shí)根據(jù)code去換取accessToken,接下來(lái)我會(huì)用postman模擬客戶端發(fā)起一個(gè)http請(qǐng)求去換取token
不知道為什么在手機(jī)瀏覽器上看回調(diào)至百度的圖片在平臺(tái)顯示違規(guī),這里我放一張另一個(gè)回調(diào)地址的圖片替代
spring-authorization-server,Spring Authorization Server,Spring Security,Spring,spring boot,spring,java

7. 根據(jù)code換取AccessToken

請(qǐng)求/oauth2/token接口

1. 設(shè)置Basic Auth

之前客戶端設(shè)置的認(rèn)證方式是CLIENT_SECRET_BASIC,所以需將客戶端信息添加至請(qǐng)求頭

spring-authorization-server,Spring Authorization Server,Spring Security,Spring,spring boot,spring,java

2. 添加表單數(shù)據(jù),發(fā)起POST請(qǐng)求

下列表單數(shù)據(jù)可添加至form-data也可添加至url params
spring-authorization-server,Spring Authorization Server,Spring Security,Spring,spring boot,spring,java
參數(shù)中的code就是第6步回調(diào)時(shí)攜帶的code
注意:添加url params時(shí)redirect_uri參數(shù)要經(jīng)過(guò)encodeURIComponent函數(shù)對(duì)回調(diào)地址進(jìn)行編碼
spring-authorization-server,Spring Authorization Server,Spring Security,Spring,spring boot,spring,java
在新版本的sas(1.2.1)中獲取token更新了授權(quán)碼校驗(yàn)邏輯,只能用form-data傳遞參數(shù),使用url-params會(huì)失敗,原因見(jiàn)issue1451
對(duì)應(yīng)的 commit 在這里: Fix to ensure endpoints distinguish between form and query parameters

8. 參數(shù)解釋

1. client_id: 客戶端的id
2. client_secret: 客戶端秘鑰
3. redirect_uri:申請(qǐng)授權(quán)成功后的回調(diào)地址
4. response_type:授權(quán)碼模式固定參數(shù)code
5. code_verifier:一段隨機(jī)字符串
6. code_challenge:根據(jù)指定的加密方式將code_verifier加密后得到的字符串
7. code_challenge_method:加密方式
8. scope:客戶端申請(qǐng)的授權(quán)范圍
9. state:跟隨authCode原樣返回,防止CSRF攻擊
10. grant_type:指定獲取token 的方式:
	1. refresh_token:刷新token
    2. authorization_code:根據(jù)授權(quán)碼模式的授權(quán)碼獲取
    3. client_credentials:客戶端模式獲取

總結(jié)

本篇文章從0到1搭建了一個(gè)簡(jiǎn)單認(rèn)證服務(wù),解釋了認(rèn)證服務(wù)的各項(xiàng)配置用意,如何設(shè)置自己的登錄頁(yè)和授權(quán)確認(rèn)頁(yè),如何讓認(rèn)證服務(wù)解析請(qǐng)求時(shí)攜帶的token,文章過(guò)長(zhǎng)難免有遺漏的地方,如果文章中有遺漏或錯(cuò)誤的地方請(qǐng)各位讀者在評(píng)論區(qū)指出。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-723709.html

到了這里,關(guān)于Spring Authorization Server入門 (二) Spring Boot整合Spring Authorization Server的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【spring authorization server系列教程】(一)入門系列,spring authorization server簡(jiǎn)介??焖贅?gòu)建一個(gè)授權(quán)服務(wù)器(基于最新版本0.3.0)

    【spring authorization server系列教程】(一)入門系列,spring authorization server簡(jiǎn)介。快速構(gòu)建一個(gè)授權(quán)服務(wù)器(基于最新版本0.3.0)

    【spring authorization server系列教程】(一)入門系列,快速構(gòu)建一個(gè)授權(quán)服務(wù)器 spring authorization server是spring團(tuán)隊(duì)最新的認(rèn)證授權(quán)服務(wù)器,之前的oauth2后面會(huì)逐步棄用。不過(guò)到現(xiàn)在發(fā)文的時(shí)候,我看到官網(wǎng)已經(jīng)把之前oauth2倉(cāng)庫(kù)廢棄了。 現(xiàn)在spring authorization server已經(jīng)到生產(chǎn)就緒階段了

    2024年02月05日
    瀏覽(26)
  • Spring Authorization Server入門 (十六) Spring Cloud Gateway對(duì)接認(rèn)證服務(wù)

    Spring Authorization Server入門 (十六) Spring Cloud Gateway對(duì)接認(rèn)證服務(wù)

    ????????之前雖然單獨(dú)講過(guò)Security Client和Resource Server的對(duì)接,但是都是基于Spring webmvc的,Gateway這種非阻塞式的網(wǎng)關(guān)是基于webflux的,對(duì)于集成Security相關(guān)內(nèi)容略有不同,且涉及到代理其它微服務(wù),所以會(huì)稍微比較麻煩些,今天就帶大家來(lái)實(shí)現(xiàn)Gateway網(wǎng)關(guān)對(duì)接OAuth2認(rèn)證服務(wù)。

    2024年02月10日
    瀏覽(40)
  • Spring Authorization Server入門 (二十) 實(shí)現(xiàn)二維碼掃碼登錄

    Spring Authorization Server入門 (二十) 實(shí)現(xiàn)二維碼掃碼登錄

    打開(kāi)網(wǎng)頁(yè),發(fā)起授權(quán)申請(qǐng)/未登錄被重定向到登錄頁(yè)面 選擇二維碼登錄,頁(yè)面從后端請(qǐng)求二維碼 頁(yè)面渲染二維碼圖片,并輪詢請(qǐng)求,獲取二維碼的狀態(tài) 事先登錄過(guò)APP的手機(jī)掃描二維碼,然后APP請(qǐng)求服務(wù)器端的API接口,把用戶認(rèn)證信息傳遞到服務(wù)器中 后端收到APP的請(qǐng)求后更改

    2024年02月21日
    瀏覽(23)
  • Spring Authorization Server入門 (十五) 分離授權(quán)確認(rèn)與設(shè)備碼校驗(yàn)頁(yè)面

    2023-12-01修改:在session-data-redis(Github)分支中添加了基于 spring-session-data-redis 的實(shí)現(xiàn),無(wú)需借助 nonceId 來(lái)保持認(rèn)證狀態(tài),該分支已去除所有 nonceId 相關(guān)內(nèi)容,需要注意的是 axios 在初始化時(shí)需要添加配置 withCredentials: true ,讓請(qǐng)求攜帶cookie。當(dāng)然一些響應(yīng)json的處理還是使用下方的

    2024年02月14日
    瀏覽(18)
  • 【Spring Authorization Server 系列】(一)入門篇,快速搭建一個(gè)授權(quán)服務(wù)器

    【Spring Authorization Server 系列】(一)入門篇,快速搭建一個(gè)授權(quán)服務(wù)器

    官方主頁(yè):https://spring.io/projects/spring-authorization-server Spring Authorization Server 是一個(gè)框架,提供了 OAuth 2.1 和 OpenID Connect 1.0 規(guī)范以及其他相關(guān)規(guī)范的實(shí)現(xiàn)。 它建立在 Spring Security 之上,為構(gòu)建 OpenID Connect 1.0 Identity Providers 和 OAuth2 Authorization Server 產(chǎn)品提供安全、輕量級(jí)和可定制

    2024年02月16日
    瀏覽(26)
  • Spring Authorization Server入門 (一) 初識(shí)SpringAuthorizationServer和OAuth2.1協(xié)議

    Spring Authorization Server入門 (一) 初識(shí)SpringAuthorizationServer和OAuth2.1協(xié)議

    經(jīng)過(guò)近些年網(wǎng)絡(luò)和設(shè)備的不斷發(fā)展,之前的oauth2.0發(fā)布的授權(quán)協(xié)議標(biāo)準(zhǔn)已經(jīng)遠(yuǎn)遠(yuǎn)不能滿足現(xiàn)在的場(chǎng)景和需求,根據(jù)其安全最佳實(shí)踐,在oauth2.0的基礎(chǔ)上移除了一些不安全的授權(quán)方式,并且對(duì)擴(kuò)展協(xié)議進(jìn)行整合。該協(xié)議定義了一系列關(guān)于授權(quán)的開(kāi)放網(wǎng)絡(luò)標(biāo)準(zhǔn),允許用戶授權(quán)第三方

    2024年02月11日
    瀏覽(23)
  • Spring Authorization Server入門 (十二) 實(shí)現(xiàn)授權(quán)碼模式使用前后端分離的登錄頁(yè)面

    Spring Authorization Server入門 (十二) 實(shí)現(xiàn)授權(quán)碼模式使用前后端分離的登錄頁(yè)面

    2023-12-01修改:在session-data-redis(Github)分支中添加了基于 spring-session-data-redis 的實(shí)現(xiàn),無(wú)需借助 nonceId 來(lái)保持認(rèn)證狀態(tài),該分支已去除所有 nonceId 相關(guān)內(nèi)容,需要注意的是 axios 在初始化時(shí)需要添加配置 withCredentials: true ,讓請(qǐng)求攜帶cookie。當(dāng)然一些響應(yīng)json的處理還是使用下方的

    2024年02月13日
    瀏覽(25)
  • Spring Authorization Server入門 (三) 集成流程說(shuō)明、細(xì)節(jié)補(bǔ)充和各種方式獲取token測(cè)試

    Spring Authorization Server入門 (三) 集成流程說(shuō)明、細(xì)節(jié)補(bǔ)充和各種方式獲取token測(cè)試

    在上一篇文章中的AuthorizationConfig.java配置類中,類上有三個(gè)注解,分別是@Configuration、@EnableWebSecurity和@EnableMethodSecurity注解,雖然在類中有注釋,但是這里在細(xì)講一下,同時(shí)放一下官網(wǎng)的說(shuō)明 @EnableWebSecurity 加載了WebSecurityConfiguration配置類, 配置安全認(rèn)證策略。 加載了Authenti

    2024年02月11日
    瀏覽(21)
  • Spring Security 6.x 系列【28】授權(quán)服務(wù)器篇之Spring Authorization Server 1.0 入門案例

    Spring Security 6.x 系列【28】授權(quán)服務(wù)器篇之Spring Authorization Server 1.0 入門案例

    有道無(wú)術(shù),術(shù)尚可求,有術(shù)無(wú)道,止于術(shù)。 本系列Spring Boot 版本 3.0.4 本系列Spring Security 版本 6.0.2 本系列Spring Authorization Server 版本 1.0.2 源碼地址:https://gitee.com/pearl-organization/study-spring-security-demo 在前幾篇文檔中,我們學(xué)習(xí)了 OAuth 2.0 協(xié)議,并使用 spring-security-oauth2-client 完成

    2024年02月12日
    瀏覽(24)
  • Spring Authorization Server入門 (十三) 實(shí)現(xiàn)聯(lián)合身份認(rèn)證,集成Github與Gitee的OAuth登錄

    Spring Authorization Server入門 (十三) 實(shí)現(xiàn)聯(lián)合身份認(rèn)證,集成Github與Gitee的OAuth登錄

    什么是聯(lián)合身份認(rèn)證? ??????通過(guò)Spring Security OAuth2 Client(Login)模塊集成第三方登錄至自己的認(rèn)證服務(wù)中,使用聯(lián)合身份認(rèn)證只需要請(qǐng)求認(rèn)證服務(wù),不通過(guò)前端來(lái)跳轉(zhuǎn)三方的授權(quán)申請(qǐng)鏈接,而是統(tǒng)一通過(guò)認(rèn)證服務(wù)來(lái)跳轉(zhuǎn),只需要維護(hù)Spring Authorization Server中身份認(rèn)證提供商

    2024年02月05日
    瀏覽(42)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包