惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cisco Blogs
Scott Helme
Scott Helme
Know Your Adversary
Know Your Adversary
NISL@THU
NISL@THU
C
Cyber Attacks, Cyber Crime and Cyber Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Schneier on Security
I
Intezer
Spread Privacy
Spread Privacy
AWS News Blog
AWS News Blog
V
Vulnerabilities – Threatpost
Cloudbric
Cloudbric
V2EX - 技术
V2EX - 技术
Google Online Security Blog
Google Online Security Blog
L
Lohrmann on Cybersecurity
Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
LINUX DO - 热门话题
S
Secure Thoughts
T
The Exploit Database - CXSecurity.com
博客园 - 【当耐特】
Recent Announcements
Recent Announcements
Security Archives - TechRepublic
Security Archives - TechRepublic
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
K
Kaspersky official blog
阮一峰的网络日志
阮一峰的网络日志
博客园_首页
Latest news
Latest news
B
Blog
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
博客园 - 叶小钗
L
LangChain Blog
GbyAI
GbyAI
Last Week in AI
Last Week in AI
S
Security Affairs
Apple Machine Learning Research
Apple Machine Learning Research
N
Netflix TechBlog - Medium
Security Latest
Security Latest
Vercel News
Vercel News
Y
Y Combinator Blog
G
GRAHAM CLULEY
S
Securelist
T
Troy Hunt's Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
雷峰网
雷峰网

博客园 - Zhang_Xiang

刚开始,你以为只是换工作 高管的 AI 精神病 代码是 AI 写的,生产事故谁背锅? AI Agent 走出 Demo 幻觉的唯一解药:Harness Engineering 从 page、page_size 到游标:深入解析C端产品的两种主流分页技术 Apache Kafka 的基本概念 Apache Kafka 移除 ZK Proposals webRTC demo Spring Authorization Server(AS)从 Mysql 中读取客户端、用户 Java 对象实现 Serializable 的原因 Spring Data JPA 使用 OAuth 2.1 框架 Spring Security dapr 本地环境升级 BuildPack 打包 spring-boot 2.5.4,nacos 作为配置、服务发现中心,Cloud Native Buildpacks 打包镜像,GitLab CI/CD 如何拆分大型单体系统为微服务 高可用 Keycloak,K8s Keycloak 13 自定义用户身份认证流程(User Storage SPI) OAuth 2.0、OIDC 讲不清楚? Mokito 单元测试与 Spring-Boot 集成测试 关于 JMeter 5.4.1 的一点记录
Spring Authorization Server 实现授权中心
Zhang_Xiang · 2022-05-10 · via 博客园 - Zhang_Xiang

源码地址

当前,Spring Security 对 OAuth 2.0 框架提供了全面的支持。Spring Authorization Server 出现的含义在于替换 Spring Security OAuth,交付 OAuth 2.1 授权框架。 Spring 官方已弃用 Spring Security OAuth

本文涉及的组件版本如下:

组件 版本
JDK 17
org.springframework.boot 2.6.7
Gradle 7.4.1
spring-security-oauth2-authorization-server 0.2.3

本文的目的:

  1. 搭建授权中心示例
  2. fork 当前项目从而免去一些工作

