一、什么是 OAuth2 协定?
OAuth 2.0 是一个对于受权的凋谢的网络协议,是目前最风行的受权机制。
数据的所有者通知零碎,批准受权第三方利用进入零碎,获取这些数据。零碎从而产生一个短期的进入令牌(token),用来代替明码,供第三方利用应用。
因为受权的场景泛滥,OAuth 2.0 协定定义了获取令牌的四种受权形式,别离是:
- 受权码模式 :受权码模式(authorization code)是性能最残缺、流程最紧密的受权模式。它的特点就是通过客户端的后盾服务器,与 ” 服务提供商 ” 的认证服务器进行互动。
- 简化模式 :简化模式(implicit grant type)不通过第三方应用程序的服务器,间接在浏览器中向认证服务器申请令牌,跳过了 ” 受权码 ” 这个步骤,因而得名。所有步骤在浏览器中实现,令牌对访问者是可见的,且客户端不须要认证。
- 明码模式 :明码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供本人的用户名和明码。客户端应用这些信息,向 ” 服务商提供商 ” 索要受权。
- 客户端模式 :客户端模式(Client Credentials Grant)指客户端以本人的名义,而不是以用户的名义,向 ” 服务提供商 ” 进行认证。严格地说,客户端模式并不属于 OAuth 框架所要解决的问题。在这种模式中,用户间接向客户端注册,客户端以本人的名义要求 ” 服务提供商 ” 提供服务,其实不存在受权问题。
四种受权模式别离应用不同的
grant_type
来辨别
二、为什么要自定义受权类型?
尽管 OAuth2 协定定义了 4 种规范的受权模式,然而在理论开发过程中还是远远满足不了各种变态的业务场景,须要咱们去扩大。
例如减少图形验证码、手机验证码、手机号明码登录等等的场景
而常见的做法都是通过减少 过滤器 Filter
的形式来扩大 Spring Security
受权,然而这样的实现形式有两个问题:
- 脱离了
OAuth2
的治理 - 不灵便:例如零碎应用 明码模式 受权,网页版须要减少图形验证码校验,然而手机端 APP 又不须要的状况下,应用减少
Filter
的形式去实现就比拟麻烦了。
所以目前在 Spring Security
中比拟优雅和灵便的扩大形式就是通过自定义 grant_type 来减少受权模式。
三、实现思路
在扩大之前首先须要先理解 Spring Security
的整个受权流程,我以 明码模式 为例去开展剖析,如下图所示
3.1. 流程剖析
整个受权流程关键点分为以下两个局部:
第一局部 :对于受权类型 grant_type
的解析
- 每种
grant_type
都会有一个对应的TokenGranter
实现类。 - 所有
TokenGranter
实现类都通过CompositeTokenGranter
中的tokenGranters
汇合存起来。 - 而后通过判断
grantType
参数来定位具体应用那个TokenGranter
实现类来解决受权。
第二局部 :对于受权登录逻辑
- 每种
受权形式
都会有一个对应的AuthenticationProvider
实现类来实现。 - 所有
AuthenticationProvider
实现类都通过ProviderManager
中的providers
汇合存起来。 TokenGranter
类会 new 一个AuthenticationToken
实现类,如UsernamePasswordAuthenticationToken
传给ProviderManager
类。- 而
ProviderManager
则通过AuthenticationToken
来判断具体应用那个AuthenticationProvider
实现类来解决受权。 - 具体的登录逻辑由
AuthenticationProvider
实现类来实现,如DaoAuthenticationProvider
。
3.2. 扩大剖析
依据下面的流程,扩大分为以下两种场景
场景一 :只对原有的受权逻辑进行加强或者扩大,如:用户名明码登录前减少图形验证码校验。
该场景须要定义一个新的 grantType
类型,并新增对应的 TokenGranter
实现类 增加扩大内容 ,而后加到 CompositeTokenGranter
中的 tokenGranters
汇合里即可。
参考代码:PwdImgCodeGranter.java
场景二 :新加一种受权形式,如:手机号加明码登录。
该场景须要实现以下内容:
- 定义一个新的
grantType
类型,并新增对应的TokenGranter
实现类增加到CompositeTokenGranter
中的tokenGranters
汇合里 - 新增一个
AuthenticationToken
实现类,用于寄存该受权所需的信息。 - 新增一个
AuthenticationProvider
实现类 实现受权的逻辑 ,并重写supports
办法绑定步骤二的AuthenticationToken
实现类
参考代码:MobilePwdGranter.java
四、代码实现
上面以 场景二 新增手机号加明码受权形式为例,展现外围的代码实现
4.1. 创立 AuthenticationToken 实现类
创立 MobileAuthenticationToken
类,用于存储手机号和明码信息
public class MobileAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private Object credentials;
public MobileAuthenticationToken(String mobile, String password) {super(null);
this.principal = mobile;
this.credentials = password;
setAuthenticated(false);
}
public MobileAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {return this.credentials;}
@Override
public Object getPrincipal() {return this.principal;}
@Override
public void setAuthenticated(boolean isAuthenticated) {if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {super.eraseCredentials();
}
}
4.2. 创立 AuthenticationProvider 实现类
创立 MobileAuthenticationProvider
类,实现登录逻辑,并绑定 MobileAuthenticationToken
类
@Setter
public class MobileAuthenticationProvider implements AuthenticationProvider {
private ZltUserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) {MobileAuthenticationToken authenticationToken = (MobileAuthenticationToken) authentication;
String mobile = (String) authenticationToken.getPrincipal();
String password = (String) authenticationToken.getCredentials();
UserDetails user = userDetailsService.loadUserByMobile(mobile);
if (user == null) {throw new InternalAuthenticationServiceException("手机号或明码谬误");
}
if (!passwordEncoder.matches(password, user.getPassword())) {throw new BadCredentialsException("手机号或明码谬误");
}
MobileAuthenticationToken authenticationResult = new MobileAuthenticationToken(user, password, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {return MobileAuthenticationToken.class.isAssignableFrom(authentication);
}
}
4.3. 创立 TokenGranter 实现类
创立 MobilePwdGranter
类并定义 grant_type
的值为 mobile_password
public class MobilePwdGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "mobile_password";
private final AuthenticationManager authenticationManager;
public MobilePwdGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices
, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
String mobile = parameters.get("mobile");
String password = parameters.get("password");
parameters.remove("password");
Authentication userAuth = new MobileAuthenticationToken(mobile, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
userAuth = authenticationManager.authenticate(userAuth);
if (userAuth == null || !userAuth.isAuthenticated()) {throw new InvalidGrantException("Could not authenticate mobile:" + mobile);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
4.4. 加到 CompositeTokenGranter 中的汇合里
// 增加手机号加明码受权模式
tokenGranters.add(new MobilePwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
4.5. 测试
应用以下地址,指定 grant_type
为 mobile_password
进行受权获取 access_token
/oauth/token?grant_type=mobile_password&mobile={mobile}&password={password}
五、参考样例
具体的代码实现能够参考
https://gitee.com/zlt2000/microservices-platform/tree/master/zlt-uaa
扫码关注有惊喜!