Authentification avec Reddit OAuth2 et Spring Security

Authentification avec Reddit OAuth2 et Spring Security

1. Vue d'ensemble

Dans ce didacticiel, nous utiliserons Spring Security OAuth pour nous authentifier avec l'API Reddit.

2. Configuration Maven

Tout d'abord, pour utiliser Spring Security OAuth, nous devons ajouter la dépendance suivante à nospom.xml (bien sûr, avec toute autre dépendance Spring que vous pourriez utiliser):


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

3. Configurer le client OAuth2

Ensuite, configurons notre client OAuth2 - leOAuth2RestTemplate - et un fichierreddit.properties pour toutes les propriétés liées à l'authentification:

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

}

Et «reddit.properties»:

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

Vous pouvez obtenir votre propre code secret en créant une application Reddit à partir dehttps://www.reddit.com/prefs/apps/

Nous allons utiliser lesOAuth2RestTemplate pour:

  1. Acquérir le jeton d'accès nécessaire pour accéder à la ressource distante.

  2. Accédez à la ressource distante après avoir obtenu le jeton d'accès.

Notez également comment nous avons ajouté la portée «identity» à RedditOAuth2ProtectedResourceDetails afin que nous puissions récupérer les informations de compte des utilisateurs plus tard.

4. PersonnaliséAuthorizationCodeAccessTokenProvider

L'implémentation Reddit OAuth2 est un peu différente de la norme. Et donc - au lieu d'étendre élégamment lesAuthorizationCodeAccessTokenProvider - nous devons en remplacer certaines parties.

Il y a des problèmes de suivi des améliorations dans github qui rendront cela inutile, mais ces problèmes ne sont pas encore résolus.

L’une des tâches non standard de Reddit est la suivante: lorsque nous redirigeons l’utilisateur et lui demandons de s’authentifier auprès de Reddit, nous avons besoin de paramètres personnalisés dans l’URL de redirection. Plus précisément - si nous demandons un jeton d'accès permanent à Reddit - nous devons ajouter un paramètre «duration» avec la valeur «permanent».

Donc, après avoir étenduAuthorizationCodeAccessTokenProvider - nous avons ajouté ce paramètre dans la méthodegetRedirectForAuthorization():

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

Vous pouvez vérifier le code source complet à partir dehere.

5. LesServerInitializer

Ensuite, créons nosServerInitializer personnalisés.

Nous devons ajouter un bean filtre avec l'idoauth2ClientContextFilter, afin de pouvoir l'utiliser pour stocker le contexte actuel:

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. Configuration MVC

Maintenant, jetons un œil à la configuration MVC de notre application Web simple:

@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. Configuration de sécurité

Ensuite, jetons un œil à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");
    }
}

Remarque: Nous avons ajouté une configuration de sécurité simple qui redirige vers «/login» qui récupère les informations utilisateur et charge l'authentification à partir de celui-ci - comme expliqué dans la section suivante.

8. RedditController

Maintenant, jetons un œil à notre contrôleurRedditController.

Nous utilisons la méthoderedditLogin() pour obtenir les informations utilisateur de son compte Reddit et charger une authentification à partir de celui-ci - comme dans l'exemple suivant:

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

}

Un détail intéressant de cette méthode trompeusement simple - le template redditchecks if the access token is available before executing any request; il acquiert un jeton s'il n'en existe pas.

Ensuite, nous présentons les informations à notre front-office très simpliste.

9. home.jsp

Enfin - jetons un œil àhome.jsp - pour afficher les informations extraites du compte Reddit de l'utilisateur:

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


    

Welcome,

10. Conclusion

Dans cet article d'introduction, nous avons exploréauthenticating with the Reddit OAuth2 API et afficher des informations très basiques dans un simple frontal.

Maintenant que nous sommes authentifiés, nous allons explorer des choses plus intéressantes avec l'API Reddit dans le prochain article de cette nouvelle série.

Lesfull implementation de ce didacticiel se trouvent dansthe github project - il s'agit d'un projet basé sur Eclipse, il devrait donc être facile à importer et à exécuter tel quel.