欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot3.X配置OAuth的代碼實(shí)踐

 更新時(shí)間:2024年09月29日 11:02:34   作者:雷襲月啟  
在進(jìn)行Java后端技術(shù)框架版本升級(jí)時(shí),特別是將SpringBoot從2.X升級(jí)到3.X,發(fā)現(xiàn)對(duì)OAuth的配置有大幅變更,新版本中刪除了多個(gè)常用配置類,本文給大家介紹SpringBoot3.X配置OAuth的相關(guān)知識(shí),感興趣的朋友一起看看吧

背景

之前在學(xué)習(xí)OAuth2時(shí),我就有一個(gè)疑惑,OAuth2中有太多的配置、服務(wù)類都標(biāo)注了@Deprecated,如下:

顯然這些寫(xiě)法已經(jīng)過(guò)時(shí)了,那么官方推薦的最新寫(xiě)法是什么樣的呢?當(dāng)時(shí)我沒(méi)有深究這些,我以為我放過(guò)了它,它就能放過(guò)我,誰(shuí)曾想不久之后,命運(yùn)的大手不由分說(shuō)的攥緊了我,讓我不得不直面自己的困惑。

最近我接了個(gè)大活,對(duì)公司的Java后端技術(shù)框架進(jìn)行版本升級(jí),將SpringBoot的版本從2.X升到3.X,JDK從1.8升到17,在對(duì)框架的父工程中的依賴版本進(jìn)行升級(jí)之后,接下來(lái)要做的就是對(duì)已有的公共服務(wù)/組件進(jìn)行升級(jí)了,比如GateWay, 流程引擎,基礎(chǔ)平臺(tái),認(rèn)證服務(wù)等。其他的服務(wù)升級(jí)都還算有驚無(wú)險(xiǎn),但是升級(jí)認(rèn)證服務(wù)OAuth時(shí),不夸張的說(shuō),我真是被折騰得死去活來(lái)。

相比于SpringBoot2.X,3.X對(duì)于OAuth的配置幾乎是進(jìn)行了巔覆式的變更,很多之前我們熟知的配置方法,要么是換了形式,要么是換了位置,想要配得和2.X一樣的效果太難了。好在經(jīng)歷了一番坎坷后,我終于把它給整理出來(lái)了,借著OAuth升版的機(jī)會(huì),我也終于弄明白了最版的配置是什么樣的。

代碼實(shí)踐

伴隨著JDK和SpringBoot的版本升級(jí),Spring Security也需要進(jìn)行相應(yīng)的升級(jí),這直接導(dǎo)致了適用于SpringBoot2.X的相關(guān)OAuth配置變得不可用,甚至我們耳熟能詳?shù)呐渲妙惾鏏uthorizationServerConfigurerAdapter, WebSecurityConfigurerAdapter等都被刪除了,下面就對(duì)比著SpringBoot2.X,詳細(xì)說(shuō)下3.X中對(duì)于配置做了哪些變更。

一、依賴包的變化

在SpringBoot2.X中要實(shí)現(xiàn)OAuth服務(wù),需要引入以下依賴:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>

而在SpringBoot3.X中,需要引入以下依賴包:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-oauth2-authorization-server</artifactId>
      <version>1.0.0</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-oauth2-core</artifactId>
    </dependency>

二、支持模式的變化

新版的spring-security-oauth2-authorization-server依賴包中,僅實(shí)現(xiàn)了授權(quán)碼模式,要想使用之前的用戶名密碼模式,客戶端模式等,還需要手動(dòng)擴(kuò)展,擴(kuò)展模式需要實(shí)現(xiàn)這三個(gè)接口:

AuthenticationConverter (用于將認(rèn)證請(qǐng)求轉(zhuǎn)換為標(biāo)準(zhǔn)的 Authentication 對(duì)象)

AuthenticationProvider (用于定義如何驗(yàn)證用戶的認(rèn)證信息)

OAuth2AuthorizationGrantAuthenticationToken(將認(rèn)證對(duì)象轉(zhuǎn)換為系統(tǒng)內(nèi)部可識(shí)別的形式)

三、數(shù)據(jù)庫(kù)表的變化

SpringBoot2.X版本時(shí),OAuth存儲(chǔ)客戶信息的表結(jié)構(gòu)如下:

