'Automatically register users in resource server
I'm trying to configure spring security with oauth2, however I am new to this topic and ran into a problem.
I have a spring backend and an angular frontend. I use Azure AD (Authorization code flow with PKCE) for my user authentication (OIDC) and authorization. In my application I have a user table in the database and I want to save any user that is not present in the database.
Basically, what this means is that I want all the users who are using my API to be automatically registered (saved in the DB) if they aren't already registered, kinda like an auto registration feature.
What I have tried so far is to configure an ApplicationListener based on AuthenticationSuccessEvent, however I'm running into a problem (I'm assuming due to race condition). When I load the Angular application and login to Azure AD, I get redirected to a page that automatically sends a couple of http requests to the backend. When the first one goes through the filter chain and hits the ApplicationListener, it registers the sees that there is no user with the specified username in the DB and goes and registers him, but by the time that happens the other requests have already gone through the if statement and end up registering the user again. So basically the same user gets registered 6 times and that obviously is not the desired behavior. Below is the ApplicationListener and the SecurityConfig. If you need any other information post a comment and I will update the question.
Also, if you guys know a better way of "auto registering users" feel free to share that as well!
@Component
public class MyAuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
private final UserRepository userRepository;
public MyAuthenticationSuccessListener(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
Authentication authentication = event.getAuthentication();
if (!userRepository.findByUsername(((Jwt) authentication.getPrincipal()).getClaimAsString("preferred_username")).isPresent()) {
User newUser = new User();
newUser.setUsername(((Jwt) authentication.getPrincipal()).getClaimAsString("preferred_username"));
newUser.setName(((Jwt) authentication.getPrincipal()).getClaimAsString("given_name"));
newUser.setSurname(((Jwt) authentication.getPrincipal()).getClaimAsString("family_name"));
if (((JSONArray) ((Jwt) authentication.getPrincipal()).getClaim("roles")).isEmpty()) {
newUser.setRole(Role.USER);
} else {
newUser.setRole(Role.ADMIN);
}
userRepository.save(newUser);
}
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/h2/**");
}
}
Solution 1:[1]
Providing a custom INSERT statement for the User entity may work:
@Entity
@Table(name = "users")
@SQLInsert(sql = "INSERT IGNORE INTO users(name, role) VALUES (?, ?)"
public class User {
Then you would not need to perform a read in Java. You would need a unique constraint on the user name:
name VARCHAR(50) NOT NULL UNIQUE
Alternatively, a custom insert on the repository may work (would also require an index on the name for efficiency):
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
@Modifying
@Query("insert into User (name,role) values(:name,:role) where (select count(*) from User where name == :name) == 0")
public int conditionallySaveUser(@Param("name") String name, @Param("role") String role);
}
NOTE: only two fields are used for simplicity while the syntax may need adjusting or a native SQL query may have to be created (native=true)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Delta George |
