spring-boot-starter-oauth2-client使用

spring-boot-starter-oauth2-client : 这个是 spring boot提供的 OAuth2/OIDC客户端自动配置的starter,, 不用的原因
它提供的是标准的oauth2规范,但是国内很多厂商都不是标准的参数,,比如,传递的参数也不一定规范,微信,qq获取资源除了需要携带access_token还需要带上openId,

  • 某些平台获取user需要带额外参数 ,,就需要自定义 OAuth2UserService去获取用户
  • 某些平台token返回的格式不标准,,这时候就需要改OAuth2AccessTokenResponseClient
  • oauth2-client需要自己去适配这些端点,justAuth里面有现成的各个适配第三方的,不需要自己扩展
    和spring security强绑定,很多系统只想要第三方用户信息,,但是spring security将获取到的用户信息封装成了 OAuth2AuthenticationToken,然后放入spring security的上下文中,,他不是简单的获取用户信息,,,oauth登录就必须走spring security的这套认证架构,,不能只用其中一部分

oauth2的登录其实也是一种Provider,去通过userService加载第三方的用户,他有两个拦截器,都是拦截指定的url,进行操作:

  • OAuth2AuthorizationRequestRedirectFilter : 拦截指定url,生成第三方授权链接
  • OAuth2LoginAuthenticationFilter : 拦截指定url,,处理登录认证

代码:
认证信息:

public interface Authentication {

    Object getPrincipal();

    Object getCredentials();

    boolean isAuthenticated();

    void setAuthenticated(boolean authenticated);

}
@Data
public class OAuth2LoginAuthenticationToken implements Authentication{


    private ClientRegistration clientRegistration;


    private String code;

    private OAuth2User user;

    private boolean authenticated;

    public OAuth2LoginAuthenticationToken(ClientRegistration clientRegistration, String code) {
        this.clientRegistration = clientRegistration;
        this.code = code;
    }

    @Override
    public Object getPrincipal() {
        return user;
    }

    @Override
    public Object getCredentials() {
        return code;
    }

    @Override
    public boolean isAuthenticated() {
        return authenticated;
    }

    @Override
    public void setAuthenticated(boolean authenticated) {
        this.authenticated = authenticated;
    }
}

第三方官方信息:

/**
 * oauth2 客户端信息
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClientRegistration {


    private String clientId;

    private String clientSecret;


    private String authorizationUri;

    private String tokenUri;

    private String userInfoUri;

}

/**
 * 客户端仓库
 * 用来多存多个  oauth2 client
 */
public class ClientRegistrationRepository {


    private Map<String, ClientRegistration> registrations = new HashMap<>();

    /**
     * 添加一个 oauth2 的客户端
     * @param id
     * @param registration
     */
    public void add(String id, ClientRegistration registration){
        registrations.put(id,registration);
    }


    public ClientRegistration find(String id){
        return registrations.get(id);
    }



}

认证器:

public class AuthenticationManager {


    private List<AuthenticationProvider> providers;

    public AuthenticationManager(List<AuthenticationProvider> providers) {
        this.providers = providers;
    }


    public Authentication authentication(Authentication authentication){
        for (AuthenticationProvider provider : providers) {

            if (provider.supports(authentication)) {
                return provider.authenticate(authentication);
            }
        }

        throw new RuntimeException("没有对应的provider");
    }
}

public interface AuthenticationProvider {


   Authentication authenticate(Authentication authentication);

    boolean supports(Authentication authentication);
}
/**
 * 用code 换 access_token
 */
public class OAuth2AuthorizationCodeAuthenticationProvider {



    private OAuth2AccessTokenResponseClient tokenClient;

    private OAuth2UserService userService;

    public OAuth2AuthorizationCodeAuthenticationProvider(OAuth2AccessTokenResponseClient tokenClient, OAuth2UserService userService) {
        this.tokenClient = tokenClient;
        this.userService = userService;
    }

