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:
-
Adquira o token de acesso necessário para acessar o recurso remoto.
-
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á.