天天看点

Spring Authorization Server

背景

8月17日,Spring官方宣布 Spring Authorization Server 已正式脱离实验状态,并进入Spring-Project家族!

Spring Authorization Server (以下简称 SAS)是 Spring 团队最新开发适配 OAuth 协议的授权服务器项目,旨在替代原有的 Spring Security OAuth Server。

经过半年的开发和孵化,目前已经发布了 0.2.0 版本,已支持授权码、客户端、刷新、注销等 OAuth 协议。

目前 SAS 项目已经迁移至官方正式仓库维护,成为官方的正式子项目。

前置知识

Oauth 2

OAuth 2.0 规定了四种获得令牌的流程。你可以选择最适合自己的那一种,向第三方应用颁发令牌。下面就是这四种授权方式。

  • 授权码(authorization-code) 
  • 隐藏式(implicit) 
  • 密码式(password): 
  • 客户端凭证(client credentials)

注意,不管哪一种授权方式,第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。

Spring Authorization Server

JWT、JWS、JWK的概念

JWT:指的是 JSON Web Token,由 header.payload.signture 组成。不存在签名的JWT是不安全的,存在签名的JWT是不可窜改的。JWS:指的是签过名的JWT,即拥有签名的JWT。JWK:既然涉及到签名,就涉及到签名算法,对称加密还是非对称加密,那么就需要加密的 密钥或者公私钥对。此处我们将 JWT的密钥或者公私钥对统一称为 JSON WEB KEY,即 JWK。

maven 依赖

<!--oauth2 server-->
<dependency>
  <groupId>org.springframework.security.experimental</groupId>
  <artifactId>spring-security-oauth2-authorization-server</artifactId>
  <version>0.1.0</version>
</dependency>
<!--security dependency-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>      

初始化配置

  • 由于官方还未提供对应的 Spring Boot Starter 自动化配置,需要自己配置相关的 @Bean
  • 本配置基于 Spring Boot 2.4.2 
@Configuration
@EnableWebSecurity
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthServerConfiguration {


    //  定义 spring security 拦击链规则
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().authenticated()
                )
                .formLogin(withDefaults());
        return http.build();
    }


  // 创建默认登录用户 lengleng / 123456
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.builder()
                .username("lengleng")
                .password("{noop}123456")
                .authorities("ROLE_USER")
                .build();
        return new InMemoryUserDetailsManager(userDetails);
    }


  // 创建默认的bean 登录客户端,基于 授权码、 刷新令牌的能力
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient client = RegisteredClient.withId("pig")
                .clientId("pig")
                .clientSecret("pig")
                .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
                .authorizationGrantTypes(authorizationGrantTypes -> {
                    authorizationGrantTypes.add(AuthorizationGrantType.AUTHORIZATION_CODE);
                    authorizationGrantTypes.add(AuthorizationGrantType.REFRESH_TOKEN);
                })
                .redirectUri("https://pig4cloud.com")
                .build();
        return new InMemoryRegisteredClientRepository(client);
    }




  // 指定token 生成的加解密密钥
    @Bean
    @SneakyThrows
    public JWKSource<SecurityContext> jwkSource() {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        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();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }
}      

测试

  • 浏览器访问如下链接,会重定向至登录页
  • Spring Authorization Server
  • 输入账号密码后,会携带 code 自动回调至目标页面
  • Spring Authorization Server

授权码认证

curl --location --request GET 'http://localhost:3000/oauth2/authorize?client_id=pig&client_secret=pig&response_type=code&redirect_uri=https://pig4cloud.com'      

获取令牌

curl --location --request POST 'http://localhost:3000/oauth2/token' \
--header 'Authorization: Basic cGlnOnBpZw==' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code={code}' \
--data-urlencode 'redirect_uri=https://pig4cloud.com'      

刷新令牌

curl --location --request POST 'http://localhost:3000/oauth2/token' \
--header 'Authorization: Basic cGlnOnBpZw==' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token={refresh_token}' \      

撤销令牌

  • 通过 access_token
curl --location --request POST 'http://localhost:3000/oauth2/revoke' \
--header 'Authorization: Basic cGlnOnBpZw==' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'token={access_token}' \
--data-urlencode 'token_type_hint=access_token'      
  • 通过 refresh_token
curl --location --request POST 'http://localhost:3000/oauth2/revoke' \
--header 'Authorization: Basic cGlnOnBpZw==' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'token={refresh_token}' \
--data-urlencode 'token_type_hint=refresh_token'      

继续阅读