Spring Boot Not Invoking SecurityFilterChain - java

The bounty expires in 2 days. Answers to this question are eligible for a +50 reputation bounty.
S. Cannon wants to draw more attention to this question:
This is stopping me from progressing work on my application, and I haven't found any helpful information online. I could really use someone to review this issue.
I setup a springboot application and am trying to get basic user logins setup. From everything I have researched, having the #Configuration and #EnableWebSecurity tags are enough to alert Spring about your class which overrides its configurations (With a #Bean method returning a SecurityFilterChain). However, when running the application, it still uses the DefaultSecurityFilterChain and wants me to login with "user" and autogenerated password that is dumped in the console. I'm not sure what I'm missing to get Spring to recognize my SecurityConfig. However I don't get any System.out/log.info messages when running (other than the main method's Hello World), and it doesn't recognize users from the UserDetailsService.
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Autowired
private ArchlandsUserDetailsService userDetailsService;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
System.out.println("In securityFilterChain");
http
.csrf().disable()
.authorizeHttpRequests((requests) -> requests
.requestMatchers("archlands/api/**").hasRole("USER")
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login").permitAll()
)
.logout((logout) -> logout.permitAll())
.authenticationProvider(authenticationProvider());
return http.build();
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
return authenticationProvider;
}
}
UserDetailsService
#RequiredArgsConstructor
#Service
#Slf4j
public class ArchlandsUserDetailsService implements UserDetailsService {
#Autowired
private UserService userService;
#Override
public UserDetails loadUserByUsername(String username) {
String cleanedUsername = ArchlandsInputSanitizer.clean(username);
log.info("User " + cleanedUsername + " is attempting to access the Archlands.");
System.out.println("User " + cleanedUsername + " is attempting to access the Archlands.");
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
UserDto user = userService.findById(cleanedUsername);
if (user == null) {
log.error("No user exists with user id: " + cleanedUsername);
throw new UsernameNotFoundException("No user exists with user id: " + cleanedUsername);
}
if (user.getStatus().equals("Active")) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
}
for (int i = 0; i < user.getRoles().length; i++) {
if (user.getRoles()[i].equals(Role.DM_ROLE)) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_DM"));
}
}
log.info("User: " + cleanedUsername + " has authorities: " + grantedAuthorities.toString());
return new User(user.getId(), user.getPassword(), grantedAuthorities);
}
}
Console Output

