使用OAuth2和JWT来实现单点登录。下面是一个简单的示例:
这个示例展示了OAuth2和JWT如何协同工作来实现单点登录和授权。通过使用Spring Cloud Security,我们可以轻松地实现这些功能,并提供强大而灵活的安全性支持。演示如何使用Spring Cloud Security和Spring Cloud Gateway来实现基于JWT和OAuth2的单点登录:
我们将使用Spring Security OAuth2来创建我们的授权服务器,以下是一些关键的配置:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client-id")
.secret("client-secret")
.authorizedGrantTypes("password")
.scopes("read", "write")
.accessTokenValiditySeconds(3600);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("signing-key");
return converter;
}
}
我们配置了一个in-memory客户端详情服务,使用用户名和密码作为授权类型。我们还设置了访问令牌的过期时间为3600秒。我们还配置了AuthorizationServerEndpointsConfigurer,以使用tokenStore和accessTokenConverter进行配置,以处理JWT令牌。在这里,我们使用一个私钥来签名JWT令牌,以确保它没有被篡改。
接下来,我们将创建一个资源服务器,以确保只有经过身份验证的用户才能访问受保护的API端点。以下是一些关键的配置:
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**")
.authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey("verifier-key");
return converter;
}
}
这个配置将启用资源服务器并配置受保护的API端点,需要经过OAuth2认证才能访问。我们还配置了一个tokenStore bean和一个jwtAccessTokenConverter bean,用于处理JWT令牌。在这里,我们使用了一个公钥来验证JWT令牌,它将被用来验证JWT令牌签名。我们需要提供一个公钥,该公钥将被用于验证JWT签名。当使用JWT时,我们需要对JWT令牌进行签名,以确保它没有被篡改。
最后,我们将创建一个网关,以处理所有传入的请求,并根据需要进行OAuth2认证。以下是一些关键的配置:
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class GatewaySecurityConfig {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.authorizeExchange()
.pathMatchers("/api/auth/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer().jwt()
.and()
.addFilterAt(jwtAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.csrf().disable();
return http.build();
}
@Bean
public ReactiveJwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.jwtTokenProvider.getPublicKey()).build();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter(jwtDecoder());
}
}
这里我们使用了@EnableWebFluxSecurity注解来启用Spring Security,然后使用@EnableReactiveMethodSecurity注解来启用方法级安全性。我们使用了一个JwtTokenProvider bean,它包含了公钥和私钥,用于验证和签名JWT令牌。我们还创建了一个SecurityWebFilterChain bean,用于配置安全过滤器链。我们允许对授权端点进行匿名访问,其他所有端点都需要经过OAuth2认证。我们使用了oauth2ResourceServer().jwt()来配置JWT令牌的验证,然后添加了一个JwtAuthenticationFilter,该过滤器用于解析JWT令牌并将其转换为Spring Security Authentication对象。最后,我们禁用了CSRF保护,因为我们不需要在网关上使用它。
我们还需要创建一个JwtTokenProvider bean,它包含了公钥和私钥,用于验证和签名JWT令牌。以下是一些关键的配置:
@Component
public class JwtTokenProvider {
private final String publicKey;
private final String privateKey;
public JwtTokenProvider() throws Exception {
KeyPair keyPair = KeyPairUtils.generateKeyPair("RSA", 2048);
this.publicKey = new String(Base64.getEncoder().encode(keyPair.getPublic().getEncoded()));
this.privateKey = new String(Base64.getEncoder().encode(keyPair.getPrivate().getEncoded()));
}
public String getPublicKey() {
return publicKey;
}
public String getPrivateKey() {
return privateKey;
}
}
这里我们使用了一个KeyPairUtils工具类来生成RSA密钥对,并将其存储在一个JwtTokenProvider bean中。我们可以使用这个bean来获取公钥和私钥,然后将其用于验证和签名JWT令牌。
最后,我们需要创建一个JwtAuthenticationFilter bean,该过滤器用于解析JWT令牌并将其转换为Spring Security Authentication对象。以下是一些关键的配置:
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final ReactiveJwtDecoder jwtDecoder;
public JwtAuthenticationFilter(ReactiveJwtDecoder jwtDecoder) {
super("/api/**");
this.jwtDecoder = jwtDecoder;
}
@Override
public Mono<Authentication> attemptAuthentication(ServerWebExchange exchange) {
String authHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String authToken = authHeader.substring(7);
return this.jwtDecoder.decode(authToken)
.map((jwt) -> new UsernamePasswordAuthenticationToken(jwt.getSubject(), null, jwt.getClaim("authorities").asList(String.class)))
.cast(Authentication.class)
.onErrorResume((ex) -> Mono.empty());
} else {
return Mono.empty();
}
}
}
这里我们创建了一个JwtAuthenticationFilter bean,并继承了AbstractAuthenticationProcessingFilter。我们使用了一个ReactiveJwtDecoder bean来解析JWT令牌,并将其转换为Spring Security Authentication对象。我们实现了attemptAuthentication方法,该方法尝试解析JWT令牌,并使用它来创建一个新的UsernamePasswordAuthenticationToken对象。最后,我们使用了onErrorResume来处理任何错误,并返回一个空的Mono对象。
现在我们可以测试我们的应用程序,确保JWT和OAuth2在网关上正常工作。我们可以使用以下curl命令来发送一个JWT令牌:
curl --request GET \
--url http://localhost:8080/api/users \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
这个命令将向网关发送一个GET请求,使用Authorization头部包含一个JWT令牌。如果一切正常,网关将转发请求到正确的微服务,并使用JWT令牌进行身份验证。如果JWT令牌无效或过期,网关将返回一个401 Unauthorized响应。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。