Аутентификация с помощью Reddit OAuth2 и Spring Security
1. обзор
В этом руководстве мы будем использовать Spring Security OAuth для аутентификации с помощью Reddit API.
2. Конфигурация Maven
Во-первых, чтобы использовать Spring Security OAuth - нам нужно добавить следующую зависимость к нашемуpom.xml (конечно, вместе с любой другой зависимостью Spring, которую вы можете использовать):
org.springframework.security.oauth
spring-security-oauth2
2.0.6.RELEASE
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. 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, чтобы:
-
Получите токен доступа, необходимый для доступа к удаленному ресурсу.
-
Получите доступ к удаленному ресурсу после получения токена доступа.
Также обратите внимание, как мы добавили область «identity» в RedditOAuth2ProtectedResourceDetails, чтобы мы могли позже получить информацию об учетной записи пользователя.
4. ПользовательскийAuthorizationCodeAccessTokenProvider
Реализация Reddit OAuth2 немного отличается от стандартной. И поэтому - вместо элегантного расширенияAuthorizationCodeAccessTokenProvider - нам нужно фактически переопределить некоторые его части.
Существуют улучшения отслеживания проблем github, которые сделают это ненужным, но эти проблемы еще не сделаны.
Одна из нестандартных вещей, которые делает Reddit, - когда мы перенаправляем пользователя и просим его пройти аутентификацию с помощью Reddit, нам нужно иметь некоторые пользовательские параметры в URL перенаправления. В частности, если мы запрашиваем у Reddit постоянный токен доступа, нам нужно добавить параметр «duration» со значением «permanent».
Итак, после расширенияAuthorizationCodeAccessTokenProvider - мы добавили этот параметр в методgetRedirectForAuthorization():
requestParameters.put("duration", "permanent");
Вы можете проверить полный исходный код изhere.
5. ServerInitializer
Далее - давайте создадим собственныйServerInitializer.
Нам нужно добавить компонент фильтра с идентификатором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.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. Конфигурация безопасности
Далее - давайте посмотрим на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");
}
}
Примечание. Мы добавили простую конфигурацию безопасности, которая перенаправляет на «/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";
}
}
Интересная деталь этого обманчиво простого метода - шаблон redditchecks if the access token is available before executing any request; он получает токен, если он недоступен.
Далее - мы представляем информацию нашему очень упрощенному интерфейсу.
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"%>
Welcome,
10. Заключение
В этой вводной статье мы изучилиauthenticating with the Reddit OAuth2 API и отображение базовой информации в простом интерфейсе.
Теперь, когда мы прошли аутентификацию, мы собираемся изучить более интересные вещи с помощью Reddit API в следующей статье этой новой серии.
full implementation этого руководства можно найти вthe github project - это проект на основе Eclipse, поэтому его должно быть легко импортировать и запускать как есть.