create table oauth_client_details (
    client_id VARCHAR(256) PRIMARY KEY,
	resource_ids VARCHAR(256),
	client_secret VARCHAR(256),
	scope VARCHAR(256),
	authorized_grant_types VARCHAR(256),
	web_server_redirect_uri VARCHAR(256),
	authorities VARCHAR(256),
	access_token_validity INTEGER,
	refresh_token_validity INTEGER,
	additional_information VARCHAR(4096),
	autoapprove VARCHAR(256)
);

升級(jí)為SpringBoot3.X后,客戶信息表結(jié)構(gòu)如下:

CREATE TABLE oauth2_registered_client (
    id varchar(100) NOT NULL,
    client_id varchar(100) NOT NULL,
    client_id_issued_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
    client_secret varchar(200) DEFAULT NULL,
    client_secret_expires_at timestamp 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,
    scopes varchar(1000) NOT NULL,
    client_settings varchar(2000) NOT NULL,
    token_settings varchar(2000) NOT NULL,
    PRIMARY KEY (id)
);

四、鏈接的變化

舊版本的OAuth服務(wù)中,相關(guān)的認(rèn)證接接口的url都是/oauth/*,如/oauth/token /oauth/authorize,而升級(jí)到新版后,所有接口的url都變成了/oauth2/*,在配置客戶端時(shí)需要格外注意。

五、配置的變化

接下來(lái)就是重頭戲:配置的變化,為了更直觀的展示SprinBoot在2.X和3.X對(duì)于配置的變化,我將把一套2.X的OAuth配置以及它轉(zhuǎn)換成3.X的配置都貼出來(lái),配置中涉及認(rèn)證自動(dòng)審批、內(nèi)存模式和數(shù)據(jù)庫(kù)模式,Token的過(guò)期時(shí)間,Token的JWT轉(zhuǎn)換,Password的加密,自定義登陸頁(yè),客戶端的授權(quán)方式等。

1、SpringBoot2.X的配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Arrays;
/**
 *
 * @author leixiyueqi
 * @since 2023/12/3 22:00
 */
