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 客户端——<em>OAuth2RestTemplate</em>——以及一個 <em>reddit.properties</em> 文件,用於存儲所有與身份驗證相關的屬性:
@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</em itemprop="description">”:
clientID=xxxxxxxx
clientSecret=xxxxxxxx
accessTokenUri=https://www.reddit.com/api/v1/access_token
userAuthorizationUri=https://www.reddit.com/api/v1/authorize您可以通過從 https://www.reddit.com/prefs/apps/ 創建 Reddit 應用來獲取您的專屬密鑰。
我們將使用 OAuth2RestTemplate 來:
- 獲取訪問遠程資源的必需的訪問令牌。
- 獲取訪問令牌後訪問遠程資源。
請注意,我們添加了“identity”範圍到 Reddit 的 OAuth2ProtectedResourceDetails,以便稍後檢索用户的帳户信息。
<h2><strong>4. 自定義 <em>AuthorizationCodeAccessTokenProvider</em></strong></h2 >
<p>Reddit OAuth2 的實現與標準有所不同。因此——而不是優雅地擴展 <em>AuthorizationCodeAccessTokenProvider</em>——我們需要實際覆蓋其中的一部分。</p>
<p>GitHub issue 正在跟蹤改進,這些改進將使此操作不再必要,但這些 issue 尚未完成。</p>
<p>Reddit 的一個非標準做法是——當我們重定向用户並要求他使用 Reddit 進行身份驗證時,我們需要在重定向 URL 中包含一些自定義參數。更具體地説——如果我們要求 Reddit 提供永久訪問令牌,則我們需要添加名為 "<em>duration</em>" 的參數,其值為 "<em>permanent</em>“。</p>
<p>因此,在擴展 <em>AuthorizationCodeAccessTokenProvider</em> 之後——我們已經在 <em>getRedirectForAuthorization()</em> 方法中添加了此參數:</p>
requestParameters.put("duration", "permanent");你可以從 這裏 查看完整源代碼。
5. ServerInitializer
接下來,讓我們創建我們的自定義 ServerInitializer。
我們需要添加一個具有 id 為 oauth2ClientContextFilter 的過濾器 Bean,以便我們可以使用它來存儲當前上下文:
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 配置
現在,讓我們來查看我們簡單 Web 應用程序的 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 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
現在,讓我們來查看我們的控制器 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";
}
}這個看似簡單的方法的一個有趣細節是,它首先檢查訪問令牌是否可用,如果在未提供令牌的情況下執行任何請求;如果未提供令牌,則獲取令牌。
接下來,我們將信息呈現給我們的非常簡單的前端。
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 進行身份驗證以及在簡單的前端中顯示一些基本信息。
現在我們已經完成了身份驗證,將在下一篇文章中探索更多有趣的內容,利用 Reddit API。