本 demo 的结构

  • root
    • [[#auth-center|授权中心]]
    • [[#user-service|用户服务]]
    • [[#client-gateway|移动端网关]]

OAuth 2.1 支持三种许可类型,[[OAuth 2.1 授权框架#授权码许可]]、[[OAuth 2.1 授权框架#客户端证书许可]]、[[OAuth 2.1 授权框架#刷新令牌许可]]。

auth-center

build.gradle

plugins {  
    id 'org.springframework.boot' version '2.6.7'  
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'  
    id 'java'  
}  
  
group = 'com.insight.into.life'  
version = '0.0.1-SNAPSHOT'  
sourceCompatibility = '17'  
  
configurations {  
    compileOnly {  
        extendsFrom annotationProcessor  
    }  
}  
  
repositories {  
    mavenCentral()  
}  
  
dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
    implementation 'org.springframework.boot:spring-boot-starter-security'  
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'  
    implementation 'org.springframework.security:spring-security-oauth2-authorization-server:0.2.3'  
    implementation 'org.springframework.boot:spring-boot-starter-actuator'  
  
    compileOnly 'org.projectlombok:lombok'  
    developmentOnly 'org.springframework.boot:spring-boot-devtools'  
//    runtimeOnly 'mysql:mysql-connector-java'  
    runtimeOnly "com.h2database:h2"  
  
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'  
    annotationProcessor 'org.projectlombok:lombok'  
  
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
    testImplementation 'org.springframework.security:spring-security-test'  
}  
  
tasks.named('test') {  
    useJUnitPlatform()  
}

config

...

@EnableWebSecurity  
@Slf4j  
public class DefaultSecurityConfig {  
  
    @Bean  
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {  
        http.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())  
                .formLogin(withDefaults());  
        return http.build();  
    }  
  
  
    @Bean  
    public UserDetailsService users() {  
        UserDetails user = User.withDefaultPasswordEncoder()  
                .username("user1")  
                .password("password")  
                .roles("USER")  
                .build();  
        return new InMemoryUserDetailsManager(user);  
    }  
}
...
@Configuration(proxyBeanMethods = false)  
public class AuthorizationServerConfig {  
  
    @Bean  
    @Order(Ordered.HIGHEST_PRECEDENCE)  
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {  
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);  
        return http.formLogin(withDefaults()).build();  
    }  
  
    @Bean  
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {  
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())  
                .clientId("mobile-gateway-client")  
                .clientSecret("{noop}123456")  
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)  
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)  
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)  
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)  
                .redirectUri("http://127.0.0.1:9100/login/oauth2/code/mobile-gateway-client-oidc")  
                .redirectUri("http://127.0.0.1:9100/authorized")  
                .scope(OidcScopes.OPENID)  
                .scope("message.read")  
                .scope("message.write")  
                .build();  
  
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);  
        registeredClientRepository.save(registeredClient);  
  
        return registeredClientRepository;  
    }  
  
    @Bean  
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {  
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);  
    }  
  
    @Bean  
    public JWKSource<SecurityContext> jwkSource() {  
        RSAKey rsaKey = Jwks.generateRsa();  
        JWKSet jwkSet = new JWKSet(rsaKey);  
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);  
    }  
  
    @Bean  
    public ProviderSettings providerSettings() {  
        return ProviderSettings.builder().issuer("http://localhost:9000").build();  
    }  
  
    @Bean  
    public EmbeddedDatabase embeddedDatabase() {  
        return new EmbeddedDatabaseBuilder()  
                .generateUniqueName(true)  
                .setType(EmbeddedDatabaseType.H2)  
                .setScriptEncoding("UTF-8")  
                .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")  
                .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")  
                .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")  
                .build();  
    }
  1. 这里的两个 config 中有两个 SecurityFilterChain 类。调用顺序是 authorizationServerSecurityFilterChain、defaultSecurityFilterChain。
  2. registeredClientRepository 用于注册 client。这里的两个 redirectUri 中地址来自于[[#mobile-gateway|移动端网关]]。

application.yml

server:  
  port: 9000  
  
logging:  
  level:  
    root: INFO  
    org.springframework.web: INFO  
    org.springframework.security: INFO  
    org.springframework.security.oauth2: INFO

启动服务

在浏览器中输入: http://localhost:9000/.well-known/openid-configuration ,得到以下内容。

// 20220510135753
// http://localhost:9000/.well-known/openid-configuration

{
  "issuer": "http://localhost:9000",
  "authorization_endpoint": "http://localhost:9000/oauth2/authorize",
  "token_endpoint": "http://localhost:9000/oauth2/token",
  "token_endpoint_auth_methods_supported": [
    "client_secret_basic",
    "client_secret_post",
    "client_secret_jwt",
    "private_key_jwt"
  ],
  "jwks_uri": "http://localhost:9000/oauth2/jwks",
  "userinfo_endpoint": "http://localhost:9000/userinfo",
  "response_types_supported": [
    "code"
  ],
  "grant_types_supported": [
    "authorization_code",
    "client_credentials",
    "refresh_token"
  ],
  "subject_types_supported": [
    "public"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "scopes_supported": [
    "openid"
  ]
}

user-service

用户服务在 demo 中的角色是资源服务器。

build.gradle

plugins {  
   id 'org.springframework.boot' version '2.6.7'  
   id 'io.spring.dependency-management' version '1.0.11.RELEASE'  
   id 'java'  
}  
  
group = 'com.insight.into.life'  
version = '0.0.1-SNAPSHOT'  
sourceCompatibility = '17'  
  
configurations {  
   compileOnly {  
      extendsFrom annotationProcessor  
   }  
}  
  
repositories {  
   mavenCentral()  
}  
  
dependencies {  
   implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'  
   implementation 'org.springframework.boot:spring-boot-starter-web'  
   compileOnly 'org.projectlombok:lombok'  
   annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'  
   annotationProcessor 'org.projectlombok:lombok'  
  
   testImplementation 'org.springframework.boot:spring-boot-starter-test'  
}  
  
tasks.named('test') {  
   useJUnitPlatform()  
}

config

...
@EnableWebSecurity  
public class ResourceServerConfig {  
  
    @Bean  
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {  
        http.mvcMatcher("/menu/**")  
                .authorizeRequests()  
                .mvcMatchers("/menu/**").access("hasAuthority('SCOPE_message.read')")  
                .and()  
                .oauth2ResourceServer()  
                .jwt();  
        return http.build();  
    }  
}

定义 menu 路径下的访问权限。

@RestController  
@RequestMapping("/menu")  
public class MenuController {  
  
    @GetMapping("/list")  
    public List<String> list() {  
        return List.of("menu1", "menu2", "menu3");  
    }  
}

application.yml

server:  
  port: 9001  
  
spring:  
  application:  
    name: user-service  
  security:  
    oauth2:  
      resourceserver:  
        jwt:  
          issuer-uri: http://localhost:9000

启动服务

资源服务器目前不需要做额外配置,只需要启动即可。

client-gateway

build.gradle

plugins {  
    id 'org.springframework.boot' version '2.6.7'  
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'  
    id 'java'  
}  
  
group = 'com.insight.into.life'  
version = '0.0.1-SNAPSHOT'  
sourceCompatibility = '17'  
  
configurations {  
    compileOnly {  
        extendsFrom annotationProcessor  
    }  
}  
  
repositories {  
    mavenCentral()  
}  
  
dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
    implementation "org.springframework:spring-webflux"  
    implementation "io.projectreactor.netty:reactor-netty"  
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.1.2'  
  
    compileOnly 'org.projectlombok:lombok'  
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'  
    annotationProcessor 'org.projectlombok:lombok'  
}  
  
tasks.named('test') {  
    useJUnitPlatform()  
}

这里引入 org.springframework:spring-webfluxio.projectreactor.netty:reactor-netty 的原因在于使用了 WebClient。

config

...
@Component  
@Order(Ordered.HIGHEST_PRECEDENCE)  
public class LoopbackIpRedirectFilter extends OncePerRequestFilter {  
  
    @Override  
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {  
        if (request.getServerName().equals("localhost") && request.getHeader("host") != null) {  
            UriComponents uri = UriComponentsBuilder.fromHttpRequest(new ServletServerHttpRequest(request))  
                    .host("127.0.0.1").build();  
            response.sendRedirect(uri.toUriString());  
            return;  
        }  
        filterChain.doFilter(request, response);  
    }  
  
}

该配置用于转换地址。将 localhost 转换为 127.0.0.1

...

@EnableWebSecurity  
@Slf4j  
public class SecurityConfig {  
  
    @Bean  
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {  
        http.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())  
                .oauth2Login(oauth2Login -> oauth2Login.loginPage("/oauth2/authorization/mobile-gateway-client-oidc"))  
                .oauth2Client(withDefaults());  
        return http.build();  
    }  
}
...
@Configuration  
public class WebClientConfig {  
  
    @Bean  
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {  
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);  
        return WebClient.builder().apply(oauth2Client.oauth2Configuration()).build();  
    }  
  
    @Bean  
    OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {  
        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()  
                .authorizationCode()  
                .refreshToken()  
                .build();  
        DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);  
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);  
        return authorizedClientManager;  
    }  
}

