由于内容相关联,我先列几篇之前的文章,欢迎阅读:
以上三篇文章分别分析了,Spring Security 是怎么完成用户认证的、如何给 Spring Security 增加新的认证逻辑(手机验证码)、Spring Security OAuth 密码模式(资源所有者模式)是如何完成认证的。
这篇我们来试着参考密码模式,给 Spring Security OAuth 增加一个手机验证码模式。以下内容会与之前的文章内容有关联,因此在此之前,可以先阅读之前的几篇文章,回顾一下这些内容。
本文有大量内容会用到 Spring Security 自定义认证逻辑 这篇文章里创建的 Java 类,强烈建议你先读一下。
实现 Granter
首先需要实现一个与 ResourceOwnerPasswordTokenGranter
类似的、手机验证码模式对应的 Granter
类,继承 AbstractTokenGranter
。
我们根据 ResourceOwnerPasswordTokenGranter
照猫画虎,写一个 SmsTokenGranter
。
public class SmsTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "sms";
private final AuthenticationManager authenticationManager;
public SmsTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
protected SmsTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
String username = parameters.get("mobile");
String password = parameters.get("smsCode");
Authentication userAuth = new SmsCodeAuthenticationToken(username, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
}
catch (AccountStatusException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/invalid grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
这是已完成的代码,根据 ResourceOwnerPasswordTokenGranter
修改而来,需要关注的地方有两个:
- 代码第 3 行,
GRANT_TYPE = "sms"
,认证类型为sms
。OAuth 2.0 共包含四种模式,分别是authorization_code
、implicit
、password
、client_credential
。我们现在为其添加sms
模式。 - 代码第 22 到 25 行,从请求中获取手机号和验证码参数,然后封装成一个
SmsCodeAuthenticationToken
对象。SmsCodeAuthenticationToken
也是我们为手机验证码模式创建的类,在 Spring Security 自定义认证逻辑 中已经写过了,具体的写法和作用,可以参考这篇文章。
Authentication 和 AuthenticationProvider
SmsCodeAuthenticationToken
是我们为了手机验证码模式创建的 Authentication 实现类。另外,在 SmsTokenGranter 的 getOAuth2Authentication 方法中,还有一行关键的代码:
userAuth = authenticationManager.authenticate(userAuth);
如果你阅读了文章开头的关联文章,你应该了解这句代码调用的方法背后,实现了认证的逻辑,并且,这里需要实现一个与手机验证码模式对应的 AuthenticationProvider
类,这个类我们在之前的文章里也是先过了,参考 Spring Security 自定义认证逻辑 中 SmsCodeAuthenticationProvider
实现的部分。
配置
以上就是所有需要创建的类,接下来完成配置。
这里有个前提,就是已经配置好了 Spring Security OAuth 的功能,以下内容直接少我们需要在此基础上需要增加那些配置。
首先,在 Spring Security OAuth 的授权服务器配置类,也就是 AuthorizationServerConfigurerAdapter
的子类中的 configure(AuthorizationServerEndpointsConfigurer endpoints)
方法中,添加如下配置:
List<TokenGranter> tokenGranters = new ArrayList<>();
tokenGranters.add(new AuthorizationCodeTokenGranter(endpoints.getTokenServices(), endpoints.getAuthorizationCodeServices(), clientDetailsService,
endpoints.getOAuth2RequestFactory()));
tokenGranters.add(new RefreshTokenGranter(endpoints.getTokenServices(), clientDetailsService, endpoints.getOAuth2RequestFactory()));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(endpoints.getTokenServices(), clientDetailsService, endpoints.getOAuth2RequestFactory());
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(endpoints.getTokenServices(), clientDetailsService, endpoints.getOAuth2RequestFactory()));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(), clientDetailsService, endpoints.getOAuth2RequestFactory()));
}
tokenGranters.add(new SmsTokenGranter(authenticationManager, endpoints.getTokenServices(),
clientDetailsService, endpoints.getOAuth2RequestFactory()));
endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
以上代码中,就是将 Spring Security OAuth 内置的四种授权模式的 Granter 添加到一个列表中,再将我们刚才创建的 SmsTokenGranter 添加进去,最后在封装成一个 CompositeTokenGranter 并配置到 endpoints 中。
这里可以参考下之前的这篇文章:Spring Security OAuth 之 @EnableAuthorizationServer 干了啥?
然后,在 Spring Security 的配置类,也就是 WebSecurityConfigurerAdapter 的子类中,添加 SmsCodeAuthenticationProvider 的 Bean:
@Bean
public SmsCodeAuthenticationProvider smsCodeAuthenticationProvider() {
SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
return smsCodeAuthenticationProvider;
}
再在 configure(HttpSecurity http) 将其配置到 http 中:
http.authenticationProvider(smsCodeAuthenticationProvider())
其余的诸如发送验证码接口等内容,参考之前的文章。