Spring Authorization Server OAuth2.1

news2024/11/17 18:59:45

 Spring Authorization Server介绍

Spring Authorization Server 是一个框架,它提供了 OAuth 2.1 OpenID Connect 1.0 规范以及其他相关规范的实现。

它建立在 Spring Security 之上,为构建 OpenID Connect 1.0 身份提供者和 OAuth2 授权服务器产品提供了一个安全、轻量级和可定制的基础

因为随着网络和设备的发展,原先的 OAuth 2.0 已经不能满足现今的需求了,OAuth 社区对 OAuth 2.0 中的几种授权模式进行了取舍和优化,并增加一些新的特性, 于是推出了 OAuth 2.1,

而原先的 Spring Security OAuth 2.0 使用的是 OAuth 2.0 协议,为满足新的变化,Spring Security 团队重新写了一套叫 Spring Authorization Server 的认证授权框架来替换原先的 Spring Security OAuth 2.0

 

实现方式的不同 

Springboot2.x Oauth2实现

  • Spring Security Oauth2 支持搭建授权服务器和资源服务器

Springboot3.x Oauth2实现

  • Spring Security6 自身提供资源和客户端类库支持
  • Spring Authorization Server支持授权服务器搭建

OAuth2.1协议

OAuth 2.1去掉了OAuth2.0中的密码模式、简化模式,增加了设备授权码模式,同时也对授权码模式增加了PKCE扩展

OAuth2.1协议 官网:https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07

OAuth 2.1 的变化 有道云笔记:https://note.youdao.com/s/DRnLx34U

授权码模式+PKCE扩展 

在OAuth2.0版本的授权码的基础上,进行了扩展。下面的之前授权码模式的流程:

  • 1、用户访问客户端,后者将前者导向授权服务器。
  • 2、用户选择是否给予客户端授权。
  • 3、假设用户给予授权,授权服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
  • 4、客户端收到授权码,附上早先的"重定向URI",向授权服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
  • 5、授权服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)

在第3个步骤中,授权服务器通过重定向URL,将授权码Code发送给客户端,此时可能会被黑客拦截,他得到了code,他去利用code进行申请token,进而去访问资源服务器 为了减轻这种攻击,官方增加PKCE扩展


  • 1. 客户端通过“/oauth2/authorize”地址向认证服务器发起获取授权码请求的时候增加两个参数,即 code_challenge 和 code_challenge_method
  • 2. 认证服务器给客户端返回授权码,同时记录下 code_challenge、code_challenge_method 的值。
  • 3. 客户端使用 code 向认证服务器获取 Access Token 的时候,带上 code_verifier 参数,其值为步骤A加密前的初始值。
  • 4. 认证服务器收到步骤 C 的请求时,将 code_verifier 的值使用 code_challenge_method 的方法进行加密,然后将加密后的值与步骤A中的 code_challenge 进行比较,看看是否一致

code_challenge_method 是加密方法(例如:S256 或 plain(废弃))code_challenge 是使用code_challenge_method加密方法加密后的值

搭建授权服务

版本要求

spring boot 3.x

jdk版本 17

Spring Authorization Server重要组件

  • SecurityFilterChain -> authorizationServerSecurityFilterChain Spring Security的过滤器链,用于协议端点的。
  • SecurityFilterChain -> defaultSecurityFilterChain Spring Security的过滤器链,用于Spring Security的身份认证
  • UserDetailsService 主要进行用户身份验证(存入库中)
  • RegisteredClientRepository 主要用于管理客户端
  • JWKSource 用于签名访问令牌
  • KeyPair 启动时生成的带有密钥的KeyPair实例,用于创建上面的JWKSource
  • JwtDecoder JwtDecoder的一个实例,用于解码已签名的访问令牌
  • AuthorizationServerSettings 用于配置Spring Authorization Server的AuthorizationServerSettings实例

准备sql

授权相关的表

