Spring Boot整合Spring Authorization Server(四)——整合网关和资源服务,添加Redis,前后端分离

前言

之前写了认证服务,实际生产中都是前后端分离的项目,现在来搭建客户端和资源服务器。

代码

客户端代码

1. 新建module

2. 添加TestController

/**
 * 测试接口
 *
 * @author lxq
 */
@RestController
public class TestController {

    @GetMapping("/test01")
    @PreAuthorize("hasAuthority('message.read')")
    public String test01() {
        Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
        return "test01";
    }

    @GetMapping("/test02")
    @PreAuthorize("hasAuthority('SCOPE_message.write')")
    public String test02() {
        return "test02";
    }

    @GetMapping("/app")
    @PreAuthorize("hasAuthority('app')")
    public String app() {
        return "app";
    }

}

2. 添加application.yml

server:
  # 修改端口
  port: 8000

spring:
  security:
    oauth2:
      client:
        provider:
          # 认证提供者,自定义名称
          custom-issuer:
            # Token签发地址(认证服务地址)
            issuer-uri: http://c.example.com:8080
            # 获取用户信息的地址,默认的/userinfo端点需要IdToken获取,为避免麻烦自定一个用户信息接口
            user-info-uri: ${spring.security.oauth2.client.provider.custom-issuer.issuer-uri}/user
        registration:
          messaging-client-oidc:
            # oauth认证提供者配置,和上边配置的认证提供者关联起来
            provider: custom-issuer
            # 客户端名称,自定义
            client-name: message-client
            # 客户端id,从认证服务申请的客户端id
            client-id: messaging-client
            # 客户端秘钥
            client-secret: 123456
            # 客户端认证方式
            client-authentication-method: client_secret_basic
            # 获取Token使用的授权流程
            authorization-grant-type: authorization_code
            # 回调地址,这里设置为Spring Security Client默认实现使用code换取token的接口
            redirect-uri: http://127.0.0.1:8000/login/oauth2/code/messaging-client-oidc
            scope:
              - message.read
              - message.write

3. pom.xml

<dependencies>
        <!-- 引入bootstrap依赖,不引入这个依赖是无法使用bootstrap配置文件的 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

客户端的代码相对比较简单,spring boot 帮我们封装好了。

资源端代码

1. ResourceServerConfig

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class ResourceServerConfig {

    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> {
                    // 下边一行是放行接口的配置,被放行的接口上不能有权限注解,e.g. @PreAuthorize,否则无效
                    // .requestMatchers("/test02").permitAll()
                    authorize.anyRequest().authenticated();
                })
                .oauth2ResourceServer(oauth2 -> {
                    oauth2
                            // 可在此处添加自定义解析设置
                            .jwt(Customizer.withDefaults())
                            // 添加未携带token和权限不足异常处理
                            .accessDeniedHandler(SecurityUtils::exceptionHandler)
                            .authenticationEntryPoint(SecurityUtils::exceptionHandler);
                });
        return http.build();
    }

    /**
     * 自定义jwt解析器,设置解析出来的权限信息的前缀与在jwt中的key
     * 添加自定义解析token配置,注入一个JwtAuthenticationConverter
     *
     * @return jwt解析器 JwtAuthenticationConverter
     */
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        // 设置解析权限信息的前缀,设置为空是去掉前缀
        grantedAuthoritiesConverter.setAuthorityPrefix("");
        // 设置权限信息在jwt claims中的key
        grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }

}

2. SecurityUtils

public class SecurityUtils {

    private SecurityUtils() {
        // 禁止实例化工具类
        throw new UnsupportedOperationException("Utility classes cannot be instantiated.");
    }

    /**
     * 认证与鉴权失败回调
     *
     * @param request  当前请求
     * @param response 当前响应
     * @param e        具体的异常信息
     */
    public static void exceptionHandler(HttpServletRequest request, HttpServletResponse response, Throwable e) {
        Map<String, String> parameters = getErrorParameter(request, response, e);
        String wwwAuthenticate = computeWwwAuthenticateHeaderValue(parameters);
        response.addHeader(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate);
        try {
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.getWriter().write(JsonUtils.objectCovertToJson(parameters));
            response.getWriter().flush();
        } catch (IOException ex) {
            log.error("写回错误信息失败", e);
        }
    }