@EnableAuthorizationServer
@Configuration
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
    @Resource
    private AuthenticationManager manager;
    private final MD5PasswordEncoder encoder = new MD5PasswordEncoder();
    @Resource
    UserDetailsService service;
    @Resource
    private DataSource dataSource;
    @Resource
    TokenStore tokenStore;
    /**
     * 這個(gè)方法是對(duì)客戶端進(jìn)行配置,比如秘鑰,唯一id,,一個(gè)驗(yàn)證服務(wù)器可以預(yù)設(shè)很多個(gè)客戶端,
     * 之后這些指定的客戶端就可以按照下面指定的方式進(jìn)行驗(yàn)證
     * @param clients 客戶端配置工具
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }
    /**
     * 以內(nèi)存的方式設(shè)置客戶端方法
     @Override
     public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
         clients
             .inMemory()   //這里我們直接硬編碼創(chuàng)建,當(dāng)然也可以像Security那樣自定義或是使用JDBC從數(shù)據(jù)庫(kù)讀取
             .withClient("client")   //客戶端名稱,隨便起就行
             .secret(encoder.encode("123456"))      //只與客戶端分享的secret,隨便寫(xiě),但是注意要加密
             .autoApprove(false)    //自動(dòng)審批,這里關(guān)閉,要的就是一會(huì)體驗(yàn)?zāi)欠N感覺(jué)
             .scopes("read", "write")     //授權(quán)范圍,這里我們使用全部all
             .autoApprove(true)    // 這個(gè)為true時(shí),可以自動(dòng)授權(quán)。
             .redirectUris("http://127.0.0.1:19210/leixi/login/oauth2/code/leixi-client",
             "http://127.0.0.1:8081/login/oauth2/code/client-id-1",
             "http://127.0.0.1:19210/leixi/callback")
             .authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token");
             //授權(quán)模式,一共支持5種,除了之前我們介紹的四種之外,還有一個(gè)刷新Token的模式
     }
     */
    // 令牌端點(diǎn)的安全配置,比如/oauth/token對(duì)哪些開(kāi)放
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .passwordEncoder(encoder)    //編碼器設(shè)定為BCryptPasswordEncoder
                .allowFormAuthenticationForClients()  //允許客戶端使用表單驗(yàn)證,一會(huì)我們POST請(qǐng)求中會(huì)攜帶表單信息
                .checkTokenAccess("permitAll()");     //允許所有的Token查詢請(qǐng)求
    }
    //令牌訪問(wèn)端點(diǎn)的配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .userDetailsService(service)
                .authenticationManager(manager)
                .tokenServices(tokenServices());
        //由于SpringSecurity新版本的一些底層改動(dòng),這里需要配置一下authenticationManager,才能正常使用password模式
        endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access");
    }
    // 設(shè)置token的存儲(chǔ),過(guò)期時(shí)間,添加附加信息等
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setReuseRefreshToken(true);
        services.setTokenStore(tokenStore);
        services.setAccessTokenValiditySeconds(120);   // 設(shè)置令牌有效時(shí)間
        services.setRefreshTokenValiditySeconds(60*5);  //設(shè)計(jì)刷新令牌的有效時(shí)間
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new CustomTokenEnhancer(), accessTokenConverter()));
        services.setTokenEnhancer(tokenEnhancerChain);
        return services;
    }
    // 對(duì)token信息進(jìn)行JWT加密
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        // 將自定義的內(nèi)容封裝到access_token中
        DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
        defaultAccessTokenConverter.setUserTokenConverter(new CustomerUserAuthenticationConverter());
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setAccessTokenConverter(defaultAccessTokenConverter);
        converter.setSigningKey("密鑰");
        return converter;
    }
}
import com.leixi.auth2.service.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
/**
 *
 * @author leixiyueqi
 * @since 2023/12/3 22:00
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private static final String loginUrl = "/login";
    /**
     * 注意,當(dāng)在內(nèi)存中獲取用戶信息時(shí),就不需要?jiǎng)?chuàng)建UserDetailService的實(shí)現(xiàn)類了
     * 
     */
    @Autowired
    private UserDetailServiceImpl userService;
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Bean
    public MD5PasswordEncoder passwordEncoder() {
        return new MD5PasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // http security 要攔截的url,這里這攔截,oauth2相關(guān)和登錄登錄相關(guān)的url,其他的交給資源服務(wù)處理
                .authorizeRequests()
                .antMatchers( "/oauth/**","/**/*.css", "/**/*.ico", "/**/*.png", "/**/*.jpg", "/**/*.svg", "/login",
                        "/**/*.js", "/**/*.map",loginUrl, "/user/*","/base-grant.html")
                .permitAll()
                .anyRequest()
                .authenticated();
        // post請(qǐng)求要設(shè)置允許跨域,然后會(huì)報(bào)401
        http.csrf().ignoringAntMatchers("/login", "/logout", "/unlock/apply");
        // 表單登錄
        http.formLogin()
                // 登錄頁(yè)面
                .loginPage(loginUrl)
                // 登錄處理url
                .loginProcessingUrl("/login");
        http.httpBasic();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    /**
     *  以內(nèi)存的方式載入用戶信息
     @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
     auth.inMemoryAuthentication()   //直接創(chuàng)建一個(gè)靜態(tài)用戶
     .passwordEncoder(encoder)
     .withUser("leixi").password(encoder.encode("123456")).roles("USER");
     }
     @Bean
     @Override
     public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
     }
     */
    @Bean   //這里需要將AuthenticationManager注冊(cè)為Bean,在OAuth配置中使用
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    //通過(guò)redis存儲(chǔ)token
    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }
}
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import java.util.Map;
public class CustomerUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
    @Override
    public Map<String, ?> convertUserAuthentication(Authentication authentication) {
        Map mapResp = super.convertUserAuthentication(authentication);
        try {
            UserDetails user = (UserDetails)authentication.getPrincipal();
            if (user != null) {
                mapResp.put("loginName", user.getUsername());
                mapResp.put("content", "測(cè)試在accessToken中添加附加信息");
                mapResp.put("authorities","hahahaha");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mapResp;
    }
}
/**
 * 密碼實(shí)現(xiàn)類,允許開(kāi)發(fā)人員自由設(shè)置密碼加密
 *
 * @author leixiyueqi
 * @since 2023/12/3 22:00
 */
public class MD5PasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] digest = md5.digest(rawPassword.toString().getBytes("UTF-8"));
            String pass = new String(Hex.encode(digest));
            return pass;
        } catch (Exception e) {
            throw new RuntimeException("Failed to encode password.", e);
        }
    }
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(encode(rawPassword));
    }
}

