相关文章:

  1. OAuth2的定义和运行流程
  2. Spring Security OAuth实现Gitee快捷登录
  3. Spring Security OAuth实现GitHub快捷登录
  4. Spring Security的过滤器链机制
  5. Spring Security OAuth Client配置加载源码分析
  6. Spring Security内置过滤器详解
  7. 为什么加载了两个OAuth2AuthorizationRequestRedirectFilter分析

前言

在之前我们已经对接过了GitHub、Gitee客户端,使用OAuth2 Client能够快速便捷的集成第三方登录,集成第三方登录一方面降低了企业的获客成本,同时为用户提供更为便捷的登录体验。
但是随着企业的发展壮大,越来越有必要搭建自己的OAuth2服务器。
OAuth2不仅包括前面的OAuth客户端,还包括了授权服务器,在这里我们要通过最小化配置搭建自己的授权服务器。
授权服务器主要提供OAuth Client注册、用户认证、token分发、token验证、token刷新等功能。实际应用中授权服务器与资源服务器可以在同一个应用中实现,也可以拆分成两个独立应用,在这里为了方便理解,我们拆分成两个应用。

授权服务器变迁

授权服务器(Authorization Server)目前并没有集成在Spring Security项目中,而是作为独立项目存在于Spring生态中,图1为Spring Authorization Server 在Spring 项目列表中的位置。

图1

Spring Authorization Server 为什么没被集成在Spring Security中呢?

起因是因为Spring 中的Spring Security OAuth、Spring Cloud Security都对OAuth有自己的实现,Spring团队开始是想把OAuth独立出来放到Spring Security中,但是后面Spring团队意识到OAuth授权服务并不适合包含在Spring Security框架中,于是在2019年11月Spring宣布不在Spring Security中支持授权服务器。原文如下:

原文:
Since the Spring Security OAuth project was created, the number of authorization server choices has grown significantly. Additionally, we did not feel like creating an authorization server was a common scenario. Nor did we feel like it was appropriate to provide authorization support within a framework with no library support. After careful consideration, the Spring Security team decided that we would not formally support creating authorization servers.

但是对于Spring Security不再支持授权服务器,社区反应强烈。于是在2020年4月,Spring推出了Spring Authorization Server项目。
目前项目最新GA版本为0.3 GA,预览版本1.0.0-M1。

最小化配置

安装授权服务器

1、新创建一个Spring Boot项目,命名为spring-security-authorization-server
2、引入pom依赖

<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><version>0.3.1</version>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

