При отправке следующего запроса:
curl -i -H "Accept:application/json" -H "Content-Type: application/json" "http://localhost:8080/api/auth/authorize?client_id=ng-zero&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fcallback&response_type=code&scope=read_profile" -X POST -d "{ \"username\" : \"xxxx@yahoo.se\", \"password\" : \"xxxxxx\" }"
Я получаю сообщение об ошибке: InsufficientAuthenticationException: User must be authenticated with Spring Security before authorization can be completed
Я пытаюсь реализовать поставщика OAuth2, используя Spring Boot 2.0.4.RELEASE
с spring-security-oauth2-autoconfigure 2.0.6.RELEASE
с типом предоставления authorization code
OAuth2.
Я понимаю, что конечная точка /oauth/authorize
является защищенной, и пользователь должен пройти проверку подлинности до отправки ей запроса.
Должен ли мой сервер авторизации иметь контроллер login
или фильтр login
?
В качестве примечания мне интересно, должен ли я использовать тип предоставления authorization code
, когда мой внешний интерфейс - Angular ... может быть, тип предоставления password
подойдет?
Но как перенести полученное состояние «да, пользователь успешно вошел в систему» на сервер авторизации в этом запросе?
Я явно открываю конечную точку /auth/login
, я явно защищаю конечную точку /auth/token
и ничего не говорю о конечной точке /auth/authorize
.
Обратите внимание, что я переназначил две конечные точки:
/oauth/authorize -> /auth/authorize
/oauth/token -> /auth/token
У меня есть следующая конфигурация безопасности для сервера авторизации:
@EnableWebSecurity
@ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "xxx.xxxxxxxxx.user.rest.service", "xxx.xxxxxxxxx.user.rest.filter" })
public class SecurityAuthorizationServerConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Autowired
private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
@Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.authorizeRequests()
.antMatchers(getUnsecuredPaths().toArray(new String[]{})).permitAll()
.antMatchers(RESTConstants.SLASH + DomainConstants.AUTH + RESTConstants.SLASH + DomainConstants.TOKEN).authenticated()
}
private List<String> getUnsecuredPaths() {
List<String> unsecuredPaths = Arrays.asList(
RESTConstants.SLASH + DomainConstants.AUTH + RESTConstants.SLASH + DomainConstants.LOGIN
);
return unsecuredPaths;
}
}
И мой сервер авторизации настроен как:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
static final String CLIENT_ID = "ng-zero";
static final String CLIENT_SECRET = "secret";
static final String GRANT_TYPE_PASSWORD = "password";
static final String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";
static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtProperties jwtProperties;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenAuthenticationService tokenAuthenticationService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(CLIENT_ID)
.secret(CLIENT_SECRET)
.redirectUris("http://localhost:4200/callback")
.authorizedGrantTypes(GRANT_TYPE_PASSWORD, GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN)
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.resourceIds("user-rest")
.scopes("read_profile", "write_profile", "read_firstname")
.accessTokenValiditySeconds(jwtProperties.getAccessTokenExpirationTime())
.refreshTokenValiditySeconds(jwtProperties.getRefreshTokenExpirationTime());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenServices(tokenServices())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.tokenEnhancer(jwtAccessTokenConverter())
.accessTokenConverter(jwtAccessTokenConverter())
.userDetailsService(userDetailsService);
endpoints
.pathMapping("/oauth/authorize", RESTConstants.SLASH + DomainConstants.AUTH + RESTConstants.SLASH + DomainConstants.AUTHORIZE)
.pathMapping("/oauth/token", RESTConstants.SLASH + DomainConstants.AUTH + RESTConstants.SLASH + DomainConstants.TOKEN);
}
class CustomTokenEnhancer extends JwtAccessTokenConverter {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
info.put("email", user.getEmail());
info.put(CommonConstants.JWT_CLAIM_USER_EMAIL, user.getEmail().getEmailAddress());
info.put(CommonConstants.JWT_CLAIM_USER_FULLNAME, user.getFirstname() + " " + user.getLastname());
info.put("scopes", authentication.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
customAccessToken.setAdditionalInformation(info);
customAccessToken.setExpiration(tokenAuthenticationService.getExpirationDate());
return super.enhance(customAccessToken, authentication);
}
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new CustomTokenEnhancer();
jwtAccessTokenConverter.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource(jwtProperties.getSslKeystoreFilename()), jwtProperties.getSslKeystorePassword().toCharArray()).getKeyPair(jwtProperties.getSslKeyPair()));
return jwtAccessTokenConverter;
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
return defaultTokenServices;
}
}
ОБНОВЛЕНИЕ: Я вижу в https://stackoverflow.com/questions/39424176/spring-security-oauth2-insufficientauthenticationexception
, что Spring Security должна быть защищена конечная точка /oauth/authorize
с перенаправлением на страницу входа в систему, чтобы аутентифицировать пользователя перед предоставлением доступа к конечной точке /oauth/authorize
. Но что, если сервер авторизации не имеет состояния? Можем ли мы иметь этот сервер авторизации в качестве сервера API? Как «войти» в систему пользователя тогда? Вот почему я открыл
/oauth/authorize
конечная точка на первом месте. Но, открыв его, показывает это сообщение User must be authenticated with Spring Security before authorization can be completed.
.