I'm struggling with spring security after implemented oauth2 in my application. That's my first experience, but will be glad to hear nice or anger comments. That's the way to improve.
Problem:
During the logging in my application I see that I retrieve from google oidcuser object of DefaultOidcUser class. This object has collection of authorities
with 4 items:
Honestly, I don't understand why I get ROLE_USER for this user in my system, cos actually it has ADMIN role in my system (role is created by me).
Generally I have 3 roles in my app: User, Manager, Admin. But can't understand how to correctly set particular role for special user.
Because of this, when I put smth like:
.antMatchers("/api/**").hasRole(Role.ADMIN.name())
.antMatchers("/administration/**").hasRole(Role.ADMIN.name())
I get 403 cos as I understand I get ROLE_USER for each user.
Can you please help me to set roles for each User but from my db (not this default ROLSER_USER from google)? Also I read about GrantedAuthorities and mapper of grantedAuthorities, but it's a bit unclear for me.
I will be glad to hear any opinion. any useful link to improve my knowledge about this. Cos main goal: to understand how it works.
Other classes of my oauth2 implementation below:
@Getter
@Setter
@RequiredArgsConstructor
public class CustomOidcUser implements OidcUser {
private final OidcUser oidcUser;
private String email;
private String firstName;
private String lastName;
@Override
public Map<String, Object> getClaims() {
return oidcUser.getClaims();
}
@Override
public OidcUserInfo getUserInfo() {
return oidcUser.getUserInfo();
}
@Override
public OidcIdToken getIdToken() {
return oidcUser.getIdToken();
}
@Override
public Map<String, Object> getAttributes() {
return oidcUser.getAttributes();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return oidcUser.getAuthorities();
}
@Override
public String getName() {
return oidcUser.getName();
}
}
@Service
@RequiredArgsConstructor
public class CustomOidcUserService extends OidcUserService {
private final UserRepository repository;
private static final String GOOGLE_KEY_LASTNAME = "family_name";
private static final String GOOGLE_KEY_FIRSTNAME = "given_name";
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
final OidcUser oidcUser = super.loadUser(userRequest);
CustomOidcUser newUser = new CustomOidcUser(oidcUser);
String email = oidcUser.getAttributes().get("email").toString();
User user = createUserIfNoExist(email, oidcUser);
newUser.setEmail(email);
newUser.setFirstName(user.getFirstName());
newUser.setLastName(user.getLastName());
return newUser;
}
public User createUserIfNoExist(String email, OidcUser oidcUser) {
return repository.findByEmail(email)
.orElseGet(() -> {
User newUser = new User();
newUser.setEmail(email);
newUser.setRole(Role.USER);
newUser.setStartWorkAt(LocalDate.now());
newUser.setLastLoginDate(LocalDateTime.now());
...
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final AuthProvider authProvider;
private final OnSuccessHandler onSuccessHandler;
private final CustomOidcUserService customOidcUserService;
private final UserService userService;
protected void configure(HttpSecurity http) throws Exception {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setUseReferer(true);
successHandler.setDefaultTargetUrl("/success");
successHandler.setAlwaysUseDefaultTargetUrl(true);
http
.addFilterAfter(new UserHasManagerFilter(userService), BasicAuthenticationFilter.class)
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/api/**").hasRole(Role.ADMIN.name())
.antMatchers("/administration/**").hasRole(Role.ADMIN.name())
.and()
.httpBasic()
.and()
.formLogin()
.loginPage("/unauthorized")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/success")
.failureUrl("/failed")
.successHandler(successHandler)
.usernameParameter("email")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/logout_success")
.permitAll()
.and()
.oauth2Login()
.loginPage("/")
.userInfoEndpoint(userInfoEndpoint ->
userInfoEndpoint.oidcUserService(customOidcUserService)
.customUserType(CustomOidcUser.class, "google")
// .userAuthoritiesMapper(userAuthoritiesMapper())
)
.defaultSuccessUrl("/dashboard", true)
.successHandler(onSuccessHandler)
.failureHandler(authenticationFailureHandler())
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/**")
.permitAll();
}
For OIDC by default Spring will map scopes and the default USER role to granted Authorities on OidcUser object. Your simply returning this in your CustomOidcUser
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return oidcUser.getAuthorities();
}
You need to create a custom GrantedAuthorities collection in your CustomOidcUser object, and then your CustomOidcUserService you need to add the roles from your database to this collection manually.
Collection<GrantedAuthority> authorities ....
authorities.add(new SimpleGrantedAuthority("ROLE_...."));