1. Overview
In this tutorial, we'll use Spring Security OAuth to authenticate with the Reddit API.
2. Maven Configuration
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):
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.6.RELEASE</version>
</dependency>
3. Configure OAuth2 Client
Next – let's configure our OAuth2 client – the OAuth2RestTemplate – and a reddit.properties file for all the authentication related 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“:
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/
We're going to use the OAuth2RestTemplate to:
- Acquire the access token needed to access the remote resource.
- 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.
4. Custom 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.
There are github issues tracking improvements that will make this not necessary, but these issues are not yet done.
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“.
So, after extending AuthorizationCodeAccessTokenProvider – we have added this parameter in the getRedirectForAuthorization() method:
requestParameters.put("duration", "permanent");
You can check the full source code from here.
5. The ServerInitializer
Next – let's create our custom ServerInitializer.
We need to add a filter bean with id oauth2ClientContextFilter, so that we can use it to store the current context:
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
Now – let's take a look at our MVC configuration of our simple web-app:
@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
Next – let's take a look at the 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");
}
}
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.
8. RedditController
Now – let's take a look at our controller 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:
@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.
Next – we present the information to our very simplistic front end.
9. home.jsp
Finally – let's take a look at home.jsp – to display the information retrieved form user's Reddit account:
<%@ 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
In this introductory article, we explored authenticating with the Reddit OAuth2 API and displaying some very basic information in a simple front end.
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.
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.