我尝试将授权代码流配置为客户端。到目前为止流程正在发挥作用。我得到了重定向到登录页面。 oauth2服务器为我提供了一个auth代码,我可以用代码交换访问令牌。
但我无法做到最后一步:回到原始资源。 这是我的SecurityConfig:
@Configuration
@EnableWebSecurity
@EnableOAuth2Client
public class SecureConfig extends WebSecurityConfigurerAdapter {
@Autowired
OAuth2ClientContext oauth2ClientContext;
@Value("${openId.userinfo}")
private String userInfoUri;
@Value("${openId.clientId}")
private String clientId;
@Value("${openId.clientSecret}")
private String clientSecret;
@Value("${openId.accessTokenUri}")
private String accessTokenUri;
@Value("${openId.userAuthorizationUri}")
private String userAuthorizationUri;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterAfter(ssoFilter(), BasicAuthenticationFilter.class);
}
private OAuth2ClientAuthenticationProcessingFilter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter openIDFilter = new OAuth2ClientAuthenticationProcessingFilter("/resource/**");
openIDFilter.setRestTemplate(restTemplate());
UserInfoTokenServices tokenServices = new UserInfoTokenServices(userInfoUri, clientId);
tokenServices.setRestTemplate(restTemplate());
openIDFilter.setTokenServices(tokenServices);
return openIDFilter;
}
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public OAuth2RestTemplate restTemplate() {
return new OAuth2RestTemplate(protectedResourceDetails(), oauth2ClientContext);
}
@Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
@Bean
public OAuth2ProtectedResourceDetails protectedResourceDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setScope(Arrays.asList("read"));
details.setUseCurrentUri(true);
return details;
}
}
这是我的控制者:
@Controller
@RequestMapping("/resource")
public class TestController {
@RequestMapping(value = "/test", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseStatus(code = HttpStatus.OK)
public void test(){
System.out.println("hello world");
}
}
我找到了this forum post
建议在RequestCache
中保存请求。但是这篇文章大约有6年的历史了,也许Spring在此期间提供了更优雅的解决方案?
编辑: 这是我的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
</dependencies>
答案 0 :(得分:1)
这就是AbstractAuthenticationProcessingFilter所说的OAuth2ClientAuthenticationProcessingFilter
扩展的内容。
试试这个:
private OAuth2ClientAuthenticationProcessingFilter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter openIDFilter = new OAuth2ClientAuthenticationProcessingFilter("/resource/**");
openIDFilter.setRestTemplate(restTemplate());
UserInfoTokenServices tokenServices = new UserInfoTokenServices(userInfoUri, clientId);
tokenServices.setRestTemplate(restTemplate());
openIDFilter.setTokenServices(tokenServices);
openIDFilter.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler());
return openIDFilter;
}
<强>更新强>
这是我的调试日志供您参考。
- Checking match of request : '/dist/i_do not_exist.html'; against '/favicon.ico'
- Checking match of request : '/dist/i_do not_exist.html'; against '/images/**'
- Checking match of request : '/dist/i_do not_exist.html'; against '/css/**'
- Checking match of request : '/dist/i_do not_exist.html'; against '/maxsession.jsp'
- /dist/i_do not_exist.html at position 1 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
- No HttpSession currently exists
- No SecurityContext was available from the HttpSession: null. A new one will be created.
- /dist/i_do not_exist.html at position 2 of 12 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
- /dist/i_do not_exist.html at position 3 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
- /dist/i_do not_exist.html at position 4 of 12 in additional filter chain; firing Filter: 'LogoutFilter'
- /dist/i_do not_exist.html at position 5 of 12 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
- /dist/i_do not_exist.html at position 6 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
- /dist/i_do not_exist.html at position 7 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
- /dist/i_do not_exist.html at position 8 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
- /dist/i_do not_exist.html at position 9 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
- /dist/i_do not_exist.html at position 10 of 12 in additional filter chain; firing Filter: 'OAuth2ClientContextFilter'
- /dist/i_do not_exist.html at position 11 of 12 in additional filter chain; firing Filter: 'OpenIdConnectFilter'
- /dist/i_do not_exist.html at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
- Secure object: FilterInvocation: URL: /dist/i_do not_exist.html; Attributes: [isFullyAuthenticated()]
11:43:23.719 [http-nio-8080-exec-3] DEBUG o.a.camel.spring.SpringCamelContext - onApplicationEvent: org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent[source=FilterInvocation: URL: /dist/i_do not_exist.html]
- Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:339)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:198)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:125)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:94)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:616)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:620)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:502)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1132)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:684)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1539)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1495)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
- Publishing event: org.springframework.security.web.session.HttpSessionCreatedEvent[source=org.apache.catalina.session.StandardSessionFacade@cd8de57]
11:43:25.895 [http-nio-8080-exec-3] DEBUG o.a.camel.spring.SpringCamelContext - onApplicationEvent: org.springframework.security.web.session.HttpSessionCreatedEvent[source=org.apache.catalina.session.StandardSessionFacade@cd8de57]
- DefaultSavedRequest added to Session: DefaultSavedRequest[http://ecuio197m0221:8080/Pagos/dist/i_do%20not_exist.html]
- Calling Authentication entry point.
- Redirecting to 'http://<server>:<port>/<AppContext>/idp-login;jsessionid=CB7BAACDAEDC3A0E37AD5F75C0E38C26'
答案 1 :(得分:0)
这是一种解决方法,我不喜欢它。如果有人找到更好的解决方案,我将不胜感激。
出于此目的,我将保持实施简单和肮脏。
首先,我实现了UpdateSavedRequestFilter
以在requestCache中保存请求:
public class UpdateSavedRequestFilter extends OncePerRequestFilter {
private RequestCache requestCache = new HttpSessionRequestCache();
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String queryString = request.getQueryString();
if(!StringUtils.contains(queryString, "code") && authentication == null) {
requestCache.saveRequest(request, response);
}
filterChain.doFilter(request, response);
}
}
它没有按照希望工作,我被重定向到&#34; / resource / test&#34;但是再次触发了auth进程。所以我实现了自己的Oauth2Filter
。它没有做太多,我主要是从http://www.baeldung.com/spring-security-openid-connect复制代码。我只是对doFilter
方法的扩展,调用requiresAuthentication
检查是否用户已经过身份验证
public class OAuth2Filter extends AbstractAuthenticationProcessingFilter {
public OAuth2RestOperations restTemplate;
private UserInfoTokenServices tokenServices;
public OAuth2Filter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
setAuthenticationManager(new NoopAuthenticationManager());
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
setAuthenticationSuccessHandler(successHandler);
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
if (requiresAuthentication()) {
super.doFilter(req, res, chain);
} else {
chain.doFilter(req, res);
}
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException{
OAuth2AccessToken accessToken;
try {
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
throw new BadCredentialsException("Could not obtain access token", e);
}
try {
OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
if (authenticationDetailsSource!=null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
result.setDetails(authenticationDetailsSource.buildDetails(request));
}
publish(new AuthenticationSuccessEvent(result));
return result;
} catch (InvalidTokenException e) {
throw new BadCredentialsException("Could not obtain user details from token", e);
}
}
private void publish(ApplicationEvent event) {
if (eventPublisher!=null) {
eventPublisher.publishEvent(event);
}
}
private static class NoopAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new UnsupportedOperationException("No authentication should be done with this AuthenticationManager");
}
}
private boolean requiresAuthentication() {
Authentication currentUser = SecurityContextHolder.getContext()
.getAuthentication();
if (currentUser == null) {
return true;
}
OAuth2AccessToken accessToken = restTemplate.getAccessToken();
if (accessToken == null) {
return true;
}
return accessToken.isExpired();
}
public void setRestTemplate(OAuth2RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
public void setTokenServices(UserInfoTokenServices tokenServices) {
this.tokenServices = tokenServices;
}
}
这是我的其他课程:
@Configuration
@EnableWebSecurity
public class SecureConfig extends WebSecurityConfigurerAdapter {
@Autowired
private OAuth2RestTemplate restTemplate;
@Value("${openId.userinfo}")
private String userInfoUri;
@Value("${openId.clientId}")
private String clientId;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
.addFilterBefore(new UpdateSavedRequestFilter(), OAuth2Filter.class);
}
@Bean
public OAuth2Filter openIdConnectFilter() {
OAuth2Filter filter = new OAuth2Filter("/resource/**");
filter.setRestTemplate(restTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(userInfoUri, clientId);
tokenServices.setRestTemplate(restTemplate);
filter.setTokenServices(tokenServices);
return filter;
}
}
@Configuration
@EnableOAuth2Client
public class OpenIdConnectConfig {
@Value("${openId.userinfo}")
private String userInfoUri;
@Value("${openId.clientId}")
private String clientId;
@Value("${openId.clientSecret}")
private String clientSecret;
@Value("${openId.accessTokenUri}")
private String accessTokenUri;
@Value("${openId.userAuthorizationUri}")
private String userAuthorizationUri;
@Bean
public OAuth2ProtectedResourceDetails protectedResourceDetails() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setScope(Arrays.asList("read"));
details.setUseCurrentUri(true);
return details;
}
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public OAuth2RestTemplate restTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(protectedResourceDetails(), clientContext);
return oAuth2RestTemplate;
}
}
答案 2 :(得分:0)
我不确定这是否涵盖了OP的原始情况,因为解决方案可能会因身份验证流程的不同而有所不同。
请考虑以下情形。
http://host/login
输入oauth登录端点http://host/home-page
http://host/login?code=XXX
/
我们想将用户重定向到原始Referer,而不是root路径。
ExceptionTranslationFilter
通常负责在引发身份验证异常后将请求保存在RequestCache
中。稍后,在验证之后,AbstractAuthenticationProcessingFilter
使用相同的缓存来检索保存的请求,以进行正确的重定向。
问题是,当我们使用上述身份验证流程时,ExceptionTranslationFilter
不涉及。
我的想法是扩展OAuth2ClientContextFilter
并使用RequestCache
在重定向开始之前。另外,我需要强制RequestCache
保存我们的请求,但要使用Referer
标头作为正确的重定向URL。我使用请求包装器覆盖原始请求URL。也许不是最漂亮的解决方案,但是它似乎正在起作用。
public class RequestCacheOAuth2ClientContextFilter extends OAuth2ClientContextFilter {
private final RequestCache cache = new HttpSessionRequestCache();
@Override
protected void redirectUser(UserRedirectRequiredException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
@Override
public String getRequestURI() {
return URI.create(getReferer()).getPath();
}
@Override
public StringBuffer getRequestURL() {
return new StringBuffer(getReferer());
}
private String getReferer() {
return super.getHeader(HttpHeaders.REFERER);
}
};
cache.saveRequest(wrapper, response);
super.redirectUser(e, request, response);
}
}
将此bean添加到您的OAuth配置中
@Bean
@Primary
public OAuth2ClientContextFilter oAuth2ClientContextFilter() {
return new RequestCacheOAuth2ClientContextFilter();
}
如果您使用@EnableOAuth2Client
,则可能需要使用@Primary