If I see it correctly, the System.out.println("In securityFilterChain") in SecurityConfig doesn't get printed. If that's the case, the problem is that the SecurityConfig Configuration doesn't get picked up by Spring.
If you don't use custom #ComponentScan-s, check your main Spring class' (the one that is annotated with #SpringBootApplication) package - it should be at least the same package level, as your SecurityConfig. Eg.
If your #SpringBootApplication is in package:
org.dummy.app.SpringBootApp
Then the Security config should be under the org.dummy.app package, like:
org.dummy.app.SecurityConfig
or like:
org.dummy.app.config.SecurityConfig

You can add #Order(SecurityProperties.BASIC_AUTH_ORDER) annotation to SecurityConfig class above #Configuration annotation. So, your security configuration becomes higher precedence than the default configuration. Also, you may think add #Bean annotation to ArchlandsUserDetailsService, in case to be sure it is properly registered.

Related

Spring Security Configuration Returning 401 Unauthorized for All Request

I set-up my Springboot project with Spring Security and implemented the Spring Configuration with In-memory authentication. Just a simple authenticated. However, no matter what I do it's not working at all and is giving me 401 Unauthorized for any request in both Spring Security Login form and Postman.
I tried different solution but nothing seems to be working. I get 401 every single time.
I tried adding the componentScan, the filter order, the auto config exclusion, simple password, encrypted password. Nothing works.
Application:
#EnableEurekaClient
#SpringBootApplication
#ComponentScan(basePackages = "sdp.training.gatewayservice.security")
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
}
Security Configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// First encrypt the password string
String encodedPassword = passwordEncoder().encode("javainuse");
// Set the password
UserDetails user = User.builder()
.username("javainuse")
.password(encodedPassword)
.roles("USER")
.build();
// Use in-memory authentication with BCryptEncoder
auth.inMemoryAuthentication()// .passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser(user)
.passwordEncoder(passwordEncoder());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/testBruh").permitAll()
.anyRequest().authenticated()
.and().formLogin().permitAll()
.and().httpBasic();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Simple Controller
#RestController
#RequestMapping(value = "/testBruh")
public class TestController {
#GetMapping
public ResponseEntity<Object> getAccounts() {
return ResponseEntity.status(HttpStatus.OK).body("YO");
}
}
Application.properties
spring.application.name: gateway-service
server.port: 8090
# spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
# spring.security.user.name=admin
# spring.security.user.password=123456
# spring.security.user.roles=manager
spring.security.filter.order=10
spring.main.allow-bean-definition-overriding=true
spring.cloud.gateway.routes[0].id=accountService
spring.cloud.gateway.routes[0].uri=lb://account-service
spring.cloud.gateway.routes[0].predicates[0].name=Path
spring.cloud.gateway.routes[0].predicates[0].args.pattern=/account-service/**
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[1].id=blogService
spring.cloud.gateway.routes[1].uri=lb://blog-service
spring.cloud.gateway.routes[1].predicates[0].name=Path
spring.cloud.gateway.routes[1].predicates[0].args.pattern=/blog-service/**
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1

Save user after success authentication [duplicate]

This question already has answers here:
Filter invoke twice when register as Spring bean
(3 answers)
Closed 6 months ago.
I have set up keycloak with a google identity provider. And I have set up a simple reactive spring-boot web application with spring security and MongoDB. I want to save users after they successfully pass the authorization filter. Here is my security configuration:
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
#Slf4j
#RequiredArgsConstructor
public class SecurityConfiguration {
private final UserSavingFilter userSavingFilter;
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange()
.anyExchange().authenticated()
.and()
.addFilterAfter(userSavingFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
And here is my filter for saving users:
public class UserSavingFilter implements WebFilter {
private final ObjectMapper objectMapper;
private final UserService userService;
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
Base64.Decoder decoder = Base64.getUrlDecoder();
var authHeader = getAuthHeader(exchange);
if (authHeader == null) {
return chain.filter(exchange);
}
var encodedPayload = authHeader.split("Bearer ")[1].split("\\.")[1];
var userDetails = convertToMap(new String(decoder.decode(encodedPayload)));
saveUserIfNotPresent(userDetails);
return chain.filter(exchange);
}
#SneakyThrows
private void saveUserIfNotPresent(Map<String, Object> map) {
var userEmail = String.valueOf(map.get("email"));
var userPresent = userService.existsByEmail(userEmail).toFuture().get();
if (userPresent) {
return;
}
log.info("Saving new user with email: {}", userEmail);
var user = new User();
user.setEmail(userEmail);
user.setFirstName(String.valueOf(map.get("given_name")));
user.setLastName(String.valueOf(map.get("family_name")));
userService.save(user).subscribe(User::getId);
}
#SuppressWarnings("java:S2259")
private String getAuthHeader(ServerWebExchange exchange) {
var authHeaders = exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION);
if (authHeaders == null) {
return null;
}
return authHeaders.get(0);
}
#SneakyThrows
private Map<String, Object> convertToMap(String payloadJson) {
return objectMapper.readValue(payloadJson,Map.class);
}
}
Problems:
For some reason, my filter executes twice per request. I can see 2 log messages about saving new user.
When I call getAll() endpoint, it does not return the user saved in this request in the filter.
Probably it is not the best way to save users, but I could not find an alternative to successHandler for the resource server with jwt. Please suggest how can I solve those two problems.
By any chance is your filter annotated with #Component ? This could explain why it is called twice, as Spring Boot automatically registers any bean that is a Filter with the servlet container (see documentation).
So you can setup a registration bean to disable it :
#Bean
public FilterRegistrationBean<UserSavingFilter> disableUserSavingFilter(final UserSavingFilter filter) {
final FilterRegistrationBean<UserSavingFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setEnabled(false);
return filterRegistrationBean;
}
By default, custom filter beans without information of ordering are automatically added to the main servlet filter chain at the very last position (actually with the lowest precedence, as if you would apply default order annotation #Order(Ordered.LOWEST_PRECEDENCE), see Order).
In debug level you should see in the logs the position of the filters when they are added to the chain, something like :
... at position 4 of 4 in additional filter chain; firing Filter:
UserSavingFilter
About your second problem, if you are sure the user is actually saved (i.e. you find it into the database afterwards) then indeed it may just be because your getAll() method gets executed before your future call is completed.

NullPointer trying to get user info with Keycloak in Spring Boot

I am using Keycloak in my REST application with spring-boot.
It works correctly, I have defined roles in Keycloak, then in my Config.class I allow access to the end-points that interest me according to the role of each user. I have the problem when trying to retrieve the user information in my back (name, principal, authorities...).
I have read various SO POSTS like these:
how-to-get-principal-from-a-keycloak-secured-spring-boot-application
nullpointer-when-securing-spring-boot-rest-service-with-keycloak
but I can't find any that work for me. If I use SecurityContext or KeycloakAuthenticationToken, I get null when calling the getAuthentication method.
In my code, in Controller class I have the following:
private SecurityContext securityContext = SecurityContextHolder.getContext();
#GetMapping(value = "/getLicenses")
public ResponseEntity<List<License>> getLicenses() {
System.out.println("SecurityContext: " + securityContext);
System.out.println("Authentication securityContext: " + securityContext.getAuthentication());
return new ResponseEntity<List<License>>(licenseService.getLicenses(), null, HttpStatus.OK);
}
The output from console after access to /getLicenses is:
SecurityContext: org.springframework.security.core.context.SecurityContextImpl#ffffffff: Null authentication
Authentication securityContext: null
My KeycloakSecurityConfig.class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/getLicenses").hasAnyRole("GUEST", "ADMIN", "SUPERADMIN")
.antMatchers("/getRevision/{id}").hasRole("SUPERADMIN")
.permitAll();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
It works fine, if I try to access to /getRevision/{id} end-point with a 'GUEST' role user, it can't permit it.
My .yml has a BD connection, JPA and keycloak connection:
keycloak.auth-server-url : XXXX
keycloak.realm: XXXX
keycloak.resource: login
keycloak.public-client: true
And Keycloak has 3 users with 3 different roles (1 rol each user). I can do login with Postman
Does anyone know why I get null when trying to access SecurityContext?
My goal is to retrieve user data
You are initializing the securityContext object in your controller when the controller is first created:
private SecurityContext securityContext = SecurityContextHolder.getContext();
At that time, there is no security context available, meaning that the context is always null.
Don't use a private variable in a controller to store the context, instead, fetch it from the SecurityContextHolder in the getLicenses method:
#GetMapping(value = "/getLicenses")
public ResponseEntity<List<License>> getLicenses() {
SecurityContext securityContext = SecurityContextHolder.getContext();
System.out.println("SecurityContext: " + securityContext);
System.out.println("Authentication securityContext: " + securityContext.getAuthentication());
return new ResponseEntity<List<License>>(licenseService.getLicenses(), null, HttpStatus.OK);
}

Error 401 Unauthorized in spring boot / spring security

I added spring security dependency, after that I started the server, everything goes well. As a result I get a form with Username and password, I enter user as username and as a password the one I get in the console,
Using generated security password: 8c3450f7-ab6c-419e-bd69-431ed336eeaa
But I always get username or password are incorrect.
Class Security:
package security;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/users")
.permitAll()
.anyRequest().authenticated();
}
}
Here is the link I'm trying to access http://localhost:8081/users
Any ideas about mistakes I'm making?
Class UserController:
RestController
#RequestMapping("/users") // localhosr:8080/users
public class userController {
#Autowired
UserService userService;
#GetMapping
public String getUser() {
return " get user was called ";
}
#PostMapping
public UserResponse createUser(#RequestBody UserRequest userRequest) {
UserDto userDto = new UserDto();
BeanUtils.copyProperties(userRequest, userDto);
UserDto createUser = userService.createUser(userDto);
UserResponse userResponse = new UserResponse();
BeanUtils.copyProperties(createUser, userResponse);
return userResponse ;
}
#PutMapping
public String updateUser() {
return " update user was called ";
}
#DeleteMapping
public String deleteUser() {
return " delete user was called ";
}
}
In insomnia, for Get Method, I'm getting error 401 Unauthorized
PS: I've removed the class WebSecurity, and I'm trying to access localhost:8081/users, I'm always getting a login form and can't get logged in.
You will configuration to ALL end-point to required authenticated by "anyRequest().authenticated()". So, ofc you will get 401 all time.
First way
You can configuration all path requires one-by-one. Like:
.antMatchers("/to/path/**").isAuthenticated() If you required auth.
.antMatchers("/to/path/**").isAnonymous() If you required no-auth, so you will get 401 again with auth
.antMatchers("/to/path/**").permitAll() It does matter user was authenticated or not
.antMatchers("/to/path/**").hasRole(...) Refined regulation (in future)
Second way:
I recommend use it with: "anyRequest().permitAll()"
Use annotation on configuration class:
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter{
...
}
After it, you can control method level. Example:
#PreAuthorize("isAuthenticated()") // this isAuthenticated() see above first way
#PutMapping
public String updateUser() {
...
}
Those that do not have a security annotation are the default permitAll.
Link: https://www.baeldung.com/spring-security-method-security
Side note:
It also work on spring react (webflux), just replace EnableGlobalMethodSecurity(...) with: #EnableReactiveMethodSecurity. And of course, the setting is a bit different, but this can be searched online.

Spring social NoSuchMethodError SocialAuthenticationFilter.getFilterProcessesUrl()

I use spring security login. Now I'm trying to add spring social facebook login, but I get many error information.
First, when I try to use the same method like spring social guide, I can't #Autowired private Facebook facebook
I found a solution
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionRepository repository) {
Connection<Facebook> connection = repository
.findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
Next, I get the error "cannot find bean". I have to add:
#Bean
public ConnectionRepository connectionRepository() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication == null) {
throw new IllegalStateException(
"Unable to get a ConnectionRepository: no user signed in");
}
return usersConnectionRepository().createConnectionRepository(
authentication.getName());
}
#Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(facebookid,
facebookSecure));
return registry;
}
#Bean
public AuthenticationNameUserIdSource authenticationNameUserIdSource(){
return new AuthenticationNameUserIdSource();
}
#Bean
public ConnectController connectController(
ConnectionFactoryLocator connectionFactoryLocator,
ConnectionRepository connectionRepository) {
return new ConnectController(connectionFactoryLocator,
connectionRepository);
}
#Bean
public UsersConnectionRepository usersConnectionRepository() {
return new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator(), Encryptors.noOpText());
}
After that, I have other issue java.lang.NoSuchMethodError: org.springframework.social.security.SocialAuthenticationFilter.getFilterProcessesUrl()Ljava/lang/String;
#Bean
public SocialAuthenticationServiceLocator socialAuthenticationServiceLocator() {
SocialAuthenticationServiceRegistry registry = new SocialAuthenticationServiceRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(facebookid,
facebookSecure));
return registry;
}
#Bean
public SocialAuthenticationFilter socialAuthenticationFilter()
throws Exception {
SocialAuthenticationFilter filter = new SocialAuthenticationFilter(
authenticationManager(), authenticationNameUserIdSource(),
usersConnectionRepository(), socialAuthenticationServiceLocator());
filter.setFilterProcessesUrl("/login");
filter.setSignupUrl("/signup");
filter.setConnectionAddedRedirectUrl("/home");
filter.setPostLoginUrl("/home"); // always open account profile
// page after login
// filter.setRememberMeServices(rememberMeServices());
return filter;
}
but always is the same.
This is my http configuration
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/home", "/css/**", "/**/*.css*", "/", "/signup",
"/facebook", "/signup.xhtml").permitAll().anyRequest()
.authenticated().and().formLogin().loginPage("/login").loginProcessingUrl("/login/authenticate")
.defaultSuccessUrl("/home").failureUrl("/login")
.permitAll().and().logout().logoutUrl("/logout")
.invalidateHttpSession(true).logoutSuccessUrl("/").and()
.apply(new SpringSocialConfigurer());
And controller
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage() {
return "redirect:/login/authenticate/connect/facebook";
}
I did a whole tutorial. Next, I removed SocialConfigurer implementation and created the same (not #Override, only #Bean) social documentation.
'Normal login '(spring security) works fine, but I can't configure spring social with spring security. I use JSF and .XHTML files.
Maybe someone knows where I make the mistakes?
Thanks for your help.
It looks like Spring Security removed getFilterProcessesUrl() in Spring Security 4.0.0.RC1 (it was marked as deprecated anyways).
It seems that other project filters have not been updated?
Try rolling back to 4.0.0.M2 or use the 3.2 train.
Please notice that spring security 4 will not accept spring social 1.1.0. Please upgrade all spring social dependencies(config, core, security and web) to 1.1.2.RELEASE. You can leave your spring social Facebook to 1.1.0
As hinted in my comment, you have the wrong version of some library. My intelligent guess is that version of Spring Security is wrong. From what I can find, you should use a version in the 3.2.x series (for example 3.2.5) of Spring Security.
Consider using version 1.1.4.
this is solved in spring-social-security 1.1.4.RELEASE (or perhaps some version before).
https://github.com/spring-projects/spring-social

Categories

Resources