JA Plus 开发者文档 JA Plus 开发者文档
首页
📖 白皮书 (opens new window)
  • 名词解释
  • 快速开始

    • 使用jap-simple
    • 使用jap-social
    • 使用jap-oauth2
    • 使用jap-oidc
    • 使用jap-sso
    • 使用jap-mfa
    • 使用jap-http-api
    • 使用jap-ldap
    • 错误代码
  • IDS

    • 简介
    • 快速开始
    • 自定义scope
    • 自定义登录页面
    • 自定义确认授权页面
    • 自定义缓存
    • 自定义Token加密密钥
    • 使用PKCE模式
    • 自动授权
    • 错误代码
  • starter

    • jap-spring-boot-starter
    • jap-simple-spring-boot-starter
    • jap-social-spring-boot-starter
    • jap-oauth2-spring-boot-starter
    • jap-oidc-spring-boot-starter
  • jap-ids的demo
  • 前后端分离架构中使用JAP
  • SpringBoot中使用JAP
  • 问题反馈
  • 项目问题
  • 异常问题
  • 功能问题
  • 数据看板🔥
  • 贡献指南
  • 行为准则
  • 用户权益
  • 贡献者们
  • 社区配套 (opens new window)
  • 教程
  • 投稿
  • 资讯
  • 关于
  • 友情链接
  • 捐赠列表
  • 其他开源
  • 更新记录
收藏
GitHub (opens new window)

FuJie Team

以开源的形式赋能开发者. Just auth into any app.
首页
📖 白皮书 (opens new window)
  • 名词解释
  • 快速开始

    • 使用jap-simple
    • 使用jap-social
    • 使用jap-oauth2
    • 使用jap-oidc
    • 使用jap-sso
    • 使用jap-mfa
    • 使用jap-http-api
    • 使用jap-ldap
    • 错误代码
  • IDS

    • 简介
    • 快速开始
    • 自定义scope
    • 自定义登录页面
    • 自定义确认授权页面
    • 自定义缓存
    • 自定义Token加密密钥
    • 使用PKCE模式
    • 自动授权
    • 错误代码
  • starter

    • jap-spring-boot-starter
    • jap-simple-spring-boot-starter
    • jap-social-spring-boot-starter
    • jap-oauth2-spring-boot-starter
    • jap-oidc-spring-boot-starter
  • jap-ids的demo
  • 前后端分离架构中使用JAP
  • SpringBoot中使用JAP
  • 问题反馈
  • 项目问题
  • 异常问题
  • 功能问题
  • 数据看板🔥
  • 贡献指南
  • 行为准则
  • 用户权益
  • 贡献者们
  • 社区配套 (opens new window)
  • 教程
  • 投稿
  • 资讯
  • 关于
  • 友情链接
  • 捐赠列表
  • 其他开源
  • 更新记录
收藏
GitHub (opens new window)
  • 教程

  • 投稿

    • 博客投稿栏目说明
    • 经验分享

      • 项目经验分享:在 JustAuth Plus 中添加 HTTP API 的登陆方式
      • 项目经验分享:开发 JuatAuth Plus 的 springboot starter 依赖包
        • 自动装配与@ConditionalOnBean
          • @ConditionalOnBean为什么失效?
        • pom.xml中<optional>和<scope>元素
        • 利用RequestContextHolder
        • Oauth2授权策略
        • maven的lifecycle和plugin
          • default lifecycle
          • Available Plugins
      • 经验总结:关于为 JAP 开发不同语言的 Demo 的总结
    • 框架集成

  • 资讯

  • 博客
  • 投稿
  • 经验分享
刘浩东
2021-09-26

项目经验分享:开发 JuatAuth Plus 的 springboot starter 依赖包

# 项目经验分享:开发JuatAuth Plus的Springboot starter 依赖包

本项目在JustAuth Plus (opens new window)授权框架(简称jap)的基础上,开发其spring boot starter,包括以下六个模块:

  • 开发jap提供的四种授权策略的对应的spring boot starter模块:jap-simple-spring-boot-starter、jap-oauth2-spring-boot-starter、jap-social-spring-boot-starter、jap-oidc-spring-boot-starter,并将它们插件化,实现按需引入。也就是说,你的web应用若需要相应模块,才添加对应的maven坐标;
  • jap-spring-boot-starter。提供一个对四种授权策略高度封装的类:JapTemplate;
  • 为上述模块抽取出的基础模块jap-common,包括上述模块都会用到的一些自动配置类和和工具类等;

除了以上模块之外,为了支持redis作为缓存数据源,也将spring-boot-starter-redis以插件化的形式引入。

模块依赖

本次分享主要涉及以下几点:

Spring Boot的自动装配过程,并与@Configurarion相区别,以及@Conditional系列注解的使用;

