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

Spring Authorization Server 1.1 擴(kuò)展實現(xiàn) OAuth2 密碼模式與 Spring Cloud 的整合實戰(zhàn)

這篇具有很好參考價值的文章主要介紹了Spring Authorization Server 1.1 擴(kuò)展實現(xiàn) OAuth2 密碼模式與 Spring Cloud 的整合實戰(zhàn)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

開局一張圖

項目源碼:youlai-mall

通過 Spring Cloud Gateway 訪問認(rèn)證中心進(jìn)行認(rèn)證并獲取得到訪問令牌。
oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)
再根據(jù)訪問令牌 access_token 獲取當(dāng)前登錄的用戶信息。
oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

前言

Spring Security OAuth2 的最終版本是2.5.2,并于2022年6月5日正式宣布停止維護(hù)。Spring 官方為此推出了新的替代產(chǎn)品,即 Spring Authorization Server。然而,出于安全考慮,Spring Authorization Server 不再支持密碼模式,因為密碼模式要求客戶端直接處理用戶的密碼。但對于受信任的第一方系統(tǒng)(自有APP和管理系統(tǒng)等),許多情況下需要使用密碼模式。在這種情況下,需要在 Spring Authorization Server 的基礎(chǔ)上擴(kuò)展密碼模式的支持。本文基于開源微服務(wù)商城項目 youlai-mall、Spring Boot 3 和 Spring Authorization Server 1.1 版本,演示了如何擴(kuò)展密碼模式,以及如何將其應(yīng)用于 Spring Cloud 微服務(wù)實戰(zhàn)。

oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

數(shù)據(jù)庫初始化

Spring Authorization Server 官方提供的授權(quán)服務(wù)器示例 demo-authorizationserver 初始化數(shù)據(jù)庫所使用的3個SQL腳本路徑如下:

oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)
根據(jù)路徑找到3張表的SQL腳本

  • 令牌發(fā)放記錄表: oauth2-authorization-schema.sql
  • 授權(quán)記錄表: oauth2-authorization-consent-schema.sql
  • 客戶端信息表: oauth2-registered-client-schema.sql

整合后的完整數(shù)據(jù)庫 SQL 腳本如下:

-- ----------------------------
-- 1. 創(chuàng)建數(shù)據(jù)庫
-- ----------------------------
CREATE DATABASE IF NOT EXISTS oauth2_server DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;

-- ----------------------------
-- 2. 創(chuàng)建表
-- ----------------------------
use oauth2_server;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- 2.1 oauth2_authorization 令牌發(fā)放記錄表
-- ----------------------------
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 timestamp DEFAULT NULL,
    authorization_code_expires_at timestamp DEFAULT NULL,
    authorization_code_metadata blob DEFAULT NULL,
    access_token_value blob DEFAULT NULL,
    access_token_issued_at timestamp DEFAULT NULL,
    access_token_expires_at timestamp 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 timestamp DEFAULT NULL,
    oidc_id_token_expires_at timestamp DEFAULT NULL,
    oidc_id_token_metadata blob DEFAULT NULL,
    refresh_token_value blob DEFAULT NULL,
    refresh_token_issued_at timestamp DEFAULT NULL,
    refresh_token_expires_at timestamp DEFAULT NULL,
    refresh_token_metadata blob DEFAULT NULL,
    user_code_value blob DEFAULT NULL,
    user_code_issued_at timestamp DEFAULT NULL,
    user_code_expires_at timestamp DEFAULT NULL,
    user_code_metadata blob DEFAULT NULL,
    device_code_value blob DEFAULT NULL,
    device_code_issued_at timestamp DEFAULT NULL,
    device_code_expires_at timestamp DEFAULT NULL,
    device_code_metadata blob DEFAULT NULL,
    PRIMARY KEY (id)
);

-- ----------------------------
-- 2.2 oauth2_authorization_consent 授權(quá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)
);

-- ----------------------------
-- 2.3 oauth2-registered-client OAuth2 客戶端信息表
-- ----------------------------
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,
    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)
);

授權(quán)服務(wù)器

youlai-auth 模塊作為認(rèn)證授權(quán)服務(wù)器

maven 依賴

在 youlai-auth 模塊的 pom.xml 添加授權(quán)服務(wù)器依賴

<!-- Spring Authorization Server 授權(quán)服務(wù)器依賴 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
    <version>1.1.1</version>
</dependency>

application.yml

認(rèn)證中心配置 oauth2_server 數(shù)據(jù)庫連接信息

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver 
    url: jdbc:mysql://localhost:3306/oauth2_server?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true
    username: root
    password: 123456

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

參考 Spring Authorization Server 官方示例 demo-authorizationserver
oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

AuthorizationServierConfig