看得出來(lái),SpringBoot2.X中SpringSecurityConfig的配置與OAuth2Configuration的配置有種相輔相成的感覺(jué),但對(duì)于初學(xué)者來(lái)說(shuō),會(huì)覺(jué)得很割裂,不知道哪些東西該配在哪個(gè)文件里。

2、Springboot3.X的配置

package com.leixi.auth2.config;
import com.leixi.auth2.custom.OAuth2PasswordAuthenticationConverter;
import com.leixi.auth2.custom.OAuth2PasswordAuthenticationProvider;
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 jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
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.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import java.util.UUID;
/**
 * OAuth的配置
 *
 * @author leixiyueqi
 * @since 2024/9/28 22:00
 */
@Configuration
@EnableWebSecurity
public class OAuth2JdbcConfiguration {
    @Autowired
    private MD5PasswordEncoder passwordEncoder;
    @Resource
    private UserDetailsService userDetailService;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private CustomTokenEnhancer customTokenEnhancer;
    private static final String loginUrl = "/loginpage.html";
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        return jdbcRegisteredClientRepository;
    }
    /**
     * 在內(nèi)存中獲取用戶信息的方式
     @Bean
     public UserDetailsService userDetailsService() {
         UserDetails userDetails = User.builder()
             .username("leixi")
             .roles("USER")
             .password(passwordEncoder.encode("123456"))
             .build();
         return new InMemoryUserDetailsManager(userDetails);
     }
     */
    /**
     * 在內(nèi)存中獲取客戶端信息的方式,還可以用于客戶端信息的入庫(kù)
     *
     @Bean
     public RegisteredClientRepository registeredClientRepository() {
         JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
         RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
             .clientId("client")
             .clientSecret(passwordEncoder.encode( "123456"))
             .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
             .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
             .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
             .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
             .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
             .authorizationGrantType(AuthorizationGrantType.PASSWORD)
             .redirectUri("http://127.0.0.1:19210/leixi/login/oauth2/code/leixi-client")
             .redirectUri("http://127.0.0.1:8081/login/oauth2/code/client-id-1")
             .redirectUri("http://127.0.0.1:19210/leixi/callback")
             .scope("read")
             .scope("write")
             // 登錄成功后對(duì)scope進(jìn)行確認(rèn)授權(quán)
             .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
             .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
             .accessTokenTimeToLive(Duration.ofHours(24))
             .refreshTokenTimeToLive(Duration.ofHours(24)).build())
             .build();
         jdbcRegisteredClientRepository.save(registeredClient);  //客戶端信息入庫(kù)
         return new InMemoryRegisteredClientRepository(registeredClient);
     }
     */
    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests
                .requestMatchers( "/oauth/*","/*/*.css", "/*/*.ico", "/*/*.png", "/*/*.jpg", "/*/*.svg", "/login",
                        "/*/*.js", "/*/*.map",loginUrl, "/user/*","/base-grant.html").permitAll() // 允許所有用戶訪問(wèn)這些路徑
                .anyRequest().authenticated()
        );
        http.csrf(csrf -> csrf.ignoringRequestMatchers("/login", "/logout", "/unlock/apply")); // 禁用CSRF保護(hù)
        // 表單登錄
        http.formLogin(formlogin -> formlogin
                        .loginPage(loginUrl)
                        .loginProcessingUrl("/login"))
                .httpBasic(httpBasic -> {})
                .authenticationProvider(daoAuthenticationProvider());
        return http.build();
    }
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider customerDaoAuthenticationProvider = new DaoAuthenticationProvider();
        // 設(shè)置userDetailsService
        customerDaoAuthenticationProvider.setUserDetailsService(userDetailService);
        // 禁止隱藏用戶未找到異常
        customerDaoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        // 使用MD5進(jìn)行密碼的加密
        customerDaoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        return customerDaoAuthenticationProvider;
    }
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .build();
    }
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        //應(yīng)用了默認(rèn)的安全配置,這些配置支持OAuth2授權(quán)服務(wù)器的功能。
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                // 自定義用戶名密碼的授權(quán)方式
                .tokenEndpoint((tokenEndpoint) -> tokenEndpoint
                        .accessTokenRequestConverter(new DelegatingAuthenticationConverter(Arrays.asList(
                                new OAuth2AuthorizationCodeAuthenticationConverter(),
                                new OAuth2RefreshTokenAuthenticationConverter(),
                                new OAuth2ClientCredentialsAuthenticationConverter(),
                                new OAuth2PasswordAuthenticationConverter()   //添加密碼模式的授權(quán)方式
                        ))).authenticationProviders((customProviders) -> {
                            // 自定義認(rèn)證提供者
                            customProviders.add(new OAuth2PasswordAuthenticationProvider(jwkSource(), userDetailService, passwordEncoder));
                        })
                )
                //啟用了OpenID Connect 1.0,這是一種基于OAuth2的身份驗(yàn)證協(xié)議。
                .oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0
        //配置了當(dāng)用戶嘗試訪問(wèn)受保護(hù)資源但未認(rèn)證時(shí)的行為。設(shè)置了一個(gè)自定義的登錄頁(yè)面作為認(rèn)證入口點(diǎn)。
        http.exceptionHandling((exceptions) -> exceptions
                        .authenticationEntryPoint(
                                new LoginUrlAuthenticationEntryPoint(loginUrl))
                )
                //配置了OAuth2資源服務(wù)器,指定使用JWT(JSON Web Token)進(jìn)行身份驗(yàn)證。
                .oauth2ResourceServer(config -> config.jwt(Customizer.withDefaults()));
        return http.build();
    }
    @Bean
    public JwtEncoder jwtEncoder() {
        NimbusJwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource());
        return jwtEncoder;
    }
    @Bean
    public JwtDecoder jwtDecoder() {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource());
    }
    @Bean
    public OAuth2TokenGenerator<?> tokenGenerator() {
        JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder());
        jwtGenerator.setJwtCustomizer(customTokenEnhancer);
        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(
                jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
    }
    @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的方式加密token,與之前的版本有些差異,之前是采用HMAC加密
    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;
    }
}
@Service
public class CustomTokenEnhancer implements OAuth2TokenCustomizer<JwtEncodingContext> {
    @Resource
    private UserDetailsService userDetailService;
    @Override
    public void customize(JwtEncodingContext context) {
        UserDetails user = userDetailService.loadUserByUsername(context.getPrincipal().getName());
        if (user != null) {
            context.getClaims().claims(claims -> {
                claims.put("loginName", user.getUsername());
                claims.put("name", user.getUsername());
                claims.put("content", "在accessToken中封裝自定義信息");
                claims.put("authorities", "hahahaha");
            });
        }
    }
}
/**
 * Jwt工具類
 *
 * @author leixiyueqi
 * @since 2024/9/28 22:00
 */
