Spring Boot整合Spring Authorization Server

SpringBoot+SpringSecurity OAuth2 认证服务搭建实战 (六)OAuth2经典场景~授权码模式 2024 最新 SpringSecurity OAuth2 授权码模式完成示例! JDK17spring-security-oauth2-authorization-server 1.3.0 以上spring-boot-starter-oauth2-client 3.3.0 以上Spring Boot 3.3.0 以上修改 C:\Windows\System32\drivers\etc\hosts文件。(如果授权服务器和客户端在不同的域名下,就不用修改了) 阅读详情
AI摘要

本文介绍如何使用Spring Boot整合OAuth2授权服务器,包括配置文件、数据库表脚本、关键配置类及自定义授权页面。

注意:在新版本的sas(1.2.1)中获取token更新了授权码校验逻辑,只能用form-data传递参数,使用url-params会失败

项目环境要求

  • IntelliJ IDEA 2023.3.7 (Ultimate Edition)
  • jdk21
  • spring boot: 3.1.4
  • spring cloud: 2022.0.4
  • spring cloud alibaba: 2022.0.0.0

pom.xml

<dependencies>
        <!-- 引入bootstrap依赖,不引入这个依赖是无法使用bootstrap配置文件的 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!-- spring boot web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- spring boot test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <!-- oauth2 认证服务器依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
        </dependency>
        <!-- 数据库相关依赖 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!-- 引入webjars和bootstrap,自定义登录页和确认页面时使用 -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>5.2.3</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

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)
);

框架自带数据库表脚本位置

boostrap配置文件

配置文件

客户端设置(ClientSettings)说明

  1. requireProofKey:当使用该客户端发起PKCE流程时必须设置为true。
  2. requireAuthorizationConsent:当设置为true时登录后会先跳转授权确认页面,确认后才会跳转到redirect_uri,为false时不会跳转至授权确认页面。
  3. jwkSetUrl:设置客户端jwks的url。
  4. tokenEndpointAuthenticationSigningAlgorithm:设置token端点对验证方法为CLIENT_SECRET_JWT,PRIVATE_KEY_JWT的客户端进行身份验证使用的签名算法。
  5. token设置(TokenSettings)说明
  6. authorizationCodeTimeToLive:授权码(authorization_code)有效时长。
  7. accessTokenTimeToLive:access_token有效时长。
  8. accessTokenFormat:access_token的格式,SELF_CONTAINED是自包含token(jwt格式),REFERENCE是不透明token,相相当于是token元数据的一个id,通过id找到对应数据(自省令牌时)
  9. deviceCodeTimeToLive:设备码有效时长。
  10. reuseRefreshTokens:刷新token时是否重用refresh token,设置为true后refresh token不变,false刷新token时会重新签发一个refresh token。
  11. refreshTokenTimeToLive:refresh token有效时长。
  12. idTokenSignatureAlgorithm:设置id token的加密算法。

