I have a project https://github.com/ndrone/sample-gateway-oauth2login/tree/feature/allowAllToHealth That I am trying to allow specific URL's open to anyone that request it. In this case, it is the health endpoint of Actuator while protect all other Actuator endpoints. What I am finding is that the TokenRelayGatewayFilterFactory is being applied to all routes when though it is only set to be applied to one route. Not sure what I got wrong.
SecurityConfig in the Resource Service
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
http.authorizeExchange().pathMatchers("/manage/health").permitAll();
http
.authorizeExchange()
.pathMatchers("/resource", "/manage/**").hasAuthority("SCOPE_resource.read")
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
Gateway Routes
#Controller
#SpringBootApplication
public class GatewayApplication {
#Autowired
private TokenRelayGatewayFilterFactory filterFactory;
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
//#formatter:off
return builder.routes()
.route("resource-health", r -> r.path("/resource/manage/health")
.filters(f -> f.stripPrefix(1))
.uri("http://localhost:9000"))
.route("resource-actuator-protected", r -> r.path("/resource/manage/**")
.filters(f -> f.stripPrefix(1).filter(filterFactory.apply()))
.uri("http://localhost:9000"))
.route("resource", r -> r.path("/resource")
.filters(f -> f.filter(filterFactory.apply()))
.uri("http://localhost:9000"))
.build();
//#formatter:on
}
#GetMapping("/")
public String index(Model model,
#RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
#AuthenticationPrincipal OAuth2User oauth2User) {
model.addAttribute("userName", oauth2User.getName());
model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());
model.addAttribute("userAttributes", oauth2User.getAttributes());
return "index";
}
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
Since I didn't have a spring security configuration detailed out in the gateway. Spring Security protected all urls. Digging into the sample more and the source of their fork from jgrandja I needed to add the following.
/**
* This code duplicates {#link org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration}
* and enhances with oauth2Login() specific configuration
*
* and with changes defined by jgrandja #see OAuth
*
* #author nd26434 on 2019-06-21.
*/
#Configuration
#ConditionalOnClass({ EnableWebFluxSecurity.class, WebFilterChainProxy.class })
#ConditionalOnMissingBean({ SecurityWebFilterChain.class, WebFilterChainProxy.class })
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
#AutoConfigureBefore(ReactiveSecurityAutoConfiguration.class)
#AutoConfigureAfter({ HealthEndpointAutoConfiguration.class,
InfoEndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ReactiveOAuth2ClientAutoConfiguration.class,
ReactiveOAuth2ResourceServerAutoConfiguration.class })
class SecurityConfig {
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
// #formatter:off
// gateway actuator
http.authorizeExchange()
.matchers(EndpointRequest.to(HealthEndpoint.class, InfoEndpoint.class)).permitAll();
// gateway resource actuator
http.authorizeExchange().pathMatchers("/manage/health").permitAll();
return http.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2Login()
.and()
.exceptionHandling()
// NOTE:
// This configuration is needed to perform the auto-redirect to UAA for authentication.
.authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/oauth2/authorization/login-client"))
.and()
.build();
// #formatter:on
}
}
The working branch: https://github.com/ndrone/sample-gateway-oauth2login/tree/feature/allowAllToHealth
Related
I have a spring boot application with a mongo databse and spring security as a dependency.
It has two services first one for authentication and second one for application resource (entities, services controllers).
This is my config class in the authentication service:
#Configuration
#EnableWebSecurity
public class AuthServerSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
protected UserDetailsService userDetailsService() {
return new MongoUserDetailsService();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().anyRequest().authenticated();
System.out.println("auth");
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
#Bean(name="authenticationManager")
#Lazy
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
this is the rest controller:
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping(value = "/api/users")
public class UserController {
#Autowired
UserServiceImpl userServiceImpl;
//Getting all users
#GetMapping(value = "")
public List<UserDTO> getAllUsers() {
return userServiceImpl.getAllUsers();
}
//Getting a user by ID
#GetMapping(value = "/profil/{userId}")
public UserDTO getUserById(#PathVariable String userId) {
return userServiceImpl.getUserById(userId);
}
//Getting a user by Username
#GetMapping(value = "/profil/username/{username}")
public UserDTO getUserByUsernameOrEmail(String username) {
return userServiceImpl.getUserByUsernameOrEmail(username);
}
//Logout user and delete token
#PostMapping("/logout")
public void logout(HttpServletRequest request) {
userServiceImpl.logout(request);
}
I changed my configure method to this :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests() // authorize
.anyRequest().authenticated() // all requests are authenticated
.and()
.httpBasic();
http.cors();
}
Now i get 401 unauthorized when acceccing protected resources.The problem is now even when i send the correct bearer token in the request header i still get 401 unauthorized "Full authentication is required to access this resource"
Update:
I changed my project architecture from microservices to one simple spring boot project.
this is the new code of the class "AuthServerSecurityConfig"
#Configuration
public class AuthServerSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
protected UserDetailsService userDetailsService() {
return new MongoUserDetailsService();
}
#Autowired
BCryptPasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(userDetailsService());
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll().and()
.httpBasic();
http.cors();
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
#Bean(name="authenticationManager")
#Lazy
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
and this "ResourceServerConfig" code:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired private ResourceServerTokenServices tokenServices;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("foo").tokenServices(tokenServices);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() // authorize
.antMatchers("/oauth/**").permitAll();
http
.authorizeRequests().antMatchers("/api/**").authenticated();
http
.headers().addHeaderWriter(new HeaderWriter() {
#Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
response.addHeader("Access-Control-Allow-Origin", "*");
if (request.getMethod().equals("OPTIONS")) {
response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
}
}
});
}
}
When i try to access protected resource i get "error": "unauthorized",
"error_description": "Full authentication is required to access this resource", Which is the normal behaviour.The problem is now i can't login to get the user aceess_token.
I get "401 unauthorized" when accessing this endpoint "http://localhost:8080/oauth/token?grant_type=password&username=user&password=user".
These are the default init user credentials and the user exists in my mongodatabase with a crypted and correct format password starts with "$2a" and has "60" caracters.
I get in "Encoded password does not look like BCrypt
Authentication failed: password does not match stored value" in the console when trying to login.
In ResourceServerConfig class file, in the configure method change to the below code.
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll().and()
.httpBasic();
Let me know if it worked.
Here's an example of configuration spring security with a jwt token check :
you can change the data source from h2 to mongodb and find the filters and providers used in my repo :
https://github.com/e2rabi/sbs-user-management/tree/main/sbs-user-management
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
#FieldDefaults(level = PRIVATE, makeFinal = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
// Put your public API here
new AntPathRequestMatcher("/public/**"),
new AntPathRequestMatcher("/h2-console/**"),
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
TokenAuthenticationProvider provider;
SecurityConfig(final TokenAuthenticationProvider provider) {
super();
this.provider = requireNonNull(provider);
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
#Override
public void configure(final WebSecurity web) {
web.ignoring().requestMatchers(PUBLIC_URLS);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.exceptionHandling()
// this entry point handles when you request a protected page and you are not yet
// authenticated
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.and()
.authenticationProvider(provider)
.addFilterBefore(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.csrf().disable()
.cors().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
// h2 console config
http.headers().frameOptions().sameOrigin();
// disable page caching
http.headers().cacheControl();
}
#Bean
TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy(new NoRedirectStrategy());
return successHandler;
}
#Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
FilterRegistrationBean disableAutoRegistration(final TokenAuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
}
Hope that my answer can help,
you can drop a breakpoint to the line change the response status, and then check who and why it returns 403, it can finally help you get the solution
Drop a breakpoint on the line set the 403 status, to see how this happen from the stackframes.
Guess that it returns 403 without much other information, but it must need to set the status to the response, right? So drop a breakpoint to the setStatus method, I don't know where it should locate, in tomcat lib, spring lib, or servlet lib. Check the HttpResponse, they're several implementation, set the breakpoints for those setStatus/setCode methods. (Next you can see it acutally happens at HttpResponseWrapper::setStatus)
Analyze the stackframes to see what's going on there
please check https://stackoverflow.com/a/73577697/4033979
I have an SpringBoot app. 2.1.3.RELEASE securized by JWT, I want to add an actuator. I added this dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
this is my configFile:
#Profile("api")
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOG = LoggerFactory.getLogger(ApiWebSecurityConfig.class);
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private UserSecurityService userSecurityService;
#Value("${jwt.header}")
private String tokenHeader;
#Value("${server.servlet.context-path}")
private String serverContextPath;
/** The encryption SALT. */
private static final String SALT = "fd&eekj§sfs23#$1*(_)nof";
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userSecurityService)
.passwordEncoder(passwordEncoder());
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// Un-secure H2 Database
.antMatchers("/h2-console/**/**").permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity
.headers()
.frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
.cacheControl();
}
#Override
public void configure(WebSecurity web) throws Exception {
// AuthenticationTokenFilter will ignore the below paths
web
.ignoring()
.antMatchers(
HttpMethod.POST,
"/auth"
)
.antMatchers(
HttpMethod.GET,
"/actuator"
)
.antMatchers(
HttpMethod.POST,
"/reg"
);
}
}
but when I access in the postman to http://127.0.0.1:8080/myApp/actuator/
I got a
{
"timestamp": "2019-03-21T16:39:47.877+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/myApp/actuator/"
}
and HTTP Status 404 – Not Found
when accessing http://127.0.0.1:8080/actuator/
By default the URL is:
http://localhost:8080/actuator
try to change your config from
.antMatchers(
HttpMethod.GET,
"/actuator"
)
to
.antMatchers(
HttpMethod.GET,
"/actuator/**"
)
The Spring boot actuator contains multiple endpoints which include health, metrics, etc.
The endpoints are accessed as follows;
http://{baseUrl}/autuator/health
http://{baseUrl}/autuator/metrics
so get all the endpoints - http://{baseUrl}/autuator/** [GET Request]
so to permit access to this endpoint in your security configuration, change your config from.
.antMatchers(
HttpMethod.GET,
"/actuator"
)
to
.antMatchers(
HttpMethod.GET,
"/actuator/**"
)
I'm trying to make a sample OAuth2 Spring authorization and resource server. My intention is to implement two separate applications - one representing authorization server ant the other representing resource server. Since I'm quite a beginner in Spring Security, I guess I need some guidance to complete my task.
I already managed to implement a simple authorization server using in-memory token store (app named "OAuth").
AuthServerOAuth2Config.java
#Configuration
#EnableAuthorizationServer
public class AuthServerOAuth2Config extends AuthorizationServerConfigurerAdapter {
private static final String RESOURCE_ID = "myResource";
#Autowired
private UserApprovalHandler handler;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients.inMemory()
.withClient("test")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.resourceIds(RESOURCE_ID)
.secret("test")
.accessTokenValiditySeconds(300).//invalid after 5 minutes.
refreshTokenValiditySeconds(600);//refresh after 10 minutes.
// #formatter:on
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).userApprovalHandler(handler).authenticationManager(authManager);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
OAuth2SecurityConfig.java
#Configuration
#EnableWebSecurity
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOG = LoggerFactory.getLogger(OAuth2SecurityConfig.class);
#Autowired
private ClientDetailsService clientService;
#Autowired
private DataSource dataSource;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
// #formatter:off
auth.inMemoryAuthentication()
.withUser("javabycode").password("123456").roles("USER")
.and()
.withUser("admin").password("admin123").roles("ADMIN");
// #formatter:on
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll();
// #formatter:on
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore) {
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientService));
handler.setClientDetailsService(clientService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
Accessing http://localhost:9081/OAuth/oauth/token?grant_type=password&username=admin&password=admin123 returns token as expected, so I'm guessing that authorization server is configured ok.
Now there's a resource server part (app named "RestTest"). I've managed to find some examples using RemoteTokenServices to access token service that resides in another app. So here's my resource server so far.
OAuth2ResourceConfig.java
#Configuration
#EnableResourceServer
#EnableWebSecurity
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "myResource";
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.
anonymous().disable()
.requestMatchers().antMatchers("/v1/**")
.and().authorizeRequests()
.antMatchers("/v1/**").access("hasRole('ADMIN')")
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
// #formatter:on
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws
Exception {
resources.tokenServices(tokenService()).resourceId(RESOURCE_ID).stateless(true);
}
#Primary
#Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl("http://localhost:9081/OAuth/oauth/check_token/");
tokenService.setClientId("test");
tokenService.setClientSecret("test");
return tokenService;
}
}
I'm trying to secure my REST API (http://localhost:9081/RestTest/v1/foobar) so I believe that configuration above is correct, right? Problem is that when I access v1/foobar endpoint (via Postman) it's accessible without any authentication. So I think I'm simply missing some part of configuration, but I can't figure it out how to connect to authorization server correctly. One more thing to mention - I'm not using Spring Boot!
I'd really appreciate some guidance to make my sample work. Thanks!
EDIT1: I've added resourceId to both authentication and resource server - no luck. Is resourceId even mandatory?
You should add RESOURCE_ID both in ResourceServer and AuthorizationServer in a way that, (you updated your question though with that snippet)
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenService()).resourceId(RESOURCE_ID).stateless(true);
}
And in your auth server
.scopes("read", "write", "trust").resourceIds(RESOURCE_ID)
Add a springSecurityFilterChain as you missing that in web.xml that you already said in comment
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
From spring docs:
It creates a Servlet Filter known as the springSecurityFilterChain which is responsible for all the security (protecting the application URLs, validating submitted username and passwords, redirecting to the log in form, etc) within your application.
I am creating an application where user can login by username and password or by facebook with spring social. I've taken an example from
this tutorial, but it's configured for Spring MVC not for REST.
Author creates special UserDetailsService and assign it to the authentication manager builder.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
At this point I think everything is okay. UserDetailsService has method to find user by user id.
In http configuration in security config class should I change 'form login' to http 'basic authentication' If I want to have REST application?
If I change this, Should I add correct http authorization header with each request? Or can I use token auth with standard login (by username and password, not social)? Token based authentication will cooperate with Spring social login(there is also token)?
#Configuration
#EnableSocial
public class SocialContext implements SocialConfigurer {
#Autowired
private DataSource dataSource;
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
cfConfig.addConnectionFactory(new FacebookConnectionFactory(
env.getProperty("facebook.app.id"),
env.getProperty("facebook.app.secret")
));
}
#Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new JdbcUsersConnectionRepository(
dataSource,
connectionFactoryLocator,
Encryptors.noOpText()
);
}
#Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
return new ConnectController(connectionFactoryLocator, connectionRepository);
}
}
Security config
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login/authenticate")
.failureUrl("/login?error=bad_credentials")
//Configures the logout function
.and()
.logout()
.deleteCookies("JSESSIONID")
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
//Configures url based authorization
.and()
.authorizeRequests()
//Anyone can access the urls
.antMatchers(
"/auth/**",
"/login",
"/signup/**",
"/user/register/**",
"/greeting"
).permitAll()
//The rest of the our application is protected.
.antMatchers("/**").hasRole("USER")
//Adds the SocialAuthenticationFilter to Spring Security's filter chain.
.and()
.apply(new SpringSocialConfigurer())
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
#Bean
public SocialUserDetailsService socialUserDetailsService() {
return new AppSocialUserDetailsService(userDetailsService());
}
#Bean
public UserDetailsService userDetailsService() {
return new AppUserDetailsService(userRepository);
}
}
I am having a problem with logout with spring security and oauth2. I have a client gateway which houses the UI, an authorization server, and several resource servers. The expected behavior is that when the user clicks on the logout link, they are just logged out from the client gateway and when the page reloads it should redirect back to the authorization server since the entire app is locked down. If the user is logged out of the authorization server that is acceptable too. The UI is in angular. The logout function I am calling is
$scope.logout = function() {
$http.post('/logout', {}).success(function() {
$location.path("/");
}).error(function(data) {
$rootScope.authenticated = false;
});
}
When this is called, the resposne I get is:
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.
Obviously this is a cross site error which does not make any sense to me because the login portion works fine.
AuthServerApplication
#SpringBootApplication
public class AuthServerApplication extends 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;
}
}
}
It would also be acceptable if I could just switch the user. If I missed any relevant code, you can look at the repo here.