public final class JwtUtils {
    private JwtUtils() {
    }
    public static JwsHeader.Builder headers() {
        return JwsHeader.with(SignatureAlgorithm.RS256);
    }
    public static JwtClaimsSet.Builder accessTokenClaims(RegisteredClient registeredClient,
                                                         String issuer, String subject,
                                                         Set<String> authorizedScopes) {
        Instant issuedAt = Instant.now();
        Instant expiresAt = issuedAt
                .plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());
        /**
         * iss (issuer):簽發(fā)人/發(fā)行人
         * sub (subject):主題
         * aud (audience):用戶
         * exp (expiration time):過(guò)期時(shí)間
         * nbf (Not Before):生效時(shí)間,在此之前是無(wú)效的
         * iat (Issued At):簽發(fā)時(shí)間
         * jti (JWT ID):用于標(biāo)識(shí)該 JWT
         */
        // @formatter:off
        JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();
        if (StringUtils.hasText(issuer)) {
            claimsBuilder.issuer(issuer);
        }
        claimsBuilder
                .subject(subject)
                .audience(Collections.singletonList(registeredClient.getClientId()))
                .issuedAt(issuedAt)
                .expiresAt(expiresAt)
                .notBefore(issuedAt);
        if (!CollectionUtils.isEmpty(authorizedScopes)) {
            claimsBuilder.claim(OAuth2ParameterNames.SCOPE, authorizedScopes);
            claimsBuilder.claim("wangcl", "aaa");
        }
        // @formatter:on
        return claimsBuilder;
    }
}
public class OAuth2EndpointUtils {
    public static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap(parameterMap.size());
        parameterMap.forEach((key, values) -> {
            if (values.length > 0) {
                String[] var3 = values;
                int var4 = values.length;
                for(int var5 = 0; var5 < var4; ++var5) {
                    String value = var3[var5];
                    parameters.add(key, value);
                }
            }
        });
        return parameters;
    }
    public static void throwError(String errorCode, String parameterName, String errorUri) {
        OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
        throw new OAuth2AuthenticationException(error);
    }
}
// 注意,以下三個(gè)類是新版OAuth的密碼模式的實(shí)現(xiàn),不需要的可以不加
/**
 *
 * @author leixiyueqi
 * @since 2024/9/28 22:00
 */
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
 * 從HttpServletRequest中提取username與password,傳遞給OAuth2PasswordAuthenticationToken
 */