參考: Spring Authorization Server 官方示例 demo-authorizationserver 下的 AuthorizationServerConfig.java 進(jìn)行授權(quán)服務(wù)器配置

package com.youlai.auth.config;

/**
 * 授權(quán)服務(wù)器配置
 *
 * @author haoxr
 * @since 3.0.0
 */
@Configuration
@RequiredArgsConstructor
@Slf4j
public class AuthorizationServerConfig {

    private final OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;

    /**
     * 授權(quán)服務(wù)器端點配置
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(
            HttpSecurity http,
            AuthenticationManager authenticationManager,
            OAuth2AuthorizationService authorizationService,
            OAuth2TokenGenerator<?> tokenGenerator

    ) throws Exception {

        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();

        authorizationServerConfigurer
                .tokenEndpoint(tokenEndpoint ->
                        tokenEndpoint
                                .accessTokenRequestConverters(
                                        authenticationConverters ->// <1>
                                                authenticationConverters.addAll(
                                                        // 自定義授權(quán)模式轉(zhuǎn)換器(Converter)
                                                        List.of(
                                                                new PasswordAuthenticationConverter()
                                                        )
                                                )
                                )
                               .authenticationProviders(authenticationProviders ->// <2>
                                        authenticationProviders.addAll(
                                            	// 自定義授權(quán)模式提供者(Provider)
                                                List.of(
                                                        new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator)
                                                )
                                        )
                                )
                                .accessTokenResponseHandler(new MyAuthenticationSuccessHandler()) // 自定義成功響應(yīng)
                                .errorResponseHandler(new MyAuthenticationFailureHandler()) // 自定義失敗響應(yīng)
                );


        RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
        http.securityMatcher(endpointsMatcher)
                .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
                .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
                .apply(authorizationServerConfigurer);

        return http.build();
    }


    @Bean // <5>
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // @formatter:off
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        // @formatter:on
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    private static KeyPair generateRsaKey() { // <6>
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);

        // 初始化 OAuth2 客戶端
        initMallAppClient(registeredClientRepository);
        initMallAdminClient(registeredClientRepository);

        return registeredClientRepository;
    }


    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
                                                           RegisteredClientRepository registeredClientRepository) {

        JdbcOAuth2AuthorizationService service = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
        JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
        rowMapper.setLobHandler(new DefaultLobHandler());
        ObjectMapper objectMapper = new ObjectMapper();
        ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
        List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
        objectMapper.registerModules(securityModules);
        objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
        // 使用刷新模式,需要從 oauth2_authorization 表反序列化attributes字段得到用戶信息(SysUserDetails)
        objectMapper.addMixIn(SysUserDetails.class, SysUserMixin.class);
        objectMapper.addMixIn(Long.class, Object.class);
        
        rowMapper.setObjectMapper(objectMapper);
        service.setAuthorizationRowMapper(rowMapper);
        return service;
    }

    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
                                                                         RegisteredClientRepository registeredClientRepository) {
        // Will be used by the ConsentController
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }


    @Bean
    OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource) {
        JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource));
        jwtGenerator.setJwtCustomizer(jwtCustomizer);

        OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
        OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
        return new DelegatingOAuth2TokenGenerator(
                jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * 初始化創(chuàng)建商城管理客戶端
     *
     * @param registeredClientRepository
     */
    private void initMallAdminClient(JdbcRegisteredClientRepository registeredClientRepository) {

        String clientId = "mall-admin";
        String clientSecret = "123456";
        String clientName = "商城管理客戶端";

        /*
          如果使用明文,客戶端認(rèn)證時會自動升級加密方式,換句話說直接修改客戶端密碼,所以直接使用 bcrypt 加密避免不必要的麻煩
          官方ISSUE: https://github.com/spring-projects/spring-authorization-server/issues/1099
         */
        String encodeSecret = passwordEncoder().encode(clientSecret);

        RegisteredClient registeredMallAdminClient = registeredClientRepository.findByClientId(clientId);
        String id = registeredMallAdminClient != null ? registeredMallAdminClient.getId() : UUID.randomUUID().toString();

        RegisteredClient mallAppClient = RegisteredClient.withId(id)
                .clientId(clientId)
                .clientSecret(encodeSecret)
                .clientName(clientName)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 密碼模式
                .authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA) // 驗證碼模式
                .redirectUri("http://127.0.0.1:8080/authorized")
                .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(1)).build())
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();
        registeredClientRepository.save(mallAppClient);
    }

    /**
     * 初始化創(chuàng)建商城APP客戶端
     *
     * @param registeredClientRepository
     */
    private void initMallAppClient(JdbcRegisteredClientRepository registeredClientRepository) {

        String clientId = "mall-app";
        String clientSecret = "123456";
        String clientName = "商城APP客戶端";

        // 如果使用明文,在客戶端認(rèn)證的時候會自動升級加密方式,直接使用 bcrypt 加密避免不必要的麻煩
        String encodeSecret = passwordEncoder().encode(clientSecret);

        RegisteredClient registeredMallAppClient = registeredClientRepository.findByClientId(clientId);
        String id = registeredMallAppClient != null ? registeredMallAppClient.getId() : UUID.randomUUID().toString();

        RegisteredClient mallAppClient = RegisteredClient.withId(id)
                .clientId(clientId)
                .clientSecret(encodeSecret)
                .clientName(clientName)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(WxMiniAppAuthenticationToken.WECHAT_MINI_APP) // 微信小程序模式
                .authorizationGrantType(SmsCodeAuthenticationToken.SMS_CODE) // 短信驗證碼模式
                .redirectUri("http://127.0.0.1:8080/authorized")
                .postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(1)).build())
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();
        registeredClientRepository.save(mallAppClient);
    }
}

