spring|spring security+oauth2 实现统一认证鉴权平台(客户端sdk部分)

接入简单,支持多租户,可配置,低代码式的快速实现一套自己的认证鉴权逻辑
包含用户管理、部门管理、角色管理、权限管理,未来还可以支持更多模块,比如数据字典管理、...
预计节省每个应用相关模块研发时间5人天
对于oauth2.0协议来说,有几个概念

  1. 授权服务器
  2. 客户端
  3. 资源服务器
这偏文章主要讲客户端sdk实现,资源服务器主要就是统一存放用户信息的,授权服务器使用springsecurity官方的进行小定制。
首先使用springboot的自动配置功能,主要配置类有SecurityConfig,通过编写META-INF/spring.factories来实现自动加载配置bean
@Configuration(proxyBeanMethods = false) @EnableWebSecurity @EnableConfigurationProperties(OauthProperties.class) public class SecurityConfig {@Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http, IntrospectorFilter filter, CustomLoginUrlAuthenticationEntryPoint entryPoint, ClientRegistrationRepository clientRegistrationRepository, CustomRequestCache customRequestCache) throws Exception { ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry expressionInterceptUrlRegistry = http.authorizeRequests(); if(!CollectionUtils.isEmpty(oauthProperties.getIgnoreUris())) { expressionInterceptUrlRegistry.requestMatchers(oauthProperties.getIgnoreUris().stream().map(AntPathRequestMatcher::new).toArray(RequestMatcher[]::new)).permitAll(); } expressionInterceptUrlRegistry.anyRequest().authenticated().and() .oauth2Login().authorizationEndpoint().authorizationRequestResolver(authorizationRequestResolver(clientRegistrationRepository)) .and() // .defaultSuccessUrl("/", true); .successHandler(authenticationSuccessHandler()); http.oauth2Client().and() .cors().and() .exceptionHandling().authenticationEntryPoint(entryPoint).and() .csrf().disable() .requestCache(requestCacheCustomizer->{ requestCacheCustomizer.requestCache(customRequestCache.getRequestCache(http)); }); http.addFilterBefore(filter, AnonymousAuthenticationFilter.class); return http.build(); }@Bean public CustomRequestCache customRequestCache() { return new CustomRequestCache(); }; private AuthenticationSuccessHandler authenticationSuccessHandler() { SavedRequestAwareAuthenticationSuccessHandler successHandler = new CustomSavedRequestAwareAuthenticationSuccessHandler(); successHandler.setUseReferer(true); return successHandler; }private OAuth2AuthorizationRequestResolver authorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver( clientRegistrationRepository, "/oauth2/authorization"); authorizationRequestResolver.setAuthorizationRequestCustomizer( authorizationRequestCustomizer()); returnauthorizationRequestResolver; }private Consumer authorizationRequestCustomizer() { return customizer -> customizer .additionalParameters(params -> params.put("tenantId", oauthProperties.getClientId())); }@Bean public CustomLoginUrlAuthenticationEntryPoint customLoginUrlAuthenticationEntryPoint(ObjectMapper objectMapper) { CustomLoginUrlAuthenticationEntryPoint customLoginUrlAuthenticationEntryPoint = new CustomLoginUrlAuthenticationEntryPoint("/oauth2/authorization/auth_server"); customLoginUrlAuthenticationEntryPoint.setObjectMapper(objectMapper); customLoginUrlAuthenticationEntryPoint.setOauthProperties(oauthProperties); return customLoginUrlAuthenticationEntryPoint; }; @Bean public IntrospectorFilter introspectorFilter(OpaqueTokenIntrospector opaqueTokenIntrospector, OAuth2AuthorizedClientService oAuth2AuthorizedClientService) { IntrospectorFilter introspectorFilter = new IntrospectorFilter(); introspectorFilter.setOpaqueTokenIntrospector(opaqueTokenIntrospector); introspectorFilter.setoAuth2AuthorizedClientService(oAuth2AuthorizedClientService); return introspectorFilter; }@Bean public NimbusOpaqueTokenIntrospector opaqueTokenIntrospector() { String introspectUri = oauthProperties.getIssuerUri() + "/oauth2/introspect"; OAuth2ResourceServerProperties.Opaquetoken opaqueToken = new OAuth2ResourceServerProperties.Opaquetoken(); opaqueToken.setIntrospectionUri(introspectUri); opaqueToken.setClientId(oauthProperties.getClientId()); opaqueToken.setClientSecret(oauthProperties.getClientSecret()); return new NimbusOpaqueTokenIntrospector(opaqueToken.getIntrospectionUri(), opaqueToken.getClientId(), opaqueToken.getClientSecret()); }//@RefreshScope 动态刷新 @Bean @ConditionalOnMissingBean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(this.clientRegistration()); }@Bean public OidcUserService oidcUserService(DefaultOAuth2UserService defaultOAuth2UserService) { CustomOidcUserService oidcUserService = new CustomOidcUserService(); oidcUserService.setAccessibleScopes0(scopes); oidcUserService.setOauth2UserService0(defaultOAuth2UserService); return oidcUserService; }@ConditionalOnMissingBean @Bean public ParameterizedTypeReference>> userInfoTypeReference () { return new ParameterizedTypeReference>>() {}; }@Bean public DefaultOAuth2UserService defaultOAuth2UserService(ParameterizedTypeReference userInfoTypeReference) { CustomDefaultOAuth2UserService defaultOAuth2UserService = new CustomDefaultOAuth2UserService(); defaultOAuth2UserService.setPARAMETERIZED_RESPONSE_TYPE(userInfoTypeReference); return defaultOAuth2UserService; }@Autowired OauthProperties oauthProperties; private Set scopes = new HashSet(){ { //add("openid"); add("user"); } }; private ClientRegistration clientRegistration() { if (oauthProperties.getScopes()!=null) scopes.addAll(oauthProperties.getScopes()); ClientRegistration.Builder auth_server = ClientRegistrations.fromIssuerLocation(oauthProperties.getIssuerUri()).registrationId("auth_server"); return auth_server.clientId(oauthProperties.getClientId()) .clientSecret(oauthProperties.getClientSecret()) .clientName(oauthProperties.getClientName()) //.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) //.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri((StringUtils.hasText(oauthProperties.getServerUrl())?oauthProperties.getServerUrl():"{baseUrl}")+"/login/oauth2/code/{registrationId}") .scope(scopes) .userInfoUri(oauthProperties.getIssuerUri()+"/client/userinfo") .userNameAttributeName(IdTokenClaimNames.SUB) .build(); }}

springsecurity对于oauth2.0的支持还是比较完善的,但是不完全符合我们对认证授权系统的要求,所以我这里做了很多自定义,可以说把大部分的拦截器都实现了一些,发现security的代码质量也不是很高,很多地方没有做到扩展性,只能重写类。
然后需要接入应用进行配置,所以定义了一个properties
@ConfigurationProperties(prefix = "oauth") @Data public class OauthProperties {/** * 客户端id */ private String clientId; /** * 客户端密钥 */ private String clientSecret; /** * 客户端名称 */ private String clientName; /** * 接入授权服务器配置uri */ private String issuerUri; /** * 权限 默认有openid,user */ private Collection scopes; /** * 过滤url白名单 */ private Collection ignoreUris; /** * 服务部署地址(可空,默认读取数据库对应的回调地址) */ private String serverUrl; }

【spring|spring security+oauth2 实现统一认证鉴权平台(客户端sdk部分)】这样接入应用就可以通过配置文件自定义了。

    推荐阅读