/**
 * 认证配置
 * {@link EnableMethodSecurity} 开启全局方法认证,启用JSR250注解支持,启用注解 {@link Secured} 支持,
 * 在Spring Security 6.0版本中将@Configuration注解从@EnableWebSecurity, @EnableMethodSecurity, @EnableGlobalMethodSecurity
 * 和 @EnableGlobalAuthentication 中移除,使用这些注解需手动添加 @Configuration 注解
 * {@link EnableWebSecurity} 注解有两个作用:
 * 1. 加载了WebSecurityConfiguration配置类, 配置安全认证策略。
 * 2. 加载了AuthenticationConfiguration, 配置了认证信息。
 *
 * @author lxq
 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class AuthorizationConfig {

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

    /**
     * 生成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;
    }

    /**
     * 配置端点的过滤器链
     *
     * @param http spring security核心配置类
     * @return 过滤器链
     * @throws Exception 抛出
     */
    @Bean
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {

        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
        RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

        /*http
                .securityMatcher(endpointsMatcher)
                .authorizeHttpRequests((authorize) -> {
                    System.out.println("process trace | authorizeHttpRequests config");
                    authorize
                            .requestMatchers("/assets/**", "/webjars/**", "/login").permitAll()
                            .anyRequest().authenticated();
                })
                // 指定登录页面
                .formLogin(formLogin -> {
                    System.out.println("process trace | formLogin.loginPage(\"/login\")");
                    formLogin.loginPage("/login");
                })
                .csrf((csrf) -> {
                    System.out.println("process trace | csrf.ignoringRequestMatchers");
                    csrf.ignoringRequestMatchers(new RequestMatcher[]{endpointsMatcher});
                })
                .apply(authorizationServerConfigurer);*/

        // web security配置 默认的设置,忽略认证端点的csrf校验
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                // 开启OpenID Connect 1.0协议相关端点
                .oidc(Customizer.withDefaults())
                // 设置自定义用户确认授权页
                .authorizationEndpoint(authorizationEndpoint -> {
                    System.out.println("process trace | authorizationEndpoint.consentPage(" + CUSTOM_CONSENT_PAGE_URI + ")");
                    authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI);
                });
        http
                // 当未登录时访问认证端点时重定向至login页面
                .exceptionHandling((exceptions) -> {
                    System.out.println("process trace | exceptions.defaultAuthenticationEntryPointFor(/login)");
                    exceptions.defaultAuthenticationEntryPointFor(new LoginUrlAuthenticationEntryPoint("/login"),
                            new MediaTypeRequestMatcher(MediaType.TEXT_HTML));
                })
                // 处理使用access token访问用户信息端点和客户端注册端点
                .oauth2ResourceServer((resourceServer) -> {
                    System.out.println("process trace | resourceServer.jwt(Customizer.withDefaults())");
                    resourceServer.jwt(Customizer.withDefaults());
                });

        return http.build();
    }

    /**
     * 配置认证相关的过滤器链
     *
     * @param http spring security核心配置类
     * @return 过滤器链
     * @throws Exception 抛出
     */
    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authorize) -> {
                    System.out.println("process trace | authorizeHttpRequests config");
                    authorize
                            .requestMatchers("/assets/**", "/webjars/**", "/login").permitAll()
                            .anyRequest().authenticated();
                })
                // 指定登录页面
                /*.formLogin(formLogin -> {
                    System.out.println("process trace | formLogin.loginPage(\"/login\")");
                    formLogin.loginPage("/login");
                })*/;
        // 添加BearerTokenAuthenticationFilter,将认证服务当做一个资源服务,解析请求头中的token
        http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));

        return http.build();
    }

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

    /**
     * 配置客户端Repository
     *
     * @param jdbcTemplate    db 数据源信息
     * @param passwordEncoder 密码解析器
     * @return 基于数据库的repository
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate
            , PasswordEncoder passwordEncoder) {
        System.out.println("password encode : " + passwordEncoder.encode("123456"));
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                // 客户端id
                .clientId("messaging-client")
                // 客户端秘钥,使用密码解析器加密
                .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:8080/login/oauth2/code/messaging-client-oidc")
                .redirectUri("https://www.baidu.com/")
                .redirectUri("http://127.0.0.1:8080/notify/oauth2/code")
                // 该客户端的授权范围,OPENID与PROFILE是IdToken的scope,获取授权时请求OPENID的scope时认证服务会返回IdToken
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                // 自定scope
                .scope("message.read")
                .scope("message.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);
        }
        // 设备码授权客户端
        RegisteredClient deviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("device-message-client")
                // 公共客户端
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                // 设备码授权
                .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                // 自定scope
                .scope("message.read")
                .scope("message.write")
                .build();
        RegisteredClient byClientId = registeredClientRepository.findByClientId(deviceClient.getClientId());
        if (byClientId == null) {
            registeredClientRepository.save(deviceClient);
        }
        return registeredClientRepository;
    }

    /**
     * 配置基于db的oauth2的授权管理服务
     *
     * @param jdbcTemplate               db数据源信息
     * @param registeredClientRepository 上边注入的客户端repository
     * @return JdbcOAuth2AuthorizationService
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
                                                           RegisteredClientRepository registeredClientRepository) {
        // 基于db的oauth2认证服务,还有一个基于内存的服务实现InMemoryOAuth2AuthorizationService
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    /**
     * 配置基于db的授权确认管理服务
     *
     * @param jdbcTemplate               db数据源信息
     * @param registeredClientRepository 客户端repository
     * @return JdbcOAuth2AuthorizationConsentService
     */
    @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);
    }

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

    /**
     * 添加认证服务器配置,设置jwt签发者、默认端点请求地址等
     *
     * @return AuthorizationServerSettings
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

    /**
     * 先暂时配置一个基于内存的用户,框架在用户认证时会默认调用
     * {@link UserDetailsService#loadUserByUsername(String)} 方法根据
     * 账号查询用户信息,一般是重写该方法实现自己的逻辑
     *
     * @param passwordEncoder 密码解析器
     * @return UserDetailsService
     */
    @Bean
    public UserDetailsService users(PasswordEncoder passwordEncoder) {
        UserDetails user = User.withUsername("admin1")
                .password(passwordEncoder.encode("123456"))
                .roles("admin", "normal", "unAuthentication")
                .authorities("app", "web", "/test2", "/test3")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

}