DefaultSecutiryConfig

  • 參考 Spring Authorization Server 官方示例 demo-authorizationserver 下的 DefaultSecurityConfig.java 進(jìn)行安全配置
package com.youlai.auth.config;

/**
 * 授權(quán)服務(wù)器安全配置
 *
 * @author haoxr
 * @since 3.0.0
 */
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class DefaultSecurityConfig {
    
    /**
     * Spring Security 安全過濾器鏈配置
     */
    @Bean
    @Order(0)
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(requestMatcherRegistry ->
                        {
                            requestMatcherRegistry.anyRequest().authenticated();
                        }
                )
                .csrf(AbstractHttpConfigurer::disable)
                .formLogin(Customizer.withDefaults());

        return http.build();
    }

    /**
     * Spring Security 自定義安全配置
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) ->
                // 不走過濾器鏈(場景:靜態(tài)資源js、css、html)
                web.ignoring().requestMatchers(
                        "/webjars/**",
                        "/doc.html",
                        "/swagger-resources/**",
                        "/v3/api-docs/**",
                        "/swagger-ui/**"
                );
    }
}

密碼模式擴(kuò)展

PasswordAuthenticationToken

package com.youlai.auth.authentication.password;

/**
 * 密碼授權(quán)模式身份驗證令牌(包含用戶名和密碼等)
 *
 * @author haoxr
 * @since 3.0.0
 */
public class PasswordAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {

    public static final AuthorizationGrantType PASSWORD = new AuthorizationGrantType("password");


    /**
     * 令牌申請訪問范圍
     */
    private final Set<String> scopes;

    /**
     * 密碼模式身份驗證令牌
     *
     * @param clientPrincipal      客戶端信息
     * @param scopes               令牌申請訪問范圍
     * @param additionalParameters 自定義額外參數(shù)(用戶名和密碼)
     */
    public PasswordAuthenticationToken(
            Authentication clientPrincipal,
            Set<String> scopes,
            @Nullable Map<String, Object> additionalParameters
    ) {
        super(PASSWORD, clientPrincipal, additionalParameters);
        this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());

    }

    /**
     * 用戶憑證(密碼)
     */
    @Override
    public Object getCredentials() {
        return this.getAdditionalParameters().get(OAuth2ParameterNames.PASSWORD);
    }

    public Set<String> getScopes() {
        return scopes;
    }
}

PasswordAuthenticationConverter

package com.youlai.auth.authentication.password;

/**
 * 密碼模式參數(shù)解析器
 * <p>
 * 解析請求參數(shù)中的用戶名和密碼,并構(gòu)建相應(yīng)的身份驗證(Authentication)對象
 *
 * @author haoxr
 * @see org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter
 * @since 3.0.0
 */
public class PasswordAuthenticationConverter implements AuthenticationConverter {

    @Override
    public Authentication convert(HttpServletRequest request) {

        // 授權(quán)類型 (必需)
        String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
        if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
            return null;
        }

        // 客戶端信息
        Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();

        // 參數(shù)提取驗證
        MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);

        // 令牌申請訪問范圍驗證 (可選)
        String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
        if (StringUtils.hasText(scope) &&
                parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
            OAuth2EndpointUtils.throwError(
                    OAuth2ErrorCodes.INVALID_REQUEST,
                    OAuth2ParameterNames.SCOPE,
                    OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
        }
        Set<String> requestedScopes = null;
        if (StringUtils.hasText(scope)) {
            requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
        }

        // 用戶名驗證(必需)
        String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
        if (StrUtil.isBlank(username)) {
            OAuth2EndpointUtils.throwError(
                    OAuth2ErrorCodes.INVALID_REQUEST,
                    OAuth2ParameterNames.USERNAME,
                    OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI
            );
        }

        // 密碼驗證(必需)
        String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
        if (StrUtil.isBlank(password)) {
            OAuth2EndpointUtils.throwError(
                    OAuth2ErrorCodes.INVALID_REQUEST,
                    OAuth2ParameterNames.PASSWORD,
                    OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI
            );
        }

        // 附加參數(shù)(保存用戶名/密碼傳遞給 PasswordAuthenticationProvider 用于身份認(rèn)證)
        Map<String, Object> additionalParameters = parameters
                .entrySet()
                .stream()
                .filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) &&
                        !e.getKey().equals(OAuth2ParameterNames.SCOPE)
                ).collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));

        return new PasswordAuthenticationToken(
                clientPrincipal,
                requestedScopes,
                additionalParameters
        );
    }

}

