如何从用户名获取访问令牌?
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
我有一个项目,我有一个 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