• OAuth2:资源服务器


    基于@EnableResourceServer实现

    前面我们讲解了将我们的服务作为单点登陆应用直接实现单点登陆,那么现在我们如果是以第三方应用进行访问呢?这时我们就需要将我们的服务作为资源服务了,作为资源服务就不会再提供验证的过程,而是直接要求请求时携带Token,而验证过程我们这里就继续用Postman来完成,这才是我们常见的模式。

    一句话来说,跟上面相比,我们只需要携带Token就能访问这些资源服务器了,客户端被独立了出来,用于携带Token去访问这些服务。

    我们也只需要添加一个注解和少量配置即可:

    1. @EnableResourceServer
    2. @SpringBootApplication
    3. public class BookApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(BookApplication.class, args);
    6. }
    7. }

    配置中只需要:

    1. security:
    2. oauth2:
    3. client:
    4. #基操
    5. client-id: web
    6. client-secret: 654321
    7. resource:
    8. #因为资源服务器得验证你的Token是否有访问此资源的权限以及用户信息,所以只需要一个验证地址
    9. token-info-uri: http://localhost:8500/sso/oauth/check_token

    配置完成后,我们启动服务器,直接访问会发现:

    这是由于我们的请求头中没有携带Token信息,现在有两种方式可以访问此资源:

    • 在URL后面添加access_token请求参数,值为Token值
    • 在请求头中添加Authorization,值为Bearer +Token值

    我们先来试试看最简的一种:

    另一种我们需要使用Postman来完成:

    添加验证信息后,会帮助我们转换成请求头信息:

    这样我们就将资源服务器搭建完成了。

    我们接着来看如何对资源服务器进行深度自定义,我们可以为其编写一个配置类,比如我们现在希望用户授权了某个Scope才可以访问此服务:

    1. @Configuration
    2. public class ResourceConfiguration extends ResourceServerConfigurerAdapter { //继承此类进行高度自定义
    3. @Override
    4. public void configure(HttpSecurity http) throws Exception { //这里也有HttpSecurity对象,方便我们配置SpringSecurity
    5. http
    6. .authorizeRequests()
    7. .anyRequest().access("#oauth2.hasScope('lbwnb')"); //添加自定义规则
    8. //Token必须要有我们自定义scope授权才可以访问此资源
    9. }
    10. }

    可以看到当没有对应的scope授权时,那么会直接返回insufficient_scope错误:

    不知道各位是否有发现,实际上资源服务器完全没有必要将Security的信息保存在Session中了,因为现在只需要将Token告诉资源服务器,那么资源服务器就可以联系验证服务器,得到用户信息,就不需要使用之前的Session存储机制了,所以你会发现HttpSession中没有SPRING_SECURITY_CONTEXT,现在Security信息都是通过连接资源服务器获取。

    接着我们将所有的服务都

    但是还有一个问题没有解决,我们在使用RestTemplate进行服务间的远程调用时,会得到以下错误:

    实际上这是因为在服务调用时没有携带Token信息,我们得想个办法把用户传来的Token信息在进行远程调用时也携带上,因此,我们可以直接使用OAuth2RestTemplate,它会在请求其他服务时携带当前请求的Token信息。它继承自RestTemplate,这里我们直接定义一个Bean:

    1. @Configuration
    2. public class WebConfiguration {
    3. @Resource
    4. OAuth2ClientContext context;
    5. @Bean
    6. public OAuth2RestTemplate restTemplate(){
    7. return new OAuth2RestTemplate(new ClientCredentialsResourceDetails(), context);
    8. }
    9. }

    接着我们直接替换掉之前的RestTemplate即可:

    1. @Service
    2. public class BorrowServiceImpl implements BorrowService {
    3. @Resource
    4. BorrowMapper mapper;
    5. @Resource
    6. OAuth2RestTemplate template;
    7. @Override
    8. public UserBorrowDetail getUserBorrowDetailByUid(int uid) {
    9. List borrow = mapper.getBorrowsByUid(uid);
    10. User user = template.getForObject("http://localhost:8101/user/"+uid, User.class);
    11. //获取每一本书的详细信息
    12. List bookList = borrow
    13. .stream()
    14. .map(b -> template.getForObject("http://localhost:8201/book/"+b.getBid(), Book.class))
    15. .collect(Collectors.toList());
    16. return new UserBorrowDetail(user, bookList);
    17. }
    18. }

    可以看到服务成功调用了:

    现在我们来将Nacos加入,并通过Feign实现远程调用。

    依赖还是贴一下,不然找不到:

    1. <dependency>
    2. <groupId>com.alibaba.cloudgroupId>
    3. <artifactId>spring-cloud-alibaba-dependenciesartifactId>
    4. <version>2021.0.1.0version>
    5. <type>pomtype>
    6. <scope>importscope>
    7. dependency>

    1. <dependency>
    2. <groupId>com.alibaba.cloudgroupId>
    3. <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.springframework.cloudgroupId>
    7. <artifactId>spring-cloud-starter-loadbalancerartifactId>
    8. dependency>

    所有服务都已经注册成功了:

    接着我们配置一下借阅服务的负载均衡:

    1. @Configuration
    2. public class WebConfiguration {
    3. @Resource
    4. OAuth2ClientContext context;
    5. @LoadBalanced //和RestTemplate一样直接添加注解就行了
    6. @Bean
    7. public OAuth2RestTemplate restTemplate(){
    8. return new OAuth2RestTemplate(new ClientCredentialsResourceDetails(), context);
    9. }
    10. }

    现在我们来把它替换为Feign,老样子,两个客户端:

    1. @FeignClient("user-service")
    2. public interface UserClient {
    3. @RequestMapping("/user/{uid}")
    4. User getUserById(@PathVariable("uid") int uid);
    5. }

    1. @FeignClient("book-service")
    2. public interface BookClient {
    3. @RequestMapping("/book/{bid}")
    4. Book getBookById(@PathVariable("bid") int bid);
    5. }

    但是配置完成之后,又出现刚刚的问题了,OpenFeign也没有携带Token进行访问:

    那么怎么配置Feign携带Token访问呢?遇到这种问题直接去官方查:https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#oauth2-support,非常简单,两个配置就搞定:

    1. feign:
    2. oauth2:
    3. #开启Oauth支持,这样就会在请求头中携带Token了
    4. enabled: true
    5. #同时开启负载均衡支持
    6. load-balanced: true

    重启服务器,可以看到结果OK了:

    这样我们就成功将之前的三个服务作为资源服务器了,注意和我们上面的作为客户端是不同的,将服务直接作为客户端相当于只需要验证通过即可,并且还是要保存Session信息,相当于只是将登录流程换到统一的验证服务器上进行罢了。而将其作为资源服务器,那么就需要另外找客户端(可以是浏览器、小程序、App、第三方服务等)来访问,并且也是需要先进行验证然后再通过携带Token进行访问,这种模式是我们比较常见的模式。

  • 相关阅读:
    HDFS免重启挂载新磁盘
    八股整理(计网,os)
    05、GO数组与切片
    Efficient Shapelet Discovery for Time Series Classification(TKDE)
    react hook: useId
    新手小白学JAVA 泛型 Collection List Set
    【力扣刷题】无重复字符的最长子串
    ideal一键部署SpringBoot项目jar包到服务器
    HazelEngine 学习记录 - ShaderLibrary
    Fushion 360齿轮组制作教程
  • 原文地址:https://blog.csdn.net/Leon_Jinhai_Sun/article/details/126073177