PasswordAuthenticationProvider

package com.youlai.auth.authentication.password;

/**
 * 密碼模式身份驗證提供者
 * <p>
 * 處理基于用戶名和密碼的身份驗證
 *
 * @author haoxr
 * @since 3.0.0
 */
@Slf4j
public class PasswordAuthenticationProvider implements AuthenticationProvider {

    private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
    private final AuthenticationManager authenticationManager;
    private final OAuth2AuthorizationService authorizationService;
    private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;

    /**
     * Constructs an {@code OAuth2ResourceOwnerPasswordAuthenticationProviderNew} using the provided parameters.
     *
     * @param authenticationManager the authentication manager
     * @param authorizationService  the authorization service
     * @param tokenGenerator        the token generator
     * @since 0.2.3
     */
    public PasswordAuthenticationProvider(AuthenticationManager authenticationManager,
                                          OAuth2AuthorizationService authorizationService,
                                          OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator
    ) {
        Assert.notNull(authorizationService, "authorizationService cannot be null");
        Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
        this.authenticationManager = authenticationManager;
        this.authorizationService = authorizationService;
        this.tokenGenerator = tokenGenerator;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        PasswordAuthenticationToken resourceOwnerPasswordAuthentication = (PasswordAuthenticationToken) authentication;
        OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils
                .getAuthenticatedClientElseThrowInvalidClient(resourceOwnerPasswordAuthentication);
        RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();

        // 驗證客戶端是否支持授權(quán)類型(grant_type=password)
        if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) {
            throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
        }

        // 生成用戶名密碼身份驗證令牌
        Map<String, Object> additionalParameters = resourceOwnerPasswordAuthentication.getAdditionalParameters();
        String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME);
        String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD);

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);

        // 用戶名密碼身份驗證,成功后返回帶有權(quán)限的認(rèn)證信息
        Authentication usernamePasswordAuthentication;
        try {
            usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        } catch (Exception e) {
            // 需要將其他類型的異常轉(zhuǎn)換為 OAuth2AuthenticationException 才能被自定義異常捕獲處理,邏輯源碼 OAuth2TokenEndpointFilter#doFilterInternal
            throw new OAuth2AuthenticationException(e.getCause() != null ? e.getCause().getMessage() : e.getMessage());
        }

        // 驗證申請訪問范圍(Scope)
        Set<String> authorizedScopes = registeredClient.getScopes();
        Set<String> requestedScopes = resourceOwnerPasswordAuthentication.getScopes();
        if (!CollectionUtils.isEmpty(requestedScopes)) {
            Set<String> unauthorizedScopes = requestedScopes.stream()
                    .filter(requestedScope -> !registeredClient.getScopes().contains(requestedScope))
                    .collect(Collectors.toSet());
            if (!CollectionUtils.isEmpty(unauthorizedScopes)) {
                throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);
            }
            authorizedScopes = new LinkedHashSet<>(requestedScopes);
        }

        // 訪問令牌(Access Token) 構(gòu)造器
        DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
                .registeredClient(registeredClient)
                .principal(usernamePasswordAuthentication) // 身份驗證成功的認(rèn)證信息(用戶名、權(quán)限等信息)
                .authorizationServerContext(AuthorizationServerContextHolder.getContext())
                .authorizedScopes(authorizedScopes)
                .authorizationGrantType(AuthorizationGrantType.PASSWORD) // 授權(quán)方式
                .authorizationGrant(resourceOwnerPasswordAuthentication) // 授權(quán)具體對象
                ;

        // 生成訪問令牌(Access Token)
        OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType((OAuth2TokenType.ACCESS_TOKEN)).build();
        OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
        if (generatedAccessToken == null) {
            OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
                    "The token generator failed to generate the access token.", ERROR_URI);
            throw new OAuth2AuthenticationException(error);
        }


        OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
                generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
                generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());

        // 權(quán)限數(shù)據(jù)(perms)比較多通過反射移除,不隨令牌一起持久化至數(shù)據(jù)庫
        ReflectUtil.setFieldValue(usernamePasswordAuthentication.getPrincipal(), "perms", null);

        OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
                .principalName(usernamePasswordAuthentication.getName())
                .authorizationGrantType(AuthorizationGrantType.PASSWORD)
                .authorizedScopes(authorizedScopes)
                .attribute(Principal.class.getName(), usernamePasswordAuthentication); // attribute 字段
        if (generatedAccessToken instanceof ClaimAccessor) {
            authorizationBuilder.token(accessToken, (metadata) ->
                    metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
        } else {
            authorizationBuilder.accessToken(accessToken);
        }

        // 生成刷新令牌(Refresh Token)
        OAuth2RefreshToken refreshToken = null;
        if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
                // Do not issue refresh token to public client
                !clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {

            tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
            OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
            if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
                OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
                        "The token generator failed to generate the refresh token.", ERROR_URI);
                throw new OAuth2AuthenticationException(error);
            }

            refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
            authorizationBuilder.refreshToken(refreshToken);
        }

        OAuth2Authorization authorization = authorizationBuilder.build();

        // 持久化令牌發(fā)放記錄到數(shù)據(jù)庫
        this.authorizationService.save(authorization);
        additionalParameters = Collections.emptyMap();

        return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
    }

    /**
     * 判斷傳入的 authentication 類型是否與當(dāng)前認(rèn)證提供者(AuthenticationProvider)相匹配--模板方法
     * <p>
     * ProviderManager#authenticate 遍歷 providers 找到支持對應(yīng)認(rèn)證請求的 provider-迭代器模式
     *
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return PasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

JWT 自定義字段

參考官方 ISSUE :Adds how-to guide on adding authorities to access tokens

package com.youlai.auth.config;

/**
 * JWT 自定義字段
 *
 * @author haoxr
 * @since 3.0.0
 */
