关于 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 - 客户端模式
更多细节和底层原理, 稍后会有专门的文章介绍.
在 上一篇 中我们知道, 第三方应用获取授权码的 URL 形如:
http://localhost:18910/authorization-server/oauth/authorize?response_type=code&client_id=&client_secret=&scope=access
而本篇要介绍的隐式授权模式, 它的 URL 形如:
http://localhost:18777/authorization-server/oauth/authorize?response_type=token&client_id=&scope=access_resource
response_type=token, 表示直接返回 token. 整个过程没有后端介入, token 可以直接在前端, 所以很不安全. 隐式授权模式要求: 用户登录并对第三方应用进行授权, 直接返回访问 access-token, 通过token访问资源. 相比授权码模式, 它少了一次授权码的颁发与客户端使用授权码换取 access-token 的过程.
用户登陆后直接返回:
http://localhost:9001/callback#access_token=edc84920-5588-44f5-80d1-8d9cf79d3d14&token_type=bearer&expires_in=119
代码结构:
├─implicit-authorization-server
│ │ implicit-authorization-server.iml
│ │ pom.xml
│ │ README.md
│ │
│ └─src
│ └─main
│ ├─java
│ │ └─c
│ │ └─c
│ │ └─d
│ │ └─s
│ │ └─s
│ │ └─o
│ │ └─i
│ │ └─authorization
│ │ └─server
│ │ │ ImplicitAuthorizationServer.java
│ │ │
│ │ └─configuration
│ │ AuthorizationServerConfiguration.java
│ │ SecurityConfiguration.java
│ │
│ └─resources
│ application.yml
│
└─implicit-resource-server
│ pom.xml
│
└─src
└─main
├─java
│ └─c
│ └─c
│ └─d
│ └─s
│ └─s
│ └─o
│ └─i
│ └─resource
│ └─server
│ │ ImplicitResourceServer.java
│ │
│ ├─configuration
│ │ ResourceServerConfiguration.java
│ │
│ └─controller
│ ResourceController.java
│
└─resources
application.yml
代码上和授权码模式差别不大, 需要关注的是在授权服务器中定义客户端的时候需要指定 authorizedGrantTypes
为 implicit
.
/**
* 授权服务器配置
*
* @author LiKe
* @version 1.0.0
* @date 2020-06-10 11:43
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private PasswordEncoder passwordEncoder;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.allowFormAuthenticationForClients()
// 参数与 security 访问控制一致
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// @formatter:off
clients.inMemory()
// ~ client-a
.withClient("client-a")
// .secret(passwordEncoder.encode("client-a-p"))
.accessTokenValiditySeconds(120)
.resourceIds("resource-server")
.scopes("access_resource")
.redirectUris("http://localhost:9001/callback")
.authorizedGrantTypes("implicit")
.and()
// ~ 资源服务器校验 token 时用的客户端信息, 仅需要 client_id 和 client_secret
.withClient("resource-server")
.secret(passwordEncoder.encode("resource-server-p"))
;
// @formatter:on
}
// ~ autowired
// -----------------------------------------------------------------------------------------------------------------
@Autowired
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
// ~ bean
// -----------------------------------------------------------------------------------------------------------------
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
/**
* Spring Security 配置
*
* @author LiKe
* @version 1.0.0
* @date 2020-06-10 11:43
*/
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(User.builder().username("caplike").password(passwordEncoder.encode("caplike-p")).authorities("USER").build());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin();
http.authorizeRequests().anyRequest().authenticated();
}
// ~ autowired
// -----------------------------------------------------------------------------------------------------------------
@Autowired
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
}
/**
* 资源服务器配置
*
* @author LiKe
* @version 1.0.0
* @date 2020-06-11 15:14
*/
@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 tokenServices = new RemoteTokenServices();
tokenServices.setCheckTokenEndpointUrl("http://localhost:18777/implicit-authorization-server/oauth/check_token");
tokenServices.setClientId("resource-server");
tokenServices.setClientSecret("resource-server-p");
return tokenServices;
}
}
本篇简要介绍了 Spring Security OAuth2 Implicit Grant 的实现, 更多细节会在后续文章中详细阐述.
可以看到, 在用户登陆成功并授权后, 授权服务器直接返回了访问令牌. 而不是想授权码模式那样, 先返回一个授权码再有第三方应用使用授权码 POST 请求授权服务器换取令牌. 所以, 隐式授权模式的安全性低于授权码模式.