配置授权服务器


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.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
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.factory.PasswordEncoderFactories;
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.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.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
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.RequestMatcher;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {//授权端点过滤器链@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =new OAuth2AuthorizationServerConfigurer<>();RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();http//没有认证会自动跳转到/login页面.exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))).requestMatcher(endpointsMatcher).authorizeRequests(authorizeRequests ->authorizeRequests.anyRequest().authenticated()).csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)).apply(authorizationServerConfigurer);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();}//配置主体用户@Beanpublic UserDetailsService userDetailsService() {UserDetails userDetails = User.withDefaultPasswordEncoder().username("user").password("user").roles("USER").build();return new InMemoryUserDetailsManager(userDetails);}//注册客户端@Beanpublic RegisteredClientRepository registeredClientRepository() {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())//客户端id.clientId("testClientId")//客户端秘钥,授权服务器需要加密存储.clientSecret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("testClientSecret"))//授权方法.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)//支持的授权类型.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)//回调地址,支持多个,本地测试不能使用localhost.redirectUri("http://127.0.0.1:8080/login/oauth2/code/customize").scope(OidcScopes.OPENID)//授权scope.scope("message.read").scope("userinfo").scope("message.write")//是否需要授权页面,开启跳转到授权页面,需要手动确认.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();return new InMemoryRegisteredClientRepository(registeredClient);}//token加密@Beanpublic 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);}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;}//配置协议端点,比如/oauth2/authorize、/oauth2/token等@Beanpublic ProviderSettings providerSettings() {return ProviderSettings.builder().build();}}

如上是最小化授权服务器的配置,这里我们将授权主体和客户端都存储在内存中,当然也可以持久化到数据库中,分别使用JdbcUserDetailsManagerJdbcRegisteredClientRepository
ProviderSettings.builder().build()使用了默认的配置,这几个地址我们后面就会用到:

public static Builder builder() {return new Builder().authorizationEndpoint("/oauth2/authorize").tokenEndpoint("/oauth2/token").jwkSetEndpoint("/oauth2/jwks").tokenRevocationEndpoint("/oauth2/revoke").tokenIntrospectionEndpoint("/oauth2/introspect").oidcClientRegistrationEndpoint("/connect/register").oidcUserInfoEndpoint("/userinfo");
}

❗ 官方指出@Import(OAuth2AuthorizationServerConfiguration.class)也可以用来最小化配置,但我亲测这种方式没多大用处,并且还有问题。

配置客户端

这里我们要使用自己的搭建授权服务器,需要自定义一个客户端,还是使用前面集成GitHub的示例,只要在配置文件中扩展就可以。
完整配置如下:

spring:security:oauth2:client:registration:gitee:client-id: gitee_clientIdclient-secret: gitee_secretauthorization-grant-type: authorization_coderedirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'client-name: Giteegithub:client-id: github_clientIdclient-secret: github_secret# 自定义customize:client-id: testClientIdclient-secret: testClientSecretauthorization-grant-type: authorization_coderedirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'client-name: Customizescope:- userinfoprovider:gitee:authorization-uri: https://gitee.com/oauth/authorizetoken-uri: https://gitee.com/oauth/tokenuser-info-uri: https://gitee.com/api/v5/useruser-name-attribute: name# 自定义customize:authorization-uri: http://localhost:9000/oauth2/authorizetoken-uri: http://localhost:9000/oauth2/tokenuser-info-uri: http://localhost:9000/userinfouser-name-attribute: username

❗ 在配置授权服务器uri的时候,请勿依旧使用127.0.0.1,由于是在本地测试,授权服务器的session和客户端的session会互相覆盖,导致莫名其妙的问题。
请区分回调地址,和授权服务器端点uri的地址。

客户端的session

授权服务器的session

体验

另外为了能够更好的调式,可以在两个应用增加@EnableWebSecurity(debug = true)和 log日志,日志如下,打开TRACE级别日志:

logging:level:root: INFOorg.springframework.web: INFOorg.springframework.security: TRACEorg.springframework.security.oauth2: TRACE

现在启动两个应用,访问http://127.0.0.1:8080/hello,自动跳转到登录页面。

点击Customize,将跳转至授权服务器,注意看地址栏地址为localhost:9000/login,输入用户名/密码登录,user/user

登录后,将跳转至授权页面,由于我们没有定制,使用的是默认页面,可以看到该页面的地址为
http://localhost:9000/oauth2/authorize?response_type=code&client_id=testClientId&scope=userinfo&state=yV1ElAN2855yq3bY5kgj_rmilnCclyvZHkxVB7a1d84%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/customize

我们勾选userinfo,提交后即跳转回客户端。
我们看下客户收到的日志,授权服务器带着code回调了我们填写的回调地址。
Request received for GET '/login/oauth2/code/customize?code=DPAlx5uyrUpfrZIlBKrpIy_mmcgiyC2qCxPFtUeLA0fBrZd238XM2vN8M1jv9XAgl0KA-D54P_KzVH7RbUw7ApBUc2pbnuSVRZUyHazozmNM4YgQ06CZryfr20qLRhW4&state=_Sgak7GLILLKbwr9JVuwA2xVp95CWPgUMByQcvePkgM%3D'

************************************************************Request received for GET '/login/oauth2/code/customize?code=DPAlx5uyrUpfrZIlBKrpIy_mmcgiyC2qCxPFtUeLA0fBrZd238XM2vN8M1jv9XAgl0KA-D54P_KzVH7RbUw7ApBUc2pbnuSVRZUyHazozmNM4YgQ06CZryfr20qLRhW4&state=_Sgak7GLILLKbwr9JVuwA2xVp95CWPgUMByQcvePkgM%3D':org.apache.catalina.connector.RequestFacade@1a8761d0servletPath:/login/oauth2/code/customize
pathInfo:null
headers:
host: 127.0.0.1:8080
connection: keep-alive
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
sec-fetch-site: cross-site
sec-fetch-mode: navigate
sec-fetch-user: ?1
sec-fetch-dest: document
sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
referer: http://127.0.0.1:8080/
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
cookie: JSESSIONID=2527F412F53FA27A30BFBC39161ABB63Security filter chain: [DisableEncodeUrlFilterWebAsyncManagerIntegrationFilterSecurityContextPersistenceFilterHeaderWriterFilterCsrfFilterLogoutFilterOAuth2AuthorizationRequestRedirectFilterOAuth2AuthorizationRequestRedirectFilterOAuth2LoginAuthenticationFilterDefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilterRequestCacheAwareFilterSecurityContextHolderAwareRequestFilterAnonymousAuthenticationFilterOAuth2AuthorizationCodeGrantFilterSessionManagementFilterExceptionTranslationFilterFilterSecurityInterceptor
]************************************************************

总结

Spring Security 的最小化授权服务器的配置,到这里结束了,该demo虽然代码量非常少,但涉及的知识非常多,并且坑也多。

Spring Security文档中的代码说明更新不及时,比如@Import(OAuth2AuthorizationServerConfiguration.class)文档中说明是最小化配置,但文档的快速开始又提供了另外一种的最小化配置方式。

另外授权服务器如果发生异常,是不会打印堆栈的,而是把错误信息放入到response中,是打算要在页面上显示,然而demo的默认错误页并不会显示错误详情,只有错误编号400,如图。


Spring Authorization Server 还需要多多完善,Spring Security也不例外,不久前我还提了一个PR,把一个持续数个版本的bug给修复了

Spring Security 自定义授权服务器实践相关推荐

  1. Spring Security 自定义资源服务器实践

    相关文章: OAuth2的定义和运行流程 Spring Security OAuth实现Gitee快捷登录 Spring Security OAuth实现GitHub快捷登录 Spring Secur ...

  2. spring security oauth2 授权服务器负载均衡解决方案

    研究了好几天的授权服务对资源服务是如何实现负载均衡的 真的是丈二和尚摸不着头脑,研究了几天今天终于找到了一篇文章 真的是翻:烂了 奈何自己太菜 上一下资源服务的yml配置(oauth-server是注 ...

  3. Spring Security Oauth2 授权码模式下 自定义登录、授权页面

    主要说明:基于若依springcloud微服务框架的2.1版本 嫌弃缩进不舒服的,直接访问我的博客站点: http://binarydance.top//aticle_view.html?aticle ...

  4. 将Spring Security OAuth2授权服务JWK与Consul 配置中心结合使用

    将Spring Security OAuth2授权服务JWK与Consul 配置中心结合使用 概述 在前文中介绍了OAuth2授权服务简单的实现密钥轮换,与其不同,本文将通过Consul实现我们的目的 ...

  5. 3.Spring Security 自定义用户认证

    Spring Security自定义用户认证 自定义认证过程 自定义认证的过程需要实现Spring Security提供的UserDetailService接口,该接口只有一个抽象方法loadUser ...

  6. spring security 自定义认证登录

    spring security 自定义认证登录 1.概要 1.1.简介 spring security是一种基于 Spring AOP 和 Servlet 过滤器的安全框架,以此来管理权限认证等. 1 ...

  7. (二)Spring Security自定义登录成功或失败处理器

    目录 一:创建登录成功处理器 二:创建登录失败处理器 三:添加处理器 三. 项目地址 我们接着上一章 Spring Security最简单的搭建,进行开发 LoginSuccessHandler 和L ...

  8. Spring Security OAuth2 授权码模式 (Authorization Code)

    前言 Spring Security OAuth2 授权码模式 (Authorization Code) 应该是授权登录的一个行业标准 整体流程 首先在平台注册获取CLIENT_ID和CLIENT_S ...

  9. spring security自定义指南

    序 本文主要研究一下几种自定义spring security的方式 主要方式 自定义UserDetailsService 自定义passwordEncoder 自定义filter 自定义Authent ...

最新文章

  1. java 坦克重叠_坦克大战中坦克一直有重叠是怎么回事
  2. C++网络游戏程序员笔试题
  3. ARM平台硬件时钟中断周期HZ值计算
  4. LeetCode 1748. 唯一元素的和
  5. 为什么需要函数式编程?
  6. anaconda下python2和python3环境共存
  7. 百度李彦宏谈Google回归:真刀真枪地再PK一次,再赢一次
  8. keep it SMPL: Automatic estimation of 3d human pose and shape from a single image
  9. 计算机原理华东理工大学期末成绩查询,华东理工大学微机原理历年真题第十一章.ppt...
  10. NYOJ 7-街区最短路径问题(曼哈顿距离)
  11. linux 查看进程与端口以及内存资源
  12. 零基础入门微信小程序开发
  13. 用matlab产生hdb3码,MATLAB仿真 HDB3码程序
  14. 设备树学习之(一)GPIO中断
  15. 阿里天池大数据竞赛(杂)
  16. 如何进行大数据处理?大数据处理的方法步骤
  17. .Net 配置系统-数据库配置提供者
  18. C语言实现双人五子棋
  19. matlab 经典循环语句,经典MATLAB循环语句
  20. 基于Java的vtt转txt程序

热门文章

  1. java drawstring 多行,GDI+ 中如何DrawString多行文本?
  2. 朗沃20140421
  3. springboot跳转html_畅游Spring Boot系列 — 自定义配置
  4. 软件缺陷是什么以及缺陷的管理
  5. 【JavaScript转义字符对照表】
  6. Android 小米手机开发APP图标更换后还显示原来的图标
  7. Foxmail7.2账号帐号邮件备份和恢复
  8. 2020年中国碳纤维原丝供需及竞争现状分析,达产率正趋近国际水平「图」
  9. 单元测试工具 BoundsChecker 【转载】
  10. scheduleAtFixedRate的用法(Java)