Spring Cloud OAuth2 自定义授权类型实现其他方式登录(短信验证码登录、手机号密码登录)

实现效果

使用手机号密码进行授权:
图片描述
使用手机号短信验证码进行授权:
图片描述
通过访问令牌获取当前用户细节:
图片描述

添加依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>

创建用户细节服务

@Service
public class CustomUserDetailsService {

    public UserDetails loadUserByPhoneAndPassword(String phone, String password) {
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(password)) {
            throw new InvalidGrantException("无效的手机号或短信验证码");
        }
        // 判断成功后返回用户细节
        return new User(phone, "", AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user,root"));
    }

    public UserDetails loadUserByPhoneAndSmsCode(String phone, String smsCode) {
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(smsCode)) {
            throw new InvalidGrantException("无效的手机号或短信验证码");
        }
        // 判断成功后返回用户细节
        return new User(phone, "", AuthorityUtils.commaSeparatedStringToAuthorityList("admin,user,root"));
    }

}

创建自定义抽象令牌授予者

public abstract class AbstractCustomTokenGranter extends AbstractTokenGranter {

    private final OAuth2RequestFactory requestFactory;

    protected AbstractCustomTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.requestFactory = requestFactory;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = tokenRequest.getRequestParameters();
        CustomUser customUser = getCustomUser(parameters);
        if (customUser == null) {
            throw new InvalidGrantException("无法获取用户信息");
        }
        OAuth2Request storedOAuth2Request = this.requestFactory.createOAuth2Request(client, tokenRequest);
        PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(customUser, null, customUser.getAuthorities());
        authentication.setDetails(customUser);
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(storedOAuth2Request, authentication);
        return oAuth2Authentication;
    }

    protected abstract UserDetails getUserDetails(Map<String, String> parameters);
}

手机号密码登录令牌授予者

public class PhonePasswordCustomTokenGranter extends AbstractCustomTokenGranter {

    private CustomUserDetailsService userDetailsService;

    public PhonePasswordCustomTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, CustomUserDetailsService userDetailsService) {
        super(tokenServices, clientDetailsService, requestFactory,"custom_phone_pwd");
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected UserDetails getUserDetails(Map<String, String> parameters) {
        String phone = parameters.get("phone");
        String password = parameters.get("password");
        return userDetailsService.loadUserByPhoneAndPassword(phone, password);
    }
}

短信验证码登录令牌授予者

public class PhoneSmsCustomTokenGranter extends AbstractCustomTokenGranter {

    private CustomUserDetailsService userDetailsService;

    public PhoneSmsCustomTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, CustomUserDetailsService userDetailsService) {
        super(tokenServices, clientDetailsService, requestFactory,"custom_phone_sms");
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected UserDetails getUserDetails(Map<String, String> parameters) {
        String phone = parameters.get("phone");
        String smsCode = parameters.get("sms_code");
        return userDetailsService.loadUserByPhoneAndSmsCode(phone, smsCode);
    }
}

启用授权服务器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public CustomUserDetailsService customUserDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //定义一个客户端支持自定义的授权类型
        clients.inMemory()
                .withClient("demo")
                .secret(passwordEncoder().encode("demo"))
                .authorizedGrantTypes("custom_phone_pwd","custom_phone_sms")
                .scopes("all");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()");
    }

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        List<TokenGranter> tokenGranters = getTokenGranters(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
        
        endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
        endpoints.tokenEnhancer(new TokenEnhancer() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
                DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken;
                CustomUser user = (CustomUser) oAuth2Authentication.getPrincipal();
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("nickname", user.getNickname());
                map.put("mobile", user.getMobile());
                map.put("avatar",user.getAvatar());
                token.setAdditionalInformation(map);
                return oAuth2AccessToken;
            }
        });

    }

    private List<TokenGranter> getTokenGranters(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        return new ArrayList<>(Arrays.asList(
                new PhoneSmsCustomTokenGranter(tokenServices, clientDetailsService, requestFactory, customUserDetailsService),
                new PhonePasswordCustomTokenGranter(tokenServices, clientDetailsService, requestFactory, customUserDetailsService)
        ));
    }
}

开启资源认证

@Configuration
@EnableResourceServer
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
    }
}

实现获取用户细节接口

@RestController
public class AuthController {
    @RequestMapping("/current-info")
    public Object getUser(Authentication authentication) {
        return authentication;
    }
}

项目源码

https://gitee.com/yugu/demo-o...

总结

通过继承AbstractCustomTokenGranter抽象令牌授予者类实现getUserDetails方法获取需要的参数并调用CustomUserDetailsService用户细节服务类的方法认证获取用户细节数据。

阅读 6.6k

推荐阅读