【Spring Security】 Oauth2 开放平台授权服务四种授权模式

Metadata

title: 【Spring Security】 Oauth2 开放平台授权服务四种授权模式
date: 2023-02-02 22:09
tags:
  - 行动阶段/完成
  - 主题场景/组件
  - 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
  - 细化主题/Module/SpringSecurity
categories:
  - SpringSecurity
keywords:
  - SpringSecurity
description: 【Spring Security】 Oauth2 开放平台授权服务四种授权模式

配置

1. 添加 WebSecurityConfigurerAdapter 配置

加入了一个密码解析器,在内存中添加一个用户,后续会添加其他配置。

@Configuration
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                // 在内存中创建用户并为密码加密
                .withUser("user").password(passwordEncoder().encode("123456")).roles("USER");
    }

    // 密码解析器
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

2. 添加 AuthorizationServerConfigurerAdapter 配置

AuthorizationServerConfigurerAdapter 是授权服务器的配置类,这里配置了一个 Oauth2 客户端在内存中,后续会集成数据库存储。

@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端
        clients
                // 使用内存设置
                .inMemory()
                // client_id
                .withClient("client")
                // client_secret
                .secret(passwordEncoder.encode("secret"))
                // 授权类型: 授权码、刷新令牌
                .authorizedGrantTypes("authorization_code","refresh_token")
                // 授权范围
                .scopes("app")
                // 注册回调地址
                .redirectUris("http://localhost:20000/code");
    }
}

默认端点 URL

提供了默认端点 URL(访问接口),我们进行 Oauth2 认证时,需要用到。

  • /oauth/authorize:授权端点。
  • /oauth/token:令牌端点。
  • /oauth/confirm_access:用户确认授权提交端点。
  • /oauth/error:授权服务错误信息端点。
  • /oauth/check_token:用于资源服务访问的令牌解析端点。
  • /oauth/token_key:提供公有密匙的端点,如果你使用 JWT 令牌的话。

四种授权模式

授权码模式

首先回顾下授权码模式流程,由图可以看出,获取令牌,主要需要以下几步:

  • Oauth Client 客户端通过客户端的账号密码获取 code
  • 使用 code 获取令牌
  • 携带令牌访问资源

1. 获取授权码

访问获取授权码的端点

http://localhost:20000/oauth/authorize?client_id=client&client_secret=secret&response_type=code

参数说明:

参数 说明 是否必填
client_id 用来标识客户的 Id YES
client_secret (需要值得信任的客户端)客户端密码 YES
response_type 授权码模式固定为 code YES
redirect_uri 跳转 uri,当授权码申请成功后会跳转到此地址,并在后边带上 code 参数(授权码) NO
scope 用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围 NO
state 可以取随机值, 用于防止 CORS 攻击 NO

之后会调转到登录接口,输入用户名密码

2. 获取授权码

登录成功后跳转到授权页面,是否允许这个客户端访问你的资源,选择 Approve,点击同意 authorize。

之后浏览器会重定向到配置的回调地址,并携带了授权码,所以这个回调地址应该是添加客户端服务器自身的地址。

3. 获取令牌

访问令牌端点获取令牌,需要 Post 请求,这里使用 postman。

Post 请求访问端点:

http://localhost:20000/oauth/token

首先需要传入客户端的 ID 及密码,采用 httpBasic 认证方式,并将其拼接成 “用户名: 密码” 格式,中间是一个冒号,再用 base64 编码成 xxx,然后在请求头中附加 Authorization:Basic xxx。这里可以使用 postman 选择 Basic Auth,然后输入客户端 ID 及密码。

添加参数:

参数说明:

参数 说明
code 授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请
grant_type 授权类型,填写 authorization_code,表示授权码模式
redirect_uri 申请授权码时的跳转 url,一定和申请授权码时用的 redirect_uri 一致。

访问,获取到访问及刷新令牌。

{
    "access_token": "0ce36dcf-79c0-490e-873b-d3e295028b73",
    "token_type": "bearer",
    "refresh_token": "7b67b4b3-87a9-4b40-970d-44598f9cb355",
    "expires_in": 42723,
    "scope": "app"
}

当再次点击时,会报错,说明 code 只能使用一次。

密码模式

密码模式直接使用用户名密码获取令牌,这种方式一般用于自家应用。

1. 添加认证管理器

之前说过,SpringSecurity 使用认证管理器进行登录认证,那么在 Oauth2 中,需要配置一个端点的认证管理器,才能使用用户名密码进行认证。

在 MyWebSecurityConfiguration 中注入一个认证管理器:

    // 配置认证管理器
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

在 MyAuthorizationServerConfiguration 配置认证管理器,添加 Oauth2 客户端密码模式支持。

@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
     private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端
        clients
                // 使用内存设置
                .inMemory()
                // client_id
                .withClient("client")
                // client_secret
                .secret(passwordEncoder.encode("secret"))
                // 授权类型: 授权码、刷新令牌、密码、客户端、
                .authorizedGrantTypes("authorization_code","refresh_token","password","client_credentials")
                // 授权范围
                .scopes("app")
                // 注册回调地址
                .redirectUris("http://localhost:20000/code");
    }
    // 端点配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置端点允许的请求方式
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        // 配置认证管理器
        endpoints.authenticationManager(authenticationManager);
    }
}

2. 获取令牌

Post 请求访问请求端点:

http://localhost:20000/oauth/token

添加参数:

参数说明:

参数 说明
grant_type 授权码类型,固定为 password
username 用户的账号
password 用户的密码

和授权码模式一样,添加认证消息头:

获取令牌,多次点击,发现我们获取到的令牌是一样的,只是 expires_in 在变化。

客户端模式

客户端模式,可以直接通过接口返回令牌。

访问端点,grant_type 为 client_credentials:

http://localhost:20000/oauth/token?grant_type=client_credentials

消息头添加 Httpbasic 认证:

访问返回令牌:

简化模式

简化模式 (Implicit) 也翻译做隐式模式或者紧凑模式,简化是针对授权码模式简化,没有授权码再获取令牌这一步。

简化模式可以通过客户端名称和一个 redirect_uri,访问认证服务器,认证服务器认证之后,直接返回一个令牌,该令牌会在 redirect_uri 的后边使用 #连接

客户端添加简化模式支持:

// 授权类型: 授权码、刷新令牌、密码、客户端、简化模式
.authorizedGrantTypes("authorization_code","refresh_token","password","client_credentials","implicit")

访问端点,主要是 response_type 为 token

http://localhost:20000/oauth/authorize?client_id=client&client_secret=secret&response_type=token

之后进行登录和授权,和授权码模式一致。

点击授权后,直接通过回调地址返回了 token 及过期时间。