类上有三个注解,@Configuration、@EnableWebSecurity和@EnableMethodSecurity注解,记录一下

1. @EnableWebSecurity

加载了WebSecurityConfiguration配置类, 配置安全认证策略。
加载了AuthenticationConfiguration, 配置了认证信息。


2. @EnableMethodSecurity 官网文档说明
默认启用方法级别的安全校验

1. 设置注解属性 jsr250Enabled = true 是为了启用JSR250注解支持,例如@RolesAllowed、@PermitAll和@DenyAll注解
2. 设置属性securedEnabled = true 是为了启用@Secured注解支持,不设置属性则添加Secured注解无效


3. @Configuration 文档说明地址


在Spring security 6.0 版本及之后版本中将@Configuration注解从@EnableWebSecurity, @EnableMethodSecurity, @EnableGlobalMethodSecurity 和 @EnableGlobalAuthentication 中移除,使用这些注解需手动添加 @Configuration 注解

Controller文件

/**
 * 认证服务器相关自定接口
 *
 * @author lxq
 */
@Controller
@RequiredArgsConstructor
public class AuthorizationController {

    private final RegisteredClientRepository registeredClientRepository;

    private final OAuth2AuthorizationConsentService authorizationConsentService;

    private static Set<ScopeWithDescription> withDescription(Set<String> scopes) {
        Set<ScopeWithDescription> scopeWithDescriptions = new HashSet<>();
        for (String scope : scopes) {
            System.out.println("withDescription | scope: " + scope);
            scopeWithDescriptions.add(new ScopeWithDescription(scope));
        }
        return scopeWithDescriptions;
    }

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

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

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

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

        return "consent";
    }

    @GetMapping("/login/oauth2/code/messaging-client-oidc")
    @ResponseBody
    public String authNotify(@RequestParam(value = "code", required = false) String code) {
        System.out.println("authNotify Code=" + code);
        return "ok";
    }

    @RequestMapping("/notify/oauth2/code")
    @ResponseBody
    public String oauth2Code(HttpServletRequest request) {
        Map<String, String[]> pMap = request.getParameterMap();
        System.out.println(pMap);
        return "success";
    }

    @PreAuthorize("hasAuthority('SCOPE_message.read')")
    @RequestMapping("/test1")
    @ResponseBody
    public String test1() {
        return "test1";
    }

    @Data
    public static class ScopeWithDescription {
        private static final String DEFAULT_DESCRIPTION = "UNKNOWN SCOPE - We cannot provide information about this " +
                "permission, use caution when granting this.";
        private static final Map<String, String> scopeDescriptions = new HashMap<>();

        static {
            scopeDescriptions.put(
                    OidcScopes.PROFILE,
                    "This application will be able to read your profile information."
            );
            scopeDescriptions.put(
                    "message.read",
                    "This application will be able to read your message."
            );
            scopeDescriptions.put(
                    "message.write",
                    "This application will be able to add new messages. It will also be able to edit and delete " +
                            "existing messages."
            );
            scopeDescriptions.put(
                    "other.scope",
                    "This is another scope example of a scope description."
            );
        }

        public final String scope;
        public final String description;

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

}