@Configuration
@RequiredArgsConstructor
public class JwtTokenClaimsConfig {

    private final RedisTemplate redisTemplate;

    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() {
        return context -> {
            if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {
                // Customize headers/claims for access_token
                Optional.ofNullable(context.getPrincipal().getPrincipal()).ifPresent(principal -> {
                    JwtClaimsSet.Builder claims = context.getClaims();
                    if (principal instanceof SysUserDetails userDetails) { 
						// 系統(tǒng)用戶添加自定義字段
                        Long userId = userDetails.getUserId();
                        claims.claim("user_id", userId);  // 添加系統(tǒng)用戶ID

                        // 角色集合存JWT
                        var authorities = AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities())
                                .stream()
                                .collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
                        claims.claim(SecurityConstants.AUTHORITIES_CLAIM_NAME_KEY, authorities);

                        // 權(quán)限集合存Redis(數(shù)據(jù)多)
                        Set<String> perms = userDetails.getPerms();
                        redisTemplate.opsForValue().set(SecurityConstants.USER_PERMS_CACHE_PREFIX + userId, perms);

                    } else if (principal instanceof MemberDetails userDetails) { 
                        // 商城會員添加自定義字段
                        claims.claim("member_id", String.valueOf(userDetails.getId())); // 添加會員ID
                    }
                });
            }
        };
    }

}

自定義認(rèn)證響應(yīng)

?? 如何自定義 OAuth2 認(rèn)證成功或失敗的響應(yīng)數(shù)據(jù)結(jié)構(gòu)符合當(dāng)前系統(tǒng)統(tǒng)一的規(guī)范?

下圖左側(cè)部份是 OAuth2 原生返回(?? ),大多數(shù)情況下,我們希望返回帶有業(yè)務(wù)碼的數(shù)據(jù)(??),以方便前端進(jìn)行處理。

oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

OAuth2 處理認(rèn)證成功或失敗源碼坐標(biāo) OAuth2TokenEndpointFilter#doFilterInternal ,如下圖:

oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

根據(jù)源碼閱讀,發(fā)現(xiàn)只要重寫? AuthenticationSuccessHandler 和? AuthenticationFailureHandler 的邏輯,就能夠自定義認(rèn)證成功和認(rèn)證失敗時的響應(yīng)數(shù)據(jù)格式。

認(rèn)證成功響應(yīng)

package com.youlai.auth.handler;