-- 用户授权确认表
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)
);
-- 用户认证信息表
CREATE TABLE oauth2_authorization
(
    id                            varchar(100) NOT NULL,
    registered_client_id          varchar(100) NOT NULL,
    principal_name                varchar(200) NOT NULL,
    authorization_grant_type      varchar(100) NOT NULL,
    authorized_scopes             varchar(1000) DEFAULT NULL,
    attributes                    blob          DEFAULT NULL,
    state                         varchar(500)  DEFAULT NULL,
    authorization_code_value      blob          DEFAULT NULL,
    authorization_code_issued_at  DATETIME      DEFAULT NULL,
    authorization_code_expires_at DATETIME      DEFAULT NULL,
    authorization_code_metadata   blob          DEFAULT NULL,
    access_token_value            blob          DEFAULT NULL,
    access_token_issued_at        DATETIME      DEFAULT NULL,
    access_token_expires_at       DATETIME      DEFAULT NULL,
    access_token_metadata         blob          DEFAULT NULL,
    access_token_type             varchar(100)  DEFAULT NULL,
    access_token_scopes           varchar(1000) DEFAULT NULL,
    oidc_id_token_value           blob          DEFAULT NULL,
    oidc_id_token_issued_at       DATETIME      DEFAULT NULL,
    oidc_id_token_expires_at      DATETIME      DEFAULT NULL,
    oidc_id_token_metadata        blob          DEFAULT NULL,
    refresh_token_value           blob          DEFAULT NULL,
    refresh_token_issued_at       DATETIME      DEFAULT NULL,
    refresh_token_expires_at      DATETIME      DEFAULT NULL,
    refresh_token_metadata        blob          DEFAULT NULL,
    user_code_value               blob          DEFAULT NULL,
    user_code_issued_at           DATETIME      DEFAULT NULL,
    user_code_expires_at          DATETIME      DEFAULT NULL,
    user_code_metadata            blob          DEFAULT NULL,
    device_code_value             blob          DEFAULT NULL,
    device_code_issued_at         DATETIME      DEFAULT NULL,
    device_code_expires_at        DATETIME      DEFAULT NULL,
    device_code_metadata          blob          DEFAULT NULL,
    PRIMARY KEY (id)
);
-- 客户端表
CREATE TABLE oauth2_registered_client
(
    id                            varchar(100)                            NOT NULL,
    client_id                     varchar(100)                            NOT NULL,
    client_id_issued_at           DATETIME      DEFAULT CURRENT_TIMESTAMP NOT NULL,
    client_secret                 varchar(200)  DEFAULT NULL,
    client_secret_expires_at      DATETIME      DEFAULT NULL,
    client_name                   varchar(200)                            NOT NULL,
    client_authentication_methods varchar(1000)                           NOT NULL,
    authorization_grant_types     varchar(1000)                           NOT NULL,
    redirect_uris                 varchar(1000) DEFAULT NULL,
    post_logout_redirect_uris     varchar(1000) DEFAULT NULL,
    scopes                        varchar(1000)                           NOT NULL,
    client_settings               varchar(2000)                           NOT NULL,
    token_settings                varchar(2000)                           NOT NULL,
    PRIMARY KEY (id)
);

用户表

CREATE TABLE `sys_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
  `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '密码',
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '姓名',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '描述',
  `status` tinyint DEFAULT NULL COMMENT '状态(1:正常 0:停用)',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='用户表';

INSERT INTO sys_user (`id`, `username`, `password`, `name`, `description`, `status`) VALUES (1, 'admin', '$2a$10$8fyY0WbNAr980e6nLcPL5ugmpkLLH3serye5SJ3UcDForTW5b0Sx.', '测试用户', 'Spring Security 测试用户', 1);

 pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>oauthServer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>oauthServer</name>
    <description>oauthServer</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

yml配置文件

 

spring:
  application:
    name: oauthServer
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://39.102.208.240:3306/auth?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8
    username: root
    password: root123456
server:
  port: 9800

在config下建配置类AuthorizationConfig

package org.example.oauthserver.config;


import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.example.oauthserver.util.SecurityUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * 认证配置
 * {@link EnableWebSecurity} 注解有两个作用:
 * 1. 加载了WebSecurityConfiguration配置类, 配置安全认证策略。
 * 2. 加载了AuthenticationConfiguration, 配置了认证信息。
 *
 * ==============================================重要组件===============================================
 * SecurityFilterChain -> authorizationServerSecurityFilterChain Spring Security的过滤器链,用于协议端点的。
 * SecurityFilterChain -> defaultSecurityFilterChain Spring Security的过滤器链,用于Spring Security的身份认证
 * UserDetailsService 主要进行用户身份验证
 * RegisteredClientRepository 主要用于管理客户端
 * JWKSource 用于签名访问令牌
 * KeyPair 启动时生成的带有密钥的KeyPair实例,用于创建上面的JWKSource
 * JwtDecoder JwtDecoder的一个实例,用于解码已签名的访问令牌
 * AuthorizationServerSettings 用于配置Spring Authorization Server的AuthorizationServerSettings实例
 *
 */