其它文件

1. consent.html

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

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

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

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

2.login.html

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

3.css

html,
body {
    height: 100%;
}

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

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

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

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

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

项目结构

测试

获取code

获取token

刷新token

请求接口成功

1. client_id: 客户端的id
2. client_secret: 客户端秘钥
3. redirect_uri:申请授权成功后的回调地址
4. response_type:授权码模式固定参数code
5. code_verifier:一段随机字符串
6. code_challenge:根据指定的加密方式将code_verifier加密后得到的字符串
7. code_challenge_method:加密方式
8. scope:客户端申请的授权范围
9. state:跟随authCode原样返回,防止CSRF攻击
10. grant_type:指定获取token 的方式:
    1. refresh_token:刷新token
    2. authorization_code:根据授权码模式的授权码获取
    3. client_credentials:客户端模式获取

Spring Authorization Server:实现OAuth2认证服务 Spring Authorization Server是一个安全框架,它提供了OAuth 2.1和OpenID Connect 1.0规范以及其他相关的实现。Spring Authorization Server是在Spring Security的基础上构建的,它为构建OAuth2授权服务和OpenID Connect 1.0身份提供者提供了一个安全、轻量级、可定制的基础。OAuth2协议定义了一系列关于认证授权的标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息。 阅读详情

相关推荐

Spring Boot整合Spring Authorization Server(二)——客户端、设备码、PKCE模式

Spring Boot整合Spring Authorization Server(二)——客户端、设备码、PKCE模式

code_fly 1480

Spring Security Oauth2.1 最新版 1.1.0 整合 (基于 springboot 3.1.0)gateway 完成授权认证

Spring Boot 3.1 提供了一个 spring-boot-starter-oauth2-authorization-server 启动器,可以支持 Spring Authorization Server 的自动配置,轻松配置基于 Servlet 的 OAuth2 授权服务器,同时@EnableAuthorizationServer这些注解也早已废弃。用这个请求来模拟客户端,实际开发中,其实是先访问资源服务,由资源服务来拼接这几个参数来重定向到授权服务的,参数意义如下,

Ricardo.M.Yu的博客 2万+

Spring Boot3.4.0整合Spring Authorization Server:自定义OAuth2授权类型与令牌扩展全指南

本文深入探讨了Spring Boot 3.4.0与Spring Authorization Server的整合实践,重点介绍了自定义OAuth2授权类型与令牌扩展的高阶技巧。通过五大核心场景的实战演示,包括架构解析、性能优化、令牌扩展、自定义授权类型及安全加固,帮助开发者构建企业级认证授权服务,提升系统安全性与性能。

weixin_30689307的博客 481

OAuth2.0 实践 Spring Authorization Server 搭建授权服务器 + Resource + Client

OAuth2.0 实践 Spring Authorization Server 搭建授权服务器 + Resource + Client

凡的博客 1万+

Spring Boot 3.4.0 整合 Authorization Server:如何自定义Opaque Token和扩展JWT Claims?

本文深入探讨了Spring Boot 3.4.0与Spring Authorization Server的整合,重点讲解了如何自定义Opaque Token生成规则和扩展JWT Claims。通过实战代码示例,展示了实现UUID短Token和在JWT中安全注入业务字段的方法,为开发者提供了OAuth2 Token定制的完整解决方案。

weixin_29224309的博客 442

Spring Security 7.0认证机制与实战应用解析

认证机制是现代应用安全的核心组件,其基本原理是通过验证用户凭证来确认身份。Spring Security作为Java生态的主流安全框架,采用过滤器链架构实现分层安全控制,核心接口包括AuthenticationManager、UserDetailsService和PasswordEncoder。在技术价值层面,它提供了可扩展的认证体系,支持从基础表单登录到OAuth2、LDAP等企业级方案。实际应用中,开发者可以基于数据库实现标准认证,或集成OAuth2资源服务器构建分布式安全体系。随着Spring Sec

weixin_32306957的博客 228

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

本篇文章从0到1搭建了一个简单认证服务,解释了认证服务的各项配置用意,如何设置自己的登录页和授权确认页,如何让认证服务解析请求时携带的token,文章过长难免有遗漏的地方,如果文章中有遗漏或错误的地方请各位读者在评论区指出。

