I know that there are some similar topics but they are about implementation difficulties, whereas my question is more architect wise.
And it is generally not springframework related.
Let's say there is an application that implements both client\resource (in terms of OAuth2) behaviors.
Also it supports Basic auth with for testing purposes (ans it has its own set of static\ldap users ).
Auth provider is done as a separate application.
This "three-type" auth is reached by
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(request -> {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Basic"));
})
.authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable()
.httpBasic()
;
}
....
}
then goes
#Configuration
#EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final static Logger logger = LoggerFactory.getLogger(OAuth2ResourceServerConfig.class);
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(request -> {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Bearer"));
})
.authorizeRequests()
.anyRequest().authenticated();
}
....
}
and then
#Configuration
#EnableOAuth2Sso
#Order(4)
public class OAuth2SsoConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable()
;
}
....
}
all in one package (altogether with UI).
That works pretty well. But.
Is that good at all to have it like that?
Some systems which this app integrates with already have "client" behavior itself (like SalesForce), so UI and #EnableOAuth2Sso configuration seem to be dead weight.
Am I missing something in terms of security vulnerabilities? I was able to see that once one bearer token is accepted by app, it creates session and postman sends cookie back to the app on the next request and app manages this session even if another bearer token(for another user) has been applied to the Authorization header.
Does it make sense to customize this via maven profiles or via splitting it to the separate apps (pure UI&client and Resource API)?
Three options as I see it:
Thanks.
Related
Since I have updated Spring Boot Version to 2.6.7 I get logs that the way I define unsecured routes is not recommended anymore.
Log message:
You are asking Spring Security to ignore Ant [pattern='/actuator/**']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
The way I was describing the config that Spring Security has to ignore these patterns was done by defining a WebSecurityConfiguration and ignoring those routes. What happens in this case is, that the whole security chain is skipped and the above mentioned logs are written. It's okay for me, but not for Spring ;).
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/actuator/**"
);
}
}
When defining these route as part of the httpSecurity as mentioned in the log. The problem occurs that an expired/invalid token cause an error (401 unauthorized) as well for unsecured routes like /actuator/health.
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Getter
private String[] unsecuredPathList;
#PostConstruct
public void postConstruct() {
this.unsecuredPathList = new String[] {
"/actuator/**"};
}
#Bean
public JwtTokenStore jwtTokenStore() {
return new JwtTokenStore(new CustomJwtAccessTokenConverter(true));
}
#Override
public void configure(ResourceServerSecurityConfigurer resourceServer) throws Exception {
resourceServer.tokenStore(jwtTokenStore());
resourceServer.tokenExtractor(new SessionCookieTokenExtractor());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous()
.authorities("ANONYMOUS")
.and()
.authorizeRequests()
.antMatchers(unsecuredPathList)
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
The goal I want to reach is:
For an unsecure resource the token will not be evaluated/the result is ignored and there is no 401-Unauthorized-Error.
Is there anything I can do in httpSecurity?
Or is there any other recommended way to reach that goal?
Thanks in advance for your help.
There is a group of methods in HttpSecurity class, which allows you to apply defined security rules only to specific paths, and thus create different security filter chains with different rules for different urls.
For example, you can exclude some urls like this:
// convert your String array into a List of RequestMatcher
List<RequestMatcher> excludedPathMatchers = Arrays.stream(unsecuredPathList)
.map(AntPathRequestMatcher::new)
.collect(Collectors.toList());
// configure HttpSecurity to apply filter chain only on paths, that don't match any of the excluded paths
http.requestMatcher(new NegatedRequestMatcher(new OrRequestMatcher(excludedPathMatchers)));
Or you can write something like this, if you have only 1 unsecured endpoint:
http.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/some_url/**")));
I'm using spring-security-saml2-service-provider for authentication in one of my spring boot applications and I'm using a custom JwtAuthorizationFilter (via a http Authentication header) in a different spring boot application.
They both work perfectly on their own.
Now I need to write a spring boot application that uses both of them. If the JWT token is available (Authentication header), then use the JwtAuthorizationFilter, otherwise use saml2Login.
The SAML2 configuration looks like this: (There is no filter, just the saml2Login)
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/saml2/service-provider-metadata/**").permitAll()
.antMatchers("/**").authenticated().and()
// use SAML2
.saml2Login()
.addObjectPostProcessor(new ObjectPostProcessor<OpenSamlAuthenticationProvider>() {
public <O extends OpenSamlAuthenticationProvider> O postProcess(O samlAuthProvider) {
samlAuthProvider.setAuthoritiesExtractor(authoritiesExtractor());
samlAuthProvider.setAuthoritiesMapper(authoritiesMapper());
return samlAuthProvider;
}
})
;
}
The JWT configuration looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/**").authenticated().and()
// use JWT
.addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUtil))
;
}
I think I need something like a JwtOrSaml2AuthenticationFilter but don't know how to do that.
The solution is to
Duplicate the configuration with #Order and
Set a header based requestMatcher before the addFilter
#EnableWebSecurity
public class SecurityConfiguration {
#Order(100) // lower number = higher priority
#Configuration
#RequiredArgsConstructor
public static class AppSecurityJWT extends WebSecurityConfigurerAdapter {
final JWTUtil jwtUtil;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/saml2/service-provider-metadata/**", "/idm-app/**").permitAll()
.antMatchers("/**").authenticated().and()
// This configuration will only be active if the Authorization header is present in the request
.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUtil))
;
}
}
#Order(101)
#Configuration
#RequiredArgsConstructor
public static class AppSecuritySAML2 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/saml2/service-provider-metadata/**", "/idm-app/**").permitAll()
.antMatchers("/**").authenticated().and()
// This whole configuration will only be active, if the previous (100) didn't match
.saml2Login()
//...
;
}
}
I am implementing OAuth2 in my App. The problem is that I want to open some endpoints to make them accessible without any authentication or role. The file that is giving problems is this: (I want to open an endpoint that is "/api/someRoute/parameters")
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests().antMatchers("/api/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
.and()
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
} ```
This should work -
.authorizeRequests().antMatchers("/api/someRoute/parameters").permitAll()
I am using spring boot 2.1.4 with dependencies of actuator. I wanted to configure separate authentication and authorization mechanisms for actuator and my application. I read the Multiple HttpSecurity and configured my WebSecurityAdapter as follows:
#Configuration
public class ProvisioningServiceSecurityConfiguration {
#Value("${actuator.user.name}")
private String actuatorUserName;
#Value("${actuator.password}")
private String actuatorPassword;
#Value("${actuator.role}")
private String actuatorRole;
#Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("ADMIN").build());
manager.createUser(
users.username(actuatorUserName).password(actuatorPassword).roles(actuatorRole).build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/locations/**")
.antMatcher("/organizations/**")
.antMatcher("/productTypes/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/manage/**")
.authorizeRequests()
.anyRequest().hasRole("ACTUATOR_ADMIN")
.and()
.httpBasic();
}
}
/*#Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}*/
}
Note: I have disabled form Login temporarily
When I run a curl request
curl -XGET http://localhost:9797/provisioningService/organizations/all
I am able to see the output. Its as though the spring security never existed. When I enable form login, I get the spring login screen. The other behavior that I observed is if I interchange the username and password of /locations with the actuator username and password, I still get a valid response back.
I understand the form login is more of a fallback but I want to disable the form login (probably we may move to cas) and use authentication and authorization only based on the spring security httpBasic. I am not able to understand the mistake I am making.
My requirement is finally :
1) a request to /organizations or /locations etc should be accessible only if the username password is "user" and "password"
2) a request to /manage which is the actuator api should be accessible only if the username and password and role matches with the actuator username and password.
3) Any other API can be permitAll / form login
How do i go about achieving this?
1) Spring Security has a function to control access by filtering by Authorities(after Authentication), but there is no function to filter by the information required for login. You need business logic to verify that you are attempting to log in with the corresponding ID and password during login.
2) As mentioned above, access control with ID and password is not provided.
I recommend creating Authorities for only the two accounts you requested.
3) .antMatcher("/form").permitAll()
Preconditions
I have two Java Spring applications(App 'A' and App 'B') that were created via JHipster(monolithic application). Both applications uses keycloak for authentication/authorization.
Both applications have an angular frontend and support login via ouath (spring-security). Here ist my SecurityConfiguration of Application A and B:
#Configuration
#Import(SecurityProblemSupport.class)
#EnableOAuth2Sso
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
#Bean
public AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler() {
return new AjaxLogoutSuccessHandler();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(corsFilter, CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler())
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/api/profile-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN);
}
}
In App B i also have an ResourceServerConfiguration. This checks if the header contains an "Authorization" key. If true, the user can login via JWT(Bearer Authentication). I tested this via Postman and it works fine:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).authorizeRequests()
.anyRequest().authenticated();
}
}
Further more both apps are in the same keycloak realm and have the access-type "public".
Problem:
Now i want to call an endpoint of App B via an Spring RestTemplate from App A. The problem is, that i do not have an access_token that i can put in my rest request/restTemplate. When i look in my request that is send from my frontend, i only got an JSESSIONID. There is no access_token/JWT in the header.
Question
Is there a way to get the access_token of the current user out of the JSESSIONID/the HttpSession or the spring security context? Do i need something like a Tokenstore where i store every token that comes from keycloak?
Did anyone else have similar problems or any idea how i could solve that problem?
After some research it turns out that the problem lies within the generated jhipster code.
I followed the authentication process in the application and saw, that there was a call to the /account endpoint directly after authentication, where the user information were retrieved. The call is triggerd by the frontend. First time this endpoint is called, there is a principal with a bearer token available. Within the /account endpoint, a call to the userService with the principal object is performed. More precisley
getUserFromAuthentication(OAuth2Authentication authentication)
is called. Within this method there is a part that replaces the OAuth2Authentication with a new UsernamePasswordAuthenticationToken and inserts it into the SecurityContext:
UsernamePasswordAuthenticationToken token = getToken(details, user,
grantedAuthorities);
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), token);
SecurityContextHolder.getContext().setAuthentication(authentication);
So after that, the access_token is lost. I am not quite sure, why it was replaced with the new OAuth2Authentication, but i tend to extend this part and keep the access_token in my securityContext for further restcalls.