该项目是对Spring OAuth2 Authorization Server
的一个封装,利用Spring Boot
自动配置的特点可以快速的集成Spring Authorization Server
框架,引入starter
后可以很方便的使用Spring Authorization Server
原有功能,并且扩展功能也可以像其它Security
的DSL
配置方式进行配置。
之前出过一篇系列文章,读者在学习时总是会在各种莫名其妙的地方踩到坑,不小心错漏一个地方都需要排查半天;所以当时就萌生了一个封装starter
的想法,将一些扩展的功能以DSL
的形式配置进认证服务的过滤器链中,直至现在算是有了一个雏形。
- Spring Boot 3.2.1
- Spring Authorization Server
- Spring Data Redis(可选)
- Spring OAuth2 Client(可选)
这些依赖都随引用项目的SpringBoot
版本决定使用的版本,如果以后无重大版本变化,则升级SpringBoot
版本即可。
- 全局验证码过滤器
- 短信登录(只负责验证)
- 邮箱登录(只负责验证)
- OAuth2密码模式
- 三方登录微信登录适配
- 提供基于Redis的核心服务实现
- 拉取代码
git clone https://gitee.com/vains-Sofia/oauth2-security-spring-boot-starter.git
- 安装至本地库
mvn clean install
- 在使用的项目中引入Maven坐标
<dependency>
<groupId>com.vains</groupId>
<artifactId>oauth2-security-spring-boot-starter</artifactId>
<version>0.0.1</version>
</dependency>
- 添加认证服务配置,在认证服务配置中添加密码模式支持
/**
* 配置端点的过滤器链
*
* @param http spring security核心配置类
* @return 过滤器链
* @throws Exception 抛出
*/
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)throws Exception{
// 自定义配置 (重要:必须写在OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);之前,让该自定义配置在
// OAuth2AuthorizationServerMetadataEndpointConfigurer配置之前)
// 添加密码模式
http.with(new ResourceOwnerPasswordConfigurer(),Customizer.withDefaults());
// 配置默认的设置,忽略认证端点的csrf校验
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);
// 其它认证服务配置
return http.build();
}
- 添加资源服务器配置,在资源服务配置中添加邮箱、短信登录
/**
* 配置认证相关的过滤器链
*
* @param http spring security核心配置类
* @return 过滤器链
* @throws Exception 抛出
*/
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)throws Exception{ // ... 其它资源服务配置
http.with(new BasicLoginConfigurer(),basicLoginConfigurer->basicLoginConfigurer
// 添加短信登录,默认登录接口:/login/sms
.smsCaptchaLogin(Customizer.withDefaults())
// 添加邮件登录,默认登录接口:/login/email
.emailCaptchaLogin(Customizer.withDefaults())
// 当添加了短信登录或者邮件登录的配置时,默认添加验证码过滤器(这两种登录方式依赖验证码校验),或如下显示添加验证码配置
.captchaAuthorization(Customizer.withDefaults())
);
// ... 其它资源服务配置
return http.build();
}
- 生成验证码接口需要调用CaptchaRepository保存验证码信息
/**
* 验证码接口
*
* @author vains
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/captcha")
public class CaptchaController {
private final CaptchaRepository captchaRepository;
@GetMapping("/image")
public BasicCaptcha imageCaptcha(HttpServletRequest request,
HttpServletResponse response) {
// 生成验证码
ImageCaptcha imageCaptcha = new ImageCaptcha();
imageCaptcha.setCode...
imageCaptcha.setType(CaptchaType.IMAGE_CAPTCHA);
captchaRepository.save(new ServletWebRequest(request, response), imageCaptcha);
return imageCaptcha;
}
@GetMapping("/sms")
public BasicCaptcha smsCaptcha(@NotBlank @RequestHeader String phone,
HttpServletRequest request,
HttpServletResponse response) {
// 生成验证码
log.debug("手机号[{}]获取验证码", phone);
BasicCaptcha captcha = new BasicCaptcha();
// 默认 1234
captcha.setCode...
captcha.setType(CaptchaType.SMS_CAPTCHA);
captchaRepository.save(new ServletWebRequest(request, response), captcha);
return captcha;
}
@GetMapping("/email")
public BasicCaptcha emailCaptcha(HttpServletRequest request,
HttpServletResponse response) {
// 生成验证码
BasicCaptcha captcha = new BasicCaptcha();
captcha.setType(CaptchaType.EMAIL_CAPTCHA);
// ...
captchaRepository.save(new ServletWebRequest(request, response), captcha);
return captcha;
}
}
CaptchaRepository
默认有两个实现,一个基于session,一个基于redis,当引入spring-boot-starter-data-redis
依赖后自动使用基于redis的验证码存储库,否则使用基于session的验证码存储库。
需要将它放在认证服务配置的第一行主要是为了在访问/.well-known/openid-configuration
时可以获取到自己添加的这个grant_type,否则获取不到,配置中也限制了只能添加在认证服务配置中。
配置 | 说明 | 默认 |
---|---|---|
usernameParameter | 设置密码模式登录账号的参数名 | username |
passwordParameter | 设置密码模式登录密码的参数名 | password |
passwordGrantType | 设置密码模式登录的grant_type值 | password |
tokenGenerator | 设置密码生成器 | HttpSecurity获取 --->>> 从ioc中获取 --->>> 创建一个实例 |
authenticationProvider | 设置用户认证逻辑 | DaoAuthenticationProvider |
authorizationService | 管理OAuth2流程中的认证信息 | HttpSecurity获取 --->>> 从ioc中获取 --->>> 创建一个实例 |
grantAuthenticationTokenGenerator | 自定义AuthenticationToken的生成,与authenticationProvider配合使用 | 默认生成UsernamePasswordAuthenticationToken |
这两个配置类都继承自AbstractAuthenticationFilterConfigurer
,也就是说这两个的配置项是和表单登录(Security
默认提供的formLogin
配置项)的配置是一致的。
配置 | 说明 | 默认 |
---|---|---|
captchaValidatorManager | 验证码校验管理器 | ioc中获取 --->>> DefaultCaptchaValidatorManager |
captchaValidator | 验证码校验器,如果设置该配置则默认的会被覆盖 | 短信、邮件、图片验证码校验 |
requestMatcher | 设置验证码类型,对应需要检验的请求的地址、方式,验证码的参数名、获取验证码时的唯一id | 短信、邮件登录默认表单登录 |
failureHandler | 验证码校验失败处理 | 默认根据请求来源处理,页面请求跳转页面,ajax请求响应JSON |
requestMatcher
提供了多个重载的方法,可以简化写法;
关于requestMatcher也提供了yml的配置,如下
vains:
captcha:
validate:
# 这里的image是type
image:
# code-parameter参数是验证码值的key
code-parameter: image
# 拦截的url(默认post),自己设置请求方式可以使用matcher-infos配置项
request-uris: /captcha/sms,/captcha/email
# request-uris配置项的配置实际上最终转换为matcher-infos了,所以两者等同,自选其一即可
matcher-infos:
# 请求地址
- url: /captcha/sms
# 请求方式
http-method: POST
# 设置image类型的验证码id在请求头/参数中的key
cache-key: deviceId
在验证码过滤器中根据当前请求在CaptchaValidatorManager
中找到对应的验证码校验器,然后对当前请求进行校验,如果校验通过请求继续执行,校验失败调用配置的failureHandler
进行验证失败处理。
graph TD
start(浏览器发起请求) -->|通过CaptchaAuthorizationFilter| cFilter(CaptchaAuthorizationFilter)
cFilter -->|是否需要校验| a(是否需要校验)
a -->|无需校验| c(请求继续执行)
a -->|需要校验| manager(CaptchaValidatorManager)
c --> chain(过滤器链)
manager -->|邮件验证码| email(EmailCaptchaValidator)
manager -->|图片验证码| image(ImageCaptchaValidator)
manager -->|短信验证码| sms(SmsCaptchaValidator)
manager -->|其他验证器| other(...CaptchaValidator)
email -->|需要校验| b(调用通用校验器验证)
image -->|需要校验| d(调用通用校验器验证)
sms -->|需要校验| e(调用通用校验器验证)
other -->|需要校验| f(自定义验证实现)
b -->|验证失败| failure(验证失败处理,响应)
d -->|验证失败| failure
e -->|验证失败| failure
f -->|验证失败| failure
b -->|验证成功| c
d -->|验证成功| c
e -->|验证成功| c
f -->|验证成功| c
默认内置了三个验证器:EmailCaptchaValidator
、ImageCaptchaValidator
和SmsCaptchaValidator
,它们三个在引入依赖后会自动注入,调用的都是验证码的统一校验,在这三个校验器中根据配置(requestMatcher/yml
)决定是否需要校验和封装参数调用统一验证码校验处理。它们在IOC中的名称分别是email
、image
和sms
,如果想重写对应的校验逻辑只需要在注入时设置bean的名字即可覆盖。
starter
提供了AbstractCaptchaValidator
类,继承该类注入ioc后会自动根据bean名称去找验证码配置(requestMatcher/yml
)中的配置,根据配置自动拦截、校验配置的请求路径,验证码配置(requestMatcher/yml
)中的type和bean的名字是对应的,所以如果继承该类实现自己的校验逻辑记得要一一对应。
starter
中注入代码如下,去掉@ConditionalOnMissingBean(name = ...)
注解,注入自己的实现即可
@Bean(DefaultConstants.SMS_CAPTCHA_VALIDATE)
@ConditionalOnMissingBean(name = DefaultConstants.SMS_CAPTCHA_VALIDATE)
public CaptchaValidator smsCaptchaValidator(CaptchaRepository captchaRepository) {
return new SmsCaptchaValidator(captchaRepository, captchaValidateProperties);
}
@Bean(DefaultConstants.EMAIL_CAPTCHA_VALIDATE)
@ConditionalOnMissingBean(name = DefaultConstants.EMAIL_CAPTCHA_VALIDATE)
public CaptchaValidator emailCaptchaValidator(CaptchaRepository captchaRepository) {
return new EmailCaptchaValidator(captchaRepository, captchaValidateProperties);
}
@Bean(DefaultConstants.IMAGE_CAPTCHA_VALIDATE)
@ConditionalOnMissingBean(name = DefaultConstants.IMAGE_CAPTCHA_VALIDATE)
public CaptchaValidator imageCaptchaValidator(CaptchaRepository captchaRepository) {
return new ImageCaptchaValidator(captchaRepository, captchaValidateProperties);
}
graph TB
base(CaptchaValidator) --> abstract(AbstractCaptchaValidator)
abstract --> email(EmailCaptchaValidator)
abstract --> image(ImageCaptchaValidator)
abstract --> sms(SmsCaptchaValidator)
验证码存储是通过实现CaptchaRepository
接口来实现存储,接口中提供了三个抽象方法对验证码进行操作,AbstractCaptchaRepository
抽象类实现了该接口,并提供了获取缓存key
的方法,子类可直接使用,但是限制了子类在实例化时必须传入CaptchaValidateProperties
的实例,因为要根据验证码类型获取配置文件中配置的缓存key
在请求头/请求参数中的key
,这里就跟前面说明配置的地方对应上了。
默认有两个实现,一个基于Redis、一个基于session存储,分别是RedisCaptchaRepository
和SessionCaptchaRepository
,如果项目有引入spring-boot-starter-data-redis
会自动注入RedisCaptchaRepository
,否则会自动注入SessionCaptchaRepository
,如果想使用自己的存储实现,可以直接注入自己的实现,如下:
@Bean
public CaptchaRepository sessionCaptchaRepository(CaptchaValidateProperties captchaValidateProperties) {
return new SessionCaptchaRepository(captchaValidateProperties);
}
上方配置即可替换默认行为。
graph TB
base(CaptchaRepository) --> abstract(AbstractCaptchaRepository)
abstract --> redis(RedisCaptchaRepository)
abstract --> session(SessionCaptchaRepository)
这两个处理登录的过滤器都是继承自AbstractAuthenticationProcessingFilter
,与默认的UsernamePasswordAuthenticationFilter
过滤器逻辑基本一致,唯一的区别就是过滤器内生成token不同,因为这两种方式不需要校验密码(过滤器链最前边有验证码过滤器),所以交给各自的provider
处理,配置类也跟原配置中的formLogin配置基本一致,跟原生账号密码登录配置是一样的配置方式。