Authentifizierung mit Reddit OAuth2 und Spring Security

Authentifizierung mit Reddit OAuth2 und Spring Security

1. Überblick

In diesem Lernprogramm verwenden wir Spring Security OAuth zur Authentifizierung bei der Reddit-API.

2. Maven-Konfiguration

Um Spring Security OAuth verwenden zu können, müssen wir zunächst unserepom.xml um die folgende Abhängigkeit erweitern (natürlich zusammen mit jeder anderen Spring-Abhängigkeit, die Sie möglicherweise verwenden):


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

3. Konfigurieren Sie den OAuth2-Client

Als Nächstes konfigurieren wir unseren OAuth2-Client - dieOAuth2RestTemplate - und einereddit.properties-Datei für alle authentifizierungsbezogenen Eigenschaften:

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

}

Und „reddit.properties“:

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

Sie können Ihren eigenen Geheimcode erhalten, indem Sie eine Reddit-App aushttps://www.reddit.com/prefs/apps/ erstellen

Wir werden dieOAuth2RestTemplate verwenden, um:

  1. Fordern Sie das Zugriffstoken an, das für den Zugriff auf die Remote-Ressource erforderlich ist.

  2. Greifen Sie auf die Remote-Ressource zu, nachdem Sie das Zugriffstoken erhalten haben.

Beachten Sie auch, wie wir den Bereich "identity" zu RedditOAuth2ProtectedResourceDetails hinzugefügt haben, damit wir die Benutzerkontoinformationen später abrufen können.

4. BenutzerdefinierteAuthorizationCodeAccessTokenProvider

Die Reddit OAuth2-Implementierung unterscheidet sich ein wenig vom Standard. Und anstatt dieAuthorizationCodeAccessTokenProvider elegant zu erweitern, müssen wir einige Teile davon tatsächlich überschreiben.

Es gibt Github-Probleme bei der Nachverfolgung von Verbesserungen, die dies nicht erforderlich machen. Diese Probleme sind jedoch noch nicht behoben.

Eine der nicht standardmäßigen Aufgaben von Reddit ist: Wenn wir den Benutzer umleiten und ihn auffordern, sich bei Reddit zu authentifizieren, müssen wir einige benutzerdefinierte Parameter in der Umleitungs-URL haben. Genauer gesagt - wenn wir von Reddit nach einem permanenten Zugriffstoken fragen - müssen wir einen Parameter "duration" mit dem Wert "permanent" hinzufügen.

Nach dem Erweitern vonAuthorizationCodeAccessTokenProvider haben wir diesen Parameter in diegetRedirectForAuthorization()-Methode eingefügt:

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

Sie können den vollständigen Quellcode vonhere überprüfen.

5. DieServerInitializer

Als Nächstes erstellen wir unsere benutzerdefiniertenServerInitializer.

Wir müssen eine Filter-Bean mit der IDoauth2ClientContextFilter hinzufügen, damit wir damit den aktuellen Kontext speichern können:

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-Konfiguration

Schauen wir uns jetzt unsere MVC-Konfiguration unserer einfachen Web-App an:

@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. Sicherheitskonfiguration

Weiter - werfen wir einen Blick aufthe 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");
    }
}

Hinweis: Wir haben eine einfache Sicherheitskonfiguration hinzugefügt, die zu "/login" umleitet, um die Benutzerinformationen abzurufen und die Authentifizierung daraus zu laden - wie im folgenden Abschnitt erläutert.

8. RedditController

Schauen wir uns jetzt unseren ControllerRedditControlleran.

Wir verwenden die MethoderedditLogin(), um die Benutzerinformationen von seinem Reddit-Konto abzurufen und eine Authentifizierung daraus zu laden - wie im folgenden Beispiel:

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

}

Ein interessantes Detail dieser täuschend einfachen Methode - die reddit-Vorlagechecks if the access token is available before executing any request; Es erhält ein Token, wenn eines nicht verfügbar ist.

Als nächstes präsentieren wir die Informationen unserem sehr simplen Frontend.

9. home.jsp

Schauen wir uns zum Schlusshome.jsp an, um die Informationen anzuzeigen, die vom Reddit-Konto des Benutzers abgerufen wurden:

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


    

Welcome,

10. Fazit

In diesem Einführungsartikel haben wirauthenticating with the Reddit OAuth2 API untersucht und einige sehr grundlegende Informationen in einem einfachen Frontend angezeigt.

Nachdem wir uns authentifiziert haben, werden wir im nächsten Artikel dieser neuen Serie untersuchen, wie Sie mit der Reddit-API interessantere Dinge tun können.

Diefull implementation dieses Tutorials finden Sie inthe github project - dies ist ein Eclipse-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.