云逸的博客 1万+

SpringBoot3与OAuth2.1实战:从/oauth/token到/oauth2/token的平滑迁移指南

本文详细介绍了如何将SpringBoot3项目中的OAuth2认证接口从/oauth/token平滑迁移到/oauth2/token。通过解析新旧版本的核心差异,提供双端点兼容方案和客户端适配改造指南,帮助开发者避免升级过程中的常见问题,确保系统稳定运行。

weixin_42553471的博客 181

Spring Authorization Server 整合 Spring Boot

你可能需要定义客户端的详细信息,包括客户端ID、密钥、授权范围等。这可以通过配置类来完成,或者直接在配置文件中指定(如上所示简单示例)。更复杂的配置可能涉及创建或的bean。根据需要,你可以通过创建配置类来自定义授权服务器的行为,覆盖默认设置,添加自定义端点、认证器、令牌存储等。Spring Authorization Server 提供了许多扩展点来满足特定需求。

qq_33240556的博客 648

Spring Boot OAuth2 快速入门示例

搭建Spring OAuth2 认证服务器并使用客户端认证模式认证演示

oscar999的专栏 1335

23、探索Spring Boot安全与消息传递技术

本文详细探讨了Spring Boot在安全与消息传递技术方面的应用,重点介绍了Spring Authorization Server与Users App的整合过程,包括客户端密钥配置、安全类设置及应用属性配置等内容。同时,深入解析了Spring Messaging的核心组件,如Message、MessageChannel和消息端点,并通过实际案例展示了如何在Spring Boot应用中实现事件发布与监听。此外,还对比了多种消息传递技术,如RabbitMQ、Apache Kafka等,帮助读者根据业务需求选择

rgv234567的博客 68

【安全篇】Spring Boot 整合 Spring Authorization Server

**Spring Authorization Server** (以下简称 **SAS**)是 Spring 团队最新开发适配 OAuth 协议的授权服务器项目,旨在**替代**原有的 Spring Security OAuth Server。目前已经发布了 0.3.1 版本,已支持授权码、客户端、刷新、注销等 OAuth 协议。SAS 项目已经迁移至官方正式仓库维护,成为官方的正式子项目。......

csp732171109的博客 5161

HMMER实战:从零部署到序列分析

本文详细介绍了HMMER工具的安装与使用,从源码编译到环境配置,再到核心工具hmmbuild、hmmsearch等的实战应用。通过球蛋白序列分析案例,展示了HMMER在生物信息学中的强大功能,帮助读者快速掌握序列分析技巧。

weixin_42529366的博客 249

告别混乱配置!Spring Boot 3.x 整合Spring Authorization Server的最佳实践与常见坑点

本文深入解析Spring Boot 3.x整合Spring Authorization Server的最佳实践,涵盖OAuth2.0架构设计、多SecurityFilterChain控制、存储策略优化等核心内容。通过模块化配置和实战避坑指南,帮助开发者构建安全高效的授权服务器,解决资源端与客户端协同工作的常见问题。

weixin_28738165的博客 327

Linux系统下Materials Studio 2020安装避坑指南:从依赖包到许可证配置全流程

本文提供了一份在Linux系统上安装Materials Studio 2020的深度避坑指南。详细解析了从系统依赖包检查、安装流程、许可证配置到Gateway服务部署的全过程,并分享了与Slurm作业调度系统集成的实战经验,旨在帮助用户高效、稳定地完成部署。

weixin_29313547的博客 387

QT实现图片ROI区域的框选、移动、缩放(附完整源码)