public class OAuth2PasswordAuthenticationConverter implements AuthenticationConverter {
    @Override
    public Authentication convert(HttpServletRequest request) {
        String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
        if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
            return null;
        }
        Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
        MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
        String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
        if (!StringUtils.hasText(username) ||
                parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
            OAuth2EndpointUtils.throwError(
                    OAuth2ErrorCodes.INVALID_REQUEST,
                    OAuth2ParameterNames.USERNAME,"");
        }
        String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
        Map<String, Object> additionalParameters = new HashMap<>();
        parameters.forEach((key, value) -> {
            if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
                    !key.equals(OAuth2ParameterNames.CLIENT_ID) &&
                    !key.equals(OAuth2ParameterNames.USERNAME) &&
                    !key.equals(OAuth2ParameterNames.PASSWORD)) {
                additionalParameters.put(key, value.get(0));
            }
        });
        return new OAuth2PasswordAuthenticationToken(username,password,clientPrincipal,additionalParameters);
    }
}
/**
 *
 * @author leixiyueqi
 * @since 2024/9/28 22:00
 */
import com.leixi.auth2.config.MD5PasswordEncoder;
import com.nimbusds.jose.jwk.source.JWKSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.security.Principal;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
/**
 * 從HttpServletRequest中提取username與password,傳遞給OAuth2PasswordAuthenticationToken
 */
/**
 * 密碼認(rèn)證的核心邏輯
 */
