前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring OAuth 简单实践

Spring OAuth 简单实践

作者头像
MashiroT
发布2023-10-18 15:42:45
2000
发布2023-10-18 15:42:45
举报
文章被收录于专栏:MashiroのBlogMashiroのBlog

Spring OAuth 简单实践

引言

最近在了解OAuth2.0,一直想搞一个自己的类似于SakuraFrp使用的OpenID授权站,就想自己写一个。找的很多国内教程用的包都是 spring-cloud 下的关于 oauth 的包,或是直接使用老版本的 security-oauth 包,由于 spring-security 最新版是 6.x ,教程的版本太老,且想使用 start.spring.io 中提供的 spring-boot-starter-oauth2-xxx 使用配置文件快速开发,写下本文记录。

环境:Jdk17, Spring 3.1.4

实现

简单使用

使用 Client Credentials Grant 客户端模式对 Authorization-Server 进行简单使用

项目创建

新建一个空项目 oauth-demo

oauth 1-1.png
oauth 1-1.png

新建一个 authorization-server 模块

oauth 1-2.png
oauth 1-2.png

添加如下依赖:

oauth 1-3.png
oauth 1-3.png
代码语言:javascript
复制
implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server")
implementation("org.springframework.boot:spring-boot-starter-web")

修改配置文件

代码语言:javascript
复制
server:
  port: 8080
spring:
  application:
    name: authorization-server
  security:
    oauth2:
      authorization-server:
        client:
          demo-0:
            registration:
              client-id: demo-0
              # {noop} 不加密,明文
              client-secret: "{noop}passwd"
              client-name: demo-0
              client-authentication-methods:
                - client_secret_basic
              # 客户端授权模式
              authorization-grant-types:
                # 客户端模式
                - client_credentials
logging:
  level:
    org.springframework.security: trace

启动项目

测试

使用 InsomniaPostman 进行测试

http://127.0.0.1:8080/oauth2/token 发送 POST 请求

  1. Basic Auth
oauth 1-4.png
oauth 1-4.png
  1. 设置请求体
oauth 1-5.png
oauth 1-5.png
oauth 1-6.png
oauth 1-6.png
  1. 发送请求
oauth 1-7.png
oauth 1-7.png

可以看到返回了 access_token 和过期时间,客户端模式的具体介绍请看参考2

授权码模式

使用 authorization-server 同时作为 授权服务和资源服务

使用 client-1 作为客户端

authorization-server

配置文件:

代码语言:javascript
复制
server:
  port: 8080
spring:
  application:
    name: authorization-server
  security:
    # 提供的user,仅供测试使用
    user:
      name: user
      password: "{noop}password"
      roles: USER
    oauth2:
      authorization-server:
        client:
          # 客户端标识
          client-1:
            registration:
              client-id: client-1
              client-secret: "{noop}password2"
              # 授权方式:授权码,或使用refresh_token拿新的access_token
              authorization-grant-types:
                - "authorization_code"
                - "refresh_token"
              # 客户端验证方式
              client-authentication-methods:
                - client_secret_basic
              # 在同意授权后重定向的uri
              redirect-uris:
                - http://127.0.0.1:8081/login/oauth2/code/client-1
              post-logout-redirect-uris:
                - http://127.0.0.1:8081/logout
              # 提供的权限
              scopes:
                - "openid"
                - "profile"
                - "read"
                - "write"
            require-authorization-consent: true
            token:
              access-token-time-to-live: 3600s
              refresh-token-time-to-live: 7200s
logging:
  level:
    org.springframework.security: trace
client-1
项目创建

依赖

代码语言:javascript
复制
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation("org.springframework.boot:spring-boot-starter-web")

Config

代码语言:javascript
复制
@Configuration
public class SecurityConfig {
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(o -> o
                        .requestMatchers("/").permitAll()
                        .anyRequest().authenticated())
                // 用于解决非https下 oauth Not injecting HSTS header since it did not match request to ... 问题
                .headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer
                        .httpStrictTransportSecurity(hstsConfig -> hstsConfig
                                .maxAgeInSeconds(0)
                                .includeSubDomains(true)
                        )
                )
                .oauth2Login(Customizer.withDefaults())
                .formLogin(Customizer.withDefaults())
                .build();
    }
}

Controller

提供两个接口,一个用于测试是否启动成功,一个用于跳转OpenID登录

代码语言:javascript
复制
@RestController
@RequestMapping("/")
public class AppController {
    private final AppService appService;

    public AppController(AppService appService) {
        this.appService = appService;
    }