@Configuration
@EnableWebSecurity
public class AuthorizationConfig {


    /**
     * 配置端点的过滤器链
     *
     * @param http spring security核心配置类
     * @return 过滤器链
     * @throws Exception 抛出
     */
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
                                                                      RegisteredClientRepository registeredClientRepository,
                                                                      AuthorizationServerSettings authorizationServerSettings) throws Exception {
        // 配置默认的设置,忽略认证端点的csrf校验
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                // 开启OpenID Connect 1.0协议相关端点
                .oidc(Customizer.withDefaults());
        http
                //将需要认证的请求,重定向到login进行登录认证。
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                );
        http
                // 处理使用access token访问用户信息端点和客户端注册端点
                .oauth2ResourceServer((resourceServer) -> resourceServer
                        .jwt(Customizer.withDefaults()));

        return http.build();
    }

    /**
     * 配置认证相关的过滤器链
     *
     * @param http spring security核心配置类
     * @return 过滤链配置
     * @throws Exception 抛出
     */
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((authorize) -> authorize
                        // 所有请求都需要认证
                        .anyRequest().authenticated()
                )
                .formLogin(Customizer.withDefaults());
        // 由Spring Security过滤链中UsernamePasswordAuthenticationFilter过滤器拦截处理“login”页面提交的登录信息 和异常处理
        http.oauth2ResourceServer((resourceServer) -> resourceServer
                .jwt(Customizer.withDefaults())
                .accessDeniedHandler(SecurityUtils::exceptionHandler)
                .authenticationEntryPoint(SecurityUtils::exceptionHandler)
        );

        return http.build();
    }

    /**
     * 自定义jwt,将权限信息放至jwt中
     *
     * @return OAuth2TokenCustomizer的实例
     */
    @Bean
    public OAuth2TokenCustomizer<JwtEncodingContext> oAuth2TokenCustomizer() {
        return context -> {
            // 检查登录用户信息是不是UserDetails,排除掉没有用户参与的流程
            if (context.getPrincipal().getPrincipal() instanceof UserDetails user) {
                // 获取申请的scopes
                Set<String> scopes = context.getAuthorizedScopes();
                // 获取用户的权限
                Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
                // 提取权限并转为字符串
                Set<String> authoritySet = Optional.ofNullable(authorities).orElse(Collections.emptyList()).stream()
                        // 获取权限字符串
                        .map(GrantedAuthority::getAuthority)
                        // 去重
                        .collect(Collectors.toSet());

                // 合并scope与用户信息
                authoritySet.addAll(scopes);

                JwtClaimsSet.Builder claims = context.getClaims();
                // 将权限信息放入jwt的claims中(也可以生成一个以指定字符分割的字符串放入)
                claims.claim("authorities", authoritySet);
            }
        };
    }

    /**
     * 自定义jwt解析器,设置解析出来的权限信息的前缀与在jwt中的key
     *
     * @return jwt解析器 JwtAuthenticationConverter
     */
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        // 设置解析权限信息的前缀,设置为空是去掉前缀
        grantedAuthoritiesConverter.setAuthorityPrefix("");
        // 设置权限信息在jwt claims中的key
        grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");

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

    /**
     * 配置密码解析器,使用BCrypt的方式对密码进行加密和验证
     *
     * @return BCryptPasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * Spring Security的配置
     * 设置用户信息,校验用户名、密码
     * 正常的流程是自定义一个service类,实现UserDetailsService接口,去查询DB 查询用户信息,封装为一个UserDetails对象返回
     * 这里就直接写一个user存入内存中进行测试
     * @return
     */