AuthController

@RestController  
@Slf4j  
@RequiredArgsConstructor  
public class AuthController {  
  
    private final WebClient webClient;  
    @Value("${user-service.base-uri}")  
    private String userServiceBaseUri;  
  
    @GetMapping("/menus")  
    public String menus(@RegisteredOAuth2AuthorizedClient("client-gateway-authorization-code") OAuth2AuthorizedClient authorizedClient) {  
        return this.webClient  
                .get()  
                .uri(userServiceBaseUri)  
                .attributes(oauth2AuthorizedClient(authorizedClient))  
                .retrieve()  
                .bodyToMono(String.class)  
                .block();  
    }  
  
}

application.yml

server:  
  port: 9100  
  
spring:  
  application:  
    name: client-gateway  
  security:  
    oauth2:  
      client:  
        registration:  
          mobile-gateway-client-oidc:  
            provider: spring  
            client-id: mobile-gateway-client  
            client-secret: 123456  
            authorization-grant-type: authorization_code  
            redirect-uri: "http://127.0.0.1:9100/login/oauth2/code/{registrationId}"  
            scope: openid  
          client-gateway-authorization-code:  
            provider: spring  
            client-id: mobile-gateway-client  
            client-secret: 123456  
            client-authentication-method: client_secret_basic  
            authorization-grant-type: authorization_code  
            redirect-uri: "http://127.0.0.1:9100/authorized"  
            scope: message.read,message.write  
        provider:  
          spring:  
            issuer-uri: http://localhost:9000  
  
user-service:  
  base-uri: http://127.0.0.1:9001/menu/list

启动服务

在浏览器中输入:http://127.0.0.1:9100

输入账号密码:user1/password,这里的用户在 [[#auth-center#config]] 中配置。得到以下内容:

总结

  1. spring-authorization-server 目前还没有正式发布。文档较少。
  2. 还有一些需要完善的点。比如用户持久化、client 持久化。
  3. 此 demo 还要继续更新,为了能和本文对应,所以对应的 git tag 为 primitive-man