Spring Authorization Server 1.4.0 使用及详细配置 搭配Spring Boot3.4.0 + Spring Security6.4.1

Spring Authorization Server 介绍

Spring Authorization Server 是一个提供OAuth 2.1和OpenID Connect 1.0规范以及其他相关规范的实现的框架。它构建在Spring Security之上,为构建 OpenID Connect 1.0 身份提供商和 OAuth2 授权服务器产品提供安全、轻量级和可定制的基础。

框架提供五种授权模式

  • Authorization Code(授权码模式)
  • Client Credentials(客户端模式)
  • Refresh Token(令牌刷新)
  • Device Code(设备码模式)
  • Token Exchange(token交换)

本篇文章主要讲解常用的授权码模式、客户端模式、令牌刷新这三种模式,以及如何自己扩展授权模式(以账号密码模式为例)

框架提供两种token格式

  1. Self-contained (JWT) (信息透明)
  2. Reference (Opaque) (信息不透明)

本篇文章会讲解两种token的原理,如何使用不同格式的token,如何自定义token内容的扩展,以及如何自己自定义token生成器(以用短字符串自定义不透明token为例)

入门

创建一个新项目 添加依赖

使用目前最新版Spring Boot 3.4.0 要求JDK版本大于等于17

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.0</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-authorization-server</artifactId>
        </dependency>
    </dependencies>

创建配置类,添加基本配置

package chick.authorization.security;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

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

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
            throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
                OAuth2AuthorizationServerConfigurer.authorizationServer();

        http
                .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
                .with(authorizationServerConfigurer, Customizer.withDefaults())
                .authorizeHttpRequests((authorize) ->
                        authorize.anyRequest().authenticated()
                )
                .exceptionHandling((exceptions) -> exceptions
                        .defaultAuthenticationEntryPointFor(
                                new LoginUrlAuthenticationEntryPoint("/login"),
                                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                        )
                );
        http
                .getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .oidc(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
            throws Exception {
        http
                .authorizeHttpRequests((authorize) -> authorize
                        .anyRequest().authenticated()
                )
                .formLogin(Customizer.withDefaults());
        return http.build();
    }

    // 用于检索用户进行身份验证
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withUsername("admin")
                .password(passwordEncoder().encode("123123"))
                .roles("admin")
                .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

    // 用于密码加密
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //	用于管理客户端
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        TokenSettings tokenSettings = TokenSettings.builder()
                .accessTokenTimeToLive(Duration.ofHours(1)) // 设置访问令牌有效期为1小时
                .refreshTokenTimeToLive(Duration.ofDays(30)) // 设置刷新令牌有效期为30天
                //.accessTokenFormat(OAuth2TokenFormat.REFERENCE) // 这个设置是开启不透明token
                .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) // 使用透明token
                .build();
        RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("chick")
                .clientSecret(passwordEncoder().encode("123456"))
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("https://www.baidu.com")
                .postLogoutRedirectUri("http://127.0.0.1:8000/")
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .tokenSettings(tokenSettings)
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();
        return new InMemoryRegisteredClientRepository(oidcClient);
    }

    // 用于签署访问令牌
    @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);
    }

    // 启动时生成的密钥,用于创建上面的JWKSource
    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;
    }

    // 用于解码签名访问令牌
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    // 用于配置 Spring Authorization Server 
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

}

目前已解锁

  • Authorization Code(授权码模式)
  • Client Credentials(客户端模式)
  • Refresh Token(令牌刷新)

测试授权码模式

浏览器访问:http://127.0.0.1:8000/oauth2/authorizeresponse_type=code&client_id=chick&scope=openid&redirect_uri=https://www.baidu.com

会重定向到登录页

输入上面UserDetailsService中设置的用户名密码 登录会重定向到百度并 获取到code

使用code获取token等信息

POST /oauth2/token HTTP/1.1
Authorization: Basic Base64Encode(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&redirect_uri=https://www.baidu.com&code=9ZAclrji.....

测试令牌刷新模式

使用上一步返回的refresh_token

POST /oauth2/token HTTP/1.1
Authorization: Basic Base64Encode(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&code=9ZAclrji.....

测试客户端授权模式

有多种方式,这里介绍常用的client_secret_basic和client_secret_post

POST /oauth2/token HTTP/1.1
Authorization: Basic Base64Encode(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

POST /oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=
### 转行网络工程师的职业规划 #### 学习路径与技能要求 对于希望在32岁时转行成为网络工程师的人士而言,尽管年龄并非决定性因素,但合理规划学习路径至关重要。考虑到行业人才缺口较大,至2027年中国网安人才缺口预计将达到327万人[^1],这表明即使是在职业生涯中期转换赛道也具有可行性。 为了顺利过渡到这一领域,建议遵循以下学习路径: 1. **基础知识积累** 需要掌握计算机基础理论、操作系统原理、编程语言(如Python)、数据库管理等核心概念。这些知识不仅有助于理解网络安全的具体应用场景,也为后续深入研究打下坚实的基础。 2. **技术专项训练** 关注特定的安全技术和工具集,比如防火墙配置、入侵检测系统(IDS)/预防系统(IPS)操作、加密算法实现等。通过实际项目练习来增强动手能力是非常必要的。 3. **认证考试准备** 获取相关证书能够显著提升个人竞争力。例如CompTIA Security+, CISSP (Certified Information Systems Security Professional), CEH (Certified Ethical Hacker)都是行业内认可度较高的资格证明。 4. **实践经验获取** 尝试参与开源社区贡献代码或加入本地黑客松活动,在真实环境中锻炼解决问题的能力;也可以考虑寻找实习机会以获得宝贵的工作经验。 5. **持续更新专业知识** 安全形势瞬息万变,保持对最新趋势和技术发展的敏感度十分重要。定期阅读专业书籍期刊、参加研讨会讲座等活动可以帮助从业者紧跟时代步伐。 #### 可利用的学习资源 针对想要全面了解并投身于网络安全行业的求学者来说,《网络安全入门+进阶学习资源包》提供了丰富的材料支持,总计容量达到282GB,覆盖了从零开始直到精通各个阶段所需的教程文档视频等内容[^4]。此外还有许多在线平台提供针对性课程和服务,如Coursera, edX等国际知名MOOC网站均开设有专门面向信息安全领域的培训项目可供选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值