/**
 * 認(rèn)證成功處理器
 *
 * @author haoxr
 * @since 3.0.0
 */
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    /**
     * MappingJackson2HttpMessageConverter 是 Spring 框架提供的一個 HTTP 消息轉(zhuǎn)換器,用于將 HTTP 請求和響應(yīng)的 JSON 數(shù)據(jù)與 Java 對象之間進(jìn)行轉(zhuǎn)換
     */
    private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();
    private Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter();


    /**
     * 自定義認(rèn)證成功響應(yīng)數(shù)據(jù)結(jié)構(gòu)
     *
     * @param request the request which caused the successful authentication
     * @param response the response
     * @param authentication the <tt>Authentication</tt> object which was created during
     * the authentication process.
     * @throws IOException
     * @throws ServletException
     */

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
                (OAuth2AccessTokenAuthenticationToken) authentication;

        OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
        OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
        Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();

        OAuth2AccessTokenResponse.Builder builder =
                OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
                        .tokenType(accessToken.getTokenType());
        if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
            builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
        }
        if (refreshToken != null) {
            builder.refreshToken(refreshToken.getTokenValue());
        }
        if (!CollectionUtils.isEmpty(additionalParameters)) {
            builder.additionalParameters(additionalParameters);
        }
        OAuth2AccessTokenResponse accessTokenResponse = builder.build();

        Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter
                .convert(accessTokenResponse);
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);

        this.accessTokenHttpResponseConverter.write(Result.success(tokenResponseParameters), null, httpResponse);
    }
}

認(rèn)證失敗響應(yīng)

package com.youlai.auth.handler;

/**
 * 認(rèn)證失敗處理器
 *
 * @author haoxr
 * @since 2023/7/6
 */
@Slf4j
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    /**
     * MappingJackson2HttpMessageConverter 是 Spring 框架提供的一個 HTTP 消息轉(zhuǎn)換器,用于將 HTTP 請求和響應(yīng)的 JSON 數(shù)據(jù)與 Java 對象之間進(jìn)行轉(zhuǎn)換
     */
    private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();


    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
        ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
        Result result = Result.failed(error.getErrorCode());
        accessTokenHttpResponseConverter.write(result, null, httpResponse);
    }
}

配置自定義處理器

AuthorizationServierConfig

public SecurityFilterChain authorizationServerSecurityFilterChain() throws Exception {

    // ...
    authorizationServerConfigurer
        .tokenEndpoint(tokenEndpoint ->
                       tokenEndpoint
                       // ...
                       .accessTokenResponseHandler(new MyAuthenticationSuccessHandler()) // 自定義成功響應(yīng)
                       .errorResponseHandler(new MyAuthenticationFailureHandler()) // 自定義失敗響應(yīng)
                      );

}

密碼模式測試

單元測試

啟動 youlai-system 模塊,需要從其獲取系統(tǒng)用戶信息(用戶名、密碼)進(jìn)行認(rèn)證

package com.youlai.auth.authentication;

/**
 * OAuth2 密碼模式單元測試
 */
@SpringBootTest
@AutoConfigureMockMvc
@Slf4j
public class PasswordAuthenticationTests {

    @Autowired
    private MockMvc mvc;

    /**
     * 測試密碼模式登錄
     */
    @Test
    void testPasswordLogin() throws Exception {
        HttpHeaders headers = new HttpHeaders();
        // 客戶端ID和密鑰
        headers.setBasicAuth("mall-admin", "123456");

        this.mvc.perform(post("/oauth2/token")
                        .param(OAuth2ParameterNames.GRANT_TYPE, "password") // 密碼模式
                        .param(OAuth2ParameterNames.USERNAME, "admin") // 用戶名
                        .param(OAuth2ParameterNames.PASSWORD, "123456") // 密碼
                        .headers(headers))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.access_token").isNotEmpty());
    }
}

單元測試通過,打印響應(yīng)數(shù)據(jù)可以看到返回的 access_token 和 refresh_token
oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

Postman 測試

  • 請求參數(shù)oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

  • 認(rèn)證參數(shù)

    Authorization Type 選擇 Basic Auth , 填寫客戶端ID(mall-admin)和密鑰(123456), oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

資源服務(wù)器

youlai-system 系統(tǒng)管理模塊也作為資源服務(wù)器

maven 依賴

<!-- Spring Authorization Server 授權(quán)服務(wù)器依賴 -->
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

application.yml

通過 Feign 請求 youlai-system 服務(wù)以獲取系統(tǒng)用戶認(rèn)證信息(用戶名和密碼),在用戶尚未登錄的情況下,需要將此請求的路徑配置到白名單中以避免攔截。

security:
  # 允許無需認(rèn)證的路徑列表
  whitelist-paths:
    # 獲取系統(tǒng)用戶的認(rèn)證信息用于賬號密碼判讀
    - /api/v1/users/{username}/authInfo

資源服務(wù)器配置

配置 ResourceServerConfig 位于資源服務(wù)器公共模塊 common-security 中

package com.youlai.common.security.config;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.json.JSONUtil;
import com.youlai.common.constant.SecurityConstants;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;

import java.util.List;

/**
 * 資源服務(wù)器配置
 *
 * @author haoxr
 * @since 3.0.0
 */
@ConfigurationProperties(prefix = "security")
@Configuration
@EnableWebSecurity
@Slf4j
public class ResourceServerConfig {

