«1. Обзор

В этом руководстве мы будем использовать Spring Security OAuth для аутентификации с помощью Reddit API.

2. Конфигурация Maven

Во-первых, чтобы использовать Spring Security OAuth, нам нужно добавить следующую зависимость в наш pom.xml (конечно, вместе с любой другой зависимостью Spring, которую вы можете использовать):

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.0.6.RELEASE</version>
</dependency>

3. Настройте клиент OAuth2

Далее — давайте настроим наш клиент OAuth2 — OAuth2RestTemplate — и файл reddit.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;
    }

}

И — «reddit.properties»:

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

Вы можете получить свой собственный секретный код, создав приложение Reddit по адресу https://www.reddit.com/prefs/apps/

Мы собираемся использовать шаблон OAuth2RestTemplate. to:

  1. Acquire the access token needed to access the remote resource.
  2. Access the remote resource after getting the access token.

Также обратите внимание, как мы добавили область «identity» в Reddit OAuth2ProtectedResourceDetails, чтобы позже мы могли получить информацию об учетной записи пользователя.

4. Custom AuthorizationCodeAccessTokenProvider

Реализация Reddit OAuth2 немного отличается от стандартной. Итак, вместо элегантного расширения AuthorizationCodeAccessTokenProvider нам нужно фактически переопределить некоторые его части.

Есть проблемы с отслеживанием улучшений github, которые сделают это ненужным, но эти проблемы еще не решены.

Одна из нестандартных вещей, которые делает Reddit, заключается в том, что когда мы перенаправляем пользователя и предлагаем ему пройти аутентификацию с помощью Reddit, нам нужно иметь некоторые настраиваемые параметры в URL-адресе перенаправления. Более конкретно — если мы запрашиваем токен постоянного доступа от Reddit — нам нужно добавить параметр «длительность» со значением «постоянный».

Итак, после расширения AuthorizationCodeAccessTokenProvider — мы добавили этот параметр в метод getRedirectForAuthorization():

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

Полный исходный код можно посмотреть здесь.

5. ServerInitializer

Теперь давайте создадим наш пользовательский ServerInitializer.

Нам нужно добавить bean-компонент фильтра с идентификатором oauth2ClientContextFilter, чтобы мы могли использовать его для хранения текущего контекста:

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

Теперь — давайте взглянем на наш MVC конфигурация нашего простого веб-приложения:

@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. Конфигурация безопасности

Далее — давайте посмотрим на основную конфигурацию безопасности Spring:

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

Примечание. Мы добавили простую конфигурацию безопасности это перенаправление на «/login», которое получает информацию о пользователе и загружает аутентификацию от него — как описано в следующем разделе.

8. RedditController

Теперь давайте посмотрим на наш контроллер RedditController.

Мы используем метод redditLogin(), чтобы получить информацию о пользователе из его учетной записи Reddit и загрузить из нее аутентификацию — как в следующем примере:

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

}

Интересная деталь этого обманчиво простого метода — шаблон reddit проверяет, доступен ли токен доступа перед выполнением любого запроса; он получает токен, если он недоступен.

Далее – мы представляем информацию нашему очень упрощенному внешнему интерфейсу.

9. home.jsp

Наконец — давайте взглянем на home.jsp — для отображения информации, полученной из учетной записи Reddit пользователя:

<%@ 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. Заключение

В этом Во вводной статье мы рассмотрели аутентификацию с помощью Reddit OAuth2 API и отображение некоторой базовой информации в простом внешнем интерфейсе.

Теперь, когда мы прошли аутентификацию, мы собираемся исследовать более интересные вещи с API Reddit в следующей статье этой новой серии.

Полную реализацию этого руководства можно найти в проекте github — это проект на основе Eclipse, поэтому его легко импортировать и запускать как есть.