【oauth2 客户端模式】Spring Authorization Server + Resource + Client 资源服务间的相互访问

1、概述

上一节中介绍了项目的搭建,并实现了授权码模式的访问。在上一节的基础上,再来实现客户端模式。【图文详解】搭建 Spring Authorization Server + Resource + Client 完整Demo

  • 用户通过客户端访问资源是 授权码模式
  • 微服务(资源)间的访问是 客户端模式;客户端模式下,只需要提供注册客户端的ID和密钥,就可以向授权服务器申请令牌,授权服务器核实ID和密钥后,会直接发放令牌,无须再认证/授权,特别适合项目内部模块间的调用。

在这里插入图片描述

2、授权服务器中注册新客户端

为了让请求资源的主体更加清晰,再注册一个客户端micro_service,专门供资源服务器之间的相互调用。也可以用原来客户端 my_client ,不过要在授权模式GrantType中添加 CLIENT_CREDENTIALS

  • 客户端模式直接返回token;不需要回调地址
  • 在授权服务器的授权服务配置类 AuthorizationServerConfiguration.java 中添加
    /**
     * 定义客户端(令牌申请方式:客户端模式)
     *
     * @param clientId 客户端ID
     * @return
     */
    private RegisteredClient createRegisteredClient(final String clientId) {
        // JWT(Json Web Token)的配置项:TTL、是否复用refrechToken等等
        TokenSettings tokenSettings = TokenSettings.builder()
                // 令牌存活时间:1年
                .accessTokenTimeToLive(Duration.ofDays(365))
                // 令牌不可以刷新
                //.reuseRefreshTokens(false)
                .build();
        // 客户端相关配置
        ClientSettings clientSettings = ClientSettings.builder()
                // 是否需要用户授权确认
                .requireAuthorizationConsent(false)
                .build();

        return RegisteredClient
                // 客户端ID和密码
                .withId(UUID.randomUUID().toString())
                //.withId(id)
                .clientId(clientId)
                //.clientSecret("{noop}123456")
                .clientSecret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123456"))
                // 客户端名称:可省略
                .clientName("micro_service")
                // 授权方法
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                // 授权模式
                // ---- 【客户端模式】
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                // 客户端模式直接返回token;不需要回调地址
                //.redirectUri("...")
                // 授权范围(当前客户端的角色)
                .scope("all")
                // JWT(Json Web Token)配置项
                .tokenSettings(tokenSettings)
                // 客户端配置项
                .clientSettings(clientSettings)
                .build();
    }
  • 修改注册方法:该方法仅注重功能,结构不够优雅,可以自行修改
    /**
     * 注册客户端
     *
     * @param jdbcTemplate 操作数据库
     * @return 客户端仓库
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        // ---------- 1、检查当前客户端是否已注册
        // 操作数据库对象
        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);

        /*
         客户端在数据库中记录的区别
         ------------------------------------------
         id:仅表示客户端在数据库中的这个记录
         client_id:唯一标示客户端;请求token时,以此作为客户端的账号
         client_name:客户端的名称,可以省略
         client_secret:密码
         */
        String clientId_1 = "my_client";
        String clientId_2 = "micro_service";
        // 查询客户端是否存在
        RegisteredClient registeredClient_1 = registeredClientRepository.findByClientId(clientId_1);
        RegisteredClient registeredClient_2 = registeredClientRepository.findByClientId(clientId_2);

        // ---------- 2、添加客户端
        // 数据库中没有
        if (registeredClient_1 == null) {
            registeredClient_1 = this.createRegisteredClientAuthorizationCode(clientId_1);
            registeredClientRepository.save(registeredClient_1);
        }
        // 数据库中没有
        if (registeredClient_2 == null) {
            registeredClient_2 = this.createRegisteredClient(clientId_2);
            registeredClientRepository.save(registeredClient_2);
        }

        // ---------- 3、返回客户端仓库
        return registeredClientRepository;
    }

3、资源服务器之间访问

3.1、案例说明

资源服务器B 调用 资源服务器A 中的资源;

具体:服务B/res1 --> 服务A/res2

服务A/res2 接口在前面用 my_client 是无法访问的;

在这里插入图片描述

当前 资源服务器B 无安全策略,可以直接访问

在这里插入图片描述

3.2、令牌申请与使用 处理逻辑

在这里插入图片描述

3.3、改造资源服务器B

  • 配置RestTemplat
@Configuration(proxyBeanMethods = false)
public class RestTemplateConfiguration {
    @Bean
    public RestTemplate oauth2ClientRestTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder.build();
    }
}
  • 修改API接口类
@RestController
public class ResourceController {
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/res1")
    public String getRes1(HttpServletRequest request) {
        // 调用资源服务器A中的资源res2
        return getServer("http://127.0.0.1:8001/res2", request);
        //return JSON.toJSONString(new Result(200, "服务B -> 资源1"));
    }

    @GetMapping("/res2")
    public String getRes2() {
        return JSON.toJSONString(new Result(200, "服务B -> 资源2"));
    }

