我正在努力配置Spring Security OAuth2来支持隐式流(我没有密码或授权代码的问题)。
这些是不同的终点:
授权服务器
http://localhost:8082/oauth/authorize
http://localhost:8082/oauth/token
...
资源服务器
http://localhost:8081/users (protected resource)
客户端
http://localhost:8080/api/users
调用http://localhost:8081/users
发起OAuth2舞蹈。
我看到的是:
http://localhost:8080/api/users
会在网址中被重定向到授权服务器:http://localhost:8082/oauth/authorize?client_id=themostuntrustedclientid&response_type=token&redirect_uri=http://localhost:8080/api/accessTokenExtractor
系统会在OAuth批准屏幕上提示我,我会授予所有范围。然后,浏览器会重定向到redirect_uri
:http://localhost:8080/api/accessTokenExtractor
,其中包含一个包含access_token 的片段:http://localhost:8080/api/accessTokenExtractor#access_token=3e614eca-4abe-49a3-bbba-1b8eea05c147&token_type=bearer&expires_in=55&scope=read%20write
问题:
一个。我如何自动恢复原始请求的执行?
规范定义了这种行为,将access_token作为URL中的一个片段:由于片段不是直接发送到服务器,我们必须使用网页脚本来提取它并将其发送给客户端(我的春天) -mvc应用程序)。这意味着设置redirect_uri
指向脚本,而不是原始请求:
http://localhost:8080/api/accessTokenExtractor#access_token=3e614eca-4abe-49a3-bbba-1b8eea05c147&token_type=bearer&expires_in=55&scope=read%20write
accessTokenExtractor网页将令牌发送给客户端。问题是我没有原始电话(http://localhost:8080/api/users)了......
湾您可以在下面看到客户端调用:
restTemplate.getOAuth2ClientContext().getAccessTokenRequest()
.setAll(['client_id': 'themostuntrustedclientid',
'response_type': 'token',
'redirect_uri': 'http://localhost:8080/api/accessTokenExtractor'])
HttpHeaders headers = new HttpHeaders()
ResponseEntity<List<String>> response = restTemplate.exchange('http://localhost:8081/users', HttpMethod.GET, null, new ParameterizedTypeReference<List<String>>(){}, [])
response.getBody()
如果我没有手动设置授权服务器抱怨的参数client_id,response_type和redirect_uri(UserRedirectRequiredException
所必需的),则需要它们。 我们是否希望手动设置?
奇怪的是,它们在ImplicitAccessorProvider.obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
中可用:
ImplicitResourceDetails resource = (ImplicitResourceDetails) details;
try {
...
resource
包含所有这些内容,但不会将其复制到请求中。
如果我们在这里与AuthorizationCodeAccessTokenProvider
进行比较,私有方法getRedirectForAuthorization()
会自动执行此操作... 为何与众不同?
配置:
授权服务器配置:
@EnableAuthorizationServer
@SpringBootApplication
class Oauth2AuthorizationServerApplication {
static void main(String[] args) {
SpringApplication.run Oauth2AuthorizationServerApplication, args
}
}
@Configuration
class OAuth2Config extends AuthorizationServerConfigurerAdapter{
@Autowired
private AuthenticationManager authenticationManager
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager([])
manager.createUser(new User("jose","mypassword", [new SimpleGrantedAuthority("ROLE_USER")]))
manager.createUser(new User("themostuntrustedclientid","themostuntrustedclientsecret", [new SimpleGrantedAuthority("ROLE_USER")]))
return manager
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
//curl trustedclient:trustedclientsecret@localhost:8082/oauth/token -d grant_type=password -d username=user -d password=cec31d99-e5ee-4f1d-b9a3-8d16d0c6eeb5 -d scope=read
.withClient("themostuntrustedclientid")
.secret("themostuntrustedclientsecret")
.authorizedGrantTypes("implicit")
.authorities("ROLE_USER")
.scopes("read", "write")
.accessTokenValiditySeconds(60)
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//security.checkTokenAccess('hasRole("ROLE_RESOURCE_PROVIDER")')
security.checkTokenAccess('isAuthenticated()')
}
}
资源服务器配置和受保护的端点:
@EnableResourceServer
@SpringBootApplication
class Oauth2ResourceServerApplication {
static void main(String[] args) {
SpringApplication.run Oauth2ResourceServerApplication, args
}
}
@Configuration
class OAuth2Config extends ResourceServerConfigurerAdapter{
@Value('${security.oauth2.resource.token-info-uri}')
private String checkTokenEndpointUrl
@Override
public void configure(HttpSecurity http) throws Exception {
http
// Since we want the protected resources to be accessible in the UI as well we need
// session creation to be allowed (it's disabled by default in 2.0.6)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers().antMatchers("/users/**")
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/users").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.PUT, "/users/**").access("#oauth2.hasScope('write')")
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices()
remoteTokenServices.setCheckTokenEndpointUrl(checkTokenEndpointUrl)
remoteTokenServices.setClientId("usersResourceProvider")
remoteTokenServices.setClientSecret("usersResourceProviderSecret")
resources.tokenServices(remoteTokenServices)
}
}
@RestController
class UsersRestController {
private Set<String> users = ["jose", "ana"]
@GetMapping("/users")
def getUser(){
return users
}
@PutMapping("/users/{user}")
void postUser(@PathVariable String user){
users.add(user)
}
}
这是客户端配置:
@EnableOAuth2Client
@SpringBootApplication
class SpringBootOauth2ClientApplication {
static void main(String[] args) {
SpringApplication.run SpringBootOauth2ClientApplication, args
}
}
@Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(false)
.inMemoryAuthentication().withUser("jose").password("mypassword").roles('USER')
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().hasRole('USER')
.and()
.formLogin()
}
}
@Configuration
class OAuth2Config {
@Value('${oauth.resource:http://localhost:8082}')
private String baseUrl
@Value('${oauth.authorize:http://localhost:8082/oauth/authorize}')
private String authorizeUrl
@Value('${oauth.token:http://localhost:8082/oauth/token}')
private String tokenUrl
@Autowired
private OAuth2ClientContext oauth2Context
@Bean
OAuth2ProtectedResourceDetails resource() {
ImplicitResourceDetails resource = new ImplicitResourceDetails()
resource.setAuthenticationScheme(AuthenticationScheme.header)
resource.setAccessTokenUri(authorizeUrl)
resource.setUserAuthorizationUri(authorizeUrl);
resource.setClientId("themostuntrustedclientid")
resource.setClientSecret("themostuntrustedclientsecret")
resource.setScope(['read', 'write'])
resource
}
@Bean
OAuth2RestTemplate restTemplate() {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resource(), oauth2Context)
//restTemplate.setAuthenticator(new ApiConnectOAuth2RequestAuthenticator())
restTemplate
}
}
我的客户端具有以下控制器,它从资源服务器调用受保护的aouth2端点:
@RestController
class ClientRestController {
@Autowired
private OAuth2RestTemplate restTemplate
def exceptionHandler(InsufficientScopeException ex){
ex
}
@GetMapping("/home")
def getHome(HttpSession session){
session.getId()
}
@GetMapping("/users")
def getUsers(HttpSession session){
println 'Session id: '+ session.getId()
//TODO Move to after authentication
Authentication auth = SecurityContextHolder.getContext().getAuthentication()
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setAll(['client_id': 'themostuntrustedclientid', 'response_type': 'token', 'redirect_uri': 'http://localhost:8080/api/users'])
HttpHeaders headers = new HttpHeaders()
ResponseEntity<List<String>> response = restTemplate.exchange('http://localhost:8081/users', HttpMethod.GET, null, new ParameterizedTypeReference<List<String>>(){}, [])
response.getBody()
}
}