How to Create a Custom SMS Code Grant Type in Spring OAuth2
This tutorial explains how to extend Spring Security's OAuth2 authorization server by implementing a custom SMS‑code grant type, covering the necessary configuration classes, bean definitions, token store setup, and client usage examples.
Overview
The article builds on a previous discussion of Spring OAuth2's four standard grant types (Authorization Code, Implicit, Password, Client) and shows how to create a custom grant type when those do not satisfy business requirements.
Spring Security’s extensibility is demonstrated by extending AuthorizationServerConfigurerAdapter and overriding three key configuration methods:
configure(ClientDetailsServiceConfigurer clients) configure(AuthorizationServerEndpointsConfigurer endpoints) configure(AuthorizationServerSecurityConfigurer oauthServer)The second method, AuthorizationServerEndpointsConfigurer, provides a rich API ( AuthorizationServerEndpointsConfigurer) with many configurable options. The most relevant ones for the custom grant are:
6 – authenticationManager(AuthenticationManager) 13 – tokenGranter(TokenGranter) 14 – tokenServices(AuthorizationServerTokenServices) 15 – tokenStore(TokenStore) 16 – userDetailsService(UserDetailsService) Example bean definitions for password encoding and global user details:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService())
.passwordEncoder(passwordEncoder());
}Token storage can be configured to use Redis:
@Bean
public RedisTemplateTokenStore tokenStore() {
return new RedisTemplateTokenStore(redisTemplate);
}To illustrate the default grant implementations, the source of ResourceOwnerPasswordTokenGranter is shown, highlighting its inheritance from AbstractTokenGranter and the overridden getOAuth2Authentication method.
Creating a custom SMS‑code grant involves defining a new class that extends AbstractTokenGranter:
public class SMSCodeTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "sms_code";
private final AuthenticationManager authenticationManager;
private MyUserDetailsService myUserDetailsService;
private StringRedisTemplate stringRedisTemplate;
public SMSCodeTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory,
MyUserDetailsService myUserDetailsService,
StringRedisTemplate stringRedisTemplate) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.myUserDetailsService = myUserDetailsService;
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
String userName = parameters.get("userName");
String userPhone = parameters.get("userPhone");
String smsCode = parameters.get("smsCode");
UserDetails user = myUserDetailsService.loadUserByUsername(userName);
if (user == null) {
throw new InvalidGrantException("用户不存在");
}
String cachedCode = stringRedisTemplate.opsForValue().get("sms:vc:" + userPhone);
if (StringUtils.isBlank(cachedCode)) {
throw new InvalidGrantException("用户没有发送验证码");
}
if (!smsCode.equals(cachedCode)) {
throw new InvalidGrantException("验证码不正确");
}
Authentication userAuth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}The custom grant must be added to the list of token granters returned by getDefaultTokenGranters() and the composite token granter must delegate to this extended list:
tokenGranters.add(new SMSCodeTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory, myUserDetailsService, stringRedisTemplate));Finally, the endpoint configuration registers the custom tokenGranter:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.userDetailsService(myUserDetailsService)
.tokenGranter(tokenGranter())
.authenticationManager(authenticationManager);
endpoints.tokenServices(defaultTokenServices());
}Usage example with RestTemplate shows how to request a token using the new sms_code grant type:
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> request = new LinkedMultiValueMap<>();
request.add("client_id", loginParam.getClientId());
request.add("client_secret", loginParam.getClientSecret());
request.add("grant_type", "sms_code");
request.add("user_name", userInfoVo.getUserName());
request.add("user_phone", loginParam.getUserPhone());
request.add("sms_code", loginParam.getSmsCode());
Object response = restTemplate.postForObject(authServiceUrl + "/oauth/token", request, Object.class);Constants for the request parameters are defined as static final strings (e.g., KEY_CLIENT_ID = "client_id").
Architect's Alchemy Furnace
A comprehensive platform that combines Java development and architecture design, guaranteeing 100% original content. We explore the essence and philosophy of architecture and provide professional technical articles for aspiring architects.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
