前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Cloud Security配置JWT和OAuth2的集成实现单点登录-示例

Spring Cloud Security配置JWT和OAuth2的集成实现单点登录-示例

原创
作者头像
堕落飞鸟
发布2023-04-14 07:40:15
2.7K0
发布2023-04-14 07:40:15
举报
文章被收录于专栏:飞鸟的专栏飞鸟的专栏

使用OAuth2和JWT来实现单点登录。下面是一个简单的示例:

  1. 用户在我们的应用程序中进行身份验证。
  2. 应用程序将向外部OAuth2认证服务器发送请求,以获取访问令牌。
  3. 认证服务器将验证用户的身份并返回访问令牌。
  4. 应用程序将使用访问令牌向资源服务器发送请求。
  5. 资源服务器将验证访问令牌,并返回受保护的数据。

这个示例展示了OAuth2和JWT如何协同工作来实现单点登录和授权。通过使用Spring Cloud Security,我们可以轻松地实现这些功能,并提供强大而灵活的安全性支持。演示如何使用Spring Cloud Security和Spring Cloud Gateway来实现基于JWT和OAuth2的单点登录:

创建一个授权服务器

我们将使用Spring Security OAuth2来创建我们的授权服务器,以下是一些关键的配置:

代码语言:javascript
复制
@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端点。以下是一些关键的配置:

代码语言:javascript
复制
@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认证。以下是一些关键的配置:

代码语言:javascript
复制
@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

我们还需要创建一个JwtTokenProvider bean,它包含了公钥和私钥,用于验证和签名JWT令牌。以下是一些关键的配置:

代码语言:javascript
复制
@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

最后,我们需要创建一个JwtAuthenticationFilter bean,该过滤器用于解析JWT令牌并将其转换为Spring Security Authentication对象。以下是一些关键的配置:

代码语言:javascript
复制
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令牌:

代码语言:javascript
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建一个授权服务器
  • 创建一个资源服务器
  • 创建一个网关
  • 创建一个JwtTokenProvider
  • 创建一个JwtAuthenticationFilter
  • 测试
相关产品与服务
多因子身份认证
多因子身份认证(Multi-factor Authentication Service,MFAS)的目的是建立一个多层次的防御体系,通过结合两种或三种认证因子(基于记忆的/基于持有物的/基于生物特征的认证因子)验证访问者的身份,使系统或资源更加安全。攻击者即使破解单一因子(如口令、人脸),应用的安全依然可以得到保障。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档