spring-authorization-server 是 Spring 新退出的认证服务器。
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖
通过https://start.spring.io/,来生成空工程。 后来将 springboot 升级到 2.7.0
并将代码上传到:https://gitee.com/xiaoyu-learning/spring-sas-server
为了加快 gradle 速度,可以添加阿里镜像
repositories {maven {url 'https://maven.aliyun.com/repository/public/'}maven {url 'https://maven.aliyun.com/repository/spring/'}maven {url 'https://maven.aliyun.com/repository/spring-plugin'}maven {url 'https://maven.aliyun.com/repository/google'}mavenLocal()mavenCentral()}
implementation 'org.springframework.boot:spring-boot-starter-security'implementation 'org.springframework.boot:spring-boot-starter-web'implementation 'org.springframework.security:spring-security-oauth2-authorization-server:0.3.0'implementation 'org.springframework.boot:spring-boot-starter-jdbc'runtimeOnly 'com.h2database:h2'compileOnly 'org.projectlombok:lombok'annotationProcessor 'org.projectlombok:lombok'testImplementation 'org.springframework.boot:spring-boot-starter-test'testImplementation 'org.springframework.security:spring-security-test'testImplementation 'net.sourceforge.htmlunit:htmlunit'testImplementation 'junit:junit:4.13.2'testImplementation 'org.mockito:mockito-core:3.9.0'
今后可以用自己的UserDetailsService
来进行
@EnableWebSecuritypublic class DefaultSecurityConfig {@BeanUserDetailsService users(){UserDetails user= User.withDefaultPasswordEncoder().username("user1").password("password").roles("USER").build();return new InMemoryUserDetailsManager(user);}@BeanSecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception{http.authorizeHttpRequests(authorizeRequests-> authorizeRequests.anyRequest().authenticated()).formLogin(Customizer.withDefaults());return http.build();}}
@Configuration(proxyBeanMethods = false)public class AuthorizationServerConfig {@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);//return http.formLogin(Customizer.withDefaults()).build();http.exceptionHandling(exceptions ->exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")));return http.build();}/*** 返回注册的客户端存储库* Spring 提供基于JDB或者是基于内存的* @param jdbcTemplate* @return*/@Beanpublic RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("messaging-client").clientSecret("{noop}secret").clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc").redirectUri("http://127.0.0.1:8080/authorized").scope(OidcScopes.OPENID).scope("message.read").scope("message.write").clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();// Save registered client in db as if in-memoryJdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);registeredClientRepository.save(registeredClient);return registeredClientRepository;}/*** 返回OAuth2的认证服务* @param jdbcTemplate* @param registeredClientRepository* @return*/@Beanpublic OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);}/*** 得到OAuth2确认服务* @param jdbcTemplate* @param registeredClientRepository* @return*/@Beanpublic OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);}@Beanpublic JWKSource<SecurityContext> jwkSource() {RSAKey rsaKey = Jwks.generateRsa();JWKSet jwkSet = new JWKSet(rsaKey);return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);}/*** spring: 使用嵌入式数据源 EmbeddedDatabaseBuilder* 嵌入式数据源作为应用的一部分运行,非常适合在开发和测试环境中使用,但是不适合用于生产环境。* 因为在使用嵌入式数据源的情况下,你可以在每次应用启动或者每次运行单元测试之前初始化测试数据。* @return*/@Beanpublic 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();}}
这里有一个类JWKSource
,其中使用了Jwks
这个类,这个类在jose
包中,具体不负责。
server:port: 9000
测试输入正确的密码
测试输入错误密码
关闭浏览器
输入:user1 non-password
跳转到:http://localhost:9000/login?error ,页面提示【用户名或密码错误】
登录完毕后,在地址栏中输入/oauth2/authorize
得到响应的 code
测试输入正确的密码
http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc?code=DeEOyLOKLiUPykkoPuYsmEJ2GOcOc_4NMgF7uzFqofdZmXi4OyvWgARjwE13hVFsercSHyf-drgSKfQ5szfblN3BMAs9xK1kr66NB_XVt6MC4dTGLjfkXVd9IZ1AvqVz&state=state
④⑤⑥
curl --location --request POST 'http://localhost:9000/oauth2/token' \--header 'Authorization: Basic cGlnOnBpZw==' \--header 'Content-Type: application/x-www-form-urlencoded' \--data-urlencode 'grant_type=authorization_code' \--data-urlencode 'code=DeEOyLOKLiUPykkoPuYsmEJ2GOcOc_4NMgF7uzFqofdZmXi4OyvWgARjwE13hVFsercSHyf-drgSKfQ5szfblN3BMAs9xK1kr66NB_XVt6MC4dTGLjfkXVd9IZ1AvqVz' \--data-urlencode 'redirect_uri=http://127.0.0.1:8080'
如果
curl --location --request POST \--url 'http://localhost:9000/oauth2/token' \--header 'content-type: application/x-www-form-urlencoded' \--data grant_type=client_credentials \--data client_id=messaging-client \--data client_secret=secret \--data-urlencode 'code=DeEOyLOKLiUPykkoPuYsmEJ2GOcOc_4NMgF7uzFqofdZmXi4OyvWgARjwE13hVFsercSHyf-drgSKfQ5szfblN3BMAs9xK1kr66NB_XVt6MC4dTGLjfkXVd9IZ1AvqVz' \--data-urlencode 'redirect_uri=http://127.0.0.1:8080'
http://auth-server:9000/oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid&state=ntYfCGNHykm1-474ZqNTZM6i1CyyiYtsylwfU8aj6b8=&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc&nonce=9VMhDHwj_J5Q_aEhWH8em4IHoArXgJTVsnYHnp_4seo
qqq
http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc?code=v7rj6pvj6RL6in7zgl2a4hZ2iqH-K9KCwhEFn9JaX0QRRCcQ-msZcNdPkVudg2quEmx0GE6iv9p_eICNzqIRY1Nayf3ZiA39N-QXBOqz8GhLYK4B8OfMsA_gmkiSFWC3&state=ntYfCGNHykm1-474ZqNTZM6i1CyyiYtsylwfU8aj6b8%3D
又跳转
http://auth-server:9000/oauth2/authorize?response_type=code&client_id=messaging-client&scope=openid&state=rF12Cw5mVVkguewo-_uw8bVuupAjN04t5WFS-0rS4Ho%3D&redirect_uri=http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc&nonce=IkVaCmBxoGgfx89j8_GeyXtk5taDlbBeWEgqQRbYL94
111
http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc?code=pjuEVvYOCj1q9Tn9EjZVynsLjbMr65PFaS1KS_WKiR12DEFFv3l8Z5A-PhdVfzSmFxaoQUnzuXpBU1fCQ2VHgrX41Cq-UrgVqab4Z9V2ZJxl24ED-SNZXYwot-yOmZy_&state=rF12Cw5mVVkguewo-_uw8bVuupAjN04t5WFS-0rS4Ho%3D
org.springframework.security.oauth2.server.authorization.authenticationOAuth2AuthorizationCodeAuthenticationProviderif (StringUtils.hasText(authorizationRequest.getRedirectUri()) &&!authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) {throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);}https%3A%2F%2Foidcdebugger.com%2Fdebughttps://oidcdebugger.com/debughttps://oidcdebugger.com/debug使用URLEncoder进行转码@EnableOAuth2Sso
{"access_token": "eyJraWQiOiJjODI4NjhkZS1jZGRlLTQ1YmQtODNhOS1lNzIwN2FhODQxZDYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6Im1lc3NhZ2luZy1jbGllbnQiLCJuYmYiOjE2MzczNzU4NDAsInNjb3BlIjpbIm9wZW5pZCJdLCJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6OTAwMCIsImV4cCI6MTYzNzM3NjE0MCwiaWF0IjoxNjM3Mzc1ODQwfQ.ZIhRrc3g4D4H8-R9z7dro0i_SSbaao1zmUTPnyRq7S2n2bJ-4dnyKfP_XMx3oZVIyCrpRx6KMlSzUGdP6EL21MjmAi1D7kHTidTiv7Y0S-loS7l1djqgCJ4G8xWfw63FVl7_acqLYgtmYmt43WbXGWeG4JJqgbK0VvM3eyljDNTW9xDUBzzKdBjjD-1Bjn1hPC61xmb41rpdrblWrLqiRNX342bvKZq9NbeNovuV33ZaS8ChiD-UiF5BrMYDpVez4qlT5SvNudRWOIAN7TwvV__XtB9bDHQr5FcMfY7ZDPjAQNrHvyHIxMR-xypX4Ru2MrATEgmyN-RfYSzHIwpyVQ","refresh_token": "H_CSpUZt4c_Dl-97RXa1REyRky14zN_n1eW531zptnV9LvBBBNaph7sqTxJI_TTtNtMKHjc7Hhg0NoLyK2jgv7qiSewy0QdADlFKWc3lyANUptsp8-ckXUOOstQcLSZZ","scope": "openid","id_token": "eyJraWQiOiJjODI4NjhkZS1jZGRlLTQ1YmQtODNhOS1lNzIwN2FhODQxZDYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6Im1lc3NhZ2luZy1jbGllbnQiLCJhenAiOiJtZXNzYWdpbmctY2xpZW50IiwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjkwMDAiLCJleHAiOjE2MzczNzc2NDAsImlhdCI6MTYzNzM3NTg0MCwibm9uY2UiOiI1bTkwaW1jZjN6cSJ9.Kfto7TgM7odTZfVJK_wSoLCzVMne-maY4TCgNg2HYEBDdIRMsfRL2Q6Gx_1lZq7uoBCAik1DFqDaQsudfZxuAso1gL3oMuE793Vvm0b2-Zmbk1bWCr5oQF9k4aU1tunnO2Ah2zmTvhOxbTP3i8F-LjVf_85c3HcCjsH7a9X-Yd9WRk185tSIDQihnw_45sXKCYY4jAtf7h73YS73icgmiIUGpR1JuKFCwJs_ZBRcOK1cyjp6kTnwOpoQ7uKABIOrzqmU0ghR1k2ZIg4OYdzIvr5XA83u6wOpx7sxNbkJCdHgNMSzMBsBLq1V_djFqOR8DbEwuAbMSG3Dfh8kEooMFQ","token_type": "Bearer","expires_in": 300}