    /**
     * 请求资源
     *
     * @param url
     * @param request
     * @return
     */
    private String getServer(String url,
                             HttpServletRequest request) {
        // ======== 1、从session中取token ========
        HttpSession session = request.getSession();
        String token = (String) session.getAttribute("micro-token");

        // ======== 2、请求token ========
        // 先查session中是否有token;session中没有
        if (StringUtils.isEmpty(token)) {
            // ===== 去认证中心申请 =====
            // 对id及密钥加密
            byte[] userpass = Base64.encodeBase64(("micro_service:123456").getBytes());
            String str = "";
            try {
                str = new String(userpass, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            // 请求头
            HttpHeaders headers1 = new HttpHeaders();
            // 组装请求头
            headers1.add("Authorization", "Basic " + str);
            // 请求体
            HttpEntity<Object> httpEntity1 = new HttpEntity<>(headers1);
            // 响应体
            ResponseEntity<String> responseEntity1 = null;
            try {
                // 发起申请令牌请求
                responseEntity1 = restTemplate.exchange("http://os.com:9000/oauth2/token?grant_type=client_credentials", HttpMethod.POST, httpEntity1, String.class);
            } catch (RestClientException e) {
                //
                System.out.println("令牌申请失败");
            }

            // 令牌申请成功
            if (responseEntity1 != null) {
                // 解析令牌
                // String t = JSON.parseObject(responseEntity1.getBody(), MyAuth.class).getAccess_token();
                Map<String, String> resMap = JSON.parseObject(responseEntity1.getBody(), HashMap.class);
                String t = resMap.get("access_token");
                // 存入session
                session.setAttribute("micro-token", t);
                // 赋于token变量
                token = t;
            }
        }

        // ======== 3、请求资源 ========
        // 请求头
        HttpHeaders headers2 = new HttpHeaders();
        // 组装请求头
        headers2.add("Authorization", "Bearer " + token);
        // 请求体
        HttpEntity<Object> httpEntity2 = new HttpEntity<>(headers2);
        // 响应体
        ResponseEntity<String> responseEntity2;
        try {
            // 发起访问资源请求
            responseEntity2 = restTemplate.exchange(url, HttpMethod.GET, httpEntity2, String.class);
        } catch (RestClientException e) {
            // 令牌失效(认证失效401) --> 清除session
            // e.getMessage() 信息格式:
            // 401 : "{"msg":"认证失败","uri":"/res2"}"   
            String str = e.getMessage();
            // 判断是否含有 401
            if(StringUtils.contains(str, "401")){
                // 如果有401,把session中 micro-token 的值设为空
                session.setAttribute("micro-token","");
            }            
            // 取两个括号中间的部分(包含两个括号)
            return str.substring(str.indexOf("{"), str.indexOf("}") + 1);
        }
        // 返回
        return responseEntity2.getBody();
    }
}

// 用于解析申请到的令牌数据
/*@Data
class MyAuth {
    private String access_token;
    private String scope;
    private String token_type;
    private long expires_in;
}*/

3.4、测试

  • 启动server、resource,无须启动client

在这里插入图片描述

  • 直接访问resource_b

在这里插入图片描述

3.5、资源服务器B添加安全策略

  • 添加依赖
<!-- 资源服务器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
  • 再次启动测试,已经无法直接访问;需要通过客户端去访问

在这里插入图片描述

3.6、继续改造资源服务器B

复制 资源服务器A 中配置策略到 资源服务器B 中来

  • 复制cer公钥文件
  • appliction.yml中添加jtw配置
# 自定义 jwt 配置(校验jwt)
jwt:
  cert-info:
    # 公钥证书存放位置
    public-key-location: myjks.cer
  claims:
    # 令牌的鉴发方:即授权服务器的地址
    issuer: http://os.com:9000
  • 复制 oauth2 配置包;如下图

在这里插入图片描述

在这里插入图片描述

3.7、客户端client访问测试

  • 用 maven 的 clean 清理项目
  • 启动 server、resource (a和b)、client

在这里插入图片描述

  • 登录客户端

在这里插入图片描述

  • 访问资源服务A

在这里插入图片描述

在这里插入图片描述

  • 访问资源服务B

在这里插入图片描述
在这里插入图片描述

如果需要 资源服务器A 调用 B 中资源;可以把 B 中的实现逻辑复制过去就行。

后期会把资源服务器中的公共部分抽离出来,制成starter…

2023-4-16:用starter实现Oauth2中资源服务的统一配置

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
spring-security-oauth2-authorization-server是一个基于Spring Security的OAuth 2.0授权服务器,用于为客户端提供安全的访问资源的授权服务。您可以按照以下步骤使用该库: 1. 在您的Spring Boot项目中添加以下依赖项: ```xml <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.1.0.RELEASE</version> </dependency> ``` 2. 配置您的授权服务器 在您的Spring Boot应用程序中创建一个配置类,并使用@EnableAuthorizationServer注释启用授权服务器。例如: ```java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { // ... } ``` 3. 配置您的客户端 您可以使用ClientDetailsServiceConfigurer配置您的客户端。例如: ```java @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client") .secret("{noop}secret") .authorizedGrantTypes("authorization_code", "refresh_token") .scopes("read", "write") .autoApprove(true) .redirectUris("http://localhost:8080/login/oauth2/code/") .and() .withClient("resource") .secret("{noop}secret") .authorizedGrantTypes("client_credentials") .scopes("read"); } ``` 上面的代码将在内存中配置两个客户端:一个用于授权码授权,另一个用于客户端凭证授权。 4. 配置您的用户 您可以使用UserDetailsServiceConfigurer配置您的用户。例如: ```java @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } ``` 上面的代码将使用authenticationManager进行身份验证,并使用userDetailsService获取用户信息。 5. 启动您的应用程序 现在,您可以启动您的应用程序,并访问http://localhost:8080/oauth/authorize以获取授权码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

土味儿~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值