public class OAuth2PasswordAuthenticationProvider implements AuthenticationProvider {
    private static final StringKeyGenerator DEFAULT_REFRESH_TOKEN_GENERATOR =
            new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
    private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
    private Supplier<String> refreshTokenGenerator = DEFAULT_REFRESH_TOKEN_GENERATOR::generateKey;
    private AuthorizationServerSettings authorizationServerSettings;
    public OAuth2PasswordAuthenticationProvider(JWKSource jwkSource, UserDetailsService userDetailService,
        MD5PasswordEncoder passwordEncoder) {
        this.jwkSource = jwkSource;
        this.userDetailService = userDetailService;
        this.passwordEncoder = passwordEncoder;
    }
    private final JWKSource jwkSource;
    private UserDetailsService userDetailService;
    private MD5PasswordEncoder passwordEncoder;
    public OAuth2PasswordAuthenticationProvider(JWKSource jwkSource){
        this.jwkSource = jwkSource;
    }
    public void setJwtCustomizer(OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer) {
        Assert.notNull(jwtCustomizer, "jwtCustomizer cannot be null");
        this.jwtCustomizer = jwtCustomizer;
    }
    public void setRefreshTokenGenerator(Supplier<String> refreshTokenGenerator) {
        Assert.notNull(refreshTokenGenerator, "refreshTokenGenerator cannot be null");
        this.refreshTokenGenerator = refreshTokenGenerator;
    }
    @Autowired(required = false)
    void setAuthorizationServerSettings(AuthorizationServerSettings authorizationServerSettings) {
        this.authorizationServerSettings = authorizationServerSettings;
    }
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        OAuth2PasswordAuthenticationToken passwordAuthentication =
                (OAuth2PasswordAuthenticationToken) authentication;
        OAuth2ClientAuthenticationToken clientPrincipal =
                getAuthenticatedClientElseThrowInvalidClient(passwordAuthentication);
        RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
        // 校驗(yàn)賬戶
        String username = passwordAuthentication.getUsername();
        if (StringUtils.isEmpty(username)){
            throw new OAuth2AuthenticationException("賬戶不能為空");
        }
        // 校驗(yàn)密碼
        String password = passwordAuthentication.getPassword();
        if (StringUtils.isEmpty(password)){
            throw new OAuth2AuthenticationException("密碼不能為空");
        }
        // 查詢賬戶信息
        UserDetails userDetails = userDetailService.loadUserByUsername(username);
        if (userDetails ==null) {
            throw new OAuth2AuthenticationException("賬戶信息不存在,請(qǐng)聯(lián)系管理員");
        }
        // 校驗(yàn)密碼
        if (!passwordEncoder.encode(password).equals(userDetails.getPassword())) {
            throw new OAuth2AuthenticationException("密碼不正確");
        }
        // 構(gòu)造認(rèn)證信息
        Authentication principal = new UsernamePasswordAuthenticationToken(username, userDetails.getPassword(), userDetails.getAuthorities());
        //region 直接構(gòu)造一個(gè)OAuth2Authorization對(duì)象,實(shí)際場(chǎng)景中,應(yīng)該去數(shù)據(jù)庫(kù)進(jìn)行校驗(yàn)
        OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
                .principalName(principal.getName())
                .authorizationGrantType(AuthorizationGrantType.PASSWORD)
                .attribute(Principal.class.getName(), principal)
                .attribute("scopes", registeredClient.getScopes() )
                .build();
        //endregion
        String issuer = this.authorizationServerSettings != null ? this.authorizationServerSettings.getIssuer() : null;
        Set<String> authorizedScopes = authorization.getAttribute("scopes");
        // 構(gòu)造jwt token信息
        JwsHeader.Builder headersBuilder = JwtUtils.headers();
        headersBuilder.header("client-id", registeredClient.getClientId());
        headersBuilder.header("authorization-grant-type", passwordAuthentication.getGrantType().getValue());
        JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims(registeredClient, issuer, authorization.getPrincipalName(), authorizedScopes);
        // @formatter:off
        JwtEncodingContext context = JwtEncodingContext.with(headersBuilder, claimsBuilder)
                .registeredClient(registeredClient)
                .principal(authorization.getAttribute(Principal.class.getName()))
                .authorization(authorization)
                .authorizedScopes(authorizedScopes)
                .tokenType(OAuth2TokenType.ACCESS_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD)
                .authorizationGrant(passwordAuthentication)
                .build();
        // @formatter:on
        this.jwtCustomizer.customize(context);
        JwsHeader headers = context.getJwsHeader().build();
        JwtClaimsSet claims = context.getClaims().build();
        JwtEncoderParameters params = JwtEncoderParameters.from(headers, claims);
        NimbusJwtEncoder jwtEncoder  = new NimbusJwtEncoder(this.jwkSource);
        Jwt jwtAccessToken = jwtEncoder.encode(params);
        //Jwt jwtAccessToken = null;
        // 生成token
        OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
                jwtAccessToken.getTokenValue(), jwtAccessToken.getIssuedAt(),
                jwtAccessToken.getExpiresAt(), authorizedScopes);
        return new OAuth2AccessTokenAuthenticationToken(
                registeredClient, clientPrincipal, accessToken);
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return OAuth2PasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
    private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
        OAuth2ClientAuthenticationToken clientPrincipal = null;
        if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
            clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
        }
        if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
            return clientPrincipal;
        }
        throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
    }
}
/**
 *
 * @author 雷襲月啟
 * @since 2024/9/28 22:00
 */
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import java.util.Map;
/**
 * 用于存放username與password
 */
public class OAuth2PasswordAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
    private static final long serialVersionUID = -559176897708927684L;
    private final String username;
    private final String password;
    public OAuth2PasswordAuthenticationToken(String username, String password, Authentication clientPrincipal, Map<String, Object> additionalParameters) {
        super(AuthorizationGrantType.PASSWORD, clientPrincipal, additionalParameters);
        this.username = username;
        this.password = password;
    }
    public String getUsername() {
        return this.username;
    }
    public String getPassword() {
        return this.password;
    }
}

如果不算上擴(kuò)展的授權(quán)模式,SpringBoot3針對(duì)OAuth的配置要較之前精簡(jiǎn)了很多,而且一個(gè)配置文件就能搞定。從配置上也可以看出來(lái),新版OAuth具有很高的靈活性,允許用戶根據(jù)自己的需要來(lái)定義授權(quán)模式,對(duì)于安全性方面也有所增強(qiáng),因此有更廣闊的使用空間。

功能測(cè)試

配置好OAuth2后,驗(yàn)證配置的準(zhǔn)確性方式就是成功啟動(dòng)OAuth,且相關(guān)的授權(quán)模式可以跑通。咱們借用之前幾篇博客里寫(xiě)的client,以及PostMan,對(duì)SpringBoot3.X版的OAuth2進(jìn)行測(cè)試,測(cè)試成果如下:

1、擴(kuò)展的用戶名密碼模式,成功

