使用 Reddit OAuth2 和 Spring Security 認證

HTTP Client-Side,Spring Security,Spring Web
Remote
1
08:45 PM · Nov 29 ,2025

1. 概述

在本教程中,我們將使用 Spring Security OAuth 與 Reddit API 進行身份驗證。

2. Maven 配置

首先,為了使用 Spring Security OAuth – 我們需要將以下依賴項添加到我們的 pom.xml (當然,也包括任何你可能使用的其他 Spring 依賴項):

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.0.6.RELEASE</version>
</dependency>

3. 配置 OAuth2 客户端

接下來,讓我們配置我們的 OAuth2 客户端——OAuth2RestTemplate——以及一個 reddit.properties 文件,用於所有與身份驗證相關的屬性:

@Configuration
@EnableOAuth2Client
@PropertySource("classpath:reddit.properties")
protected static class ResourceConfiguration {

    @Value("${accessTokenUri}")
    private String accessTokenUri;

    @Value("${userAuthorizationUri}")
    private String userAuthorizationUri;

    @Value("${clientID}")
    private String clientID;

    @Value("${clientSecret}")
    private String clientSecret;

    @Bean
    public OAuth2ProtectedResourceDetails reddit() {
        AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
        details.setId("reddit");
        details.setClientId(clientID);
        details.setClientSecret(clientSecret);
        details.setAccessTokenUri(accessTokenUri);
        details.setUserAuthorizationUri(userAuthorizationUri);
        details.setTokenName("oauth_token");
        details.setScope(Arrays.asList("identity"));
        details.setPreEstablishedRedirectUri("http://localhost/login");
        details.setUseCurrentUri(false);
        return details;
    }

    @Bean
    public OAuth2RestTemplate redditRestTemplate(OAuth2ClientContext clientContext) {
        OAuth2RestTemplate template = new OAuth2RestTemplate(reddit(), clientContext);
        AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(
          Arrays.<AccessTokenProvider> asList(
            new MyAuthorizationCodeAccessTokenProvider(), 
            new ImplicitAccessTokenProvider(), 
            new ResourceOwnerPasswordAccessTokenProvider(),
            new ClientCredentialsAccessTokenProvider())
        );
        template.setAccessTokenProvider(accessTokenProvider);
        return template;
    }

}

以及 “reddit.properties“:

clientID=xxxxxxxx
clientSecret=xxxxxxxx
accessTokenUri=https://www.reddit.com/api/v1/access_token
userAuthorizationUri=https://www.reddit.com/api/v1/authorize

您可以通過從 https://www.reddit.com/prefs/apps/ 創建 Reddit 應用來獲取自己的密鑰代碼。

我們將使用 OAuth2RestTemplate 來:

  1. 獲取訪問遠程資源的訪問令牌。
  2. 獲取訪問令牌後訪問遠程資源。

請注意,我們添加了範圍 “identity” 到 Reddit OAuth2ProtectedResourceDetails,以便稍後可以檢索用户的帳户信息。

4. 自定義 AuthorizationCodeAccessTokenProvider

Reddit OAuth2 的實現與標準有所不同。因此——而不是優雅地擴展 AuthorizationCodeAccessTokenProvider——我們需要實際覆蓋其中的一部分。

Github issue 正在跟蹤改進,這些改進將使此操作不再必要,但這些 issue 尚未完成。

Reddit 的一個非標準之處在於——當我們重定向用户並要求他使用 Reddit 進行身份驗證時,我們需要在重定向 URL 中包含一些自定義參數。更具體地説——如果我們要求從 Reddit 獲取永久訪問令牌,則需要添加一個名為 "duration" 的參數,其值為 "permanent"。

因此,在擴展 AuthorizationCodeAccessTokenProvider之後——我們已經在 getRedirectForAuthorization() 方法中添加了此參數:

    requestParameters.put("duration", "permanent");

您可以在 這裏 找到完整源代碼。

5. TheServerInitializer

接下來,讓我們創建我們的自定義 ServerInitializer

我們需要添加一個帶有 id oauth2ClientContextFilter 的過濾器 Bean,以便我們可以使用它來存儲當前上下文:

public class ServletInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext context = 
          new AnnotationConfigWebApplicationContext();
        context.register(WebConfig.class, SecurityConfig.class);
        return context;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        registerProxyFilter(servletContext, "oauth2ClientContextFilter");
        registerProxyFilter(servletContext, "springSecurityFilterChain");
    }

    private void registerProxyFilter(ServletContext servletContext, String name) {
        DelegatingFilterProxy filter = new DelegatingFilterProxy(name);
        filter.setContextAttribute(
          "org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher");
        servletContext.addFilter(name, filter).addMappingForUrlPatterns(null, false, "/*");
    }
}

6. MVC 配置

現在,讓我們來查看我們簡單 Web 應用程序的 MVC 配置:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "org.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public static PropertySourcesPlaceholderConfigurer 
      propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    @Override
    public void configureDefaultServletHandling(
      DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home.html");
    }

7. 安全配置

接下來,讓我們看一下 主要的 Spring Security 配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) 
      throws Exception {
        auth.inMemoryAuthentication();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .anonymous().disable()
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/home.html").hasRole("USER")
            .and()
            .httpBasic()
            .authenticationEntryPoint(oauth2AuthenticationEntryPoint());
    }

    private LoginUrlAuthenticationEntryPoint oauth2AuthenticationEntryPoint() {
        return new LoginUrlAuthenticationEntryPoint("/login");
    }
}

注意:我們添加了一個簡單的安全配置,將重定向到 “/login,該配置從它獲取用户信息並加載身份驗證——如以下部分所述。

8. RedditController

現在,讓我們來查看我們的控制器 RedditController

我們使用方法 redditLogin() 從他的 Reddit 賬户獲取用户信息並加載認證,如以下示例所示:

@Controller
public class RedditController {

    @Autowired
    private OAuth2RestTemplate redditRestTemplate;

    @RequestMapping("/login")
    public String redditLogin() {
        JsonNode node = redditRestTemplate.getForObject(
          "https://oauth.reddit.com/api/v1/me", JsonNode.class);
        UsernamePasswordAuthenticationToken auth = 
          new UsernamePasswordAuthenticationToken(node.get("name").asText(), 
          redditRestTemplate.getAccessToken().getValue(), 
          Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
        
        SecurityContextHolder.getContext().setAuthentication(auth);
        return "redirect:home.html";
    }

}

這個看似簡單的方法的一個有趣細節是——Reddit 模板 檢查訪問令牌是否可用之前執行任何請求;如果未提供令牌,則獲取令牌。

接下來,我們將信息呈現給我們的非常簡單的前端。

9. home.jsp

最後,讓我們來查看 home.jsp – 用於顯示從用户 Reddit 帳户檢索的信息:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<html>
<body>
    <h1>歡迎,<small><sec:authentication property="principal.username" /></small></h1>
</body>
</html>

10. 結論在本文中,我們探討了使用 Reddit OAuth2 API 進行身份驗證以及在簡單的前端中顯示一些基本信息。

現在我們已經進行了身份驗證,將在下一篇文章中探索更多有趣的內容,使用 Reddit API。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.