接入简单,支持多租户,可配置,低代码式的快速实现一套自己的认证鉴权逻辑
包含用户管理、部门管理、角色管理、权限管理,未来还可以支持更多模块,比如数据字典管理、...
预计节省每个应用相关模块研发时间5人天
对于oauth2.0协议来说,有几个概念
- 授权服务器
- 客户端
- 资源服务器
这偏文章主要讲客户端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<HttpSecurity>.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());
return authorizationRequestResolver;
}
private Consumer<OAuth2AuthorizationRequest.Builder> 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<Result<OpenAppUser<Map<String, Object>>>> userInfoTypeReference () {
return new ParameterizedTypeReference<Result<OpenAppUser<Map<String, Object>>>>() {};
}
@Bean
public DefaultOAuth2UserService defaultOAuth2UserService(ParameterizedTypeReference userInfoTypeReference) {
CustomDefaultOAuth2UserService defaultOAuth2UserService = new CustomDefaultOAuth2UserService();
defaultOAuth2UserService.setPARAMETERIZED_RESPONSE_TYPE(userInfoTypeReference);
return defaultOAuth2UserService;
}
@Autowired
OauthProperties oauthProperties;
private Set<String> 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<String> scopes;
/**
* 过滤url白名单
*/
private Collection<String> ignoreUris;
/**
* 服务部署地址(可空,默认读取数据库对应的回调地址)
*/
private String serverUrl;
}
这样接入应用就可以通过配置文件自定义了。