Java实现OAuth2客户端案例

wen java案例 1

本文目录导读:

Java实现OAuth2客户端案例

  1. Maven依赖配置
  2. 配置文件 application.yml
  3. 主应用类
  4. 安全配置类
  5. 自定义请求实体转换器
  6. 自定义OAuth2用户服务
  7. 自定义OAuth2用户类
  8. 控制器类
  9. API客户端调用示例
  10. HTML模板
  11. 配置文件 application.properties (备用)
  12. 运行说明
  13. 完整功能说明

我来提供一个完整的Java实现OAuth2客户端的案例,使用Spring Security OAuth2 Client框架。

Maven依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>oauth2-client-demo</artifactId>
    <version>1.0.0</version>
    <name>oauth2-client-demo</name>
    <description>OAuth2 Client Demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Security OAuth2 Client -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Thymeleaf模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- WebClient for HTTP requests -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

配置文件 application.yml

spring:
  application:
    name: oauth2-client-demo
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: your-github-client-id
            client-secret: your-github-client-secret
            scope:
              - read:user
              - user:email
          google:
            client-id: your-google-client-id
            client-secret: your-google-client-secret
            scope:
              - email
              - profile
          # 自定义OAuth2服务提供商
          custom:
            client-id: your-custom-client-id
            client-secret: your-custom-client-secret
            client-name: Custom OAuth2 Provider
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope:
              - read
              - write
        provider:
          github:
            authorization-uri: https://github.com/login/oauth/authorize
            token-uri: https://github.com/login/oauth/access_token
            user-info-uri: https://api.github.com/user
            user-name-attribute: id
          google:
            authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
            token-uri: https://oauth2.googleapis.com/token
            user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
            user-name-attribute: sub
          custom:
            authorization-uri: https://your-provider.com/oauth2/authorize
            token-uri: https://your-provider.com/oauth2/token
            user-info-uri: https://your-provider.com/api/userinfo
            user-name-attribute: sub
server:
  port: 8080
logging:
  level:
    org.springframework.security: DEBUG

主应用类

package com.example.oauth2client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OAuth2ClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(OAuth2ClientApplication.class, args);
    }
}

安全配置类

