SpringSecurtiy OAuth2 (4) Resource Owner Password Grant - 密码模式

本文深入探讨Spring Security OAuth2的密码模式,适用于高度信任的第三方应用。介绍了使用场景,包括内部应用认证,以及整体流程,从用户信息提交到access_token的获取。详细解析了代码结构,包括授权服务器和资源服务器的配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于 SpringSecurity OAuth2 的 4 种模式的简要介绍性文章:
SpringSecurtiy OAuth2 (2) Authorization Code Grant - 授权码模式
SpringSecurtiy OAuth2 (3) Implicit Grant - 隐式授权模式
SpringSecurtiy OAuth2 (4) Resource Owner Password Grant - 密码模式
SpringSecurtiy OAuth2 (5) Client Credentials Grant - 客户端模式


更多细节和底层原理, 稍后会有专门的文章介绍.

关于

密码模式有一个前提就是你高度信任第三方应用. 举个不恰当的例子: 如果要在第三方应用上接入微信登录, 使用了密码模式, 那你就要在第三方应用输入微信的用户名和密码, 这肯定是不安全的, 所以密码模式需要你非常信任第三方应用.

使用场景

☼ 自己本身有一套用户体系, 在认证时需要带上自己的用户名和密码, 以及客户端的 client_id, client_secret. 此时, access-token 包含的权限是用户本身的权限, 而不是客户端的权限
可以没有前端介入 (有别于 Authorization Code GrantImplicit Grant)

整体流程

http://localhost:18907/password-authorization-server/oauth/token?grant_type=password&client_id=client-a&client_secret=client-a-p&username=caplike&password=caplike-p&scope=read_scope

授权服务器返回数据:

{
    "access_token": "aa5a459e-4da6-41a6-bf67-6b8e50c7663b",
    "token_type": "bearer",
    "expires_in": 119,
    "scope": "read_scope"
}

在这里插入图片描述

整体流程

  1. 让用户填写表单提交到授权服务器, 表单中包含用户的用户名, 密码 ,客户端的id和密钥的加密串;
  2. 授权服务器先解析并校验客户端信息, 然后校验用户信息, 完全通过返回access_token, 否则默认都是401 http状态码, 提示未授权无法访问;

实现

代码结构

├─password-authorization-server
│  │  password-authorization-server.iml
│  │  pom.xml
│  │  README.md
│  │  
│  └─src
│     └─main
│        ├─java
│        │  └─c
│        │      └─c
│        │          └─d
│        │              └─s
│        │                  └─s
│        │                      └─o
│        │                          └─p
│        │                              └─authorization
│        │                                  └─server
│        │                                      │  PasswordAuthorizationServer.java
│        │                                      │  
│        │                                      └─configuration
│        │                                              AuthorizationServerConfiguration.java
│        │                                              SecurityConfiguration.java
│        │                                              
│        └─resources
│                application.yml
│
└─password-resource-server
    │  pom.xml
    │  
    └─src
       └─main
           ├─java
           │  └─c
           │      └─c
           │          └─d
           │              └─s
           │                  └─s
           │                      └─o
           │                          └─p
           │                              └─resource
           │                                  └─server
           │                                      │  PasswordResourceServer.java
           │                                      │  
           │                                      ├─configuration
           │                                      │      ResourceServerConfiguration.java
           │                                      │      
           │                                      └─controller
           │                                              ResourceController.java
           │                                              
           └─resources
                   application.yml

授权服务器 (Authorization Server)

AuthorizationServerConfiguration

密码模式需要提供 AuthenticationManager 用户用户信息的同步验证.

@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    private PasswordEncoder passwordEncoder;

    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // @formatter:off
        clients.inMemory()

                // 客户端配置.
                // 可以是前端, 密码模式适合微服务的 Auth?
                .withClient("client-a")
                    .secret(passwordEncoder.encode("client-a-p"))
                    .resourceIds("resource-server")
                    .accessTokenValiditySeconds(60 * 12)
                    .authorizedGrantTypes("password")
                    .scopes("read_scope")

                .and()

                // 资源服务器身份配置, 用于请求授权服务器的 /oauth/check_token, 校验 access_token
                .withClient("resource-server")
                    .secret(passwordEncoder.encode("resource-server-p"))
        ;
        // @formatter:on
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        Assert.notNull(authenticationManager, "The AuthenticationManager can not be null!");
        log.info("AuthenticationManager's type: {}", authenticationManager.getClass().getCanonicalName());
        // ~ 密码模式需要 AuthenticationManager
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()");
    }

    // ~ autowired
    // -----------------------------------------------------------------------------------------------------------------

    @Autowired
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Autowired
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    // ~ bean
    // -----------------------------------------------------------------------------------------------------------------

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

SecurityConfiguration

密码模式不需要前端介入, 这里可以禁用表单登陆. 同时, 提供一个 AuthenticationMnagerAuthorizationServerConfigurer

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private PasswordEncoder passwordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off

        // 禁用 表单登陆
        http.formLogin().disable();

        // 禁用 Basic Auth
        http.httpBasic().disable();

        // 所有请求都需要认证
        http.authorizeRequests().anyRequest().authenticated();

        // @formatter:on
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser(User.builder().username("caplike").password(passwordEncoder.encode("caplike-p")).authorities("USER").build());
    }

    // ~ autowired
    // -----------------------------------------------------------------------------------------------------------------

    @Autowired
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    // ~ bean
    // -----------------------------------------------------------------------------------------------------------------

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

}

资源服务器 (Resource Server)

ResourceServerConfiguration

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("resource-server").tokenServices(remoteTokenServices()).stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
    }

    /**
     * @return {@link RemoteTokenServices}
     */
    private RemoteTokenServices remoteTokenServices() {
        final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        remoteTokenServices.setClientId("resource-server");
        remoteTokenServices.setClientSecret("resource-server-p");
        remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:18907/password-authorization-server/oauth/check_token");
        return remoteTokenServices;
    }
}

测试

在这里插入图片描述

总结

本篇简单过了一下密码模式的实现, 这种方式和前两种最大的区别就是它不需要前端介入, 用户信息和客户端信息都一次性 POST 给授权服务器. 也因为没有授权服务器接管用户登陆这一环节, 所以这种模式显得格外不安全. 适用于内部应用.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值