spring|spring security+oauth2 实现统一认证鉴权平台(客户端sdk部分)
接入简单,支持多租户,可配置,低代码式的快速实现一套自己的认证鉴权逻辑
包含用户管理、部门管理、角色管理、权限管理,未来还可以支持更多模块,比如数据字典管理、...
预计节省每个应用相关模块研发时间5人天
对于oauth2.0协议来说,有几个概念
- 授权服务器
- 客户端
- 资源服务器
首先使用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部分)】这样接入应用就可以通过配置文件自定义了。
推荐阅读
- Activiti(一)SpringBoot2集成Activiti6
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- MybatisPlus使用queryWrapper如何实现复杂查询
- python学习之|python学习之 实现QQ自动发送消息
- 孩子不是实现父母欲望的工具——林哈夫
- SpringBoot调用公共模块的自定义注解失效的解决
- opencv|opencv C++模板匹配的简单实现
- Node.js中readline模块实现终端输入
- 解决SpringBoot引用别的模块无法注入的问题
- java中如何实现重建二叉树