I implemented oAuth2 authorization server using Spring Boot version 1.5.2.RELEASE. The authorization server supports implicit flow. With the WebSecurityConfig below the login form (http://localhost:8200/login) works well.
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JpaUserDetailsService userDetailsService;
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return userDetailsService;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationProvider authenticationProvider() throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsServiceBean());
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return new ProviderManager(singletonList(authenticationProvider()));
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/")
.antMatchers("/docs/**")
.antMatchers("/swagger/**")
.antMatchers("/token/**")
.antMatchers("/v2/*")
.antMatchers(HttpMethod.OPTIONS, "/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/login**").permitAll().anyRequest().authenticated().and()
.formLogin().loginPage("/login").permitAll().and()
.logout().permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}
I want resource server be a part of the same application. The purpose is I need a /me endpoint that will provide me details of logged in user and endpoints for managing users. But as soon as I add ResourceServerConfig annotated with EnableResourceServer below I start getting an error "Full authentication is required to access this resource" when I request http://localhost:8200/login.
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "proclaim-auth";
#Autowired
private ResourceServerTokenServices tokenServices;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.resourceId(RESOURCE_ID)
.tokenServices(tokenServices);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/api/ **").authenticated();
}
}
I suspect that resource server security chain precedes authorization server security chain. I tried to annotate WebSecurityConfig with annotation Order but it did not fix my problem:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
}
What am I doing wrong? Please advise.
Thanks in advance!
EDIT 1
I added method configure(HttpSecurity http) into ResourceServerConfig and changed value of Order annotation to -1 on WebSecurityConfig. Now the security filted defined in WebSecurityConfig is applied and the one defined in ResourceServerConfig is ignored. So when I call /me endpoint with valid token I'm redirected to login page.
The cause of the problem was wrong configuration of http security in the ResourceServerConfig class. The correct configuration is as follows:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/api/**").and()
.authorizeRequests().anyRequest().authenticated();
}
The requestMatchers will ensure that only requests on paths starting with "/api/" will be processed by this security chain. All other requests will be passed to the security chain defined in the WebSecurityConfig class. I was missing this in my config so all requests were processed by the ResourceServerConfig security chain and none request reached the WebSecurityConfig security chain.
Related
I have Spring Boot 2 based Security Gateway performing OAuth2 authentication sitting before GUI app and back-end.
It is configured like
#Configuration
#EnableOAuth2Client
#EnableWebSecurity
public class SecurityGatewayConfig extends WebSecurityConfigurerAdapter{
#Bean
public SecurityGatewayAuthenticationFilter filter() {
return new SecurityGatewayAuthenticationFilter("/login");
}
#Override
public void configure(HttpSecurity http) {
http
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(filter(), OAuth2ClientContextFilter.class)
.httpBasic().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
.and()
...
It redirect requests to /login and SecurityGatewayAuthenticationFilter performs authentication against external OAuth2 provider.
It is good for GIU app. However, when accessing back-end services(they have /api/ in the path) I need different behaviour: If request is not authenticated, do not redirect, but immediately return 401 error.
Any idea, how to configure Spring Security for that?
If i got you questions right, what you can do is work with different ConfigurationAdapters. The basic idea looks like:
#Order(1)
#Configuration
#EnableOAuth2Sso
public static class SecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("defaultMatcher")
private RequestMatcher defaultMatcher;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(defaultMatcher)...
}
}
#Order(2)
#Configuration
public static class OtherConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(yourRequestMatcher())...
}
}
Spring will evalutate each request in the order you add with Order() if you use #EnableResourceServer, this will always have Order(3)
You can then build your request matchers as example like this (in this example it matches all, but excludes explicitly some other):
#Bean
public RequestMatcher defaultMatcher(#Qualifier("apiMatcher") RequestMatcher api, #Qualifier("anyother") RequestMatcher anyother) {
final RequestMatcher all = new AntPathRequestMatcher("/**");
final RequestMatcher nonApi = new NegatedRequestMatcher(new OrRequestMatcher(api, anyother));
return new AndRequestMatcher(all, nonApi);
}
Hope that helps.
best regards,
WiPu
you need add entry point filter
#Component
public final class CustomAuthenticationEntryPoint implements
AuthenticationEntryPoint {
#Override
public void commence(final HttpServletRequest request, final
HttpServletResponse response, final AuthenticationException
authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
When a client accesses resources without authentication...
I have configured multiple custom auth providers,using auth2 and spring boot, but it always executes the CustomInternalAuthenticationProvider only.can you please explain the how to apply ant matcher rules in order?i have used two WebSecurityConfigurerAdapter classes and one is orderded and one is default.guide me on how to handle the antmatcher rules properly?
#EnableResourceServer
#EnableWebSecurity
public class WebSecurityConfig{
#Autowired
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("#order");
http.antMatcher("/../main/**")
.requestMatchers()
.antMatchers("/","/login*", "/oauth/authorize**","/exit","**/logout")
.and().authenticationProvider(daoInternalAuthenticationProvider())
.formLogin().loginPage("/login")
;
}
#Bean
public AuthenticationProvider daoInternalAuthenticationProvider() throws Exception {
return new CustomInternalAuthenticationProvider();
}
}
#Configuration
public static class ApiTokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("default");
http.antMatcher("/../user/**")
.requestMatchers()
.antMatchers("/","/login*", "/oauth/authorize**","/exit","**/logout")
.and() .authenticationProvider(daoExternalAuthenticationProvider())
.formLogin().loginPage("/login")
;
}
#Bean
public AuthenticationProvider daoExternalAuthenticationProvider() throws Exception {
return new CustomExternalAuthonticationProvider();
}
}
I'm trying to setup a very basic oAuth2 authentication in spring boot 2.x.x using either authorization code grant or implicit grant but I can't seem to access the Resource server (which resides in the same spring boot app as the Authorization server) after the token is obtained.
Following is the configuration of WebSecurityConfigurerAdapter
#EnableWebSecurity
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String[] IGNORE_URIS = {
"/swagger-resources/**",
"/swagger-ui.html",
"/v2/api-docs",
"/webjars/**",
"/resources/**",
"/h2-console/**",
"/common/**",
"/configuration/ui",
"/configuration/security",
"/error"
};
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(IGNORE_URIS);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/product/**")
.hasAnyRole("ADMIN").and()
.httpBasic().and().formLogin().and().authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("{noop}admin").roles("ADMIN");
}
#Bean
public PasswordEncoder bCrypt() {
return new BCryptPasswordEncoder();
}
And the AuthorizationServerConfigurerAdapter
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
#Autowired
public AuthorizationServerConfiguration(AuthenticationConfiguration authenticationConfiguration) throws Exception {
this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("my-client-id")
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("ADMIN")
.scopes("all")
.resourceIds("product_api")
.secret("{noop}secret").redirectUris("https://google.com").accessTokenValiditySeconds(0);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
So far so good. I am able to reach the default Spring login page by typing the following Url in the browser.
http://localhost:8080/oauth/authorize?response_type=token&client_id=my-client-id&redirect_uri=https://google.com
Then The login page shows up and I enter my credentials.
After I log in I can then grant access to "my-client-id" app.
Eventually after I approve the app I can see the newly generated access token in the URL bar of the browser which is something like this.
https://www.google.com/#access_token=f2153498-6a26-42c6-93f0-80825ef03b16&token_type=bearer&scope=all
My question is that All of this flow won't work when I also configure a Resource Server.
#EnableResourceServer
#Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("product_api");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/**")
.and().authorizeRequests()
.antMatchers("/**").permitAll();
}
}
What am I doing wrong? When I try to access the oauth/authorize url as before I get the following:
Why? How can one access the login page and retrieve the token? What Am I missing?
You need to use
#Order
Annotation to specify order for WebMvc and ResourceServer classes
#EnableWebSecurity
#Configuration
#Order(1)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
...
}
and for Resource Server
#EnableResourceServer
#Configuration
#Order(2)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
...
}
If you want to see workable example, you can check it here https://github.com/alex-petrov81/stackoverflow-answers/tree/master/auth-server-also-resource
I've created it from your code example.
Literally able to hit the endpoint: http://localhost:8080/oauth2-password/helloworld and still get the String "Hello World!".. Check out my configurations below and please tell me why. This is extremely frustrating.
AUTHORIZATION SERVER
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
AuthenticationManager authenticationManager;
#Primary
#Bean
InMemoryTokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager).tokenStore(this.tokenStore());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client")
.resourceIds("app")
.authorizedGrantTypes("password")
.scopes("read", "write", "trust")
.refreshTokenValiditySeconds(20000)
.accessTokenValiditySeconds(600);
}
}
RESOURCE SERVER
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
AuthenticationManager authManager;
#Autowired
TokenStore tokenStore;
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/helloworld/**").authenticated();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("app").tokenStore(this.tokenStore).authenticationManager(this.authManager);
}
}
WEB SECURITY CONFIG
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Wow, surprised no one was able to catch this one. Extremely poorly documented but found the answer after days of searching.
For anyone who comes this way and finds they are configuring the ResourceServer, AuthorizationServer, and WebSecurityConfigurerAdapter correctly yet you are still hitting the endpoint perfectly fine as if the freaking filter weren't even alive, here is the answer:
Add an #Configuration annotated class in your classpath that implements AbstractSecurityWebApplicationInitializer. Call the class SecurityWebAppInitializer or whatever you would like that makes senes. Make sure to override all the methods and just leave them as their default implementations. Make sure you register this class into your Spring context (along with the other config classes).
Re-compile, re-start the server etc...
Boom. Works, just like that. Hit an endpoint and was unauthorized with a 401.
What this Abstract class does is register the DelegatingFilterProxy to use the springSecurityFilterChain before any other registered Filter. UGH. Something done so easily in XML when you register springSecurityFilterChain.
I have written a sample spring application which have some rest services protected using spring-security-oauth2. Now I want to move these services to the original application which uses spring security form login.
In the original application I want rest services to be protected from spring-security-oauth2 and other spring controllers to be protected using a form login. What I want to know is, is this approach is right or wrong, if right, how could I complete this action.
This is Sample app codes, which uses ouath2,
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService; // Is this really needed?
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "restservice";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// #formatter:off
resources.resourceId(RESOURCE_ID);
// #formatter:on
}
#Override
public void configure(HttpSecurity http) throws Exception {
// http.authorizeRequests().antMatchers("/test").not().permitAll();
http.authorizeRequests().antMatchers("/test").authenticated();
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
#Autowired
// #Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// #formatter:off
endpoints.tokenStore(this.tokenStore).authenticationManager(this.authenticationManager);
// #formatter:on
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients.inMemory().withClient("clientapp").authorizedGrantTypes("password", "refresh_token").authorities("USER")
.scopes("read", "write").resourceIds(RESOURCE_ID).secret("123456");
// #formatter:on
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(this.tokenStore);
return tokenServices;
}
}
}
Here is a part of the original app configuration.
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyStaysureSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.inMemoryAuthentication().withUser("mycompany").password("mypsswd").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/rest/*", "/api-docs/**").permitAll().antMatchers("/**").authenticated().and().formLogin().defaultSuccessUrl("/dashboard").and().csrf().disable();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Spring Security is built on an ordered list of filter chains, and for each request the first one with a matching path handles the authentication. You have 3 filter chains in your combined app, one created by #EnableAuthorizationServer (with default order=0), one created by #EnableResourceServer (with default order=3), and one created by your MyStaysureSecurityConfiguration (also with order=0). You aren't allowed to have 2 filters with the same order so you need to re-arrange them and give them request matchers that make sense for your use case. Maybe you didn't need the #EnableAuthorizationServer anyway (it was unclear from the question)? In any case it is pretty simple - you have 2 choices (roughly):
exclude the oauth2 resources from the request matchers in your MyStaysureSecurityConfiguration and allow them to be handled by the resource server filter.
re-order the resource server filter to a lower order and give it a request matcher that only matches the oauth2 resources.