    /**
     * 白名單路徑列表
     */
    @Setter
    private List<String> whitelistPaths;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        log.info("whitelist path:{}", JSONUtil.toJsonStr(whitelistPaths));
        http.authorizeHttpRequests(requestMatcherRegistry ->
                        {
                            if (CollectionUtil.isNotEmpty(whitelistPaths)) {
                                requestMatcherRegistry.requestMatchers(Convert.toStrArray(whitelistPaths)).permitAll();
                            }
                            requestMatcherRegistry.anyRequest().authenticated();
                        }
                )
                .csrf(AbstractHttpConfigurer::disable)
        ;
        http.oauth2ResourceServer(resourceServerConfigurer ->
                resourceServerConfigurer.jwt(jwtConfigurer -> jwtAuthenticationConverter())
        ) ;
        return http.build();
    }

    /**
     * 不走過濾器鏈的放行配置
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                .requestMatchers(
                        "/webjars/**",
                        "/doc.html",
                        "/swagger-resources/**",
                        "/v3/api-docs/**",
                        "/swagger-ui/**"
                );
    }


    /**
     * 自定義JWT Converter
     *
     * @return Converter
     * @see JwtAuthenticationProvider#setJwtAuthenticationConverter(Converter)
     */
    @Bean
    public Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix(Strings.EMPTY);
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.AUTHORITIES_CLAIM_NAME_KEY);

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

認(rèn)證流程測試

分別啟動 youlai-mall 的 youai-auth (認(rèn)證中心)、youlai-system(系統(tǒng)管理模塊)、youali-gateway(網(wǎng)關(guān))

登錄認(rèn)證授權(quán)

  • 請求參數(shù)oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

  • 認(rèn)證參數(shù)

    Authorization Type 選擇 Basic Auth , 填寫客戶端ID(mall-admin)和密鑰(123456), oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

  • 成功響應(yīng)

    認(rèn)證成功,獲取到訪問令牌(access_token )

    oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

獲取用戶信息

使用已獲得的訪問令牌 (access_token) 向資源服務(wù)器發(fā)送請求以獲取登錄用戶信息
oauth2,# youlai-mall,# Spring Authorization Server,1024程序員節(jié),spring cloud,spring boot,gateway,微服務(wù)

成功地獲取登錄用戶信息的響應(yīng),而不是出現(xiàn)未授權(quán)的401錯誤。

結(jié)語

關(guān)于 Spring Authorization Server 1.1 版本的密碼模式擴(kuò)展和在 Spring Cloud 中使用新的授權(quán)方式,可以說與 Spring Security OAuth2 的代碼相似度極高。如果您已經(jīng)熟悉 Spring Security OAuth2,那么學(xué)習(xí) Spring Authorization Server 將變得輕而易舉。后續(xù)文章會更新其他常見授權(quán)模式的擴(kuò)展,敬請期待~

源碼

本文完整源碼: youlai-mall

參考

  • Spring Security 棄用 授權(quán)服務(wù)器和資源服務(wù)器

  • Spring Security OAuth 生命周期終止通知

    Spring Security OAuth 2.0 更新路線圖文章來源地址http://www.zghlxwxcb.cn/news/detail-757528.html