//    @Bean
//    public UserDetailsService users(PasswordEncoder passwordEncoder) {
//        UserDetails user = User.withUsername("admin")
//                .password(passwordEncoder.encode("123456"))
//                .roles("admin", "normal")
//                .authorities("app", "web")
//                .build();
//        return new InMemoryUserDetailsManager(user);
//    }


    /**
     * 配置客户端Repository
     *
     * @param jdbcTemplate    db 数据源信息
     * @param passwordEncoder 密码解析器
     * @return 基于数据库的repository 对应表:oauth2_registered_client
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                // 客户端id
                .clientId("oidc-client")
                // 加密 客户端秘钥{noop}开头,表示“secret”以明文存储
                .clientSecret(passwordEncoder.encode("123456"))
                // 客户端认证方式,就使用默认的认证方式基于请求头的认证
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                // 配置资源服务器使用该客户端获取授权时支持的方式
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                // 授权码模式回调地址,oauth2.1已改为精准匹配,不能只设置域名,并且屏蔽了localhost,本机使用127.0.0.1访问
                .redirectUri("http://127.0.0.1:9700/login/oauth2/code/messaging-client-oidc")
                .redirectUri("https://www.baidu.com")
                // 该客户端的授权范围,OPENID与PROFILE是IdToken的scope,获取授权时请求OPENID的scope时认证服务会返回IdToken
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                // 自定scope
                .scope("read")
                .scope("write")
                // 客户端设置,设置用户需要确认授权
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();

        // 基于db存储客户端,还有一个基于内存的实现 InMemoryRegisteredClientRepository
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);

        // 初始化客户端
        RegisteredClient repositoryByClientId = registeredClientRepository.findByClientId(registeredClient.getClientId());
        if (repositoryByClientId == null) {
            registeredClientRepository.save(registeredClient);
        }
        // PKCE客户端
        RegisteredClient pkceClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("pkce-oidc-client")
                // 公共客户端
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                // 授权码模式,因为是扩展授权码流程,所以流程还是授权码的流程,改变的只是参数
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                // 授权码模式回调地址,oauth2.1已改为精准匹配,不能只设置域名,并且屏蔽了localhost,本机使用127.0.0.1访问
                .redirectUri("http://oauthClient:9700/login/oauth2/code/messaging-client-oidc")
                .redirectUri("https://www.baidu.com")
                .clientSettings(ClientSettings.builder().requireProofKey(Boolean.TRUE).build())
                // 自定scope
                .scope("read")
                .scope("write")
                .build();
        RegisteredClient findPkceClient = registeredClientRepository.findByClientId(pkceClient.getClientId());
        if (findPkceClient == null) {
            registeredClientRepository.save(pkceClient);
        }
        return registeredClientRepository;
    }

    /**
     * 授权信息
     * 对应表:oauth2_authorization
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        // 基于db的oauth2认证服务,还有一个基于内存的服务实现InMemoryOAuth2AuthorizationService
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 授权确认
     *对应表:oauth2_authorization_consent
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        // 基于db的授权确认管理服务,还有一个基于内存的服务实现InMemoryOAuth2AuthorizationConsentService
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 配置jwk源,使用非对称加密,公开用于检索匹配指定选择器的JWK的方法
     *
     * @return JWKSource
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

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

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

    /**
     * 添加授权服务器配置,配置授权服务器请求地址
     * @return AuthorizationServerSettings
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        // 什么都不配置,则使用默认地址
        return AuthorizationServerSettings.builder().build();
    }

}

 添加用户信息

实体信息如下

package org.example.oauthserver.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;


@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class SysUserEntity implements Serializable {

    /**
     * 主键
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 名字
     */
    private String name;

    /**
     * 描述
     */
    private String description;

    /**
     * 状态
     */
    private Integer status;

}

mapper如下 

package org.example.oauthserver.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.example.oauthserver.entity.SysUserEntity;


@Mapper
public interface SysUserMapper extends BaseMapper<SysUserEntity> {

}

service如下 

package org.example.oauthserver.service;

import org.example.oauthserver.entity.SysUserEntity;

public interface SysUserService {

    /**
     *
     * 根据用户名查询用户信息
     */
    SysUserEntity selectByUsername(String username);

}
package org.example.oauthserver.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.example.oauthserver.entity.SysUserEntity;
import org.example.oauthserver.mapper.SysUserMapper;
import org.example.oauthserver.service.SysUserService;
import org.springframework.stereotype.Service;


@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUserEntity> implements SysUserService {


    @Override
    public SysUserEntity selectByUsername(String username) {
        LambdaQueryWrapper<SysUserEntity> lambdaQueryWrapper = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(SysUserEntity::getUsername,username);
        return this.getOne(lambdaQueryWrapper);
    }
}

 UserDetailsService如下

 

package org.example.oauthserver.service.impl;

import jakarta.annotation.Resource;
import org.example.oauthserver.entity.SysUserEntity;
import org.example.oauthserver.service.SysUserService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;


@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SysUserEntity sysUserEntity = sysUserService.selectByUsername(username);
        List<SimpleGrantedAuthority> grantedAuthorityList = Arrays.asList("USER").stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());

        return new User(username,sysUserEntity.getPassword(),grantedAuthorityList);
    }
}

