Я использую поток кода авторизации openid connect с springboot. У меня есть 2 службы, работающие на локальном хосте. Один из них содержит защищенный ресурс, а другой - клиент. Прямо сейчас они находятся в одном проекте, потому что это всего лишь проверка концепции.
Когда пользователь пытается получить доступ к домашней странице, я хочу перенаправить его на нашу настраиваемую страницу входа. После авторизации я хочу разрешить им доступ к защищенному ресурсу
Конфигурация моего клиента:
@Configuration
@EnableOAuth2Client
@EnableWebSecurity
@ComponentScan("com.myclient”)
@Profile({"dev"})
public class OpenIdConnectClientConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**").authorizeRequests()
.antMatchers("/login**", “/##/token.oauth2", “/##/authorization.oauth2").permitAll()
.and()
.anonymous().disable()
.logout()
.permitAll()
.deleteCookies("remove")
.clearAuthentication(true)
.invalidateHttpSession(true)
.logoutSuccessUrl("https://mylogout/logout")
.and().httpBasic().disable();
}// @formatter:on
@Override
public void configure(WebSecurity web) throws Exception {// @formatter:off
web
.ignoring()
.mvcMatchers(
"/favicon.ico",
"/webjars/**",
"/css/**");
}// @formatter:on
// thymeleaf
@Bean
public ClassLoaderTemplateResolver templateResolver() {
final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setPrefix("templates/");
templateResolver.setSuffix(".html");
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver() {
final ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
// end thymeleaf
@Configuration
@EnableOAuth2Client
protected static class OAuthClientConfig {
@Bean
public OAuth2ProtectedResourceDetails resourceDetails() {
final AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId(“#######”);
details.setClientSecret(“#######”);
details.setAccessTokenUri("https://tokenserver/as/token.oauth2");
details.setUserAuthorizationUri("https://authserver/as/authorization.oauth2");
details.setScope(Arrays.asList("openid"));
details.setGrantType("authorization_code");
details.setPreEstablishedRedirectUri("https://localhost:8443/login");
details.setUseCurrentUri(false);
return details;
}
@Bean
public OAuth2RestTemplate restTemplate(final OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(resourceDetails(), clientContext);
}
}
}
Я использую для клиента следующие пакеты:
<!-- security -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
Конфигурация моего сервера ресурсов выглядит так:
@Configuration
@EnableResourceServer
@Profile({"dev"})
public class ResourceServerConfig extends
ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {// @formatter:off
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").permitAll()
.antMatchers("/login**", "/##/token.oauth2", "/as/authorization.oauth2").permitAll()
// .antMatchers("/**").hasAuthority("ROLE_USER")
.and()
.logout()
.deleteCookies("remove")
.invalidateHttpSession(true)
.permitAll()
.and()
.csrf().disable();
}// @formatter:on
@Bean
public ResourceServerTokenServices tokenService() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId(“####”);
tokenServices.setClientSecret(“#####”);
tokenServices.setCheckTokenEndpointUrl("https://tokenserver/##/token.oauth2");
return tokenServices;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(this.tokenService());
}
}
Я использую следующие пакеты:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
Мой клиентский контроллер пытается получить доступ к этому ресурсу следующим образом:
@RestController
@Profile({"dev"})
class MainController {
@Autowired
private OAuth2RestTemplate restTemplate;
@RequestMapping("/somepath")
public final String home() {
return "/";
}
@SuppressWarnings("unused")
@RequestMapping
public ModelAndView login() {
// OAuth2AccessToken accessToken = restTemplate.getAccessToken();
final HelloWorld helloWorld = restTemplate.getForObject("https://localhost:8444/openidconnect-resource-server/api/1", HelloWorld.class);
return new ModelAndView("landingPage");
}
}
К сожалению, я получаю следующую ошибку:
OrderedRequestContextFilter : Bound request context to thread: org.apache.catalina.connector.RequestFacade@7e4514ea
AntPathRequestMatcher : Request '/login' matched by universal pattern '/**'
security.web.FilterChainProxy : /login?code=#####&state=## at position 1 of 10 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
security.web.FilterChainProxy : /login?code=######&state=## at position 2 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
HttpSessionSecurityContextRepository : No HttpSession currently exists
HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
security.web.FilterChainProxy : /login?code=####&state=## at position 3 of 10 in additional filter chain; firing Filter: 'HeaderWriterFilter'
security.web.FilterChainProxy : /login?code=#####&state=## at position 4 of 10 in additional filter chain; firing Filter: 'CsrfFilter'
security.web.FilterChainProxy : /login?code=####&state=## at position 5 of 10 in additional filter chain; firing Filter: 'LogoutFilter'
matcher.AntPathRequestMatcher : Request 'GET /login' doesn't match 'POST /logout
security.web.FilterChainProxy : /login?code=XRuHsIOKxNEzUkljeWn7lVfZcxTisziI7ZsAAADJ&state=UV13ug at position 6 of 10 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
security.web.FilterChainProxy : /login?code=XRuHsIOKxNEzUkljeWn7lVfZcxTisziI7ZsAAADJ&state=UV13ug at position 7 of 10 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
security.web.FilterChainProxy : /login?code=XRuHsIOKxNEzUkljeWn7lVfZcxTisziI7ZsAAADJ&state=UV13ug at position 8 of 10 in additional filter chain; firing Filter: 'SessionManagementFilter'
security.web.FilterChainProxy : /login?code=XRuHsIOKxNEzUkljeWn7lVfZcxTisziI7ZsAAADJ&state=UV13ug at position 9 of 10 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
security.web.FilterChainProxy : /login?code=XRuHsIOKxNEzUkljeWn7lVfZcxTisziI7ZsAAADJ&state=UV13ug at position 10 of 10 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
AntPathRequestMatcher : Request 'GET /login' doesn't match 'POST /logout
AntPathRequestMatcher : Checking match of request : '/login'; against '/actuator/health'
AntPathRequestMatcher : Checking match of request : '/login'; against '/login**'
FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /login?code=#####&state=##; Attributes: [permitAll]
audit.listener.AuditListener : AuditEvent [timestamp=2018-08-07T22:28:38.785Z, principal=, type=AUTHENTICATION_FAILURE, data = {type=org.springframework.security.authentication.AuthenticationCredentialsNotFoundException, message=An Authentication object was not found in the SecurityContext}]
ExceptionTranslationFilter : Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext





В моей реализации было несколько проблем, основная из которых заключалась в отсутствии конечной точки userinfo в классе расширения OAuth2ProtectedResourceDetails. Вместо этого я переместил эту конфигурацию в application.properties.
Конфигурация моего клиента теперь выглядит так:
@Configuration
@EnableOAuth2Sso
@ComponentScan("com.myclient")
@Profile({"dev"})
public class OIDCClientConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {// @formatter:off
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/**").hasRole("USER")
.and()
.csrf().disable()
.logout()
.permitAll()
.deleteCookies("remove")
.clearAuthentication(true)
.invalidateHttpSession(true)
.logoutSuccessUrl("https://####/logout")
.and().httpBasic().disable();
}// @formatter:on
@Override
public void configure(WebSecurity web) throws Exception {// @formatter:off
web
.ignoring()
.mvcMatchers(
"/**/favicon.ico",
"/webjars/**");
}// @formatter:on
// thymeleaf
@Bean
public ClassLoaderTemplateResolver templateResolver() {
final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setPrefix("templates/");
templateResolver.setSuffix(".html");
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver() {
final ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
// end thymeleaf
}
Мой MainController выглядит так:
package myclient.controller;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@RestController
class MainController {
@RequestMapping(value = "/home")
public ModelAndView landingPage() {
return new ModelAndView("landingPage");
}
}
Мои application.properties выглядят так:
# Allow Thymeleaf templates to be reloaded at dev time
spring.thymeleaf.cache=false
server.tomcat.accesslog.enabled=true
server.tomcat.basedir=target/tomcat
server.ssl.key-store-type=JKS
server.ssl.key-store=classpath:keystore
server.ssl.key-store-password=changeit
server.ssl.key-alias=tomcat
server.port=8443
#context path
server.servlet.context-path=/
spring.main.web-application-type=SERVLET
spring.main.banner-mode=off
spring.profiles.active=default,dev
# spring.profiles.active=dev
# SECURITY OAUTH2 CLIENT (OAuth2ClientProperties)
security.oauth2.client.client-id=####
security.oauth2.client.client-secret=#####
security.oauth2.client.authorized-grant-types=authorization_code
security.oauth2.client.access-token-uri=https://###/##/###
security.oauth2.client.user-authorization-uri=https://###/##/###
security.oauth2.client.scope=openid
security.oauth2.client.use-current-uri=true
security.oauth2.resource.user-info-uri=####
logging.level.org.springframework=DEBUG
Да, я сделал шаг назад от защиты ресурса, но маленькими шагами :)