文章目录
关于 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 Grant 和 Implicit 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"
}
整体流程
- 让用户填写表单提交到授权服务器, 表单中包含用户的用户名, 密码 ,客户端的id和密钥的加密串;
- 授权服务器先解析并校验客户端信息, 然后校验用户信息, 完全通过返回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
密码模式不需要前端介入, 这里可以禁用表单登陆. 同时, 提供一个 AuthenticationMnager
给 AuthorizationServerConfigurer
@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 给授权服务器. 也因为没有授权服务器接管用户登陆这一环节, 所以这种模式显得格外不安全. 适用于内部应用.