访问测试授权码模式

访问授权端点

http://127.0.0.1:9800/oauth2/authorize?client_id=oidc-client&response_type=code&scope=read&redirect_uri=https://www.baidu.com
http://127.0.0.1:9800/oauth2/authorize?client_id=oidc-client&response_type=code&scope=read&redirect_uri=https://www.baidu.com

  • response_type=code 表示当前是授权码模式     
  •  client_id、scope这两个请求参数要和客户端往授权服务器注册信息对应上,如果客户端注册时没有profile openid中的某一个,那么授权服务器就不会返回code
  • redirect_uri 重定向地址,也就是用户授权之后 code应该发给谁,目前我们还没有编写客户端的接口,所以这里就直接写百度,然后从浏览器拿code进行测试

 拿到授权码

申请token端点/oauth/token ,别忘记填写客户端信息

 

刷新token 

访问测试客户端模式

客户端模式高度信任,所以很简单,直接输入客户端信息和授权类型即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2242345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《生成式 AI》课程 第3講 CODE TASK 任务3:自定义任务的机器人

课程 《生成式 AI》课程 第3講&#xff1a;訓練不了人工智慧嗎&#xff1f;你可以訓練你自己-CSDN博客 我们希望你创建一个定制的服务机器人。 您可以想出任何您希望机器人执行的任务&#xff0c;例如&#xff0c;一个可以解决简单的数学问题的机器人0 一个机器人&#xff0c…

Python知识点精汇!字符串:定义、截取(索引)和其内置函数

目录 一、字符串的定义 二、字符串的截取 1.截取干啥的 2.怎么用截取 3.打印多次 4.两个字符串拼接在一起 三、字符串内置函数 1.查询函数&#xff1a; &#xff08;1&#xff09;find(str,start,end) &#xff08;2&#xff09;index&#xff08;str,start,end&#…

创建vue+electron项目流程

一个vue3和electron最基本的环境搭建步骤如下&#xff1a;// 安装 vite vue3 vite-plugin-vue-setup-extend less normalize.css mitt pinia vue-router npm create vuelatest npm i vite-plugin-vue-setup-extend -D npm i less -D npm i normalize.css -S &#xff0…

从0开始机器学习--Day27--主成分分析方法

主成分分析方法(Principal components analysis) 在降维算法中&#xff0c;比较普遍的是使用主成分分析方法&#xff08;PCA&#xff09; PCA算法简单示例 如图&#xff0c;假设我们有一个二维的特征&#xff0c;想要将其降为一维&#xff0c;简单的方法是寻找一条直线&#…

无效的目标发行版17和无法连接Maven进程问题

起因&#xff1a;我clean了一个模块的Maven想要重新下&#xff0c;他就开始报错。两次了都是这样。如果和我一样一开始都是好好的&#xff0c;直接找Maven的设置&#xff0c;在运行程序改&#xff0c;jre变成了11.它自己变成了我其他的jdk

【Android、IOS、Flutter、鸿蒙、ReactNative 】启动页

Android 设置启动页 自定义 splash.xml 通过themes.xml配置启动页背景图 IOS 设置启动页 LaunchScreen.storyboard 设置为启动页 storyboard页面绘制 Assets.xcassets 目录下导入图片 AppLogo Flutter 设置启动页 Flutter Android 设置启动页 自定义 launch_background.xm…

Java实现多线程编程

目录 一、创建线程 1.1.第一种方法&#xff1a;继承Thread类 1.2.第二种方法&#xff1a;实现Runnable接口 1.3.其他创建线程的方法 二、多线程的优势-增加运行速度 三、Thread类及常见方法 3.1 Thread常见的构造方法 3.2Thread的几个常见方法 3.2.1启动一个线程——sta…

【快速解决】kafka崩了,重启之后,想继续消费,怎么做?

目录 一、怎么寻找我们关心的主题在崩溃之前消费到了哪里&#xff1f; 1、一个问题&#xff1a; 2、查看消费者消费主题__consumer_offsets 3、一个重要前提&#xff1a;消费时要提交offset 二、指定 Offset 消费 假如遇到kafka崩了&#xff0c;你重启kafka之后&#xff0…

【设计模式】行为型模式(四):备忘录模式、中介者模式