package com.example.oauth2client.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/login", "/oauth2/**", "/error").permitAll()
                .anyRequest().authenticated()
                .and()
            .oauth2Login()
                .loginPage("/login")
                .defaultSuccessUrl("/home", true)
                .successHandler(successHandler())
                .userInfoEndpoint()
                    .userService(oAuth2UserService())
                .and()
                .failureUrl("/login?error=true")
                .and()
            .logout()
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID");
    }
    @Bean
    public OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService() {
        DefaultOAuth2UserService service = new DefaultOAuth2UserService();
        service.setRequestEntityConverter(new CustomRequestEntityConverter());
        return new CustomOAuth2UserService(service);
    }
    @Bean
    public AuthenticationSuccessHandler successHandler() {
        SavedRequestAwareAuthenticationSuccessHandler handler = 
            new SavedRequestAwareAuthenticationSuccessHandler();
        handler.setDefaultTargetUrl("/home");
        handler.setTargetUrlParameter("redirect");
        handler.setAlwaysUseDefaultTargetUrl(false);
        return handler;
    }
}

自定义请求实体转换器

package com.example.oauth2client.config;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequestEntityConverter;
public class CustomRequestEntityConverter 
    implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> {
    private final OAuth2AuthorizationCodeGrantRequestEntityConverter 
        defaultConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
    @Override
    public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest request) {
        RequestEntity<?> entity = defaultConverter.convert(request);
        // 可以在这里添加自定义的Headers或修改请求参数
        return entity;
    }
}

自定义OAuth2用户服务

package com.example.oauth2client.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final DefaultOAuth2UserService defaultOAuth2UserService;
    public CustomOAuth2UserService() {
        this.defaultOAuth2UserService = new DefaultOAuth2UserService();
    }
    public CustomOAuth2UserService(DefaultOAuth2UserService defaultOAuth2UserService) {
        this.defaultOAuth2UserService = defaultOAuth2UserService;
    }
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        log.info("Loading user from provider: {}", userRequest.getClientRegistration().getRegistrationId());
        OAuth2User oauth2User = defaultOAuth2UserService.loadUser(userRequest);
        // 创建自定义User对象,可以在这里添加业务逻辑
        Map<String, Object> attributes = new HashMap<>(oauth2User.getAttributes());
        attributes.put("provider", userRequest.getClientRegistration().getRegistrationId());
        return new CustomOAuth2User(
            oauth2User.getAuthorities(),
            attributes,
            userRequest.getClientRegistration().getProviderDetails()
                .getUserInfoEndpoint().getUserNameAttributeName()
        );
    }
}

自定义OAuth2用户类

package com.example.oauth2client.config;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import java.util.Collection;
import java.util.Map;
@Getter
public class CustomOAuth2User extends DefaultOAuth2User {
    private final String provider;
    public CustomOAuth2User(Collection<? extends GrantedAuthority> authorities,
                           Map<String, Object> attributes,
                           String nameAttributeKey) {
        super(authorities, attributes, nameAttributeKey);
        this.provider = (String) attributes.get("provider");
    }
    public String getEmail() {
        return (String) this.getAttributes().get("email");
    }
    public String getNameAttribute() {
        return this.getAttribute(this.getNameAttributeKey());
    }
    public String getPicture() {
        return (String) this.getAttributes().get("picture");
    }
    public String getAvatarUrl() {
        // 对GitHub支持
        return (String) this.getAttributes().getOrDefault("avatar_url", 
                this.getAttributes().get("picture"));
    }
}

控制器类

package com.example.oauth2client.controller;
import com.example.oauth2client.config.CustomOAuth2User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.security.Principal;
import java.util.Map;
@Slf4j
@Controller
public class HomeController {
    @GetMapping("/")
    public String index(Principal principal) {
        return principal != null ? "redirect:/home" : "index";
    }
    @GetMapping("/login")
    public String login() {
        return "login";
    }
    @GetMapping("/home")
    public String home(@AuthenticationPrincipal OAuth2User oauth2User, Model model) {
        model.addAttribute("name", oauth2User.getAttribute("name"));
        model.addAttribute("email", oauth2User.getAttribute("email"));
        model.addAttribute("avatar", getAvatar(oauth2User));
        if (oauth2User instanceof CustomOAuth2User) {
            CustomOAuth2User customUser = (CustomOAuth2User) oauth2User;
            model.addAttribute("provider", customUser.getProvider());
        }
        return "home";
    }
    @GetMapping("/profile")
    public String profile(@AuthenticationPrincipal OAuth2User oauth2User, Model model) {
        Map<String, Object> attributes = oauth2User.getAttributes();
        model.addAttribute("attributes", attributes);
        model.addAttribute("authorities", oauth2User.getAuthorities());
        return "profile";
    }
    @GetMapping("/user")
    public String user(@AuthenticationPrincipal OAuth2User oauth2User, Model model) {
        model.addAttribute("user", oauth2User);
        return "user";
    }
    private String getAvatar(OAuth2User oauth2User) {
        String avatarUrl = oauth2User.getAttribute("avatar_url");
        if (avatarUrl == null) {
            avatarUrl = oauth2User.getAttribute("picture");
        }
        return avatarUrl != null ? avatarUrl : "/images/default-avatar.png";
    }
}

API客户端调用示例

package com.example.oauth2client.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Slf4j
@Controller
public class ApiController {
    @Autowired
    private OAuth2AuthorizedClientService authorizedClientService;
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("/api/data")
    public String getApiData(Model model) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication instanceof OAuth2AuthenticationToken) {
            OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
            String clientRegistrationId = oauthToken.getAuthorizedClientRegistrationId();
            OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(
                clientRegistrationId,
                oauthToken.getName()
            );
            if (client != null) {
                OAuth2AccessToken accessToken = client.getAccessToken();
                model.addAttribute("accessToken", accessToken.getTokenValue());
                // 使用access token调用第三方API
                String apiUrl = "https://api.github.com/user/emails";
                HttpHeaders headers = new HttpHeaders();
                headers.setBearerAuth(accessToken.getTokenValue());
                headers.setContentType(MediaType.APPLICATION_JSON);
                HttpEntity<String> entity = new HttpEntity<>(headers);
                try {
                    ResponseEntity<String> response = restTemplate.exchange(
                        apiUrl, HttpMethod.GET, entity, String.class);
                    model.addAttribute("apiResponse", response.getBody());
                } catch (Exception e) {
                    log.error("Error calling API", e);
                    model.addAttribute("error", e.getMessage());
                }
            }
        }
        return "api-data";
    }
}

HTML模板

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">Login</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-5">
        <div class="row justify-content-center">
            <div class="col-md-6">
                <div class="card">
                    <div class="card-header">
                        <h3 class="text-center">Login</h3>
                    </div>
                    <div class="card-body">
                        <th:block th:if="${param.error}">
                            <div class="alert alert-danger">
                                Login failed. Please try again.
                            </div>
                        </th:block>
                        <div class="text-center">
                            <h4>Choose a provider:</h4>
                            <a th:href="@{/oauth2/authorization/github}" 
                               class="btn btn-dark btn-lg btn-block mb-3">
                                Login with GitHub
                            </a>
                            <br>
                            <a th:href="@{/oauth2/authorization/google}" 
                               class="btn btn-primary btn-lg btn-block">
                                Login with Google
                            </a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

home.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">Home</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="/">OAuth2 Client</a>
            <div class="navbar-nav ms-auto">
                <a class="nav-link" th:href="@{/profile}">Profile</a>
                <a class="nav-link" th:href="@{/api/data}">API Data</a>
                <form th:action="@{/logout}" method="post" class="d-inline">
                    <button type="submit" class="btn btn-outline-light">Logout</button>
                </form>
            </div>
        </div>
    </nav>
    <div class="container mt-5">
        <div class="row">
            <div class="col-md-6 mx-auto">
                <div class="card">
                    <div class="card-body text-center">
                        <img th:src="${avatar}" class="rounded-circle mb-3" 
                             style="width: 100px; height: 100px;" alt="Avatar">
                        <h2 th:text="${name}">User Name</h2>
                        <p class="text-muted" th:text="${email}">email@example.com</p>
                        <p>
                            <span class="badge bg-success" th:text="${provider}">
                                Provider
                            </span>
                        </p>
                        <div class="mt-4">
                            <h5>User Information:</h5>
                            <ul class="list-group">
                                <li class="list-group-item" th:text="${name}">Name</li>
                                <li class="list-group-item" th:text="${email}">Email</li>
                                <li class="list-group-item">Provider: <span th:text="${provider}">Provider</span></li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

配置文件 application.properties (备用)

# 应用配置
spring.application.name=oauth2-client-demo
server.port=8080
# GitHub OAuth2配置
spring.security.oauth2.client.registration.github.client-id=${GITHUB_CLIENT_ID}
spring.security.oauth2.client.registration.github.client-secret=${GITHUB_CLIENT_SECRET}
spring.security.oauth2.client.registration.github.scope=read:user,user:email
# Google OAuth2配置
spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=email,profile
# OAuth2 Provider配置
spring.security.oauth2.client.provider.github.authorization-uri=https://github.com/login/oauth/authorize
spring.security.oauth2.client.provider.github.token-uri=https://github.com/login/oauth/access_token
spring.security.oauth2.client.provider.github.user-info-uri=https://api.github.com/user
spring.security.oauth2.client.provider.github.user-name-attribute=id

运行说明

  1. 获取OAuth2客户端凭证:在GitHub、Google等平台上注册应用,获取Client ID和Client Secret。

  2. 配置环境变量

    export GITHUB_CLIENT_ID=your_client_id
    export GITHUB_CLIENT_SECRET=your_client_secret
    export GOOGLE_CLIENT_ID=your_client_id
    export GOOGLE_CLIENT_SECRET=your_client_secret
  3. 启动应用

    mvn spring-boot:run
  4. 访问应用:打开浏览器访问 http://localhost:8080

完整功能说明

  • OAuth2登录:支持GitHub、Google等OAuth2提供商
  • 用户信息获取:从提供商获取用户基本信息
  • 自定义用户服务:扩展默认的用户服务,处理自定义业务逻辑
  • API调用:使用访问令牌调用受保护的API
  • 会话管理:管理OAuth2会话和令牌
  • 多提供商支持:支持多个OAuth2提供商

这个案例提供了完整的OAuth2客户端实现,包括配置、安全设置、控制器、模板和API调用示例,可以根据实际需求进行扩展和定制。

抱歉,评论功能暂时关闭!