Xác thực bằng Reddit OAuth2 và Spring Security

1. Khái quát chung

Trong hướng dẫn này, chúng tôi sẽ sử dụng Spring Security OAuth để xác thực với API Reddit.

2. Cấu hình Maven

Đầu tiên, để sử dụng Spring Security OAuth - chúng ta cần thêm phần phụ thuộc sau vào pom.xml của mình (tất nhiên cùng với bất kỳ phần phụ thuộc Spring nào khác mà bạn có thể sử dụng):

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

3. Định cấu hình ứng dụng khách OAuth2

Tiếp theo - hãy định cấu hình ứng dụng khách OAuth2 của chúng tôi - OAuth2RestTemplate - và tệp reddit.properties cho tất cả các thuộc tính liên quan đến xác thực:

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

Và " reddit.properties ":

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

Bạn có thể nhận mã bí mật của riêng mình bằng cách tạo ứng dụng Reddit từ //www.reddit.com/prefs/apps/

Chúng tôi sẽ sử dụng OAuth2RestTemplate để:

  1. Nhận mã thông báo truy cập cần thiết để truy cập tài nguyên từ xa.
  2. Truy cập tài nguyên từ xa sau khi nhận được mã thông báo truy cập.

Cũng lưu ý cách chúng tôi thêm phạm vi “ danh tính ” vào Reddit OAuth2ProtectedResourceDetails để chúng tôi có thể truy xuất thông tin tài khoản người dùng sau này.

4. Custom AuthorizationCodeAccessTokenProvider

Việc triển khai Reddit OAuth2 hơi khác so với tiêu chuẩn. Và vì vậy - thay vì mở rộng AuthorizationCodeAccessTokenProvider một cách thanh lịch - chúng ta cần thực sự ghi đè một số phần của nó.

Có những vấn đề github theo dõi các cải tiến sẽ khiến điều này không cần thiết, nhưng những vấn đề này vẫn chưa được thực hiện.

Một trong những điều không chuẩn mà Reddit thực hiện là - khi chúng tôi chuyển hướng người dùng và nhắc anh ta xác thực bằng Reddit, chúng tôi cần có một số thông số tùy chỉnh trong URL chuyển hướng. Cụ thể hơn - nếu chúng tôi đang yêu cầu mã thông báo truy cập vĩnh viễn từ Reddit - chúng tôi cần thêm thông số " thời lượng " với giá trị " vĩnh viễn ".

Vì vậy, sau khi mở rộng AuthorizationCodeAccessTokenProvider - chúng tôi đã thêm tham số này trong phương thức getRedirectForAuthorization () :

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

Bạn có thể kiểm tra mã nguồn đầy đủ từ đây.

5. ServerInitializer

Tiếp theo - hãy tạo ServerInitializer tùy chỉnh của chúng tôi .

Chúng ta cần thêm một bộ lọc bean với id oauth2ClientContextFilter , để chúng ta có thể sử dụng nó để lưu trữ ngữ cảnh hiện tại:

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. Cấu hình MVC

Bây giờ - hãy xem cấu hình MVC của ứng dụng web đơn giản của chúng tôi:

@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. Cấu hình bảo mật

Tiếp theo - hãy xem cấu hình Spring Security chính :

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

Lưu ý: Chúng tôi đã thêm một cấu hình bảo mật đơn giản chuyển hướng đến “ / login ” để lấy thông tin người dùng và tải xác thực từ nó - như được giải thích trong phần sau.

8. RedditController

Bây giờ - chúng ta hãy xem bộ điều khiển RedditController của chúng tôi .

We use method redditLogin() to get the user information from his Reddit account and load an authentication from it – as in the following example:

@Controller public class RedditController { @Autowired private OAuth2RestTemplate redditRestTemplate; @RequestMapping("/login") public String redditLogin() { JsonNode node = redditRestTemplate.getForObject( "//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"; } }

An interesting detail of this deceptively simple method – the reddit template checks if the access token is available before executing any request; it acquires a token if one is not available.

Next – we present the information to our very simplistic front end.

9. home.jsp

Finally – let's take a look at home.jsp – to display the information retrieved form user's Reddit account:

10. Conclusion

In this introductory article, we explored authenticating with the Reddit OAuth2 API and displaying some very basic information in a simple front end.

Bây giờ chúng ta đã được xác thực, chúng ta sẽ khám phá làm nhiều điều thú vị hơn với API Reddit trong bài viết tiếp theo của loạt bài mới này.

Việc triển khai đầy đủ hướng dẫn này có thể được tìm thấy trong dự án github - đây là một dự án dựa trên Eclipse, vì vậy nó sẽ dễ dàng nhập và chạy như nó vốn có.