OAuth2.0 - 自定义模式授权 - 短信验证码登录

Posted 小毕超

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OAuth2.0 - 自定义模式授权 - 短信验证码登录相关的知识,希望对你有一定的参考价值。

一、OAuth2.0 - 自定义模式授权

上篇文章我们分析了目前的情况,演示了微服务的大环境下在保证安全的情况下通过SpringGateWay实现统一的鉴权处理,但是前面的演示中,我们都是基于用户名密码的方式,但是现在已经普及短信验证码登录、微信登录、QQ登录等这些第三方的登录方式,这些方式显然不在Oauth2.0提供的四种授权模式下,因此我们如果要实现第三方的登录需要自定义一个授权模式,下面我们就以短信验证码登录为例进行实现。

下面是上篇文章的地址:

https://blog.csdn.net/qq_43692950/article/details/122566821

二、短信验证码登录

SpringOauth2.0虽说只有四种授权模式,但是他提供了非常高的扩展性,我们可以很方便的进行授权模式的扩展,而无需大量的改动,在SpringOauth2.0中扩展授权模式,只需继承AbstractTokenGranter类,并在构造方法中super父类时,指定自己的授权标识,当用户认证传入grant_type为该标识,则会触发该生成器的getOAuth2Authentication方法,我们可以在这里获取客户端传入的参数,比如手机号和验证码,然后我们再和预先的验证码对比,如果OK,可以根据手机号获取用户的信息组成UsernamePasswordAuthenticationToken对象再交给Oauth2即可:

下面编写一个SmsCodeTokenGranter这里只是演示功能,直接将用户和验证码写死了:

public class SmsCodeTokenGranter extends AbstractTokenGranter 

    private static final String SMS_GRANT_TYPE = "sms_code";

    private UserService userService;

    public SmsCodeTokenGranter(AuthorizationServerTokenServices tokenServices,
                               ClientDetailsService clientDetailsService,
                               OAuth2RequestFactory requestFactory,
                               UserService userService) 
        super(tokenServices, clientDetailsService, requestFactory, SMS_GRANT_TYPE);
        this.userService = userService;
    


    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client,
                                                           TokenRequest tokenRequest) 

        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());

        System.out.println(parameters.toString());
        // 客户端提交的手机号码
        String phoneNumber = parameters.get("phone");
        if (StringUtils.isBlank(phoneNumber)) 
            throw new AccessDeniedException("get phone is null !");
        

        // 客户端提交的验证码
        String smsCode = parameters.get("code");
        if (!smsCode.equals("9876")) 
            throw new AccessDeniedException("code err!");
        

        UserDetails user = userService.loadUserByUsername("admin");

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

        userAuth.setDetails(parameters);
        OAuth2Request oAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(oAuth2Request, userAuth);
    

下面还需修改AuthorizationServer将上面的生成器配制进AuthorizationServerEndpointsConfigurer中:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter 

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    @Qualifier("jdbcClientDetailsService")
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Autowired
    UserService userService;

    @Autowired
    PasswordEncoder passwordEncoder;


    @Bean("jdbcClientDetailsService")
    public ClientDetailsService clientDetailsService(DataSource dataSource) 
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
        clients.withClientDetails(clientDetailsService);
    


    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) 
        return new JdbcAuthorizationCodeServices(dataSource);//设置授权码模式的授权码如何存取
    

    @Bean
    public AuthorizationServerTokenServices tokenService() 
        DefaultTokenServices service = new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService);
        service.setSupportRefreshToken(true);
        service.setTokenStore(tokenStore);
        //令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        //内容增强
        tokenEnhancers.add(tokenEnhancer());
        tokenEnhancers.add(accessTokenConverter);
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
        service.setTokenEnhancer(tokenEnhancerChain);

        service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
        service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
        return service;
    

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) 
        endpoints
                .authenticationManager(authenticationManager)//认证管理器
                .authorizationCodeServices(authorizationCodeServices)//授权码服务
                .tokenGranter(tokenGranter(endpoints))
                .tokenServices(tokenService())//令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    


    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception 
        security
                .tokenKeyAccess("permitAll()")                    //oauth/token_key是公开
                .checkTokenAccess("permitAll()")                  //oauth/check_token公开
                .allowFormAuthenticationForClients();                //表单认证(申请令牌)
    

    /**
     * JWT内容增强
     */
    @Bean
    public TokenEnhancer tokenEnhancer() 
        return (accessToken, authentication) -> 
            Map<String, Object> additionalInfo = new HashMap<>();
            additionalInfo.put("userid", -1);
            Authentication auth = authentication.getUserAuthentication();
            if (auth != null) 
                UserEntity user = (UserEntity) auth.getPrincipal();
                additionalInfo.put("userid", user.getId());
            else 
                String clientId = authentication.getOAuth2Request().getClientId();
                String grantType = authentication.getOAuth2Request().getRequestParameters().get("grant_type");
                if (Objects.equals(clientId, "c1") && Objects.equals(grantType, "client_credentials")) 
                    additionalInfo.put("userid", "root");
                
            
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        ;
    

    /**
     * 添加自定义授权类型
     *
     * @return List<TokenGranter>
     */
    private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) 

        // endpoints.getTokenGranter() 获取SpringSecurity OAuth2.0 现有的授权类型
        List<TokenGranter> granters = new ArrayList<TokenGranter>(Collections.singletonList(endpoints.getTokenGranter()));

        // 构建短信验证授权类型
        SmsCodeTokenGranter smsCodeTokenGranter = new SmsCodeTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
                        endpoints.getOAuth2RequestFactory(),userService);
        // 向集合中添加短信授权类型
        granters.add(smsCodeTokenGranter);
        // 返回所有类型
        return new CompositeTokenGranter(granters);
    
    

三、测试

使用PostMan发送POST请求:

http://localhost:8020/oauth/token?client_id=c1&client_secret=secret&grant_type=sms_code&phone=110&code=9562

注意:grant_type要传sms_code

可以看到返回的是Unauthorized grant type: sms_code未授权的类型,这个是因为c1这个客户端没有sms_code认证类型,给c1添加上sms_code类型:


下面重新测试:

已经有我我们写的返回了,下面输入正确的验证码:

http://localhost:8020/oauth/token?client_id=c1&client_secret=secret&grant_type=sms_code&phone=110&code=9876


已经成功获取到令牌。


喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!

以上是关于OAuth2.0 - 自定义模式授权 - 短信验证码登录的主要内容,如果未能解决你的问题,请参考以下文章

OAuth2.0学习(4-12)spring-oauth-server分析 - 授权码模式验证过程

使用 Keycloak 构建 Java OAuth2.0 授权服务器

OAuth2.0的四种授权模式

Spring Security OAuth2.0认证授权介绍

#yyds干货盘点# OAuth2.0授权码模式实战

SpringSecurity+Oauth2.0登录认证+自定义验证