Agenda: To create an authorization and resource server such that.
Rest clients can authenticate and authorize and use tokens to fetch resources about the user.
this worked. accessing resources defined at /rest/user endpoint is working fine
Web clients can SSO using this authorization server
I tried using #EnableOAuth2Sso and also using #EnableOAuth2Client. Both didn't work.
When using EnableOAuth2Sso redirect to oauth server's login happened but redirection back to the app didn't happen.
Users can directly log into the authorization server and see if they have an account.
this is working but it is skipping authentication and authorization and the page is getting displayed immediately
I have a OAuth server with ResourceConfig and WebSecurityConfig
#Configuration
#EnableWebSecurity
#Order(1)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired private AuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired private AuthenticationFailureHandler authenticationFailureHandler;
#Autowired
#Qualifier("userAccountDetailsService")
UserAccountDetailsService userAccountDetailsService;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// #formatter:off
httpSecurity
.csrf().disable()
.anonymous().disable()
.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.antMatchers("/**", "/css/**", "/js/**", "/images/**").permitAll()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/userPage/*").hasAnyRole("USER", "HRADMIN")
.antMatchers("/adminPage/*").hasRole("HRADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/login")
.failureHandler(authenticationFailureHandler)
.successHandler(authenticationSuccessHandler)
.and()
.logout()
.logoutUrl("/logout");
// #formatter:on
}
#Autowired
protected void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userAccountDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
#Configuration
#EnableResourceServer
#Order(2)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "my_rest_api";
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.
anonymous().disable()
.authorizeRequests()
.antMatchers("/rest/user/**").authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
Isn't it possible to combine and use web resource and oauth resources in the same server?
I'm using
Spring Boot: 2.2.0.BUILD-SNAPSHOT and
Spring security-oauth2-autoconfigure: 2.1.3.RELEASE
The whole source is available in github
Authorization and Resource server:
https://github.com/john77eipe/SpringSecurityDrills/tree/master/securestore-oauth
Spring Web client using EnableOAuth2Client:
https://github.com/john77eipe/SpringSecurityDrills/tree/master/securestore-web-resource-1
Spring Web client using EnableOAuth2Sso:
https://github.com/john77eipe/SpringSecurityDrills/tree/master/securestore-web-resource-2
Related
Now there is authentication only once when I open my browser. I want to authenticate each request without need to reopen the browser.
This is my configuration class. What can I add to configure function?
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").hasRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable().httpBasic();
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("").password("{noop}").roles("ADMIN");
}
}
I am new to Spring Security and Oauth2. In my spring boot application, I have implemented authentication with Oauth2 with following set of changes:
Custom Ouath2 User service is as follows:
#Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserRepository userRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
...
}
}
Security Configuration is as follows:
#EnableWebSecurity
#Import(SecurityProblemSupport.class)
#ConditionalOnProperty(
value = "myapp.authentication.type",
havingValue = "oauth",
matchIfMissing = true
)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
public SecurityConfiguration(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/bundle.js")
.antMatchers("/slds-icons/**")
.antMatchers("/assets/**")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/swagger-resources")
.antMatchers("/v2/api-docs")
.antMatchers("/api/redirectToHome")
.antMatchers("/test/**");
}
public void configure(HttpSecurity http) throws Exception {
RequestMatcher csrfRequestMatcher = new RequestMatcher() {
private RegexRequestMatcher requestMatcher =
new RegexRequestMatcher("/api/", null);
#Override
public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}
};
http.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher)
.and()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.anyRequest().authenticated()//.and().oauth2ResourceServer().jwt()
.and()
.oauth2Login()
.redirectionEndpoint()
.baseUri("/oauth2**")
.and()
.failureUrl("/api/redirectToHome")
.userInfoEndpoint().userService(oauth2UserService())
;
http.cors().disable();
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return customOAuth2UserService;
}
}
Content of application.properties is as follows:
spring.security.oauth2.client.registration.keycloak.client-id=abcd
spring.security.oauth2.client.registration.keycloak.client-name=Auth Server
spring.security.oauth2.client.registration.keycloak.scope=api
spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-authentication-method=basic
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
myapp.oauth2.path=https://internal.authprovider.com/oauth2/
spring.security.oauth2.client.provider.keycloak.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.keycloak.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.keycloak.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.keycloak.user-name-attribute=name
myapp.authentication.type=oauth
Now, with the existing authentication mechanism, I would like to add support for multiple authentication providers: LDAP, Form-Login, etc.
In this regard, I have gone through a few articles:
https://www.baeldung.com/spring-security-multiple-auth-providers
Custom Authentication provider with Spring Security and Java Config
But, I am not getting any concrete idea regarding what changes should I do in the existing code base in order to achieve this.
Could anyone please help here? Thanks.
I've created a simplified setup starting from your code with support for both OAuth2 and Basic Auth.
/tenant2/** will start a basic authentication.
/** (everything else) triggers an OAuth2 Authorization Code authentication.
The key to achieve this is to have one #Configuration class per authentication type.
Let's start with the controllers:
Tenant1HomeController
#Controller
public class Tenant1HomeController {
#GetMapping("/tenant1/home")
public String home() {
return "tenant1Home";
}
}
Tenant2HomeController
#Controller
public class Tenant2HomeController {
#GetMapping("/tenant2/home")
public String home() {
return "tenant2Home";
}
}
Now, the configuration classes:
Tenant1SecurityConfiguration
#Configuration
public class Tenant1SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority("ADMIN")
.antMatchers("/tenant1/**").authenticated()
.and()
.oauth2Login()
.and()
.cors()
.disable();
}
}
Tenant2SecurityConfiguration (Notice the #Order(90), that's important
#Order(90)
#Configuration
public class Tenant2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcher("/tenant2/**"))
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/tenant2/**").hasAuthority("BASIC_USER")
.and()
.httpBasic();
http.cors().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("{noop}password")
.roles("BASIC_USER");
}
}
Finally the configuration:
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: myclient
client-secret: c6dce03e-ea13-4b76-8aab-c876f5c2c1d9
provider:
keycloak:
issuer-uri: http://localhost:8180/auth/realms/myrealm
With this in place, if we hit http://localhost:8080/tenant2/home, will be prompted with the basic auth popup:
Trying with http://localhost:8080/tenant1/home sends you to Keycloak's login form:
UPDATE:
It's completely viable to configure a multitenant application with the configuration above.
The key would be that each authentication provider works with a different set of users (tenants), e.g.:
TENANT 1 (OAuth2 authentication):
#Configuration
public class Tenant1SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
...
.and()
.oauth2Login()
.and()
...
This first subset of users is federated by the OAuth2 provider, Keycloak in this case.
TENANT 2 (Basic / form /xxx authentication):
#Order(90)
#Configuration
public class Tenant2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(XXX)
For the second tenant, you can use a userDetailsService that points to a different repository of users (LDAP, database...).
My working spring boot SecurityConfig currently looks like this:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**", "/js/**", "/img/**", "/error", "/webjars/**", "/login", "**/favicon.ico").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().failureUrl("/login?state=badauth")
.loginPage("/login")
.loginProcessingUrl("/processLogin")
.successHandler(successHandler())
.failureHandler(failureHandler())
.permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login?state=loggedout");
;
}
I am trying to allow users on the localhost to gain access to all resources without logging in. I have tried to insert the following into various locations within the chain:
.antMatchers("/**").access("hasIpAddress(\"127.0.0.1\") or hasIpAddress(\"::1\")")
Which always causes non-localhost access to fail with a forbidden.
How can I bypass login for users on the localhost only?
Try this
DemoApplication.java
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
TestController.java
#RestController
public class TestController {
#GetMapping("/secured")
public ResponseEntity secured() {
return ResponseEntity.ok("Access granted");
}
}
WebSecurityConfig.java
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**")
.access("hasIpAddress('127.0.0.1') or hasIpAddress('::1') or isAuthenticated()") // 127.0.0.1 and localhost do not need to authenticate on any url
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("password"))
.authorities("ROLE_USER");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Try to add hasIpAddress() before authenticated like this:
.antMatchers("/**").hasIpAddress("127.0.0.1").anyRequest().authenticated();
You can try disabling default security in Spring Boot by adding this line in application.properties file of your local/dev profile:
security.basic.enabled=false
I would like to configure web security layer based on my request type.
If the request starts with /rest then it should use Basic authentication with stateless session management and for login authentication then it should use CSRF with stateful session management.
I have tried below code.
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers("/rest/**").hasRole("SUPER_ADMIN")
.anyRequest().fullyAuthenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.formLogin().and().logout().permitAll();
}
It works with basic authentication but it doesn't work for the login request because the session is not stateful. Can anyone please help me to configure Spring security. I am new to Spring security.
You need
1. Rest API's to be authenticated by basic authentication
2. Your web application be authenticated by form login.
And authorization is other part in both cases that you can set it as per your requirement.
Let me explain what was wrong with your approach. By your approach you can achieve only one authentication entry point from one configuration. i.e, you can't achieve multiple authentication entry point.
Now coming to your first requirement of achieving multiple authentication entry point.
1. For Rest API resources -- authentication by HttpBasicAuthentication for antMatcher /rest/**
2. For WebApp resources -- authentication by Form Login for antMatcher other than /rest/**
To achieve this
1. You need to have implementation of WebSecurityConfigurerAdapter of different configuration order and different antMatcher patterns.
2. Order of each configuration is important.
- wildcard pattern(/**) should be placed last order
- non wildcard pattern or restricted pattern(/rest/**) should be placed first order
3. As those configuration classes are static and inner classes for a class which is annotated #EnableWebSecurity you should be careful while defining bean using #bean and autowiring using #Autowired.
Note:
Most of people makes mistake by not defining antmather for authorizeRequest()
If first configuration #Order(1) class is configured as below
http.authorizeRequests()
2nd configuration will become dead configuration because
http.authorizeRequests() => http.antMatcher("/**").authorizeRequests()
And all URL's will be configured only for first configuration only.
Refer code given below for better understanding.
#Configuration
#EnableWebSecurity
public class SpringSecurityConfiguration
{
#Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
#Configuration
#Order(1)
public static class BasicAuthSecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
public void configureInMemoryAuthentication(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication()
.withUser("superadmin")
.password(passwordEncoder.encode("superadmin#123#"))
.roles("SUPER_ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.antMatcher("/rest/**")
.authorizeRequests()
.antMatchers("/rest/**").hasRole("SUPER_ADMIN")
.and().httpBasic();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
#Configuration
#Order(2)
public static class LoginFormSecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
public void configureInMemoryAuthentication(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder.encode("user#123#"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.antMatcher("/**") //wild card i.e, allow all (But already /rest/** is filtered by 1st config)
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/**").authenticated()
.and().formLogin()
.defaultSuccessUrl("/app/user/dashboard")
.and().exceptionHandling()
.accessDeniedPage("/403")
.and().logout()
.invalidateHttpSession(true);
http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired");
}
}
}
This question has requirement of different sets of URL's(/rest/** and other than /rest/**) for different authentication filters. Here user's (for both basic auth and form login) may be authenticated against a single table (say user_details) or multiple tables (say api_users and web_users)
If you have requirement like there is no different set of URL's but two sets of users say customer and employees(staff) both are accessing same application but they needs to be authenticated against different tables(say users and customer table) in that case refer my another answer Spring Security user authentication against customers and employee table
You have to allow users to access login page without authentication and same you can do with static pages. See below configuration.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
I have a problem regarding on the controllers request from a spring boot application.
I have made a certificate in order to run the app on https. The certificate works fine, it is valid.
My main problem is when i test my methods from the controller through postman they(the url reques) work fine on https and http...it shouldn't work on http. Can someone help on this ?
This is my WebSecurityConfig class:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
public static final String AUTHENTICATED_HEADER_NAME = "Authenticated";
public static final String AUTHENTICATED_TRUE = "true";
public static final String AUTHENTICATED_FALSE = "false";
#Autowired
public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.userDetailsService(authenticationManager).passwordEncoder(passwordEncoder);
}
#Override
#Bean(value = "authenticationManagerBean")
public org.springframework.security.authentication.AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Configuration
#Order(1)
public static class HTTPBasicSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
//todo check how we can change the root url of swagger
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/documentation**", "/configuration/**", "/v2/api-docs**", "/swagger-ui.html", "/webjars/**", "/swagger-resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//todo http basic allows access to all urls after login
http
.httpBasic()
.and()
.csrf().disable()
.antMatcher("/api/**")
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest()
.authenticated();
}
}
#Configuration
#Order(2)
public static class FormLoginSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
//todo more investigation is required to check if it is safe to ignore csrf for login
.ignoringAntMatchers("/login")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.successHandler((httpServletRequest, httpServletResponse, authentication) -> {
httpServletResponse.setHeader(AUTHENTICATED_HEADER_NAME, AUTHENTICATED_TRUE);
})
.failureHandler((httpServletRequest, httpServletResponse, e) -> {
httpServletResponse.setHeader(AUTHENTICATED_HEADER_NAME, AUTHENTICATED_FALSE);
httpServletResponse.setStatus(SC_UNAUTHORIZED);
})
.and()
.logout().permitAll()
.and()
.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class)
.addFilterBefore(new CsrfHeaderFilter(), CsrfFilter.class);
http.exceptionHandling().authenticationEntryPoint((HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) -> {
if (authException != null) {
response.setStatus(SC_UNAUTHORIZED);
}
});
}
}
#Configuration
#Order(3)
public static class TestClass extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.httpStrictTransportSecurity()
.includeSubDomains(true)
.maxAgeInSeconds(31536000);
}
}
}
and this is my spring boot version
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
<relativePath></relativePath>
</parent>
Your question is touching on several points:
you can require clients to require secure channels, by adding the security.require_ssl=true configuration property (see the Spring Boot reference documentation about HTTPS)
or use the following configuration snippet http.requiresChannel().anyRequest().requiresSecure();
you might want to enforce that as well with HSTS in Spring Security
None of the above helped the situation I was in.
I figured out that chrome (postman) was automatically transforming my http requests to https.
On the other browsers http requests didn't worked.