Oauth2 实现短信验证码登陆

一. 背景
最近在学习并使用SpringSecurty Oauth2, 已经实现账号密码的授权登陆, 需要新增一个手机号验证码的授权登陆.

在翻阅大量文章, 发现实现方式都比较复杂, 大部分是自己写filter和拦截器来做处理. 代码量较大, 而且不利于阅读跟扩展.

经过一整天的学习和探索, 大概明白Oauth2四种场景的授权流程, 又恰好有幸看到某一个大神的文章, 给予启发: https://www.appblog.cn/2019/10/09/Spring Security Oauth2 中优雅的扩展自定义(短信验证码)登录方式/

如果您正好和我有一样的需求, 那么恭喜您, 参考此文章能解决您的问题 (本人已踩大量的坑)

二. 实现思路
经过学习发现, SpringSecurityOauth2的登陆逻辑, 是有该org.springframework.security.oauth2.provider.TokenGranter完成的, 账号密码的验证是在对应实现类org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter里完成.
SpringSecurity找对应的TokenGranter是根据grant_type找到的, 比如账号密码的登陆, 我们需要传一个grant_type:password, SpringSecurty就会根据grant_type找到对应的TokenGranter.
所以发现, 我们可以自定义一个SMSCodeTokenGranter也去实现TokenGranter,用来验证手机号验证码的登陆, 然后添加一个grant_type为sms.

这里的难点是, SpringSecurity内置写死了TokenGranter, 所以我们需要覆盖掉原来的 使用自己的.

三. 代码实现
授权认证配置

@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
     
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private UserDetailServiceImpl userDetailService;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;
    @Autowired
    public CustomUserDetailsService customUserDetailsService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
     
        clients.inMemory()
                .withClient("tplus-client")
                .secret(passwordEncoder.encode("Jason295"))
                .scopes("all")
                // sms 为自己添加的授权方式
                .authorizedGrantTypes("password", "refresh_token", "sms")
                .accessTokenValiditySeconds(3600*24) // 24小时
                .refreshTokenValiditySeconds(3600*24*7); // 7天
    }

    /**
     * 自定义TokenGranter
     */
    private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
     
        TokenGranter tokenGranter = new TokenGranter() {
     
            private CompositeTokenGranter delegate;

            @Override
            public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
     
                if (delegate == null) {
     
                    delegate = new CompositeTokenGranter(getDefaultTokenGranters(endpoints));
                }
                return delegate.grant(grantType, tokenRequest);
            }
        };
        return tokenGranter;
    }

    /**
     * 这是从spring 的代码中 copy出来的, 默认的几个TokenGranter, 还原封不动加进去.
     * 主要目的是覆盖原来的List,方便我们添加自定义的授权方式,比如SMSCodeTokenGranter短信验证码授权
     */
    private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {
     
        AuthorizationServerTokenServices tokenServices = endpoints.getDefaultAuthorizationServerTokenServices();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
        OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();
        List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices,
                authorizationCodeServices, endpoints.getClientDetailsService(), requestFactory));
        tokenGranters.add(new RefreshTokenGranter(tokenServices, endpoints.getClientDetailsService(), requestFactory));
        ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, endpoints.getClientDetailsService(),
                requestFactory);
        tokenGranters.add(implicit);
        tokenGranters.add(
                new ClientCredentialsTokenGranter(tokenServices, endpoints.getClientDetailsService(), requestFactory));
        if (authenticationManager != null) {
     
            tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager,
                    tokenServices, endpoints.getClientDetailsService(), requestFactory));
        }
        // 这里就是我们自己的授权验证
        tokenGranters.add(new SMSCodeTokenGranter(tokenServices, endpoints.getClientDetailsService(), requestFactory, "sms"));
        // 再有其他的验证, 就往下面添加....
        return tokenGranters;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
     
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(accessTokenConverter());
        enhancerChain.setTokenEnhancers(delegates); // 配置JWT的内容增强器
        // 替换成我们自定义的TokenGranter,因为里面包含我们自己的授权验证
        endpoints.tokenGranter(tokenGranter(endpoints));
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailService) // 配置加载用户信息的服务
                .accessTokenConverter(accessTokenConverter())
                .tokenEnhancer(enhancerChain);
    }

    /**
     * 必须要有这个配置,不然Oauth2的授权请求会报错401没有权限.
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
     
        security.allowFormAuthenticationForClients();
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
     
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setKeyPair(keyPair());
        return jwtAccessTokenConverter;
    }
	
	/**
	 * 这里使用的是jwt的keyPair
	 */
    @Bean
    public KeyPair keyPair() {
     
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
    }
}

自定义的短信验证码授权验证

public class SMSCodeTokenGranter extends AbstractTokenGranter {
     

    public SMSCodeTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
     
        super(tokenServices, clientDetailsService, requestFactory, grantType);
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
     
        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
        String userMobileNo = parameters.get("mobile");  //客户端提交的用户名
        String smsCode = parameters.get("smscode");  //客户端提交的验证码
        
        /** 下面写自己的验证逻辑 */
        SecurityUser user = new SecurityUser();
        user.setAppId("111");
        user.setPhone("11111111");

        Authentication userAuth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);

        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }
}

四. Postman测试

  1. 账号密码授权方式
    Oauth2 实现短信验证码登陆_第1张图片
  2. 手机号验证码方式
    Oauth2 实现短信验证码登陆_第2张图片

你可能感兴趣的:(SpringCloud,SpringSecurity,Oauth2)