QT实现图片ROI区域的框选、移动、缩放(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 1193

Spring Boot整合Spring Authorization Server(三)——登录图形验证码

目前登录接口没有做任何限制,代表任何人都可以编写脚本的方式暴力破解,会造成安全问题,如果写一个循环一直尝试访问登录接口,那么服务器就一直会收到请求,一次请求代表一次查表,会给服务器造成很大的压力,本篇文章就来给登录接口添加一个验证码校验。

code_fly 472

7.12真题华为OD机试真题 新系统 - 字符串压缩编码 (Java/Py/C/C++/Js/Go)

7.12真题华为OD机试真题 新系统 - 字符串压缩编码 (Java/Py/C/C++/Js/Go)

算法大师! 835

Spring Boot整合Spring Authorization Server(四)——整合网关和资源服务,添加Redis,前后端分离

之前已经搭建了统一认证中心,实际环境中往往需要客户端和资源服务器,同时之前的只是用了session,这个在高并发下是顶不住的,所以加上了redis,那就开整吧。

code_fly 1043

【教培管家】小程序实战(五):体验课预约——从流量到转化的闭环

本节我们通过变量状态管理和数据联动,实现了孩子选择和日期选定。下一篇我们继续讲解预约页面,根据选择的日期来筛选排课,供用户进行预约。

低代码布道师的博客 1562

AMD KFD的SDMA Packet 类型和定义解析

本文详细分析了AMD GPU架构中的SDMA(System Direct Memory Access)数据传输机制,重点介绍了其核心数据结构——SDMA Packet。SDMA通过8种主要Packet类型(包括内存拷贝、数据写入、屏障同步、中断触发等)实现高效主机-设备通信。每种Packet采用位域结构和联合体封装设计,在保证功能完整性的同时实现空间优化。这些Packet支持丰富的访问属性和扩展字段,适用于高性能数据搬运、内存初始化、任务同步、性能分析等场景,为GPU计算提供了灵活高效的数据传输解决方案。

图形/计算GPU的驱动与虚拟化技术分享园地 1246

Spring Boot整合OAuth2:从授权码模式到生产环境部署全解析

OAuth 2.0是现代应用安全认证与授权的核心协议,它通过标准化流程解决了第三方应用安全访问用户资源的问题。其核心原理基于令牌(Token)机制,将资源所有者、客户端、授权服务器和资源服务器四大角色分离,实现了权限的细粒度控制与安全委托。在技术价值层面,OAuth 2.0不仅提升了系统的安全性,避免了密码直接暴露,还通过授权码模式等标准化流程,为构建开放平台、单点登录(SSO)和微服务架构下的统一认证中心提供了坚实基础。其典型的应用场景包括社交账号登录(如微信、GitHub登录)、API开放平台授权以及企

weixin_30745553的博客 451

安卓逆向 | 某社交类APP gsid

*本文章仅供学习交流,切勿用于非法通途,如有侵犯贵司请及时联系删除

爬虫进击之路 3082

Java Web应用安全认证:JWT、OAuth2与SSO整合实战指南

在分布式系统与微服务架构中,身份认证与授权是保障应用安全的核心基础。其原理在于通过标准化的协议,在服务间安全地传递和验证用户身份,从而解决传统Session方案在扩展性、安全性和开发效率上的局限。这一技术价值体现在实现了无状态、可扩展的认证体系,并提供了安全的授权委托机制。典型的应用场景包括单点登录(SSO)、第三方应用授权(如微信登录)以及微服务间的安全通信。本文聚焦于Java生态,深入探讨如何整合JWT、OAuth2与SSO三大组件,构建一套现代、标准的认证授权方案。通过结合Spring Securit

weixin_30514745的博客 422

构建栅格地图matlab代码

1 使用MATLAB实现地图栅格化 效果图如下所示 图1 ...

qq_59747472的博客 4517

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Spring Security 学习路线:从入门到实战的完整指南

本文系统介绍了学习Spring Security的路线图,从基础到进阶共分为四个阶段。首先需要具备Java、Spring Boot等基础知识,然后从核心概念入手,学习认证与授权机制。第二阶段深入认证体系,包括多种登录方式和密码安全。第三阶段聚焦高级授权控制,如动态权限管理和方法级安全。最后通过整合JWT和OAuth 2.0进行实战应用。全文配有ER图详解权限模型,适合开发者循序渐进掌握Spring Security的核心功能与应用场景。学习周期约8-12周,最终目标是构建安全可靠的Web应用系统。

ywl470812087的博客 265

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

上一篇: Mysql8创建用户及授权
下一篇: Spring Boot整合Spring Authorization Server(二)——客户端、设备码、PKCE模式
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值