综合案例

①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖

  • 认证

    • 记住我 : 这个需要修改核心类
    • 自定义 Login 页面
      • 本地资源文件:org.webjars
    • Logout
    • 短信登陆
    • 微信等第三方登陆
  • 个人信息管理

    • 用户注册
    • 用户信息查看
  • 系统管理

1. 微信登陆

Spring 自带的 github 等登陆,但是追加自定义微信,需要写一部分代码。

  • 配置第三方登陆
    • 简化配置:一些常见的,封装地址,封装到代码中
  • 扩展化框架
    • 支持 Spring 自带的第三方登陆
    • 支持扩展微信登陆登陆
  • 可定制化部分
    • 可定制请求 code
    • 可定制获取 token
    • 可定义获取用户信息
    • 可定义登陆成功后的逻辑扩展

1.1 添加微信功能

1.1.1 引用类包

今后会将微信登陆作为一个单独的包,打包。

1.1.2 配置 yml 文件

这里兼用了github与微信网页登陆两种形式。

spring:
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
github:
client-id: cadd65bbbc65ab9111179
client-secret: 62b27f0df2fcebb11118402
wechat_open:
client-id: wxa111ae
client-secret: a11111193
redirect-uri: 'https://frp.redhtc.com/login/oauth2/code/wechat-open'

微信登陆的redirect-uri做个特殊的制定,因为使用了nginx做了跳转。

1.1.3 在 config 中引用

下面是一个完整的代码包,核心的地址在配置oAuth2LoginConfigurer类。

@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http
, ClientRegistrationRepository clientRegistrationRepository
, OAuth2AccessTokenResponseClient oAuth2AccessTokenResponseClient
, OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver
, OAuth2UserService oAuth2UserService
) throws Exception {
http
.authorizeRequests((authorize) -> authorize
// 这个必须添加不然返回:/login?logout会认为没有权限,然后直接给跳转到/login,这样参数就没有了。
.antMatchers(HttpMethod.GET,"/login").permitAll()
.anyRequest().authenticated()
)
//配置login
.exceptionHandling(exceptionHandling->{
exceptionHandling.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"));
})
.formLogin(withDefaults());
// 添加第三方登陆接口
OAuth2LoginConfigurer oAuth2LoginConfigurer=new OAuth2LoginConfigurer(clientRegistrationRepository
,oAuth2AccessTokenResponseClient
,oAuth2AuthorizationRequestResolver
,oAuth2UserService
);
http.apply(oAuth2LoginConfigurer);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}

1.1.4 修改 html 登陆功能

在登陆页面下面添加两个链接:

  • /oauth2/authorization/github
  • /oauth2/authorization/wechat_open

1.2 扩展

常见的第三方登陆都会封装到代码中,自己需要扩展的地方很少。如果真需要扩展,可以自定义@Bean 来扩展下面 4 部分:

  • 获取 code:OAuth2AuthorizationRequestResolver
  • 获取 token: OAuth2AccessTokenResponseClient
  • 获取 userInfo:OAuth2UserService

关于成功handler,可以通过OAuth2LoginConfigurer.setSuccessHandler 来实现。

实际上,上面的三个代码都是通过代理来实现的,也可以这么来扩展:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http
, ClientRegistrationRepository clientRegistrationRepository
, OAuth2AccessTokenResponseClient oAuth2AccessTokenResponseClient
, OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver
, OAuth2UserService oAuth2UserService
) throws Exception {
//例如要扩展OAuth2UserService
((DelegatingOAuth2UserService)oAuth2UserService).addOAuth2UserService(这里添加自己的逻辑类);
// 添加第三方登陆接口
OAuth2LoginConfigurer oAuth2LoginConfigurer=new OAuth2LoginConfigurer(clientRegistrationRepository
,oAuth2AccessTokenResponseClient
,oAuth2AuthorizationRequestResolver
,oAuth2UserService
);
http.apply(oAuth2LoginConfigurer);
return http.build();
}

1.3 代码分析

这里只分析 Spring 的代码,参考了文档:Spring 全家桶-Spring Security 之 OAuth2.0 认证

1.3.1 程序加载

OAuth2ClientAutoConfiguration 处理配置文件的 bean,这个类包含了两个子类:

  • OAuth2ClientRegistrationRepositoryConfiguration:用来加载第三方登陆的配置文件,返回InMemoryClientRegistrationRepository
    • 上面的类使用到了OAuth2ClientPropertiesRegistrationAdapter
    • CommonOAuth2Provider提供了google,github,facebook,OKTA的处理方式并设置回调地址等
    • OAuth2ClientProperties属性维护类
  • OAuth2WebSecurityConfiguration: 用来返回 3 个内容:SecurityFilterChain OAuth2AuthorizedClientRepository OAuth2AuthorizedClientService

其中主要 Filter 的主要配置内容在OAuth2ClientConfigurer类中。

  • OAuth2AuthorizationCodeAuthenticationProvider
  • OAuth2AuthorizationRequestRedirectFilter
    • OAuth2AuthorizationRequestResolver:DefaultOAuth2AuthorizationRequestResolver
  • OAuth2AuthorizationCodeGrantFilter
  • OAuth2AuthorizationCodeGrantFilter
    • AuthenticationManager
    • OAuth2ClientConfigurerUtils
  • OAuth2AccessTokenResponseClient

1.3.2 流程处理

入口

FilterURI作用
OAuth2AuthorizationRequestRedirectFilter/oauth2/authorization/{registrationId}从客户端,跳转到 OAuth2 认证服务器的入口
OAuth2LoginAuthenticationFilter/login/oauth2/code/{registrationId}redirect-uri 的回调地址
OAuth2AuthorizationRequestRedirectFilter

这个类的主要工作:

//解析出OAuth2AuthorizationRequest
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
//保存OAuth2AuthorizationRequest
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
//进行跳转
this.authorizationRedirectStrategy.sendRedirect(request, response,
authorizationRequest.getAuthorizationRequestUri());

OAuth2AuthorizationRequestRedirectFilter 相关的子类:

  • OAuth2AuthorizationRequest
  • OAuth2AuthorizationRequestResolver
  • AuthorizationRequestRepository
  • ClientRegistration
  • ClientRegistrationRepository

OAuth2AuthorizationRequest

一个用来存储 OAuth2 请求过程中数据的类。代码很简单,中间使用了几个工具:UriBuilder CollectionUtils StringUtils UriUtils

OAuth2AuthorizationRequestResolver 接口

默认实现类是:DefaultOAuth2AuthorizationRequestResolver,请求 code 链接的转换器。

为了便于外部扩展,这个类定义了一个customer插件Consumer<Builder> authorizationRequestCustomizer,并通过setAuthorizationRequestCustomizer方法来设置。

AuthorizationRequestRepository 接口

用来保存OAuth2AuthorizationRequest的,主要的方法有:loadAuthorizationRequest saveAuthorizationRequest removeAuthorizationRequest。默认的实现类是:HttpSessionOAuth2AuthorizationRequestRepository

  • 默认会保存在 session,每次只能保存一个,如果要保存多个,可以用 state 作为 key,保存到一个 map 中。
OAuth2LoginAuthenticationFilter

可以参考到的类

* @see AbstractAuthenticationProcessingFilter
* @see OAuth2LoginAuthenticationToken
* @see OAuth2AuthenticationToken
* @see OAuth2LoginAuthenticationProvider
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationResponse
* @see AuthorizationRequestRepository
* @see OAuth2AuthorizationRequestRedirectFilter
* @see ClientRegistrationRepository
* @see OAuth2AuthorizedClient
* @see OAuth2AuthorizedClientRepository