将四种授权策略抽取为单独的starter模块,并为了实现四种授权策略的插件化,需要掌握的maven中<optional>和<scope>标签;

充分利用Spring的web支持,采用RequestContextHolder获取当前线程请求上下文,进一步封装JustAuth Plus框架的授权过程;

打包并发布jar包到maven仓库,涉及maven的lifecycle和plugin。

# 自动装配与@ConditionalOnBean

若不熟悉Spring Boot的自动装配使用和原理,可以阅读这篇文章:Spring Boot面试杀手锏————自动配置原理 (opens new window),这里主要分享自动装配与@Configuration的差别。

# @ConditionalOnBean为什么失效?

以jap-social-spring-boot-starter模块为例,其中的SocialAutoConfiguration作为自动配置(auto-configuration)类会创建SocialStrategy的bean。在jap-spring-boot-starter模块中SocialOperations的bean需要只在SocialStrategy 的bean存在的情况下创建。

SocialAutoConfiguration:

@Configuration
public class SocialAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public SocialStrategy socialStrategy(ApplicationContext applicationContext,
                                         JapBasicProperties basicProperties,
                                         SocialProperties socialProperties,
                                         AuthStateCache authStateCache,
                                         JapCache japCache){
        //......略
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

OperationAutoConfiguration$Social:

@Configuration
@ConditionalOnBean({SocialStrategy.class})//在SocialStrategy的bean存在的情况下才创建SocialOperations bean
static class Social{
    @Bean
    @ConditionalOnMissingBean
    public SocialOperations socialOperations(SocialStrategy socialStrategy, 
                                             SocialProperties socialProperties){
        //......略
    }
}
1
2
3
4
5
6
7
8
9
10

在debug后发现,其实存在SocialStrategy的bean,但SocialOperations的实例并不会因此创建,看起来@ConditionalOnBean失效了?通过debug发现,OperationAutoConfiguration$Social居然在SocialAutoConfiguration之前被加载^1:

image-20210919160951490

原来不是@ConditionalOnBean失效了,而是配置类的加载顺序有问题。所以,现在需要解决的问题是,如何控制配置类的加载顺序?

想到了@AutoConfigureAfter,但debug后结果还是一样,似乎这个注解也是无效的,似乎@COnfiguration注解的配置类并不能控制加载顺序?

在StackOverflow上找到了解决方案:Spring annotation conditionalOnBean not working (opens new window),回答非常精彩:

The javadoc for @ConditionalOnBean describes it as:

Conditional that only matches when the specified bean classes and/or names are already contained in the BeanFactory.

In this case, the key part is "already contained in the BeanFactory". Your own configuration classes are considered before any auto-configuration classes. This means that the auto-configuration of the MetricsEndpoint bean hasn't happened by the time that your own configuration is checking for its existence and, as a result, your MetricsFormatEndpoint bean isn't created.

One approach to take would be to create your own auto-configuration class (opens new window) for your MetricsFormatEndpoint bean and annotate it with @AutoConfigureAfter(EndpointAutoConfiguration.class). That will ensure that its conditions are evaluated after the MetricsEndpoint bean has been defined.

加粗的两点很关键。在此之前我一直认为有@Configuration注解的配置类和写入META-INF/spring.factories中的配置类都一样,以为是Spring Boot提供的实现自动配置的两种殊途同归的方式而已。然而@Configuration早在Spring时期就有,应被称作Externalized Configuration (opens new window),也即上边引用中加粗的***Your own configuration classes***。而META-INF/spring.factories是Spring Boot提供的,这才是auto-configuration本身。

访问上面的链接create your own auto-configuration class (opens new window),在7.10.2节里有个note:

Auto-configurations must be loaded that way only. Make sure that they are defined in a specific package space and that they are never the target of component scanning. Furthermore, auto-configuration classes should not enable component scanning to find additional components. Specific @Imports should be used instead.

自动配置类只能被 那种 方式加载。(后面的略)

第一句话非常关键,“那种”方式指的就是在META-INF/spring.factories文件中指定配置类,而不是采用@Configuration注解。

了解了两者的区别后,我将所有自动配置类的@Configuration注解去掉,让它们只通过auto-configuration的方式被加载,再使用上@AutoConfigureAfter注解,debug后发现配置类的加载顺序得到了控制,所有策略的自动配置类XxxAutoConfiguration都优先于OperationAutoConfiguration$Xxx:

image-20210919164216084

# pom.xml中<optional>和<scope>元素

以本项目开发的jap-common和jap-spring-boot-starter模块的pom.xml文件中引入的部分maven依赖为例,主要区别<scope>provided</scope>和<optional>true</optional>。

首先假设有一个名叫***demo***的maven项目引入了jap-spring-boot-starter依赖:

<dependency>
    <groupId>com.fujieid.jap.spring.boot</groupId>
    <artifactId>jap-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
1
2
3
4
5

而jap-spring-boot-starter中有jap-common依赖,因此通过依赖传递,demo项目中也间接引入了jap-common模块:

image-20210919194030355

在jap-common的pom.xml中,有:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.5.4</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.5.4</version>
    <scope>provided</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12

这两个依赖配置中都有<scope>provided</scope>元素,这样做想达到的目的是,在demo项目 中引入了jap-common模块的maven坐标,那它作为jap-common的子项目,也一定是一个Spring Boot项目,则一定会使用spring-boot-starter和spring-boot-starter-web。因此,这两个依赖没有必要参与依赖传递,应该由demo项目自己提供这两个依赖。这样做的考量大概有两点,一是为了避免jar包冲突,避免一些冗余的重复依赖;二是为了让发布的jar包或者war包”瘦身“,此时的jap-common打包时是不会包含spring-boot-starter和spring-boot-starter-web的。

接下来考察<optional>true</optional>元素,在jap-spring-boot-starter中:

<dependency>
    <groupId>com.fujieid.jap.spring.boot</groupId>
    <artifactId>jap-simple-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.fujieid.jap.spring.boot</groupId>
    <artifactId>jap-social-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.fujieid.jap.spring.boot</groupId>
    <artifactId>jap-oauth2-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.fujieid.jap.spring.boot</groupId>
    <artifactId>jap-oidc-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <optional>true</optional>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

可以发现四种策略的starter模块都有<optional>true</optional>元素,与<scope>provided</scope>相似的地方是,有<optional>true</optional>元素的模块仍然不具有依赖传递,且打包时也不会包含该模块。更重要的是区别,有<scope>provided</scope>元素的maven依赖,如jap-common中的spring-boot-starter,表明虽然jap-common模块不传递该依赖,但是demo项目 作为jap-common的使用方,你必须自行提供。然而有<optional>true</optional>元素的依赖就没有那么强制的要求了,它的意思是“可选的”。可以把jap-simple-spring-boot-starter理解为插件,如果demo项目 中用不上授权策略simple,那完全可以不在demo项目 中引入该依赖。

扩展阅读:Maven中Optional和Scope元素的使用场景,你弄明白了? (opens new window)、Maven 中true和provided之间的区别 (opens new window)

# 利用RequestContextHolder

在jap框架中,所有的授权方法authenticate(...)都需要传入HttpServletRequest和HttpServletResponse参数,如jap-social进行授权:

JapResponse japResponse = socialStrategy.authenticate(config, request, response);
1

其实在Spring框架下可以利用RequestContextHolder类:

Holder class to expose the web request in the form of a thread-bound RequestAttributes object. The request will be inherited by any child threads spawned by the current thread if the inheritable flag is set to true.

该类(RequestContextHolder)是一个持有web请求的类,web请求是RequestContextHolder以绑定在线程上的RequestAttributes的形式暴露。……

RequestContextHolder中有这样一个私有属性:

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
			new NamedThreadLocal<>("Request attributes");
1
2

是ThreadLocal类型的,也就是说,不同的线程RequestAttributes是不同的。同时,一个web后台应用的线程通常都是通过web请求触发而创建,因此,其实可以通过这种方式获取request,对jap框架作进一步封装。

HttpServletRequest request = requestAttributes.getRequest();
HttpServletResponse response = requestAttributes.getResponse();
1
2

扩展阅读:SpringMVC之RequestContextHolder分析 (opens new window)

# Oauth2授权策略

Oauth2是一个很重要的授权策略,我在阅读OAuth 2.0 的四种方式 (opens new window)这篇文章后画了一个授权UML序列图,方便理解。

oauth2

以授权码方式为例,需要特别注意的地方是,应用服务器实际上只需要提供一个访问接口,也就是redirect_uri参数对应的地址,访问这个地址必须携带code参数。这种授权方式特特别考验对重定向的理解,请注意gitee的oauth服务并不会访问redirect_uri地址,而是让给户浏览器重定向(注意区别转发)到redirect_uri对应的地址(同时携带code参数),让用户浏览器去访问redirect_uri地址。这也是为什么在测试过程中通常redirect_uri可以是localhost,毕竟gitee oauth服务能不能访问到localhost不重要,只要用户浏览器能够访问localhost即可。

# maven的lifecycle和plugin

主要参考maven官方文档:http://maven.apache.org/ref/3.5.0/maven-core/lifecycles.html

# default lifecycle

maven定义了三类lifecycle (opens new window):

  • default Lifecycle
  • clean Lifecycle
  • site Lifecycle

不论是哪类lifecycle,其中都包含多个阶段(phase),而阶段只是一个抽象的概念,它本身不会有具体的执行。某个phase的具体执行是交由绑定到该phase的插件(plugin)来完成。一个plugin会有多个goal,这些goal会最终完成具体的执行。比如compiler插件,就有三个goal可供选择:

image-20210916120002498

需要重点理解的是default lifecycle的定义:

default lifecycle is defined without any associated plugin. Plugin bindings for this lifecycle are defined separately for every packaging (opens new window):

<!-- default lifecycle的定义。只定义了一些列phase,没有为这些phase绑定任何plugin。有点长,截取了一部分 -->
<phases>
<phase>validate</phase>
<phase>initialize</phase>
......
<phase>deploy</phase>
</phases>
1
2
3
4
5
6
7

我的理解是,default lifecycle定义的时候没有为其中的phase默认关联一些plugin,理由是,根据packaging的不同,有些phase绑定了一个或多个plugin和相应goal的,而有些phase却没有绑定任何的plugin,这就意味着这个packaging方式在该阶段(phase)不作任何操作。

举个栗子,下边分别是pom packaging和jar packaging (opens new window)。可以很明显的发现,相比于jar packaging,pom packaging只有install和deploy这两个phase绑定(bind)了plugin和相应goal的。但不论是jar还是pom,它们都采用的是default lifecycle,所以虽然pom packaging在complie这个phase没有绑定plugin,但是它还是会经历complie阶段(phase),只是什么都不做而已。

<!-- pom packaging -->
<phases>
  <install>
    org.apache.maven.plugins:maven-install-plugin:2.4:install
  </install>
  <deploy>
    org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
  </deploy>
</phases>
1
2
3
4
5
6
7
8
9
<!-- jar packaging -->
<phases>
  <process-resources>
    org.apache.maven.plugins:maven-resources-plugin:2.6:resources
  </process-resources>
  <compile>
    org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
  </compile>
  <process-test-resources>
    org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
  </process-test-resources>
  <test-compile>
    org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile
  </test-compile>
  <test>
    org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
  </test>
  <package>
    org.apache.maven.plugins:maven-jar-plugin:2.4:jar
  </package>
  <install>
    org.apache.maven.plugins:maven-install-plugin:2.4:install
  </install>
  <deploy>
    org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
  </deploy>
</phases>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

下面进行一个测试,对我的项目执行install阶段。jar 是 maven 默认的 packaging 类型,packaging 未显式指定时为 jar 类型。下面执行的phase和顺序刚好和上面的jar packaing相吻合。image-20210915213258698

最后看看clean lifecycle的定义,可以发现不像default phase一样,只有光秃秃的定义了一堆phase,这里在定义了一些phase的同时还为一些phase绑定了默认的plugin,比如这里,对clean phase绑定了maven-clean-plugin以及其中的clean目标。

clean lifecycle is defined directly with its plugin bindings.

clean lifecycle直接和它相绑定的plugin一起定义。

<phases>
<phase>pre-clean</phase>
<phase>clean</phase>
<phase>post-clean</phase>
</phases>
<default-phases>
<clean>
 org.apache.maven.plugins:maven-clean-plugin:2.5:clean
</clean>
</default-phases>
1
2
3
4
5
6
7
8
9
10

# Available Plugins

在maven官方文档Available Plugins (opens new window)中第一句话很关键:

Maven is - at its heart - a plugin execution framework; all work is done by plugins. Looking for a specific goal to execute? This page lists the core plugins and others. There are the build and the reporting plugins:

maven,本质上是一个基于插件执行的框架;所有的工作都由plugin来完成。在寻找并执行一个具体的goal(目标)吗?本文列出了一些核心的plugin和别的plugin,它们被分为build plugin和report plugin。

扩展阅读:Maven 的 Lifecycle 和 plugins (opens new window)


#starter#springboot#投稿
Last Updated: 2021/09/26, 11:32:13
项目经验分享:在 JustAuth Plus 中添加 HTTP API 的登陆方式
经验总结:关于为 JAP 开发不同语言的 Demo 的总结

← 项目经验分享:在 JustAuth Plus 中添加 HTTP API 的登陆方式 经验总结:关于为 JAP 开发不同语言的 Demo 的总结→

最近更新
01
经验总结:关于为 JAP 开发不同语言的 Demo 的总结
11-02
02
jap-spring-boot-starter 使用帮助
10-28
03
使用jap-ldap
10-25
更多文章>
Theme by Vdoing | Copyright © 2021-2022

友情链接:UniAdmin | 江如意的博客

Copyright © 2021-2040 FUJIE. All rights reserved. 北京符节科技有限公司版权所有 | 京ICP备2020044519号-4
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式