• Web应用接入OAuth2


    上一篇博客介绍了OAuth2的基础概念,此篇博客将介绍对于web应用而言如何介入OAuth2,这里需要同时开启之前OAuth2的授权码模式的应用作为授权服务器,另外开启新的客户端应用,客户端应用代码在这里(备注:源码基本来源于bobo老师在网上公开的的demo代码)。

    客户端服务启动在9001端口,启动后,会弹出altert的dialog框,这里输入在授权服务器的application.properties中设置的用户名和密码。

    输入正确的用户名、密码后会显示OAuth Approve界面,Approve后,获取到接口信息并显示在页面上。

    上面是整个演示的效果,接下来看看代码中国呢是如何实现的。 首先来看看SecurityConfiguration配置类,这个类通过继承WebSecurityConfigurerAdapter来控制哪些请求必须通过授权才能访问的资源。从这里的配置可以看到/和/index.html页面可以直接访问,其他URL必须授权后才能访问。

    对于OAuth2而言,核心就是获取Token和使用Token,在客户端应用中通过RestTemplate获取授权码和token,然后带上token获取资源服务器的相关接口信息并显示在页面上。下面是获取Token并存入数据库的代码。这里通过SecurityContextHolder来获取当前认证的用户的上下文信息。通过日志打印可以看到ClientUser包含如下信息,显示的用户名和密码是在登陆框中输入的用户名和密码。

    获取token的具体逻辑封装在AuthorizationCodeTokenService中,和前面通过post获取的过程一样,这里知识通过RestTemplate来获取而已。通过打印日志可以看到发送的获取token的POST请求和返回的token信息,如下所示

    总结而言,整个代码实现的关键步骤有两部分:

    1.通过继承 WebSecurityConfigurerAdapter来配置资源控制器的控制范围

    2.通过RestTemplate来调用API获取code和token

    上面用的RestTemplate,这里做一下简要介绍,RestTemplate是Spring提供的用于访问 Rest 服务的客户端库。下面是一些RestTemplate使用的小例子。

    1. //1. 简单Get请求
    2. String result = restTemplate.getForObject(rootUrl + "get1?para=my", String.class);
    3. System.out.println("简单Get请求:" + result);
    4. //2. 简单带路径变量参数Get请求
    5. result = restTemplate.getForObject(rootUrl + "get2/{1}", String.class, 239);
    6. System.out.println("简单带路径变量参数Get请求:" + result);
    7. //3. 返回对象Get请求(注意需包含compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
    8. ResponseEntity<Test1> responseEntity = restTemplate.getForEntity(rootUrl + "get3/339", Test1.class);
    9. System.out.println("返回:" + responseEntity);
    10. System.out.println("返回对象Get请求:" + responseEntity.getBody());
    11. //4. 设置header的Get请求
    12. HttpHeaders headers = new HttpHeaders();
    13. headers.add("token", "123");
    14. ResponseEntity<String> response = restTemplate.exchange(rootUrl + "get4", HttpMethod.GET, new HttpEntity<String>(headers), String.class);
    15. System.out.println("设置header的Get请求:" + response.getBody());
    16. //5. Post对象
    17. Test1 test1 = new Test1();
    18. test1.name = "buter";
    19. test1.sex = 1;
    20. result = restTemplate.postForObject(rootUrl + "post1", test1, String.class);
    21. System.out.println("Post对象:" + result);
    22. //6. 带header的Post数据请求
    23. response = restTemplate.postForEntity(rootUrl + "post2", new HttpEntity<Test1>(test1, headers), String.class);
    24. System.out.println("带header的Post数据请求:" + response.getBody());
    25. //7. 带header的Put数据请求
    26. //无返回值
    27. restTemplate.put(rootUrl + "put1", new HttpEntity<Test1>(test1, headers));
    28. //带返回值
    29. response = restTemplate.exchange(rootUrl + "put1", HttpMethod.PUT, new HttpEntity<Test1>(test1, headers), String.class);
    30. System.out.println("带header的Put数据请求:" + response.getBody());
    31. //8. del请求
    32. //无返回值
    33. restTemplate.delete(rootUrl + "del1/{1}", 332);
    34. //带返回值
    35. response = restTemplate.exchange(rootUrl + "del1/332", HttpMethod.DELETE, null, String.class);
    36. System.out.println("del数据请求:" + response.getBody());

    getForObject和getForEntity 这两个方法都是采用get请求去获取数据,只是getForObject()比getForEntity()多包含了将HTTP转成POJO的功能。postForObject和postForEntity是发送Post请求,于Get请求相比较,Post请求需要构造request body类,也就是HttpEntity。下面是通过MultiValueMap构造body的小例子。

    1. String url = "http://www.xxxx.com";
    2. HttpHeaders headers = new HttpHeaders();
    3. headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    4. MultiValueMap<String, String> map= new LinkedMultiValueMap<>();
    5. map.add("userId", "3212xxxxxxxxxxxxxx");
    6. HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
    7. ResponseEntity<String> response = restTemplate.postForEntity( url, request , String.class );
    8. System.out.println(response.getBody());

    exchange()方法跟上面的方法不同之处在于它可以指定请求的HTTP类型。在前面的客户端代码中也通过MultiValueMap构造获取token的请求的。通过这个例子也实现了授权服务器和资源服务器的分离,另外,授权的token等信息也存入了数据库。

    前面的例子里面refresh_token是空,且token过期时间是默认的43199(12小时),如果要添加token过期时间和生成refresh_token则修改授权服务器中的配置即可。

    通过打印日志可以看到token的过期时间已经修改,且会生成refresh_token.

    有了refresh_token,当从数据库获取token时,先判断有效时间,如果有效时间小于当前时间,那么就需要通过refresh_token获取新的token并存入数据库。

    这里再补充解释下如何通过数据库存储token等信息,因为spring-security中就提供了UserDetails对象,所以第一步是implement userDetails,里面主要包括getClientUser等方法。

    接着还需要定义ClientUser对象。因为getClientUser获取的就是ClientUser对象信息。这里存入数据库是采用JDBC的方式,所以ClientUser是一个Entity,内容如下:

    除了对象定义,还创建了UserRepository class,负责对数据进行增删改查,因为有很多默认方法,例如通过save就可以存入数据库,故这里只自定义了findByUsername方法。

    以上就是对web应用接入OAuth2的介绍。

  • 相关阅读:
    CCF CSP认证 历年题目自练Day24
    一体化研发协作赋能平台:Apipost
    AndroidAuto 解决连接手机启动AA屏闪一下问题
    基于Java的家电销售网站管理系统设计与实现(源码+lw+部署文档+讲解等)
    devops 流程整理
    第一章 信息化和信息系统
    2022 年全年 Java 岗面试题总结 + 一线互联网大厂 Java 岗面经 / 面试题总结!
    【Leetcode】2471. Minimum Number of Operations to Sort a Binary Tree by Level
    Vue3中Compositions API的使用(二)
    网络解析(二)
  • 原文地址:https://blog.csdn.net/qiaotl/article/details/126467867