《设计模式之行为型模式》系列&#xff0c;共包含以下文章&#xff1a; 行为型模式&#xff08;一&#xff09;&#xff1a;模板方法模式、观察者模式行为型模式&#xff08;二&#xff09;&#xff1a;策略模式、命令模式行为型模式&#xff08;三&#xff09;&#xff1a;责…

GRE做题笔记(零散的个人经验)

locomotive机车By 1813, the Luddite resistance had all but vanished. all but表示“几乎完全”的程度&#xff0c;或者表示排除piston活塞attributed to 归因于how a sportsperson accounted for their own experience of stress 运动员如何解释自己的压力经历 &#xff0c;…

【vmware+ubuntu16.04】vm虚拟机及镜像安装-tools安装包弹不出来问题

学习机器人这门课需要下载虚拟机&#xff0c;做一下记录 首先我下载的是vm虚拟机16&#xff0c; 下载版本可参考该文章课堂上我下载 的镜像是16.04&#xff0c;虚拟机安装教程和镜像添加可参考该博主 按照教程安装成功 安装tools&#xff0c;但是我的弹不出来那个压缩包&…

Redis设计与实现 学习笔记 第十七章 集群

Redis集群是Redis提供的分布式数据库方案&#xff0c;集群通过分片&#xff08;sharding&#xff0c;水平切分&#xff09;来进行数据共享&#xff0c;并提供复制和故障转移功能。 17.1 节点 一个Redis集群通常由多个节点&#xff08;node&#xff09;组成&#xff0c;在刚开…

第03章 文件编程

目标 了解Linux系统文件IO/标准IO基本概念掌握Linux系统文件IO/标准IO常用函数掌握Linux系统文件属性常用函数掌握Linux系统目录文件常用函数 3.1 Linux系统概述 3.1.1 预备知识&#xff08;相关概念&#xff09; &#xff08;1&#xff09;应用程序 和 内核程序 应用程序是…

51c大模型~合集42

我自己的原文哦~ https://blog.51cto.com/whaosoft/11859244 #猎户座 「草莓」即将上线&#xff0c;OpenAI新旗舰大模型曝光&#xff0c;代号「猎户座」 ChatGPT 要进化了&#xff1f; 本月初&#xff0c;OpenAI 创始人、CEO 山姆・奥特曼突然在 X 上发了一张照片&#xff0…

SpringBootTest常见错误解决

1.启动类所在包错误 问题 由于启动类所在包与需要自动注入的类的包不在一个包下&#xff1a; 启动类所在包&#xff1a; com.exmaple.test_02 但是对于需要注入的类却不在com.exmaple.test_02下或者其子包下&#xff0c;就会导致启动类无法扫描到该类&#xff0c;从而无法对…

初始JavaEE篇 —— 网络编程(2):了解套接字,从0到1实现回显服务器

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 TCP 与 UDP Socket套接字 UDP TCP 网络基础知识 在一篇文章中&#xff0c;我们了解了基础的网络知识&#xff0c;网络的出…

机器学习 AdaBoost 算法

AdaBoost 提升学习算法是通过训练多个弱分类算法实现一个强分类算法&#xff0c;做法非常朴素&#xff0c;在训练过程中&#xff0c;提供分类错误的数据权重&#xff0c;降低分类正确的权重&#xff0c;提高分类效果好的弱分类器权重&#xff0c;降低分类效果差的若分类器权重。…

C++编程技巧与规范-类和对象

类和对象 1. 静态对象的探讨与全局对象的构造顺序 静态对象的探讨 类中的静态成员变量(类类型静态成员) 类中静态变量的声明与定义&#xff08;类中声明类外定义&#xff09; #include<iostream> using namespace std;namespace _nmspl {class A{public:A():m_i(5){…

golang中的init函数

程序的初始化和执行都起始于 main 包。如果 main 包还导入了其它的包&#xff0c;那么就会在编译时将它们依次 导入。有时一个包会被多个包同时导入&#xff0c;那么它只会被导入一次&#xff08;例如很多包可能都会用到 fmt 包&#xff0c;但 它只会被导入一次&#x…

【大数据学习 | flume】flume之常见的sink组件

Flume Sink取出Channel中的数据&#xff0c;进行相应的存储文件系统&#xff0c;数据库&#xff0c;或者提交到远程服务器。Flume也提供了各种sink的实现&#xff0c;包括HDFS sink、Logger sink、Avro sink、File Roll sink、HBase sink&#xff0c;。 ​ Flume Sink在设置存…