Перейти к основному содержимому

Аутентификация с помощью Reddit OAuth2 и Spring Security

· 5 мин. чтения

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 для:

  1. Получите маркер доступа, необходимый для доступа к удаленному ресурсу.
  2. Получите доступ к удаленному ресурсу после получения токена доступа.

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

4. Пользовательский AuthorizationCodeAccessTokenProvider

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

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

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

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

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

Вы можете проверить полный исходный код отсюда .

5. Инициализатор сервера

Далее — давайте создадим наш пользовательский 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.foreach.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 Security :

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

Мы используем метод 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. дом.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, поэтому его легко импортировать и запускать как есть.