    /**
     * 获取异常信息map
     *
     * @param request  当前请求
     * @param response 当前响应
     * @param e        本次异常具体的异常实例
     * @return 异常信息map
     */
    private static Map<String, String> getErrorParameter(HttpServletRequest request, HttpServletResponse response, Throwable e) {
        Map<String, String> parameters = new LinkedHashMap<>();
        if (request.getUserPrincipal() instanceof AbstractOAuth2TokenAuthenticationToken) {
            // 权限不足
            parameters.put("error", BearerTokenErrorCodes.INSUFFICIENT_SCOPE);
            parameters.put("error_description",
                    "The request requires higher privileges than provided by the access token.");
            parameters.put("error_uri", "https://tools.ietf.org/html/rfc6750#section-3.1");
            response.setStatus(HttpStatus.FORBIDDEN.value());
        }
        if (e instanceof OAuth2AuthenticationException authenticationException) {
            // jwt异常,e.g. jwt超过有效期、jwt无效等
            OAuth2Error error = authenticationException.getError();
            parameters.put("error", error.getErrorCode());
            if (StringUtils.hasText(error.getUri())) {
                parameters.put("er
<think>好的,我需要解决用户的问题:如何将Spring Authorization Server官网提供的SPA示例应用于分布式微服务架构。首先,我得了解用户提到的SPA示例的基本结构以及分布式微服务的需求。 用户提供的引用[1]提到了Spring CloudNetflix OSS,这些是构建分布式应用的关键工具。而引用[2]指出Spring Authorization Server是为了填补OAuth2授权服务器的空缺。因此,用户可能希望在微服务架构中整合授权服务器,确保各服务的安全通信。 首先,SPA示例通常是单页应用,使用OAuth 2.0进行身份验证。在微服务架构中,可能有多个独立服务需要安全地通信。需要将授权服务器作为独立的服务部署,这样其他微服务可以统一从这里获取令牌。 接下来,需要考虑如何配置资源服务器。每个微服务作为资源服务器,需要验证JWT令牌。这涉及到在Spring Security配置中设置JWT解析权限验证。同时,SPA前端需要与授权服务器交互,获取令牌后访问各个微服务。 API网关也是一个重要部分,用于路由请求、统一认证限流。比如使用Spring Cloud Gateway,可以在网关层处理安全验证,避免在每个微服务中重复配置。 服务间通信的安全性也不能忽视。微服务之间的调用需要使用令牌,可以通过Feign Client传递Authorization头,确保内部调用的合法性。 另外,分布式会话管理可能需要Redis这样的工具来存储会话信息,保证无状态的服务架构。同时,跨域问题(CORS)需要正确配置,特别是在前端SPA调用不同微服务时。 用户可能遇到的挑战包括令牌的传递、网关的配置、服务发现的集成等。需要确保所有微服务都能正确识别验证由授权服务器签发的令牌,并且网络架构支持服务间的安全通信。 最后,测试监控也是关键。确保整个系统在分布式环境下的性能安全性,可能需要集成Spring Boot Actuator监控工具,如PrometheusGrafana。 总结来说,步骤包括:独立部署授权服务器、配置资源服务器、设置API网关、处理服务间通信、管理会话CORS,以及测试监控。需要参考Spring官方文档示例,确保各组件正确集成。</think>要将Spring Authorization Server提供的SPA(单页应用)示例应用于分布式微服务架构,需结合OAuth 2.0授权流程、微服务间的安全通信以及服务治理组件。以下是关键步骤注意事项: --- ### 1. **授权服务器的独立部署** 将Spring Authorization Server作为独立的微服务部署,承担OAuth 2.0授权服务器角色[^2]: - **配置示例**:参考官方SPA示例中的`AuthorizationServerConfig`,确保支持`authorization_code`流程PKCE(Proof Key for Code Exchange)。 - **服务注册**:集成服务发现工具(如Spring Cloud Netflix Eureka),使其他微服务能动态定位授权服务器。 --- ### 2. **资源服务器(微服务)配置** 每个微服务作为资源服务器,需验证JWT令牌: ```java @Configuration @EnableWebSecurity public class ResourceServerConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(auth -> auth.anyRequest().authenticated()) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); } } ``` - **依赖**:添加`spring-security-oauth2-resource-server`以支持JWT解析。 --- ### 3. **SPA与微服务的安全交互** SPA前端通过OAuth 2.0与授权服务器交互,获取访问令牌后调用微服务: - **前端配置**:使用`@okta/okta-auth-js`等库实现PKCE流程。 - **反向代理**:通过API网关(如Spring Cloud Gateway)路由SPA请求,避免CORS问题。 --- ### 4. **API网关的作用** 网关处理统一的安全逻辑: ```yaml # Spring Cloud Gateway路由配置 spring: cloud: gateway: routes: - id: auth-server uri: lb://auth-server predicates: - Path=/oauth2/** - id: service-a uri: lb://service-a predicates: - Path=/api/** filters: - TokenRelay # 传递令牌到下游服务 ``` --- ### 5. **服务间通信的安全** 微服务间调用需传递JWT令牌: - **OpenFeign集成**:通过拦截器自动添加令牌头: ```java public class FeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication instanceof JwtAuthenticationToken) { String token = ((JwtAuthenticationToken) authentication).getToken().getTokenValue(); template.header("Authorization", "Bearer " + token); } } } ``` --- ### 6. **分布式会话管理** 若需要会话状态,需使用分布式存储(如Redis): ```properties # 配置Spring Session使用Redis spring.session.store-type=redis spring.redis.host=redis-server ``` --- ### 注意事项 - **跨域问题(CORS)**:在网关或各个微服务中配置允许SPA域名的跨域请求。 - **令牌增强**:在授权服务器中自定义JWT声明(如用户角色),供微服务鉴权使用。 - **监控与日志**:集成Spring Boot Actuator分布式追踪工具(如Sleuth/Zipkin)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值