如何从用户名获取访问令牌?

How to get an access token from a username?

我有一个项目,我有一个 JWT 令牌的自定义实现,我可以通过调用以下方法从集成测试中获取访问令牌:

private void addTokenToRequestHeader(HttpHeaders headers, String username) {
  tokenAuthenticationService.addAccessTokenToHeader(headers, username);
}

现在我正在更改安全性以使用 OAuth2,并且我的配置不再使用自定义 JWT 实现:

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
    // The client id and client secret
    .withClient(OAUTH_CLIENT_ID)
    .secret(OAUTH_CLIENT_SECRET)
    // The endpoint at the client application to redirect to
    .redirectUris(OAUTH_CLIENT_URL)
    // The type of request the authorization server expects for the client
    .authorizedGrantTypes(OAUTH_GRANT_TYPE_PASSWORD, OAUTH_GRANT_TYPE_AUTHORIZATION_CODE, OAUTH_GRANT_TYPE_REFRESH_TOKEN)
    // The permissions the client needs to send requests to the authorization server
    .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
    // The resources server id
    .resourceIds(RESOURCE_SERVER_ID)
    // The scope of content offered by the resources servers
    .scopes("read_profile", "write_profile", "read_firstname")
    // The lifespan of the tokens for the client application
    .accessTokenValiditySeconds(jwtProperties.getAccessTokenExpirationTime())
    .refreshTokenValiditySeconds(jwtProperties.getRefreshTokenExpirationTime());
}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security
    .tokenKeyAccess("permitAll()")
    .checkTokenAccess("isAuthenticated()")
    .passwordEncoder(oauthClientPasswordEncoder);
}

@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints
    .authenticationManager(authenticationManager)
.tokenServices(defaultTokenServices())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.tokenEnhancer(jwtAccessTokenConverter())
.accessTokenConverter(jwtAccessTokenConverter())
    .userDetailsService(userDetailsService);
}

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
  JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
  jwtAccessTokenConverter.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource(jwtProperties.getSslKeystoreFilename()), jwtProperties.getSslKeystorePassword().toCharArray()).getKeyPair(jwtProperties.getSslKeyPair()));
  return jwtAccessTokenConverter;
}

@Bean
public TokenEnhancer tokenEnhancer() {
    return new CustomTokenEnhancer();
}

// Add user information to the token
class CustomTokenEnhancer implements TokenEnhancer {

  @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(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()));
    info.put("organization", authentication.getName());
    DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
    customAccessToken.setAdditionalInformation(info);
    customAccessToken.setExpiration(tokenAuthenticationService.getExpirationDate());
    return customAccessToken;
  }

}

@Bean
public TokenStore tokenStore() {
  return new JwtTokenStore(jwtAccessTokenConverter());
}

@Bean
@Primary
public DefaultTokenServices defaultTokenServices() {
  DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
  defaultTokenServices.setTokenStore(tokenStore());
  defaultTokenServices.setSupportRefreshToken(true);
  return defaultTokenServices;
}

及其附带的安全配置:

@Bean
public UserDetailsService userDetailsService() {
  return new UserDetailsServiceImpl();
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
  return super.authenticationManagerBean();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(userDetailsService).passwordEncoder(userPasswordEncoder);
}

// Allow preflight requests
@Override
public void configure(WebSecurity web) throws Exception {
  web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}

现在,要获取访问令牌,我必须发送一个请求:

@Before
public void setup() throws Exception {
  super.setup();

  addTokenToRequestHeader(httpHeaders, ClientFixtureService.CLIENT_ID, UserFixtureService.USER_EMAIL, UserFixtureService.USER_PASSWORD);
}

private void addTokenToRequestHeader(HttpHeaders headers, String oauthClientId, String username, String password) throws Exception {
  String token = getOAuthAccessToken(oauthClientId, username, password);
  headers.remove(CommonConstants.ACCESS_TOKEN_HEADER_NAME);
  headers.add(CommonConstants.ACCESS_TOKEN_HEADER_NAME, tokenAuthenticationService.buildOAuthAccessToken(token));
}

private void addBase64UserPasswordHeaders(String username, String password, HttpHeaders httpHeaders) {
  httpHeaders.setContentType(MediaType.APPLICATION_JSON);
  httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
  String usernamePassword = username + ":" + password;
  String encodedAuthorisation = Base64.getEncoder().encodeToString(usernamePassword.getBytes(UTF_8));
  httpHeaders.add(CommonConstants.ACCESS_TOKEN_HEADER_NAME,
      CommonConstants.AUTH_BASIC + " " + new String(encodedAuthorisation));
}

private String getOAuthAccessToken(String oauthClientId, String username, String password) throws Exception {
  MultiValueMap<String, String> oauthParams = new LinkedMultiValueMap<>();
  oauthParams.add("grant_type", AuthorizationServerConfiguration.OAUTH_GRANT_TYPE_PASSWORD);
  oauthParams.add("client_id", oauthClientId);
  oauthParams.add("username", username);
  oauthParams.add("password", password);
  addBase64UserPasswordHeaders(AuthorizationServerConfiguration.OAUTH_CLIENT_ID, AuthorizationServerConfiguration.OAUTH_CLIENT_SECRET, httpHeaders);
  ResultActions mvcResult = this.mockMvc
      .perform(post(RESTConstants.SLASH + DomainConstants.AUTH + RESTConstants.SLASH + DomainConstants.TOKEN)
      .headers(httpHeaders)
      .params(oauthParams)
      .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
      .andDo(print())
      .andExpect(status().isOk());
  String resultString = mvcResult.andReturn().getResponse().getContentAsString();
  JacksonJsonParser jsonParser = new JacksonJsonParser();
  return jsonParser.parseMap(resultString).get("access_token").toString();
}

但是当 运行 调试器时,我的用户详细信息实现 loadUserByUsername 方法永远不会被调用。之前当我有一个 JWT 令牌的自定义实现并且没有 OAuth2 配置时调用它:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

  @Autowired
  private CredentialsService credentialsService;

  @Override
  @Transactional
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    if (username != null && !username.isEmpty()) {
      try {
        User user = credentialsService.findByEmail(new EmailAddress(username));
        return new UserDetailsWrapper(user);
      } catch (EntityNotFoundException e) {
        throw new UsernameNotFoundException("The user " + username + " was not found.");
      }
    }
    throw new UsernameNotFoundException("The user " + username + " was not found.");
  }

}

我的请求如下:

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /auth/token
       Parameters = {grant_type=[password], client_id=[ng-xxx], username=[xxx@yahoo.xx], password=[xxxx]}
          Headers = {Content-Type=[application/json, application/json], Accept=[application/json], Authorization=[Basic bmctemxxbzpzZWNyZXQ=]}
             Body = <no character encoding set>

控制台日志的内容如下:

2019-01-08 09:08:11.841 DEBUG 18338 --- [           main] o.s.s.w.a.www.BasicAuthenticationFilter  : Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2019-01-08 09:08:11.842 DEBUG 18338 --- [           main] s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]
2019-01-08 09:08:11.842 DEBUG 18338 --- [           main] s.w.a.DelegatingAuthenticationEntryPoint : No match found. Using default entry point org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint@6cb84986

当您尝试使用 Authorization header 使用基本凭据 POST 到 /oauth/token 时,您会收到以下异常。事实上,您的 UserDetailsService.loadUserByUsername 方法永远不会被调用意味着您的 OAuth2 客户端凭据 ng-zlqo:secret 未在 OAuth2 AuthorizationServer 中注册。

o.s.s.w.a.www.BasicAuthenticationFilter  : Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials