Autenticando com Reddit OAuth2 e Spring Security

Autenticando com Reddit OAuth2 e Spring Security

1. Visão geral

Neste tutorial, usaremos Spring Security OAuth para autenticar com a API Reddit.

2. Configuração do Maven

Primeiro, para usar Spring Security OAuth - precisamos adicionar a seguinte dependência ao nossopom.xml (é claro, junto com qualquer outra dependência Spring que você possa usar):


    org.springframework.security.oauth
    spring-security-oauth2
    2.0.6.RELEASE

3. Configurar cliente OAuth2

A seguir - vamos configurar nosso cliente OAuth2 - oOAuth2RestTemplate - e um arquivoreddit.properties para todas as propriedades relacionadas à autenticação:

@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. asList(
            new MyAuthorizationCodeAccessTokenProvider(),
            new ImplicitAccessTokenProvider(),
            new ResourceOwnerPasswordAccessTokenProvider(),
            new ClientCredentialsAccessTokenProvider())
        );
        template.setAccessTokenProvider(accessTokenProvider);
        return template;
    }

}

E “reddit.properties“:

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

Você pode obter seu próprio código secreto criando um aplicativo Reddit dehttps://www.reddit.com/prefs/apps/

Vamos usarOAuth2RestTemplate para:

  1. Adquira o token de acesso necessário para acessar o recurso remoto.

  2. Acesse o recurso remoto depois de obter o token de acesso.

Observe também como adicionamos o escopo “identity” ao RedditOAuth2ProtectedResourceDetails para que possamos recuperar as informações da conta do usuário posteriormente.

4. AuthorizationCodeAccessTokenProvider personalizado

A implementação do Reddit OAuth2 é um pouco diferente do padrão. E assim - em vez de estender elegantemente oAuthorizationCodeAccessTokenProvider - precisamos realmente substituir algumas partes dele.

Existem problemas no rastreamento do github que tornarão isso desnecessário, mas esses problemas ainda não foram concluídos.

Uma das coisas não padronizadas que o Reddit faz é - quando redirecionamos o usuário e solicitamos que ele se autentique no Reddit, precisamos ter alguns parâmetros personalizados no URL de redirecionamento. Mais especificamente - se estamos pedindo um token de acesso permanente do Reddit - precisamos adicionar um parâmetro “duration” com o valor “permanent“.

Então, depois de estenderAuthorizationCodeAccessTokenProvider - adicionamos este parâmetro no métodogetRedirectForAuthorization():

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

Você pode verificar o código-fonte completo emhere.

5. OServerInitializer

A seguir - vamos criar nossoServerInitializer personalizado.

Precisamos adicionar um bean de filtro com idoauth2ClientContextFilter, para que possamos usá-lo para armazenar o contexto atual:

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. Configuração MVC

Agora - vamos dar uma olhada em nossa configuração MVC de nosso aplicativo web simples:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "org.example.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. Configuração de segurança

A seguir - vamos dar uma olhada emthe main Spring Security configuration:

@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");
    }
}

Observação: adicionamos uma configuração de segurança simples que redireciona para “/login”, que obtém as informações do usuário e carrega a autenticação a partir dela - conforme explicado na seção a seguir.

8. RedditController

Agora - vamos dar uma olhada em nosso controladorRedditController.

Usamos o métodoredditLogin() para obter as informações do usuário de sua conta do Reddit e carregar uma autenticação a partir dela - como no exemplo a seguir:

@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";
    }

}

Um detalhe interessante deste método aparentemente simples - o modelo redditchecks if the access token is available before executing any request; ele adquire um token se não houver um disponível.

Em seguida - apresentamos as informações ao nosso front end muito simplista.

9. home.jsp

Finalmente - vamos dar uma olhada emhome.jsp - para exibir as informações recuperadas da conta do usuário no Reddit:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>


    

Welcome,

10. Conclusão

Neste artigo introdutório, exploramosauthenticating with the Reddit OAuth2 APIe exibimos algumas informações básicas em um front end simples.

Agora que estamos autenticados, vamos explorar a realização de coisas mais interessantes com a API do Reddit no próximo artigo desta nova série.

Ofull implementation deste tutorial pode ser encontrado emthe github project - este é um projeto baseado em Eclipse, portanto, deve ser fácil de importar e executar como está.