到了這里,關(guān)于Spring Authorization Server 1.1 擴(kuò)展實現(xiàn) OAuth2 密碼模式與 Spring Cloud 的整合實戰(zhàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

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

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

相關(guān)文章

  • Spring-Security+OAuth2+redis實現(xiàn)密碼登錄

    一、OAuth2認(rèn)證模式 ????????一共四種認(rèn)證方式,授權(quán)碼模式、密碼模式、簡化模式和客戶端模式。實現(xiàn)單點登錄,比較流行的方法是使用jwt方式,jwt是無狀態(tài)的,其本身就能攜帶信息,因此服務(wù)端可以不用保存他的信息,但只要token不過期,用戶就可以一直訪問,這樣就無

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

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

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

    2024年02月05日
    瀏覽(42)
  • 淺析spring-security-oauth2-authorization-server

    淺析spring-security-oauth2-authorization-server

    ????????在 Spring 中, Shiro 和 spring-security 是比較常用的安全框架解決方案,? shiro 在中小型項目中使用通常來說既簡單,?也能達(dá)到常規(guī)的需求,?如果項目較為復(fù)雜,?建議 spring-security ? ? ? ? Spring Security OAuth2.0 已經(jīng)停止維護(hù),? oauth2-authorization-server 是目前官方推薦的安全框架

    2024年02月11日
    瀏覽(21)
  • spring-security-oauth2-authorization-server(一)SpringBoot3.1.3整合

    spring-security-oauth2-authorization-server(一)SpringBoot3.1.3整合

    因為SpringBoot3.x是目前最新的版本,整合spring-security-oauth2-authorization-server的資料很少,所以產(chǎn)生了這篇文章,主要為想嘗試SpringBoot高版本,想整合最新的spring-security-oauth2-authorization-server的初學(xué)者,旨在為大家提供一個簡單上手的參考,如果哪里寫得不對或可以優(yōu)化的還請大家

    2024年02月03日
    瀏覽(18)
  • Spring Security實現(xiàn)OAuth2協(xié)議及實戰(zhàn)

    Spring Security實現(xiàn)OAuth2協(xié)議及實戰(zhàn)

    文章篇幅較長,愿讀者耐心看完。如有不足之處,請指正。 一.OAuth2介紹 1.1 OAuth2是什么 怎么用 OAuth2是目前最流行的授權(quán)協(xié)議,用來授權(quán)第三方應(yīng)用,獲取用戶數(shù)據(jù)。 舉個例子:快遞員想要進(jìn)入小區(qū),有3種方式。1是業(yè)主遠(yuǎn)程開門,2是業(yè)主告訴門禁密碼,3是使用令牌(Oaut

    2024年02月08日
    瀏覽(21)
  • Spring Boot 中如何使用 Spring Security OAuth2 來實現(xiàn)單點登錄

    Spring Boot 中如何使用 Spring Security OAuth2 來實現(xiàn)單點登錄

    在現(xiàn)代 Web 應(yīng)用程序中,單點登錄(Single Sign-On,簡稱 SSO)是一個非常重要的功能。Spring Security OAuth2 是 Spring Security 框架的一個擴(kuò)展,它提供了一種簡單的方式來實現(xiàn) SSO。在本文中,我們將介紹如何在 Spring Boot 應(yīng)用程序中使用 Spring Security OAuth2 來實現(xiàn)單點登錄。 在開始之前

    2024年02月06日
    瀏覽(24)
  • Spring Cloud Gateway 整合OAuth2.0 實現(xiàn)統(tǒng)一認(rèn)證授權(quán)

    Spring Cloud Gateway 整合OAuth2.0 實現(xiàn)統(tǒng)一認(rèn)證授權(quán) GateWay——向其他服務(wù)傳遞參數(shù)數(shù)據(jù) https://blog.csdn.net/qq_38322527/article/details/126530849 @EnableAuthorizationServer Oauth2ServerConfig 驗證簽名 網(wǎng)關(guān)服務(wù)需要RSA的公鑰來驗證簽名是否合法,所以認(rèn)證服務(wù)需要有個接口把公鑰暴露出來 接下來搭建網(wǎng)

    2024年02月13日
    瀏覽(23)
  • Spring Boot整合OAuth2實現(xiàn)GitHub第三方登錄

    Github OAuth 第三方登錄示例 第三方登錄的原理是借助OAuth授權(quán)來實現(xiàn),首先用戶先向客戶端提供第三方網(wǎng)站的數(shù)據(jù)證明自己的身份獲取授權(quán)碼,然后客戶端拿著授權(quán)碼與授權(quán)服務(wù)器建立連接獲得一個Access Token,之后客戶端就可以通過Access Token來與資源服務(wù)器進(jìn)行交互。 使用O

    2024年02月08日
    瀏覽(92)
  • SpringCloud整合spring security+ oauth2+Redis實現(xiàn)認(rèn)證授權(quán)

    SpringCloud整合spring security+ oauth2+Redis實現(xiàn)認(rèn)證授權(quán)

    在微服務(wù)構(gòu)建中,我們一般用一個父工程來通知管理依賴的各種版本號信息。父工程pom文件如下: 在SpringCloud微服務(wù)體系中服務(wù)注冊中心是一個必要的存在,通過注冊中心提供服務(wù)的注冊和發(fā)現(xiàn)。具體細(xì)節(jié)可以查看我之前的博客,這里不再贅述。我們開始構(gòu)建一個eureka注冊中

    2024年02月06日
    瀏覽(25)
  • Spring Cloud Gateway + Oauth2 實現(xiàn)統(tǒng)一認(rèn)證和鑒權(quán)!

    Spring Cloud Gateway + Oauth2 實現(xiàn)統(tǒng)一認(rèn)證和鑒權(quán)!

    micro-oauth2-gateway:網(wǎng)關(guān)服務(wù),負(fù)責(zé)請求轉(zhuǎn)發(fā)和鑒權(quán)功能,整合Spring Security+Oauth2; micro-oauth2-auth:Oauth2認(rèn)證服務(wù),負(fù)責(zé)對登錄用戶進(jìn)行認(rèn)證,整合Spring Security+Oauth2; micro-oauth2-api:受保護(hù)的API服務(wù),用戶鑒權(quán)通過后可以訪問該服務(wù),不整合Spring Security+Oauth2。 我們首先來搭建認(rèn)

    2024年01月16日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包