Yes, your understanding is correct.
- Allow the user to connect using the OAUTH account (Google, Azure, Custom).
- The first time, the user must log in using OAuth, and the service will generate a local account using the person’s information returned. The service will then send an email with the temporary username and password.
- Next time, the user can log in using either the local account or OAUTH (which will look up the local account, “successHandler”).
I did not send the other classes because I could not attach the files.
Below, I included the source code for all the classes.
Best regards,
Luis Laurentino
AppSecurityConfig.class
@EnableWebSecurity
@Configuration
public class AppSecurityConfig extends VaadinWebSecurity {
private final Logger logger = LoggerFactory.getLogger(AppSecurityConfig.class.getSimpleName());
@Autowired
private UsersService userService;
@Autowired
private ServletContext servletContext;
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth.requestMatchers(
new AntPathRequestMatcher("/", "/auth/login/**"),
new AntPathRequestMatcher("/public/**"),
new AntPathRequestMatcher("/oauth2/authorization/google"))
.permitAll()
.requestMatchers("/home").authenticated());
http.oauth2Login(oauthLogin ->
oauthLogin.loginPage("/auth/login")
.successHandler((request, response, authentication) -> {
logger.trace("Authentication success");
DefaultOidcUser userLogged = (DefaultOidcUser) authentication.getPrincipal();
AppUsers userInfo = new AppUsers()
.setUserName(userLogged.getAttribute("given_name"))
.setFullName(userLogged.getAttribute("name"))
.setUserPassword("")
.setEmail(userLogged.getAttribute("email"))
.setImageLink(userLogged.getAttribute("picture"))
.setRoles(new ArrayList<>(List.of("USER")));
// final SecurityContext context = SecurityContextHolder.createEmptyContext();
final SecurityContext context = SecurityContextHolder.getContext();
final Authentication auth = new UsernamePasswordAuthenticationToken(userInfo, userInfo.getPassword(), userInfo.getAuthorities());
context.setAuthentication(auth);
// SecurityContextHolder.setContext(context);
if (SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) {
response.sendRedirect(servletContext.getContextPath() + "/home");
} else {
throw new UsernameNotFoundException("Access denied, invalid credentials.");
}
}));
super.configure(http);
setLoginView(http, LoginView.class);
}
}
AppUsers.class
public class AppUsers implements UserDetails, OAuth2User {
private UUID userID;
private String userName;
private String fullName;
private String email;
private String disabledAt;
private String token;
private Boolean temporaryLocked;
private String userPassword;
private String pwdExpirationDate;
private String imageLink;
private List<LastConnection> connectionList;
private List<UserGroups> userProfiles;
private ArrayList<String> roles;
public AppUsers(String userName, String token, String role) {
this.userName = userName;
this.token = token;
if (this.roles == null)
this.roles = new ArrayList<>();
this.roles.add(role);
}
public AppUsers() {
}
public String getUserName() {
return userName;
}
public AppUsers setUserName(String userName) {
this.userName = userName;
return this;
}
public String getToken() {
return token;
}
public AppUsers setToken(String token) {
this.token = token;
return this;
}
public ArrayList<String> getRoles() {
if (roles == null && !this.userProfiles.isEmpty()) {
setRoles(null);
}
return roles;
}
public AppUsers setRoles(ArrayList<String> roles) {
if (roles == null && this.userProfiles != null && !this.userProfiles.isEmpty()) {
this.roles = new ArrayList<>();
this.userProfiles.forEach((userProfile) -> this.roles.add(userProfile.getGroupName()));
} else {
this.roles = roles;
}
return this;
}
public UUID getUserID() {
return userID;
}
public AppUsers setUserID(UUID userId) {
this.userID = userId;
return this;
}
public String getFullName() {
return fullName;
}
public AppUsers setFullName(String fullName) {
this.fullName = fullName;
return this;
}
public String getEmail() {
return email;
}
public AppUsers setEmail(String email) {
this.email = email;
return this;
}
public String getDisabledAt() {
return disabledAt;
}
public AppUsers setDisabledAt(String disabledAt) {
this.disabledAt = disabledAt;
return this;
}
public Boolean getTemporaryLocked() {
return temporaryLocked;
}
public AppUsers setTemporaryLocked(Boolean temporaryLocked) {
this.temporaryLocked = temporaryLocked;
return this;
}
public String getUserPassword() {
return userPassword;
}
public AppUsers setUserPassword(String userPassword) {
this.userPassword = userPassword;
return this;
}
public String getPwdExpirationDate() {
return pwdExpirationDate;
}
public AppUsers setPwdExpirationDate(String pwdExpirationDate) {
this.pwdExpirationDate = pwdExpirationDate;
return this;
}
public List<LastConnection> getConnectionList() {
return connectionList;
}
public AppUsers setConnectionList(List<LastConnection> connectionList) {
this.connectionList = connectionList;
return this;
}
public List<UserGroups> getUserProfiles() {
return userProfiles;
}
public AppUsers setUserProfiles(List<UserGroups> userProfiles) {
this.userProfiles = userProfiles;
return this;
}
public String getImageLink() {
return imageLink;
}
public AppUsers setImageLink(String imageLink) {
this.imageLink = imageLink;
return this;
}
@Override
public Map<String, Object> getAttributes() {
return Map.of();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> grantedAuthority = new ArrayList<>();
roles.forEach(role -> grantedAuthority.add(new SimpleGrantedAuthority(role)));
return grantedAuthority;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return userName;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getName() {
return "";
}
private static class LastConnection {
private String sourceIP;
private String timestamp;
public String getSourceIP() {
return sourceIP;
}
public LastConnection setSourceIP(String sourceIP) {
this.sourceIP = sourceIP;
return this;
}
public String getTimestamp() {
return timestamp;
}
public LastConnection setTimestamp(String timestamp) {
this.timestamp = timestamp;
return this;
}
}
private static class UserGroups {
private String profileID;
private String groupID;
private String groupName;
private String disabledAt;
public String getProfileID() {
return profileID;
}
public UserGroups setProfileID(String profileID) {
this.profileID = profileID;
return this;
}
public String getGroupID() {
return groupID;
}
public UserGroups setGroupID(String groupID) {
this.groupID = groupID;
return this;
}
public String getGroupName() {
return groupName;
}
public UserGroups setGroupName(String groupName) {
this.groupName = groupName;
return this;
}
public String getDisabledAt() {
return disabledAt;
}
public UserGroups setDisabledAt(String disabledAt) {
this.disabledAt = disabledAt;
return this;
}
}
}
CustomI18NProvider.class
@SpringComponent
public class CustomI18NProvider implements I18NProvider {
private final Logger logger = LoggerFactory.getLogger(CustomI18NProvider.class.getSimpleName());
private static final String BUNDLE_PREFIX = "i18n/translate";
private final Locale LOCALE_EN = new Locale("en", "US");
private List<Locale> locales = Collections
.unmodifiableList(Arrays.asList(LOCALE_EN)); //Arrays.asList(Locale.getAvailableLocales()
@Override
public List<Locale> getProvidedLocales() {
return locales;
}
@Override
public String getTranslation(String key, Locale locale, Object... params) {
if (key == null) {
logger.warn("Got lang request for key with null value!");
return "";
}
logger.info("Language :" + locale.getLanguage() + " Country :"+ locale.getCountry());
ResourceBundle bundle;
if (getProvidedLocales().contains(locale)) {
bundle = ResourceBundle.getBundle(BUNDLE_PREFIX, locale);
} else {
bundle = ResourceBundle.getBundle(BUNDLE_PREFIX, LOCALE_EN);
}
String value;
try {
value = bundle.getString(key);
} catch (final MissingResourceException e) {
logger.warn("Missing resource", e);
return "!" + locale.getLanguage() + ": " + key;
}
if (params.length > 0) {
value = MessageFormat.format(value, params);
}
return value;
}
}
LoginView.class
@Route("auth/login")
@RouteAlias(value="/")
@AnonymousAllowed
public class LoginView extends Composite<LoginOverlay> implements AfterNavigationObserver, BeforeEnterObserver {
protected final Logger logger = LoggerFactory.getLogger(LoginView.class);
private final UsersService userService;
private final LoginI18n loginForm;
private final ServletContext servletContext;
/**
* The first view presented to every user trying to access the application.
*/
public LoginView(UsersService loginService, ServletContext servletContext) {
this.userService = loginService;
this.servletContext = servletContext;
loginForm = LoginI18n.createDefault();
getContent().addClassName("login-view");
LoginI18n.Header loginHeader = new LoginI18n.Header();
loginHeader.setTitle("Sample Title ");
loginHeader.setDescription("Sample description");
loginForm.setHeader(loginHeader);
this.loginForm.getForm().setTitle(null);
this.loginForm.getForm().setUsername("Username");
this.loginForm.getForm().setPassword("Password");
this.loginForm.getForm().setSubmit("Login");
this.loginForm.getForm().setForgotPassword(null);
this.loginForm.setAdditionalInformation("Please, contact your administrator if you're experiencing issues " +
"logging into your account.");
getContent().setForgotPasswordButtonVisible(false);
getContent().setI18n(this.loginForm);
Anchor googleUserLogin = new Anchor(servletContext.getContextPath() + "/oauth2/authorization/google",
"Login using your google account");
googleUserLogin.addClassName("oauth-login");
googleUserLogin.getElement().setAttribute("router-ignore", true);
getContent().getCustomFormArea().add(googleUserLogin);
getContent().setOpened(true);
getContent().getElement().setAttribute("no-autofocus", "");
getContent().addLoginListener(this::verifyLogin);
getContent().setOpened(true);
getContent().setAction("login");
logger.debug("lc - constructing base LoginView class");
}
private void verifyLogin(AbstractLogin.LoginEvent loginEvent) {
try {
SecurityContextHolder.getContext();
String clientIP = UI.getCurrent().getSession().getBrowser().getAddress();
var userInfo = userService.loadUser(loginEvent.getUsername(), loginEvent.getPassword(), clientIP);
if (validateUserInformation(userInfo)) {
// final SecurityContext context = SecurityContextHolder.createEmptyContext();
final Authentication auth = new UsernamePasswordAuthenticationToken(userInfo, userInfo.getPassword(), userInfo.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
// SecurityContextHolder.setContext(context);
VaadinSession.getCurrent().getSession().setMaxInactiveInterval(300);
UI.getCurrent().navigate(MainView.class);
getContent().close();
} else {
UI.getCurrent().getPage().setLocation(servletContext.getContextPath() + "/auth/login?error");
}
} catch (Exception ex) {
logger.error("Error validating user credentials.", ex);
UI.getCurrent().getPage().setLocation(servletContext.getContextPath() + "/auth/login?error");
}
}
@Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
boolean hasError = false;
Map<String, List<String>> parameters = beforeEnterEvent.getLocation().getQueryParameters().getParameters();
if (parameters.isEmpty() ||
(SecurityContextHolder.getContext().getAuthentication() instanceof AnonymousAuthenticationToken
&& parameters.containsKey("logout"))) {
return;
}
if (parameters.containsKey("error")) {
LoginI18n.ErrorMessage loginErrorMessage = loginForm.getErrorMessage();
loginErrorMessage.setTitle("Invalid credentials");
loginErrorMessage.setMessage("Check that you entered the correct information and try again.");
loginForm.setErrorMessage(loginErrorMessage);
hasError = true;
}
// VaadinSession.getCurrent().getSession().invalidate();
// SecurityContextHolder.clearContext();
// SecurityContextHolder.setContext(new SecurityContextImpl(new AnonymousAuthenticationToken("anonymousUser",
// "anonymousUser", Collections.singletonList(new SimpleGrantedAuthority("ROLE_ANONYMOUS")))));
// new HttpSessionSecurityContextRepository().saveContext(SecurityContextHolder.getContext(),
// VaadinServletRequest.getCurrent(), VaadinServletResponse.getCurrent());
if (hasError) {
getContent().setI18n(loginForm);
getContent().setError(true);
}
}
private boolean validateUserInformation(AppUsers userAPIResponse) {
if (userAPIResponse == null || userAPIResponse.getUserName() == null ||
userAPIResponse.getUserName().isEmpty() || userAPIResponse.getAuthorities() == null ||
userAPIResponse.getAuthorities().isEmpty()) {
LoginI18n.ErrorMessage loginErrorMessage = loginForm.getErrorMessage();
loginErrorMessage.setTitle("Invalid credentials");
loginErrorMessage.setMessage("Access denied.");
loginForm.setErrorMessage(loginErrorMessage);
return false;
} else if (userAPIResponse.getTemporaryLocked() != null && userAPIResponse.getTemporaryLocked()) {
LoginI18n.ErrorMessage loginErrorMessage = loginForm.getErrorMessage();
loginErrorMessage.setTitle("Account locked");
loginErrorMessage.setMessage("Please contact your administrator.");
loginForm.setErrorMessage(loginErrorMessage);
return false;
}
return true;
}
@Override
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
}
@Override
public void afterNavigation(AfterNavigationEvent afterNavigationEvent) {
logger.info("After navigation - login view");
}
}
MainView.class
@Route("/home")
@PermitAll
public class MainView extends VerticalLayout {
/**
* Construct a new Vaadin view.
* <p>
* Build the initial UI state for the user accessing the application.
*
* @param service
* The message service. Automatically injected Spring managed
* bean.
*/
public MainView(@Autowired UsersService service) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String givenName = "";
String familyName = "";
String picture = "";
AppUsers principal = (AppUsers) authentication.getPrincipal();
givenName = principal.getFullName();
familyName = principal.getUserName();
picture = principal.getImageLink();
H2 header = new H2("Hello " + givenName + " (" + familyName + ")");
Image image = new Image();
image.setText("User Image");
if (picture != null && !picture.isBlank() && !picture.isEmpty()) {
image.setSrc(picture);
}
Button logoutButton = new Button("Logout", click -> {
UI.getCurrent().getPage().setLocation("/auth/login");
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.logout(
VaadinServletRequest.getCurrent().getHttpServletRequest(), null,
null);
});
setAlignItems(Alignment.CENTER);
add(header, image, logoutButton);
}
}
UserService.class
@Service
public class UsersService implements UserDetailsService {
@Override
public AppUsers loadUserByUsername(String username) throws UsernameNotFoundException {
throw new UsernameNotFoundException("Access denied, invalid credentials.");
}
public AppUsers loadUser(String username, String password, String ip) throws UsernameNotFoundException {
AppUsers user = null;
try {
if (username.equalsIgnoreCase("admin")) {
user = new AppUsers()
.setUserName("admin")
.setFullName("Administrator")
.setUserPassword("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.setEmail("admin@test.net")
.setRoles(new ArrayList<>(List.of("ADMIN")));
} else {
user = new AppUsers();
user.setUserName(username);
user.setFullName(username);
user.setUserPassword("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW");
user.setRoles(new ArrayList<>(List.of("USER")));
}
} catch (Exception ex) {
ex.printStackTrace();
throw new UsernameNotFoundException("Access denied, invalid credentials.");
}
return user;
}
}