    public Authentication authenticate(OAuth2LoginAuthenticationToken token){
        ClientRegistration registration = token.getClientRegistration();
        String code = token.getCode();
        OAuth2AccessToken accessToken = tokenClient.getToken(registration, code);

        // 换用户信息
        OAuth2User user = userService.loadUser(accessToken);

        token.setAuthenticated(true);
        token.setUser(user);

        return token;
    }

}
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider{


    /**
     * 为什么要多拆一个 provider,,,是因为这个provider不止给login用,,,OAuth2的其他场景也需要,通过code换取token
     */
    private OAuth2AuthorizationCodeAuthenticationProvider codeProvider;

    public OAuth2LoginAuthenticationProvider(OAuth2AuthorizationCodeAuthenticationProvider codeProvider) {
        this.codeProvider = codeProvider;
    }

    @Override
    public Authentication authenticate(Authentication authentication) {

        OAuth2LoginAuthenticationToken token = (OAuth2LoginAuthenticationToken) authentication;

        return codeProvider.authenticate(token);

    }

    @Override
    public boolean supports(Authentication authentication) {
        return OAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication.getClass());
    }
}

获取用户信息:

/**
 * oauth2 用户
 */
@Getter
public class OAuth2User {

    private String username;

    public OAuth2User(String username) {
        this.username = username;
    }


}
public interface OAuth2UserService {

    OAuth2User loadUser(OAuth2AccessToken accessToken);
}
public class DefaultOAuth2UserService implements OAuth2UserService{
    @Override
    public OAuth2User loadUser(OAuth2AccessToken accessToken) {
        System.out.println("request userinfo with token"+accessToken.getTokenValue());

        OAuth2User githubUser = new OAuth2User("githubUser");
        return githubUser;
    }
}
public interface OAuth2AccessTokenResponseClient {

    /**
     * 获取token
     * @param registration
     * @param code
     * @return
     */
    OAuth2AccessToken getToken(ClientRegistration registration,String code);
}

public class DefaultAuthorizationCodeTokenResponseClient implements OAuth2AccessTokenResponseClient{


    @Override
    public OAuth2AccessToken getToken(ClientRegistration registration, String code) {
        System.out.println("code = " + code);
        System.out.println("post "+registration.getTokenUri());
        System.out.println("clientId="+registration.getClientId());


        return new OAuth2AccessToken("mock_access_token");
    }
}

过滤器:


/**
 * 处理oauth的回调
 *  /login/oauth2/code/{}
 */
public class OAuth2LoginAuthenticationFilter {

    private ClientRegistrationRepository repository;

    private AuthenticationManager authenticationManager;

    public OAuth2LoginAuthenticationFilter(ClientRegistrationRepository repository, AuthenticationManager authenticationManager) {
        this.repository = repository;
        this.authenticationManager = authenticationManager;
    }


    public void attemptAuthentication(String registrationId,String code){
        ClientRegistration registration = repository.find(registrationId);

        OAuth2LoginAuthenticationToken token = new OAuth2LoginAuthenticationToken(registration, code);

        Authentication authentication = authenticationManager.authentication(token);


        System.out.println("认证成功:"+((OAuth2User) authentication.getPrincipal()).getUsername());
    }
}

测试:

public class Main {

    public static void main(String[] args) {


        ClientRegistration github = new ClientRegistration(
                        "github",
                        "client-id",
                        "secret",
                        "https://github.com/login/oauth/access_token","http://xxx/xx");


        ClientRegistrationRepository repository = new ClientRegistrationRepository();
        repository.add("github",github);


        // 用code换token
        DefaultAuthorizationCodeTokenResponseClient tokenClient = new DefaultAuthorizationCodeTokenResponseClient();

        DefaultOAuth2UserService userService = new DefaultOAuth2UserService();

        OAuth2AuthorizationCodeAuthenticationProvider codeProvider = new OAuth2AuthorizationCodeAuthenticationProvider(tokenClient, userService);


        OAuth2LoginAuthenticationProvider provider = new OAuth2LoginAuthenticationProvider(codeProvider);


        AuthenticationManager manager = new AuthenticationManager(List.of(provider));

        OAuth2LoginAuthenticationFilter filter = new OAuth2LoginAuthenticationFilter(repository, manager);


        filter.attemptAuthentication("github","mock_code");


    }
}

公众号:代码源记

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