Как аутентифицировать моего пользователя до запроса кода авторизации? И да, почему я должен? - PullRequest
0 голосов
/ 01 ноября 2018

При отправке следующего запроса:

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..

1 Ответ

0 голосов
/ 05 ноября 2018

Проблема заключалась в том, что мне нужно было защитить конечную точку /oauth/authorize. Таким образом, я сделал это и, используя токен доступа от имени пользователя, мог затем использовать этот токен доступа, чтобы отправить запрос конечной точке /oauth/authorize и получить взамен html-форму с запросом о подтверждении области.

...