我遇到了使用spring security和oauth2注销的问题。我有一个客户端网关,其中包含UI,授权服务器和多个资源服务器。预期的行为是,当用户单击注销链接时,它们只是从客户端网关注销,当页面重新加载时,它应该重定向回授权服务器,因为整个应用程序都被锁定。如果用户已从可接受的授权服务器注销。用户界面是有角度的。我正在调用的注销功能是
$scope.logout = function() {
$http.post('/logout', {}).success(function() {
$location.path("/");
}).error(function(data) {
$rootScope.authenticated = false;
});
}
当调用它时,我得到的resposne是:
XMLHttpRequest cannot load http://localhost:9999/auth/oauth/authorize?client_id=ui&redirect_uri=http://localhost:8080/login?logout&response_type=code&state=mj2HVD. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.
显然这是一个跨站点错误,对我没有任何意义,因为登录部分工作正常。
AuthServerApplication
@SpringBootApplication
公共类AuthServerApplication扩展了WebMvcConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(AuthServerApplication.class);
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
protected static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Autowired // <-- This is crucial otherwise Spring Boot creates its own
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
log.info("Defining inMemoryAuthentication (2 users)");
auth
.inMemoryAuthentication()
.withUser("user").password("password")
.roles("USER")
.and()
.withUser("admin").password("password")
.roles("USER", "ADMIN")
;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.httpBasic().disable()
.anonymous().disable()
.authorizeRequests().anyRequest().authenticated()
;
}
}
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Value("${config.oauth2.privateKey}")
private String privateKey;
@Value("${config.oauth2.publicKey}")
private String publicKey;
@Autowired
private AuthenticationManager authenticationManager;
@Bean
public JwtAccessTokenConverter tokenEnhancer() {
log.info("Initializing JWT with public key:\n" + publicKey);
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
@Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(tokenEnhancer());
}
/**
* Defines the security constraints on the token endpoints /oauth/token_key and /oauth/check_token
* Client credentials are required to access the endpoints
*
* @param oauthServer
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("isAnonymous() || hasRole('ROLE_TRUSTED_CLIENT')") // permitAll()
.checkTokenAccess("hasRole('TRUSTED_CLIENT')"); // isAuthenticated()
}
/**
* Defines the authorization and token endpoints and the token services
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// Which authenticationManager should be used for the password grant
// If not provided, ResourceOwnerPasswordTokenGranter is not configured
.authenticationManager(authenticationManager)
// Use JwtTokenStore and our jwtAccessTokenConverter
.tokenStore(tokenStore())
.accessTokenConverter(tokenEnhancer())
;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// Confidential client where client secret can be kept safe (e.g. server side)
.withClient("confidential").secret("secret")
.authorizedGrantTypes("client_credentials", "authorization_code", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:9000/admin").autoApprove(true)
.and()
.withClient("ui").secret("secret")
.authorizedGrantTypes("client_credentials", "authorization_code", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:8080/").autoApprove(true)
.and()
// Public client where client secret is vulnerable (e.g. mobile apps, browsers)
.withClient("public") // No secret!
.authorizedGrantTypes("client_credentials", "implicit")
.scopes("read")
.redirectUris("http://localhost:9000/").autoApprove(true)
.and()
// Trusted client: similar to confidential client but also allowed to handle user password
.withClient("trusted").secret("secret")
.authorities("ROLE_TRUSTED_CLIENT")
.authorizedGrantTypes("client_credentials", "password", "authorization_code", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:9000/").autoApprove(true)
;
}
}
GatewayApplication
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
@EnableCircuitBreaker
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Configuration
@EnableOAuth2Sso
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.logout()
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and()
.authorizeRequests()
.antMatchers("/login", "/beans", "/user").permitAll()
.anyRequest().authenticated().and()
.csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterBefore(new RequestContextFilter(), HeaderWriterFilter.class)
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
}
如果我可以切换用户也是可以接受的。如果我错过了任何相关代码,您可以查看回购here。