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.

Architect's Alchemy Furnace
Architect's Alchemy Furnace
Architect's Alchemy Furnace
How to Create a Custom SMS Code Grant Type in Spring OAuth2

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").

JavaSpringOAuth2Custom GrantSMS Authentication
Architect's Alchemy Furnace
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.