Springboot Security how to get role inside "Principal" - spring

I have this fonts
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/**").hasRole("myRole")
.anyRequest().authenticated()
.and()
.oauth2Login(Customizer.withDefaults());
}
I know "hasRole" looks at permissions inside securityContext.authentication.authorities but is there a way for "hasRole" to another place?
My roles are inside securityContext.authentication.principal.attributes.role :
https://i.stack.imgur.com/yl6bC.png
I even created an endpoint that returns if the role I want exists, but I don't know how it would help me inside the "configure" method:
public boolean isAllowed() {
UserAttributesDTO user = getUser();
if (nonNull(user)) {
return user.getRoles().stream().anyMatch(role -> role.equals("admin_cadastro_externo"));
}
return false;
}

Since you're using the built-in oauth2Login() and your principal is a DefaultOidcUser, you want to use .hasAuthority("myAuthority") instead. You can influence what authorities are present by providing a GrantedAuthoritiesMapper as an #Bean.
If you must access the attributes in place, you may be interested in .access(...) using an #Bean reference.
See Referring to Beans in Web Security Expressions. In that case, you should use the authentication passed to the method instead of the SecurityContextHolder to access the authentication.

Related

Spring security requires login for a URL even though it is permitted without authentication in Configure method

I am using Spring Boot and Spring Security. I have multiple URLs, I want some of them to be accessed with authentication and some of them to be allowed access without authentication. I have written Configure method in such a manner. But browser is redirecting me to a login page even for URL that has been allowed access without authentication. For example, I am asked for login when I try to access localhost:8080/emailExists. What could me wrong?
My Controller class :
#RestController
class AppController {
#Autowired
private ShopSearchRepository searchRepository;
#Autowired
private UserRepository userRepository;
#GetMapping("/emailExists")
public Boolean emailExists(#RequestParam String email) {
User user = userRepository.findByEmail(email);
if (user == null) return false;
else return true;
}
#GetMapping("/ShopsSearch")
List<Shop> search(#RequestParam String name) {
return searchRepository.findByName(name);
}
}
My Configure method :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/ShopsSearch/").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic();
}
You need to tell which URL to bypass
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST,"/emailExists").permitAll() // allow without authentication
.antMatchers(HttpMethod.POST, "/login").permitAll() // allow without authentication
.antMatchers(HttpMethod.POST,"/newuser/*").permitAll() // allow without authentication
.antMatchers(HttpMethod.GET,"/master/*").permitAll() // allow without authentication
.antMatchers(HttpMethod.GET,"/exploreCourse").permitAll() // allow without authentication
.anyRequest().authenticated() //all other need auth
}

Spring Security OAuth - how to disable login page?

I want to secure my application with Spring Security, using OAuth 2. However, I don't want the server to redirect incoming unauthorized requests, but instead to respond with HTTP 401. Is it possible?
Example: this code redirects requests to a default login page.
application.properties
spring.security.oauth2.client.registration.google.client-id=...
spring.security.oauth2.client.registration.google.client-secret=...
AuthConfig.java
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login();
// https://stackoverflow.com/questions/31714585/spring-security-disable-login-page-redirect
// deos not work
// .and()
// .formLogin().successHandler((request, response, authentication) -> {});
}
}
You need to create new authentication entry point and set it in configuration.
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login();
}
}
public class AuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
public AuthenticationEntryPoint() {
super("");
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(401, "Unauthorized");
}
}
You need to set oauth2Login.loginPage in your HttpSecurity config and create a controller mapping to return whatever you want. Here's a simple example.
So in your security config
http
.authorizeRequests()
.antMatchers("/noauth").permitAll()
.oauth2Login()
.loginPage("/noauth")
In a controller
#GetMapping("/noauth")
public ResponseEntity<?> noAuth() {
Map<String, String> body = new HashMap<>();
body.put("message", "unauthorized");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body);
}
You can pass a map or pojo to the body method.
I would like to expand on Petr's answer by explaining that apparently for the time being first of all, the default login page is shown when there are more than one OAuth2 configured providers. I would expect that Spring Boot would have a smart trick to bypass this page easily and choose the right provider automatically, basing e.g. on the existence of the provider's client ID in the original request. I found out the hard way that this is not the case. So the way to do this is.. this not very apparent trick of providing a custom handler for failures - that will REDIRECT the user to the correct OAuth2 endpoint for each provider, based on the original HTTP request URL. I tried this and it works and I spent a whole day trying all manners of other solutions - my original scenario was to pass additional parameters to OAuth2 scheme in order to be able to get them back on successful authentication - they used to do this appending Base64 encoded information to the "state" URL request parameter, but Spring Security does not allow this at the moment. So the only alternative was to call a Spring Security-protected URL with those parameters already there, so when the successful authentication happens, this URL is accessed again automatically with those parameters intact.
Related: Multiple Login endpoints Spring Security OAuth2

Spring boot security block non RestController endpoints

I have an app that is exposing a bunch of endpoints that I did not expect. For example localhost:8080/app/ returns a list of URL that among other things exposes information related to the hibernate entities.
I DO NOT want basic auth enabled as I have my own authentication configured.
But if the URL is not one that is represented by a RestController I have written then I want it to an existing forbidden page that I have.
Here is my current config but it does not prevent the unwanted endpoints:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**").antMatchers("/v2/**").antMatchers("/webjars/**").antMatchers("/swagger-resources/**")
.antMatchers("/swagger-ui.html");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.httpBasic().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
http.authenticationProvider(new CustomAuthenticationProvider()).authorizeRequests().anyRequest().authenticated()
.antMatchers("/v2/**").permitAll().antMatchers("/webjars/**").permitAll().antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/health").permitAll();
http.rememberMe().rememberMeServices(rememberMeService).useSecureCookie(useSecureCookie);
//Show 403 on denied access
http.exceptionHandling().authenticationEntryPoint(new Forbidden());
}
So in this case localhost:8080/app/api/SearchControler/{var} should work but localhost:8080/app/ should go to my Forbidden entry point. Instead localhost:8080/app/ is going to the spring username and password page.
First off I don't know why these endpoints are even showing up when there is no RestController for them and second why is redirecting to a 403 page so difficult.
I'm not sure what config I am missing.
* EDIT *
I have also tried:
http.formLogin().and().httpBasic().disabled();
as well as:
#EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class})
and nothing seems to stop spring from taking over and doing whatever it feels like doing.
Try again after removing super.configure(http); in your configure(HttpSecurity http) method.
Documentation
Override this method to configure the {#link HttpSecurity}. Typically
subclasses * should not invoke this method by calling super as it
may override their * configuration. The default configuration is:
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
I think there is more configuration which you didn't show to as, but anyway:
#Override
public void configure(WebSecurity web) throws Exception {
//this is only for ignoring static resources in your app, sth that is never changed (you can cash it) and public (available for any user on the internet (ex. /js /css - sth else static img etc)
web.ignoring().antMatchers("/webjars/**").antMatchers("/swagger-resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); this call the default configuration, if you implement this method you shouldn't call the default one
http.httpBasic().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
http.authenticationProvider(new CustomAuthenticationProvider())
.authorizeRequests() // the order is matter here, if antMatchers() will match the request the others after him will not be checked, anyRequest() will match any request, so it should be at the end
//.permitAll().antMatchers("/webjars/**").permitAll().antMatchers("/swagger-resources/**").permitAll() - there is no need to duplicate what is in web.ignoring() - such requests will not reach this point
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
http.rememberMe().rememberMeServices(rememberMeService).useSecureCookie(useSecureCookie);
//Show 403 on denied access
http.exceptionHandling().authenticationEntryPoint(new Forbidden());
}
This issue is completely related to transitive dependencies. After removing some dependencies and adding excludes to others the core problem has been solved.

Securing same endpoint with multiple configurations

We have a microservice architecture with securities for front to back with JWT, and back-to-back security with HTTP Basic.
Here is our configuration class for JWT :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers(endpointsProperties.getJwtWithWildcard())
.and()
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
jwtFilter is a simple filter that reads the Authorization header, and set the SecurityContextHolder.
And the HTTP Basic :
#Override
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
for (Map<String, String> userData : properties.getUsers()) {
auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser(userData.get("login")).password(userData.get("password")).authorities(BASIC_AUTH_AUTHORITY);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers(endpoints.getBasicWithWildcard() )
.and().csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().httpBasic();
}
Those configuration class are used in differnets services, with distinct JWT and HTTP Auth endpoints. They are used either at the same time or independently. EndpointsProperties are loaded from application.yml.
But now, we have some referential microservices that we want to be reached either by other services or direclty by a (web) frontend application. We want to know if it is possible to secure a same URL ('/api/referential', for example) with the two different methods. Combining those configuration class with the same endpoints does not work, and it seems one configuration eraze the other.
Is it possible to secure a same URL path with different methods like that ? If yes, what do we need to change to enable it ?
Thanks for your support.
I think you can just add the two filters to the filter chain in this order
BasicAuthenticationFilter
JwtFilter
and make sure the ignoreFailure property of the BasicAuthenticationFilter is set to true.
This will make The basicAuthFilter authenticate requests with basicAuth and just continue down the filter chain if no basicAuth is sent - thus delegating to the JwtFilter.
then remove .httpBasic() from the WebsecurityConfig - as it will try to add another BasicSecurityFilter.
Just an off the cuff idea

Spring's WebSecurity 'ignore' still creating sessions

I want to avoid creating a session when a user requests static resources like css files. Howerver, even after telling WebSecurity to ignore this static resource path, I noticed that all of the responses from my SpringBoot application still have the JSESSIONID cookie. Why?
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login")
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/login", "/error").permitAll()
.anyRequest().authenticated(); //all other pages require users to be authenticated
}
I'm using Spring Security to protect my application.... but for requests to these static resources, I don't want a session created or validated.
I found the issue. It was my fault. I'm using Spring Security and extended RequestHeaderAuthenticationFilter. While web.ignoring()... does avoid all Spring Security authentication/authorization checks, it still does run through the custom filters.
In one of my filters I had the following line:
HttpServletRequest req = ((HttpServletRequest) request);
HttpSession session = req.getSession();
I use it to check if a session already exists and if I need to refresh it (too complicated to explain here). But that method, req.getSession() will create a new session if it doesn't already exist. I didn't realize it had that side effect.
Instead, I should be calling: req.getSession(false). This will return a session if it already exists and return null if it doesn't.

Resources