I have a client service that distribute a single page application. All the requests from the single page app pass through the client service that uses proxies (Feign) to redirect the calls.
I'd like to allow anonymous calls but I'm not able to do that with my current configuration.
So to make it simpler I have three services : a client, an oauth2 server and an oauth2 resource server.
The oauth2 server is also a resource server.
The client is connected to the oauth2-server with this configuration
security:
oauth2:
client:
clientId: autorisation_code_client
clientSecret: *******
accessTokenUri: https://localhost:****/oauth2-server/oauth/token
userAuthorizationUri: https://localhost:****/oauth2-server/oauth/authorize
#tokenCheckUri: https://localhost:****/oauth2-server/oauth/check_token
resource:
userInfoUri: https://localhost:****/oauth2-server/me
Here is the WebSecurityConfigurerAdapter class of the client, when an user try to access to the login path he's redirected to the oauth2-server to authenticate himself.
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers(
"/",
"/index.html",
"/login**",
"/logout**",
//resources
"/assets/**",
"/static/**",
"/*.ico",
"/*.js",
"/*.json").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class);
}
The feign proxy used by the client, I'd like to configure the oauth2-server/user/like/*** path to be accessible by anonymous users.
#RestController
#FeignClient(name = "oauth2-server", url = "https://localhost:****")
public interface ProxyOauth2Server {
#GetMapping(value = "oauth2-server/user/like/{name}")
ResponseEntity<?> getUserLikeName(#PathVariable("name") String name);
}
To transmit the token through Feign I have this configuration in the client Main class.
#EnableConfigurationProperties
#SpringBootApplication
#EnableFeignClients("com.tutosharing.client.proxies")
public class ClientUiApplication {
#Autowired
private SecurityPropertiesConfig config;
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri(config.getAccessTokenUri());
resource.setUserAuthorizationUri(config.getUserAuthorizationUri());
resource.setClientId(config.getClientId());
resource.setClientSecret(config.getClientSecret());
return resource;
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource);
}
}
Now the oauth2 server which also serves as a resource server
#SpringBootApplication
#EnableResourceServer
#EnableAuthorizationServer
#EnableConfigurationProperties
public class AuthorizationServerApplication {}
the oauth2 server WebSecurityConfigurerAdapter class
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/",
"/login",
"/login.do",
"/oauth/authorize**")
.and()
.authorizeRequests()
.antMatchers(
"/",
"/login",
"/login.do")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login.do")
.usernameParameter("*********")
.passwordParameter("*********")
.and()
.userDetailsService(userDetailsServiceBean())
.requiresChannel()
.anyRequest()
.requiresSecure();
}
}
The Rest controler method I'd like to allow to anonymous users
#RestController
public class UserRControllerRest {
#GetMapping({"/user/like/{name}"})
#JsonView(View.SimpleUser.class)
#PreAuthorize("hasRole('ROLE_USER')")
public ResponseEntity getUserLikeName(#PathVariable String name) {
Set<AuthUser> users = this.userRepository.findByNameLike(name);
return new ResponseEntity(users, HttpStatus.OK);
}
}
If I configure the Rest method with #PreAuthorize("hasRole('ROLE_ANONYMOUS')")
and the WebSecurityConfigurerAdapter like this
http.requestMatchers()
.antMatchers(
...
"/user/like/**",
...)
.and()
.authorizeRequests()
.antMatchers("/user/like/**")
.anonymous()
...
}
} // #formatter:on
I'm able to get an answer if I contact directly the oauth2-server with Postman, but not if I pass through the client service that uses Feign, I'm always redirected to the login page.
So how can I allow anonymous request Through Feign ?
I've found a solution but I'm not sure this is the Best way. So if you have another solution you are welwome.
So far I used this configuration to get the Token from the oauth2-server anytime an user made a request from the client through Feign.
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri(config.getAccessTokenUri());
resource.setUserAuthorizationUri(config.getUserAuthorizationUri());
resource.setClientId(config.getClientId());
resource.setClientSecret(config.getClientSecret());
return resource;
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(#Qualifier("oauth2ClientContext") OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource);
}
The problem with that configuration is that anytime I made a request with Feign a request is sent to the oauth2-client to the /oauth/authorize endpoint. But if the user is not connected it fails, so an unauthenticated user cannot make any request from the client service.
So I used another RequestInterceptor.
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return requestTemplate -> {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (!principal.equals("anonymousUser")) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)
SecurityContextHolder.getContext().getAuthentication().getDetails();
requestTemplate.header("Authorization", "bearer " + details.getTokenValue());
}
};
}
This way the token that the client service already has, once the user is connected, is added to the request whitout making another request to the /oauth/authorize endpoint. I think the token is sent with every request, I don't think it's a good practice for security matters.
Also in the WebSecurityConfigurerAdapter classes of the client-server I need to add the path so that it is accessible to non-connected users
http.antMatcher("/**")
.authorizeRequests()
.antMatchers(
"/oauth2-server/user/like/**",
...)
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
...;
same for the oauth2-server
http.antMatcher("/**")
.authorizeRequests()
.antMatchers(
"/user/like/**",
...)
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
...;
With that configuration an unauthenticated user can make a request to an unprotected endpoint.
Related
Being new to Spring security, was trying a simple REST GET call with HTTP Basic Authentication. Scenario :
For correct usr/pwd, gives http ok. Then I change only the pwd. For incorrect pwd also it gives 200 ok. And the server log says :
o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate
Question - how do I reauth every time and send http 401 unauthorized to the client?
Code :
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
log.info("In securityFilterChain with HttpSecurity");
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()//Customizer.withDefaults())
.authenticationEntryPoint(authEntryPoint);
return http.build();
}
#Bean
public UserDetailsService initUser() {
UserDetails user = User.builder()
.username("basic")
.password(encoder.encode("basic123"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
#Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(initUser())
.and()
.build();
}
In my controller I have two endpoints where one has PreAuthorize annotation and the other does not:
#GetMapping
public UserResponse getUser(){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//get and return User...
}
#PreAuthorize("hasRole('ROLE_ADMIN')")
#PostMapping
public UserResponse createUser(#RequestBody UserRequestDetails userDetails){...}
Secured endpoint its ok, works only when I am logged and token with right role is placed in request header. But when I want access to endpoint without PreAuthorize annotation I always got status 403 forbidden. I want access to the getUser endpoint when users are logged and regardless of the possible roles they have.
Here is my security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception{
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(getAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthorizationFilterBean(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().cors();
}
}
thank!
I have the following SpringBoot web security configuration.
For authorization, I want to automatically forbid all requests that authentication does not include the roles ADMIN, SUPER_ADMIN, CUSTOMER but this denies all requests and only picks up the denyAll attribute in the springExprFilter hence it votes to deny access.
What am I missing from my configuration?
#EnableWebSecurity
#RequiredArgsConstructor
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final MemberDetailsService memberDetailsService;
private final JwtRequestFilter jwtRequestFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberDetailsService).passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.disable()
.csrf()
.disable()
.authorizeRequests()
// permit all request for authentication
.antMatchers("/v1/authenticate")
.permitAll()
.and()
.authorizeRequests()
// permit all request with the following list of roles
// methods will enforce their own authorization logic
.antMatchers("/v1/members/")
.hasAnyAuthority("ADMIN", "CUSTOMER", "SUPER_ADMIN")
.and()
.authorizeRequests()
.anyRequest()
.denyAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
I figured out the spring security configuration expression was correct. The issue was that the antmatcher .antMatchers("/v1/members/") was incorrect. It was implying to match a request with the path /v1/members/ which was not the intended functionality.
For any interested party, the request I was making was GET v1/members/:uuid.
I ought to have used a wildcard .antMatchers("/v1/members/**") to catch all request for the member endpoint.
I have a simple Spring Boot + Spring Security REST app with quotations. Only 3 endpoints for GET, POST, DELETE. Only moderator and admin accounts defined. GET rest method works fine - it shows list of quotations. The problem is with POST and DELETE methods. When I try to invoke them in Postman it returns HTML (logging form defined in SecurityConfig).
QuotationApi.java
#RestController
public class QuotationApi {
private List<Quotation> quotations;
public QuotationApi() {
this.quotations = new ArrayList<>();
quotations.add(new Quotation("Those who dare to fail miserably can achieve greatly.", "John F. Kennedy"));
quotations.add(new Quotation("Get busy living or get busy dying.", "Stephen King"));
}
#GetMapping("/api")
public List<Quotation> getQuotation() {
return quotations;
}
#PostMapping("/api")
public boolean addQuotation(#RequestBody Quotation quotation) {
return quotations.add(quotation);
}
#DeleteMapping("/api")
public void deleteQuotation(#RequestParam int index) {
quotations.remove(index);
}
}
SecurityConfig.java
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// creating users
#Bean
public UserDetailsService userDetailsService() {
UserDetails moderator = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("MODERATOR")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(moderator, admin);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/api").permitAll()
.antMatchers(HttpMethod.POST,"/api").hasRole("MODERATOR")
.antMatchers(HttpMethod.DELETE,"/api").hasRole("ADMIN")
.anyRequest().hasRole("ADMIN")
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
.and()
.csrf().disable();
}
}
I have Basic_auth in Postman:
EDIT after Andreas's help (working code):
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/api").permitAll()
.antMatchers(HttpMethod.POST,"/api").hasRole("MODERATOR")
.antMatchers(HttpMethod.DELETE,"/api").hasRole("ADMIN")
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
.and()
.csrf().disable();
}
Doesn't matter that Postman is sending Basic authentication header, when you haven't enabled Basic authentication in Spring.
Since you only called formLogin() to enable form based authentication, you have to login using the form POST.
Of course, you could just call httpBasic() to enable Basic authentication too.
im exploring a little of spring.
i got across spring boot for easy endpoints see:
#Controller
#EnableAutoConfiguration
public class SampleController {
#RequestMapping("/sample")
#ResponseBody
String sample() {
return "Hello sample!";
}
#RequestMapping("/sample2")
#ResponseBody
String sample2() {
return "Hello sample secured!";
}
}
logically the endpoints are accessible on localhost:8181/sample
but on using spring security the "protected" endpoint becames unaccessible because the login page gives me 404
my security class is as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/sample" ).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 am able to access /sample as is not protected. but unable to access /sample2 as it redirects to /login
im configuring my security class according to this guide: https://spring.io/guides/gs/securing-web/
I am able to access /sample as is not protected. But unable to access
/sample2 as it redirects to /login
Because you have not by-passed /sample2 in your security configuration.
.antMatchers("/sample2" ).permitAll()
Another thing is that as you have specified custom login page
.formLogin()
.loginPage("/login")
you have to provide a login page.
Inject userDetailsService into authenticationProvider:
#Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider=new CustomAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
return authenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.authenticationProvider(authenticationProvider());
}
Add this configuration to spring security:
.antMatchers("/sample2").hasRole("USER")