2、授權(quán)碼模式,通過(guò)該問(wèn)如下鏈接獲取code http://127.0.0.1:19200/oauth2/authorize?response_type=code&client_id=client&scope=read&redirect_uri=http://127.0.0.1:19210/leixi/callback

再利用postman,通過(guò)code來(lái)獲取token

接下來(lái),咱們對(duì)token進(jìn)行解析,檢查封裝在access_token里的信息是否存在,咱們通過(guò)之前寫(xiě)好的OAuth-Client對(duì)它進(jìn)行解析,結(jié)果如下:

通過(guò)以上測(cè)試,可知新版的配置完全達(dá)到了我們的要求。

踩坑記錄

1、也不算是坑吧,SpringBoot3.X配置OAuth的方式在網(wǎng)上的相關(guān)資料很少,而且很難搜到,所以搜索這部分內(nèi)容的資料,關(guān)鍵字很重要,一個(gè)是“Spring Security2.7”,一個(gè)是“spring-security-oauth2-authorization-server 配置”,可以搜到很多有用的信息。

2、client的配置很關(guān)鍵,我之前在接口測(cè)試時(shí),怎么都無(wú)法通過(guò),結(jié)果打斷點(diǎn)發(fā)現(xiàn)不同的client調(diào)用時(shí)支持不同的方法,而方法不對(duì),就會(huì)報(bào)invalid_client,調(diào)用方法配置如下:

3、千萬(wàn)不要用http://localhost:8080這種方式調(diào)用OAuth服務(wù),但凡遇到localhost,都會(huì)報(bào)invalid_grant等bug。

4、通過(guò)http://IP:PORT/oauth2/authorize訪問(wèn)OAuth時(shí),鏈接中一定要帶上client_id, scope,不然無(wú)法授權(quán),且鏈接中如果有redirect_uri,則redirect_uri一定要在客戶端配置的redirect_uri列表內(nèi),且通過(guò)/oauth2/authorize獲得code后,通過(guò)code來(lái)獲取token時(shí),請(qǐng)求中要有redirect_uri,且要和初始鏈接一致。

5、同一個(gè)code只能用一次,之前我調(diào)試時(shí),獲取到了code,并根據(jù)code獲得了token,結(jié)果在解析token時(shí)出了問(wèn)題,我嘗試再用那個(gè)code來(lái)獲取token時(shí)就報(bào)錯(cuò)code過(guò)期,這算是一個(gè)常識(shí)吧,希望新上手的能吸取教訓(xùn)。

6、遇到解決不了的問(wèn)題,還是debug吧,通過(guò)OAuth2ClientAuthenticationFilter可以進(jìn)入過(guò)濾器鏈,再打斷點(diǎn)一步步的調(diào)試,耐心一點(diǎn),總能找到原因的。

后記與致謝

最近一個(gè)月我都在死磕著OAuth,也是想憑著一鼓作氣,把它的運(yùn)用給一次性琢磨透徹了,然而事與愿違,越鉆研下去,越發(fā)覺(jué)得它的博大精深,感覺(jué)不能靠一天兩天就完全掌握,還是需要持續(xù)的學(xué)習(xí)和積累。之前的博客里我有提到,學(xué)習(xí)OAuth時(shí)感覺(jué)到一種深深的挫敗感,因?yàn)槲椰F(xiàn)在研究的東西,在17,18年已經(jīng)被好多人研究透了。而這兩天我又發(fā)現(xiàn)了一些變化,在SpringSecurity升級(jí)之后,很多大佬也整理了博客教新人如何使用spring-security-oauth2-authorization-server,這讓我覺(jué)得前行的道路并不孤單,以下是我覺(jué)得對(duì)我?guī)椭艽蟮牟┛停葜x大佬,感激不盡!

參考資料

Spring Boot 最新版3.x 集成 OAuth 2.0實(shí)現(xiàn)認(rèn)證授權(quán)服務(wù) (首推,我就是看他的博客才配好服務(wù)端客戶端的。)

新版本Spring Security 2.7 + 用法

SpringSecurity最新學(xué)習(xí),spring-security-oauth2-authorization-server

Springboot2.7 OAuth2 server使用jdbc存儲(chǔ)RegisteredClient

到此這篇關(guān)于SpringBoot3.X配置OAuth的文章就介紹到這了,更多相關(guān)SpringBoot3.X配置OAuth內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論