    @GetMapping
    public String getPublicData() {
        return "Public data";
    }

    @GetMapping("/private-data")
    public String getPrivateData() {
        return appService.getJwtToken();
    }
}

Service

getJwtToken() 需要权限 profile

代码语言:javascript
复制
@Service
public class AppService {
    private final OAuth2AuthorizedClientService authorizedClientService;

    public AppService(OAuth2AuthorizedClientService authorizedClientService) {
        this.authorizedClientService = authorizedClientService;
    }

    @PreAuthorize("hasAuthority('SCOPE_profile')")
    public String getJwtToken() {
        var authentication = SecurityContextHolder.getContext().getAuthentication();
        var accessToken = getAccessToken(authentication);
        var refreshToken = getRefreshToken(authentication);
        return String.format("Access Token = %s <br /><br /><br /> Refresh Token = %s",
                accessToken.getTokenValue(), refreshToken.getTokenValue());
    }

    public OAuth2AccessToken getAccessToken(Authentication authentication) {
        var authorizedClient = this.getAuthorizedClient(authentication);
        return authorizedClient.getAccessToken();
    }

    public OAuth2RefreshToken getRefreshToken(Authentication authentication) {
        var authorizedClient = this.getAuthorizedClient(authentication);
        return authorizedClient.getRefreshToken();
    }

    private OAuth2AuthorizedClient getAuthorizedClient(Authentication authentication) {
        if (authentication instanceof OAuth2AuthenticationToken oauthToken) {
            String clientRegistrationId = oauthToken.getAuthorizedClientRegistrationId();
            String principalName = oauthToken.getName();
            return authorizedClientService.loadAuthorizedClient(clientRegistrationId, principalName);
        }
        return null;
    }
}

配置文件

代码语言:javascript
复制
server:
  port: 8081
spring:
  application:
    name: client-1
  security:
    oauth2:
      client:
        registration:
          # 客户端标识,务必与授权服务注册的一致
          client-1:
            # 已在下面的provider中配置
            provider: spring
            # 标识自己
            client-id: client-1
            client-secret: password2
            # 展示授权服务的名字
            client-name: authorization-server
            # 授权方式
            authorization-grant-type: authorization_code
            # redirect-uri 参数的内容
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            # 请求的权限,与授权服务注册的一致
            scope:
              - openid
              - profile
              - read
              - write
            # 验证方式
            client-authentication-method: client_secret_basic
        provider:
          # 使用google,github等不需要配置,已内置
          spring:
            issuer-uri: http://localhost:8080
      resource-server:
        jwt:
          issuer-uri: http://localhost:8080
流程图
oauth 2-1.png
oauth 2-1.png
测试

访问 http://127.0.0.1:8081

访问 http://127.0.0.1:8081/private-data

跳转到 http://127.0.0.1:8081/login

选择 Login with OAuth 2.0 中的 authorization-server

相当于访问 http://127.0.0.1:8081/oauth2/authorization/client-1,然后重定向到:

代码语言:javascript
复制
http://localhost:8080/oauth2/authorize?
    response_type=code
    &client_id=client-1
    &scope=openid%20profile%20read%20write
    &state=Za7PN3i8F-yLcxBUO0AkLFEm-Clg-s5RjJf46pDF6hE%3D
    // 回调的重定向url
    &redirect_uri=http://127.0.0.1:8081/login/oauth2/code/client-1
    &nonce=1qN2mFKbEGaJb4jGFulwFDkR72dzEkgdZLrOSlRvfbk

登录并授权

授权后重定向到:

代码语言:javascript
复制
http://127.0.0.1:8081/login/oauth2/code/client-1?
    // 授权码
    code=CNL5dhSCyohdsRFVYkL4r9RNFhX-V-EWix8KIWd4jOYQW8gWiyihLLdAafnrswacxXaAZguuMphIbv_VLku50Q9LVehmkcpD0MbBgXlzABKhPU1X-FfkUZ8U0KqwMoxF
    &state=Za7PN3i8F-yLcxBUO0AkLFEm-Clg-s5RjJf46pDF6hE%3D

​ 然后重定向到:http://127.0.0.1:8081/private-data?continue

  1. 拿取access_token和refresh_token在Service中完成

<br />

<br />

参考资料:

  1. Syed Hasan's blog
  2. 阮一峰 - 理解OAuth 2.0
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring OAuth 简单实践
    • 引言
      • 实现
        • 简单使用
        • 授权码模式
    相关产品与服务
    腾讯云服务器利旧
    云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档