Spring Authorization Server 实现授权中心

源码地址

当前,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
spring-security-oauth2-authorization-server 项目由 Spring Security 团队领导,**社区驱动**。

本文的目的:

  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 路径下的访问权限。

MenuController

@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

Spring Authorization Server 实现授权中心的更多相关文章

  1. Spring Authorization Server 全新授权服务器整合使用

    前言 Spring Authorization Server 是 Spring 团队最新开发适配 OAuth 协议的授权服务器项目,旨在替代原有的 Spring Security OAuth 经过半年 ...

  2. Spring Authorization Server(AS)从 Mysql 中读取客户端、用户

    Spring AS 持久化 jdk version: 17 spring boot version: 2.7.0 spring authorization server:0.3.0 mysql ver ...

  3. Spring Authorization Server授权服务器入门

    11月8日Spring官方已经强烈建议使用Spring Authorization Server替换已经过时的Spring Security OAuth2.0,距离Spring Security OA ...

  4. Spring Authorization Server的使用

    Spring Authorization Server的使用 一.背景 二.前置知识 三.需求 四.核心代码编写 1.引入授权服务器依赖 2.创建授权服务器用户 3.创建授权服务器和客户端 五.测试 ...

  5. Spring Authorization Server 0.2.3发布,放出联合身份DEMO

    很快啊Spring Authorization Server又发新版本了,现在的版本是0.2.3.本次都有什么改动呢?我们来了解一下. 0.2.3版本特性 本次更新的新特性不少. 为公开客户端提供默认 ...

  6. Spring Authorization Server 0.3.0 发布,官方文档正式上线

    基于OAuth2.1的授权服务器Spring Authorization Server 0.3.0今天正式发布,在本次更新中有几大亮点. 文档正式上线 Spring Authorization Ser ...

  7. Spring Cloud(6.1):搭建OAuth2 Authorization Server

    配置web.xml 添加spring-cloud-starter-security和spring-security-oauth2-autoconfigure两个依赖. </dependency& ...

  8. spring cloud 2.x版本 Eureka Server服务注册中心教程

    本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 1.创建服务注册中心 1.1 新建Spring boot工程:eureka-server 1 ...

  9. Spring Security OAuth2 Demo —— 授权码模式

    本文可以转载,但请注明出处https://www.cnblogs.com/hellxz/p/oauth2_oauthcode_pattern.html 写在前边 在文章OAuth 2.0 概念及授权流 ...

随机推荐

  1. 为什么要用Spring

    1.方便解耦,简化开发 通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合.有了Spring,用户不必再为单实例模式类.属性文件解析 ...

  2. Tcp三次握手四次挥手个人学习

    最近想跳槽,学习了tcp中的三次握手与四次挥手,特意记录下,加深记忆 SYN 代表请求创建连接 FIN 表示请求关闭连接 ACK 代表确认接受,不管是三次握手还是四次分手,在回应的时候都会加上ACK= ...

  3. yum下载安装git服务

    yum install git 安装成功后,配置 用户 邮箱信息 注: youxiu326 github账号名称 youxiu326@163.com    github账号对应邮箱 git confi ...

  4. 2.安装Spark与Python练习

    一.安装Spark <Spark2.4.0入门:Spark的安装和使用> 博客地址:http://dblab.xmu.edu.cn/blog/1307-2/ 1.1 基础环境 1.1.1 ...

  5. C#和TS/JS的对比学习02:函数与方法

    程序本质上,就是由数据和处理数据的方法构成.函数和方法,这两个名词虽然字面不同,但意义上其实没有区别.只是因为它们出现的地方有异,给予了不同的名称,比如在全局环境中,叫函数,在对象或类中,叫方法.而C ...

  6. PC端免费高效的同声翻译

    疫情期间上网课,对于英语听力较差或者需要观看英文视频,但实际上并没有双语字幕的这种情况下需要找一个实时的翻译工具.虽然说手机上此类软件比较多,但电脑上没有特别合适的应用可以做为一个免费实时翻译.哪怕是 ...

  7. SecureCRT显示连接失败的原因

    问题描述:连接后像192.168.111.140那样的红色图标 原因:没有开启对应的虚拟机 解决办法:打开对应的虚拟机

  8. Linux---必备命令(1)

    文件和目录 # 更改目录位置 cd /tmp # 进入文件夹 cd dirr # 新建文件夹 mkdir dirr # 创建文本 touch text.txt # 显示当前目录下的所有文件,包含已'. ...

  9. layui下拉框渲染问题,以及回显问题

    最近实习公司给的新人练手项目用的layui,layui之前自己也接触过但是也就是用了用table组件,没有用过layer弹层这些东西,所以就了解了一下. 首先遇到的一个问题就是下拉框没有样式,然后加样 ...

  10. uniapp-uni.setNavigationBarColor 动态修改顶部背景颜色

    uni.setNavigationBarColor({ frontColor: '#ffffff', backgroundColor: "#3583ff" })