我已经使用 Spring Security OAuth 2.0 和 JWT 为我的身份验证服务器实现了密码授予。我通过扩展 AuthorizationServerConfigurerAdapter 创建服务器。我可以给服务器一个用户名/密码并取回一个 JWT 令牌。我使用 ResourceConfiguration 来扩展其他服务上的 ResourceServerConfigurerAdapter 类,以在任何时候进行 Web 服务调用时验证 JWT。下面粘贴的是我的代码。
我想让我的 Native Mobile 应用程序能够使用 Facebook、Gmail、Linked 等登录……我想按照所附文章中的步骤进行操作:
https://ole.michelsen.dk/blog/social-signin-spa-jwt-server.html
- 用户在移动端执行 OAuth 舞蹈并向我发送他们正在使用的社交服务的访问令牌。
- 我收到访问令牌,并使用它来调用相应的社交服务。
- 如果 Token 有效,则用户无法登录,会抛出错误。
- 如果令牌有效,我会从社交服务中获取用户详细信息,并使用它在我的数据存储中创建一个“社交用户”,该“社交用户”将与现有或新的系统用户相关联。
- 一旦使用社交用户创建系统用户,或者将社交用户绑定到现有系统用户,JWT 令牌就会被发送回移动应用程序。
- 这个 JWT 令牌应该类似于 Spring Security OAuth 2.0 密码授予流程创建的 JWT 令牌,并且在授权用户时应该被我的 ResourceServerConfiguration 接受。
我在网上搜索了符合我标准的解决方案,但我找不到任何解决方案。我的要求合理吗?有没有更简单的方法来做到这一点,允许用户通过用户名/密码和社交媒体身份验证登录,同时取回 JWT 令牌。我发现的一个示例使用 OAuth2ClientAuthenticationProcessingFilter 来执行我上面提到的逻辑,但我不知道 OAuth2ClientAuthenticationProcessingFilter 是如何工作的,也找不到任何文档。如果有人不得不使用类似的技术堆栈来实现类似的要求,请告诉我您使用什么技术来实现此解决方案。
在身份验证服务器上:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Value("${clientId}")
private String clientId;
@Value("${clientSecret}")
private String clientSecret;
@Value("${jwtSigningKey}")
private String jwtSigningKey;
@Value("${accessTokenValiditySeconds}")
private String accessTokenValiditySeconds;
@Value("${refreshTokenValiditySeconds}")
private String refreshTokenValiditySeconds;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey(jwtSigningKey);
return accessTokenConverter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new JWTTokenEnhancer();
}
// Added for refresh token capability
@Bean
@Primary
public DefaultTokenServices tokenServices(){
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(clientSecret)
.authorizedGrantTypes("password", "refresh_token")
.scopes("read","write")
.accessTokenValiditySeconds(Integer.valueOf(accessTokenValiditySeconds)) // 1 hour
.refreshTokenValiditySeconds(Integer.valueOf(refreshTokenValiditySeconds));// 30 days
}
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
// Add the JWT token enhancer to the token enhancer chain then add to endpoints
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
.accessTokenConverter(accessTokenConverter());
}
@Override
public void configure(final AuthorizationServerSecurityConfigurer securityConfigurer) throws Exception {
securityConfigurer.checkTokenAccess("permitAll()");
super.configure(securityConfigurer);
}
}
public class JWTTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(final OAuth2AccessToken accessToken,
final OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
// Get the user detail implementation
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
// add userId and roles to the JWT token
additionalInfo.put("user_id", userDetails.getUserId());
additionalInfo.put("email", userDetails.getEmail());
additionalInfo.put("user_name", userDetails.getUsername());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
在每个微服务上:
@Configuration
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@ComponentScan("com.test.security")
@Profile({"prod", "qa", "dev"})
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Value("${jwtSigningKey}")
private String jwtSigningKey;
// http security concerns
@Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/hystrix/**").permitAll()
.antMatchers("/admin/hystrix.stream/**").permitAll()
.antMatchers("/admin/health/**").permitAll()
.antMatchers("/admin/info/**").permitAll()
.antMatchers("/admin/**").authenticated()
.antMatchers("/greetings/**").authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable();
}
@Override
public void configure(final ResourceServerSecurityConfigurer config) {
config.tokenServices(tokenServices());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey(jwtSigningKey);
return accessTokenConverter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
// Added for refresh token capability
@Bean
@Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
}