Authenticating with Reddit OAuth2 and Spring Security – 用Reddit OAuth2和Spring Security进行认证

最后修改: 2015年 2月 28日

1. Overview

1.概述

In this tutorial, we’ll use Spring Security OAuth to authenticate with the Reddit API.

在本教程中,我们将使用Spring Security OAuth对Reddit API进行认证。

2. Maven Configuration

2.Maven配置

First, in order to use Spring Security OAuth – we need to add the following dependency to our pom.xml (of course along any other Spring dependency you might use):

首先,为了使用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. Configure OAuth2 Client

3.配置OAuth2客户端

Next – let’s configure our OAuth2 client – the OAuth2RestTemplate – and a reddit.properties file for all the authentication related properties:

接下来 – 让我们配置我们的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;
    }

}

And “reddit.properties“:

还有”reddit.properties“。

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

You can get your own secret code by creating a Reddit app from https://www.reddit.com/prefs/apps/

你可以从https://www.reddit.com/prefs/apps/创建一个Reddit应用程序,获得你自己的秘密代码。

We’re going to use the OAuth2RestTemplate to:

我们将使用OAuth2RestTemplate来。

  1. Acquire the access token needed to access the remote resource.
  2. Access the remote resource after getting the access token.

Also note how we added the scope “identity” to Reddit OAuth2ProtectedResourceDetails so that we can retrieve the users account information later.

还要注意我们是如何将作用域”identity“添加到Reddit OAuth2ProtectedResourceDetails中的,这样我们就可以在以后检索到用户的账户信息。

4. Custom AuthorizationCodeAccessTokenProvider

4.自定义AuthorizationCodeAccessTokenProvider

The Reddit OAuth2 implementation is a little different from the standard. And so – instead of elegantly extending the AuthorizationCodeAccessTokenProvider – we need to actually override some portions of it.

Reddit的OAuth2实现与标准有一些不同。因此,我们不需要优雅地扩展AuthorizationCodeAccessTokenProvider,而是需要实际覆盖它的某些部分。

There are github issues tracking improvements that will make this not necessary, but these issues are not yet done.

有github问题跟踪改进,这将使其成为不必要的,但这些问题还没有完成。

One of the non-standard things that Reddit does is – when we redirect the user and prompt him to authenticate with Reddit, we need to have some custom parameters in the redirect URL. More specifically – if we’re asking for a permanent access token from Reddit – we need to add a parameter “duration” with the value “permanent“.

Reddit做的一个非标准的事情是–当我们重定向用户并提示他用Reddit进行认证时,我们需要在重定向URL中设置一些自定义参数。更具体地说–如果我们要求Reddit提供一个永久的访问令牌–我们需要添加一个参数”duration“,值为”permanent“。

So, after extending AuthorizationCodeAccessTokenProvider – we have added this parameter in the getRedirectForAuthorization() method:

因此,在扩展AuthorizationCodeAccessTokenProvider之后–我们在getRedirectForAuthorization()方法中加入了这个参数。

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

You can check the full source code from here.

你可以从这里查看完整的源代码。

5. The ServerInitializer

5.TheServerInitializer

Next – let’s create our custom ServerInitializer.

接下来–让我们创建我们的自定义ServerInitializer

We need to add a filter bean with id oauth2ClientContextFilter, so that we can use it to store the current context:

我们需要添加一个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 Configuration

6.MVC配置

Now – let’s take a look at our MVC configuration of our simple web-app:

现在–让我们看看我们的简单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. Security Configuration

7.安全配置

Next – let’s take a look at the main Spring Security configuration:

接下来–让我们看一下主要的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");
    }
}

Note: We added a simple security configuration that redirect to “/login” which get the user information and load authentication from it – as explained in the following section.

注意:我们添加了一个简单的安全配置,重定向到”/login“,获得用户信息并从中加载认证–如下一节所解释。

8. RedditController

8.RedditController

Now – let’s take a look at our controller RedditController.

现在–让我们看一下我们的控制器RedditController

We use method redditLogin() to get the user information from his Reddit account and load an authentication from it – as in the following example:

我们使用方法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";
    }

}

An interesting detail of this deceptively simple method – the reddit template checks if the access token is available before executing any request; it acquires a token if one is not available.

这个看似简单的方法有一个有趣的细节–reddit模板在执行任何请求之前检查访问令牌是否可用;如果没有访问令牌,它就获得一个令牌。

Next – we present the information to our very simplistic front end.

接下来–我们将信息呈现给我们非常简单的前端。

9. home.jsp

9.home.jsp

Finally – let’s take a look at home.jsp – to display the information retrieved form user’s Reddit account:

最后–让我们看一下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>Welcome, <small><sec:authentication property="principal.username" /></small></h1>
</body>
</html>

10. Conclusion

10.结论

In this introductory article, we explored authenticating with the Reddit OAuth2 API and displaying some very basic information in a simple front end.

在这篇介绍性文章中,我们探讨了用Reddit OAuth2 API进行认证,并在一个简单的前端显示一些非常基本的信息。

Now that we’re authenticated, we’re going to explore doing more interesting things with the Reddit API in the next article of this new series.

现在我们已经通过了认证,我们将在这个新系列的下一篇文章中探索用Reddit API做更有趣的事情。

The full implementation of this tutorial can be found in the github project – this is an Eclipse based project, so it should be easy to import and run as it is.

本教程的完整实现可以在github 项目中找到 – 这是一个基于 Eclipse 的项目,因此应该可以轻松导入并按原样运行。