package com.monkeyk.sos.config; import com.monkeyk.sos.domain.oauth.ClaimsOAuth2TokenCustomizer; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.util.IOUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.function.Consumer; import static com.monkeyk.sos.domain.shared.SOSConstants.CUSTOM_CONSENT_PAGE_URI; /** * 2018/2/8 *

*

* OAuth2 config * * @author Shengzhao Li */ @Configuration public class OAuth2ServerConfiguration { /** * Fixed, resource-id * * @deprecated Not used from v3.0.0 */ public static final String RESOURCE_ID = "sos-resource"; /** * keystore file name * TODO: 请在不同的环境使用不同的key(即更换jwks.json中的文件内容); 如何生成不同的key请参考 JwksTest.java 类. * * @since 3.0.0 */ public static String KEYSTORE_NAME = "jwks.json"; /** * @since 3.0.0 */ @Autowired private JdbcTemplate jdbcTemplate; /** * @since 3.0.0 */ private AuthenticationManager authenticationManagerOAuth2; /** * authorizationServerSecurityFilterChain * * @since 3.0.0 */ @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http.sessionManagement(sessionManagementConfigurer -> { sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); }); http.authorizeHttpRequests(registry -> { registry // 所有以 /unity/ 开头的 URL属于此资源 .requestMatchers("/unity/**").hasAnyRole("UNITY") // 所有以 /m/ 开头的 URL属于此资源 .requestMatchers("/m/**").hasAnyRole("MOBILE"); }); OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) // .deviceAuthorizationEndpoint(deviceAuthorizationEndpoint -> // deviceAuthorizationEndpoint.verificationUri("/activate") // ) .deviceVerificationEndpoint(deviceVerificationEndpoint -> deviceVerificationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI) ) .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI)) // Enable OpenID Connect 1.0 .oidc(oidcConfigurer -> { oidcConfigurer.providerConfigurationEndpoint(endpointConfigurer -> { //扩展oidc默认能力 endpointConfigurer.providerConfigurationCustomizer(oidcProviderConfigurationCustomizer()); }); }); http .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) ) ) .oauth2ResourceServer(oauth2ResourceServer -> //ext jwt oauth2ResourceServer.jwt(Customizer.withDefaults())); DefaultSecurityFilterChain filterChain = http.build(); this.authenticationManagerOAuth2 = http.getSharedObject(AuthenticationManager.class); return filterChain; } /** * 获取 OAuth2流程中的 AuthenticationManager * * @return AuthenticationManager * @since 3.0.0 */ public AuthenticationManager authenticationManagerOAuth2() { return authenticationManagerOAuth2; } /** * 扩展 oidc 的默认能力配置项 * * @since 3.0.0 */ private Consumer oidcProviderConfigurationCustomizer() { return builder -> { builder.idTokenSigningAlgorithms(strings -> { strings.add(SignatureAlgorithm.ES256.getName()); }).grantTypes(grantTypes -> { //向下兼容添加,v3.0.0 // grantTypes.add(AuthorizationGrantType.PASSWORD.getValue()); grantTypes.add(AuthorizationGrantType.JWT_BEARER.getValue()); }) .scopes(strings -> { strings.add(OidcScopes.PROFILE); strings.add(OidcScopes.EMAIL); strings.add(OidcScopes.ADDRESS); strings.add(OidcScopes.PHONE); }); }; } /** * 注册客户端管理 * * @return RegisteredClientRepository * @since 3.0.0 */ @Bean public RegisteredClientRepository registeredClientRepository() { // return new InMemoryRegisteredClientRepository(client); return new JdbcRegisteredClientRepository(this.jdbcTemplate); } /** * 授权准许存储配置, jdbc实现 * * @since 3.0.0 */ @Bean public OAuth2AuthorizationConsentService oAuth2AuthorizationConsentService(RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationConsentService(this.jdbcTemplate, registeredClientRepository); } /** * 授权信息存储配置, jdbc实现 * * @since 3.0.0 */ @Bean public OAuth2AuthorizationService oAuth2AuthorizationService(RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(this.jdbcTemplate, registeredClientRepository); } /** * 提供加密/解密的 source * 可多个 key, 根据不同的需要来选择使用 * TODO: 请在不同的环境使用不同的key(即更换jwks.json中的文件内容); 如何生成不同的key请参考 JwksTest.java 类. * 更安全的建议:定期更新jwk(替换旧的jwk或新旧轮流) * * @return JWKSource * @since 3.0.0 */ @Bean public JWKSource jwkSource() throws IOException, ParseException { try (InputStream is = getClass().getClassLoader().getResourceAsStream(KEYSTORE_NAME)) { String keyJson = IOUtils.readInputStreamToString(is, StandardCharsets.UTF_8); return new ImmutableJWKSet<>(JWKSet.parse(keyJson)); } } /** * 扩展 jwt id_token 等生成 * * @since 3.0.0 */ @Bean public OAuth2TokenCustomizer jwtCustomizer() { return new ClaimsOAuth2TokenCustomizer(); } }