SpringAuthorizationServer实现授权中心

博客 动态
0 168
优雅殿下
优雅殿下 2022-05-10 17:59:03
悬赏:0 积分 收藏

Spring Authorization Server 实现授权中心

Spring Authorization Server 实现授权中心

源码地址

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

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

组件版本
JDK17
org.springframework.boot2.6.7
Gradle7.4.1
spring-security-oauth2-authorization-server0.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 路径下的访问权限。

@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
posted @ 2022-05-10 17:27 Zhang_Xiang 阅读(5) 评论(0) 编辑 收藏 举报
回帖
    优雅殿下

    优雅殿下 (王者 段位)

    2018 积分 (2)粉丝 (47)源码

    小小码农,大大世界

     

    温馨提示

    亦奇源码

    最新会员