Add OAUTH2 OIDC login support (#1140)
* Somewhat working * Change Autocreate logic * Add OAuth Error Message if Auto create Disabled * Display OAUTH2 username(email) in Account Settings * Disable Change user/pass for Oauth2 user * Hide SSO Button if SSO login Disabled * Remove some spaces and comments * Add OAUTH2 Login example docker-compose file * Add Some Comments * Hide Printing of Client secret * Remove OAUTH2 Beans and replace with applicationProperties * Add conditional annotation to Bean Creation * Update settings.yml.template Add OAUTH2 enabling template. * Update messages_en_GB.properties
This commit is contained in:
parent
777e512e61
commit
d9fa8f7b48
12 changed files with 282 additions and 5 deletions
|
@ -99,6 +99,7 @@ dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.4'
|
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.4'
|
||||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
|
||||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.4"
|
implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.4"
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client:3.2.4'
|
||||||
|
|
||||||
//2.2.x requires rebuild of DB file.. need migration path
|
//2.2.x requires rebuild of DB file.. need migration path
|
||||||
implementation "com.h2database:h2:2.1.214"
|
implementation "com.h2database:h2:2.1.214"
|
||||||
|
|
39
exampleYmlFiles/docker-compose-latest-security-with-sso.yml
Normal file
39
exampleYmlFiles/docker-compose-latest-security-with-sso.yml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Security
|
||||||
|
image: frooodle/s-pdf:latest
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 4G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /stirling/latest/data:/usr/share/tessdata:rw
|
||||||
|
- /stirling/latest/config:/configs:rw
|
||||||
|
- /stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
SECURITY_OAUTH2_ENABLED: "true"
|
||||||
|
SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Striling-PDF
|
||||||
|
SECURITY_OAUTH2_ISSUER: "https://accounts.google.com" # Change with any other provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
||||||
|
SECURITY_OAUTH2_CLIENTID: "<YOUR CLIENT ID>.apps.googleusercontent.com" # Client ID from your provider
|
||||||
|
SECURITY_OAUTH2_CLIENTSECRET: "<YOUR CLIENT SECRET>" # Client Secret from your provider
|
||||||
|
PUID: 1002
|
||||||
|
PGID: 1002
|
||||||
|
UMASK: "022"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
restart: on-failure:5
|
|
@ -0,0 +1,43 @@
|
||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.session.SessionRegistry;
|
||||||
|
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||||
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
|
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler
|
||||||
|
{
|
||||||
|
@Bean
|
||||||
|
public SessionRegistry sessionRegistry() {
|
||||||
|
return new SessionRegistryImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session != null) {
|
||||||
|
String sessionId = session.getId();
|
||||||
|
sessionRegistry()
|
||||||
|
.removeSessionInformation(
|
||||||
|
sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.getParameter("oauth2AutoCreateDisabled") != null)
|
||||||
|
{
|
||||||
|
response.sendRedirect(request.getContextPath()+"/login?error=oauth2AutoCreateDisabled");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.sendRedirect(request.getContextPath() + "/login?logout=true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,11 @@
|
||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
@ -10,20 +14,30 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
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.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.session.SessionRegistry;
|
import org.springframework.security.core.session.SessionRegistry;
|
||||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity()
|
@EnableWebSecurity()
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
|
@ -42,6 +56,8 @@ public class SecurityConfiguration {
|
||||||
@Qualifier("loginEnabled")
|
@Qualifier("loginEnabled")
|
||||||
public boolean loginEnabledValue;
|
public boolean loginEnabledValue;
|
||||||
|
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
|
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
|
||||||
|
|
||||||
@Autowired private LoginAttemptService loginAttemptService;
|
@Autowired private LoginAttemptService loginAttemptService;
|
||||||
|
@ -87,7 +103,7 @@ public class SecurityConfiguration {
|
||||||
logout ->
|
logout ->
|
||||||
logout.logoutRequestMatcher(
|
logout.logoutRequestMatcher(
|
||||||
new AntPathRequestMatcher("/logout"))
|
new AntPathRequestMatcher("/logout"))
|
||||||
.logoutSuccessUrl("/login?logout=true")
|
.logoutSuccessHandler(new CustomLogoutSuccessHandler()) // Use a Custom Logout Handler to handle custom error message if OAUTH2 Auto Create is disabled
|
||||||
.invalidateHttpSession(true) // Invalidate session
|
.invalidateHttpSession(true) // Invalidate session
|
||||||
.deleteCookies("JSESSIONID", "remember-me")
|
.deleteCookies("JSESSIONID", "remember-me")
|
||||||
.addLogoutHandler(
|
.addLogoutHandler(
|
||||||
|
@ -124,6 +140,7 @@ public class SecurityConfiguration {
|
||||||
: uri;
|
: uri;
|
||||||
|
|
||||||
return trimmedUri.startsWith("/login")
|
return trimmedUri.startsWith("/login")
|
||||||
|
|| trimmedUri.startsWith("/oauth")
|
||||||
|| trimmedUri.endsWith(".svg")
|
|| trimmedUri.endsWith(".svg")
|
||||||
|| trimmedUri.startsWith(
|
|| trimmedUri.startsWith(
|
||||||
"/register")
|
"/register")
|
||||||
|
@ -140,6 +157,33 @@ public class SecurityConfiguration {
|
||||||
.authenticated())
|
.authenticated())
|
||||||
.userDetailsService(userDetailsService)
|
.userDetailsService(userDetailsService)
|
||||||
.authenticationProvider(authenticationProvider());
|
.authenticationProvider(authenticationProvider());
|
||||||
|
|
||||||
|
// Handle OAUTH2 Logins
|
||||||
|
if (applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
|
||||||
|
|
||||||
|
http.oauth2Login( oauth2 -> oauth2
|
||||||
|
.loginPage("/oauth2")
|
||||||
|
/*
|
||||||
|
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
||||||
|
If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
|
||||||
|
is set as true, else login fails with an error message advising the same.
|
||||||
|
*/
|
||||||
|
.successHandler(new AuthenticationSuccessHandler() {
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Authentication authentication) throws ServletException , IOException{
|
||||||
|
OAuth2User oauthUser = (OAuth2User) authentication.getPrincipal();
|
||||||
|
if (userService.processOAuth2PostLogin(oauthUser.getAttribute("email"), applicationProperties.getSecurity().getOAUTH2().getAutoCreateUser())) {
|
||||||
|
response.sendRedirect("/");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
response.sendRedirect("/logout?oauth2AutoCreateDisabled=true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
http.csrf(csrf -> csrf.disable())
|
http.csrf(csrf -> csrf.disable())
|
||||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
|
@ -148,6 +192,24 @@ public class SecurityConfiguration {
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client Registration Repository for OAUTH2 OIDC Login
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(value = "security.oauth2.enabled" , havingValue = "true", matchIfMissing = false)
|
||||||
|
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||||
|
return new InMemoryClientRegistrationRepository(this.oidcClientRegistration());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientRegistration oidcClientRegistration() {
|
||||||
|
return ClientRegistrations.fromOidcIssuerLocation(applicationProperties.getSecurity().getOAUTH2().getIssuer())
|
||||||
|
.registrationId("oidc")
|
||||||
|
.clientId(applicationProperties.getSecurity().getOAUTH2().getClientId())
|
||||||
|
.clientSecret(applicationProperties.getSecurity().getOAUTH2().getClientSecret())
|
||||||
|
.scope("openid", "profile", "email")
|
||||||
|
.userNameAttributeName("email")
|
||||||
|
.clientName("OIDC")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IPRateLimitingFilter rateLimitingFilter() {
|
public IPRateLimitingFilter rateLimitingFilter() {
|
||||||
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
||||||
|
|
|
@ -30,6 +30,24 @@ public class UserService implements UserServiceInterface {
|
||||||
|
|
||||||
@Autowired private PasswordEncoder passwordEncoder;
|
@Autowired private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
// Handle OAUTH2 login and user auto creation.
|
||||||
|
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser) {
|
||||||
|
Optional<User> existUser = userRepository.findByUsernameIgnoreCase(username);
|
||||||
|
if (existUser.isPresent()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (autoCreateUser) {
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(username);
|
||||||
|
user.setEnabled(true);
|
||||||
|
user.setFirstLogin(false);
|
||||||
|
user.addAuthority(new Authority( Role.USER.getRoleId(), user));
|
||||||
|
userRepository.save(user);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public Authentication getAuthentication(String apiKey) {
|
public Authentication getAuthentication(String apiKey) {
|
||||||
User user = getUserByApiKey(apiKey);
|
User user = getUserByApiKey(apiKey);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
|
|
@ -6,9 +6,11 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
@ -19,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.Authority;
|
import stirling.software.SPDF.model.Authority;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
@ -28,12 +31,16 @@ import stirling.software.SPDF.repository.UserRepository;
|
||||||
@Tag(name = "Account Security", description = "Account Security APIs")
|
@Tag(name = "Account Security", description = "Account Security APIs")
|
||||||
public class AccountWebController {
|
public class AccountWebController {
|
||||||
|
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@GetMapping("/login")
|
@GetMapping("/login")
|
||||||
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
return "redirect:/";
|
return "redirect:/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.addAttribute("oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
|
||||||
|
|
||||||
model.addAttribute("currentPage", "login");
|
model.addAttribute("currentPage", "login");
|
||||||
|
|
||||||
if (request.getParameter("error") != null) {
|
if (request.getParameter("error") != null) {
|
||||||
|
@ -85,14 +92,29 @@ public class AccountWebController {
|
||||||
}
|
}
|
||||||
if (authentication != null && authentication.isAuthenticated()) {
|
if (authentication != null && authentication.isAuthenticated()) {
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
|
String username = null;
|
||||||
|
|
||||||
if (principal instanceof UserDetails) {
|
if (principal instanceof UserDetails) {
|
||||||
// Cast the principal object to UserDetails
|
// Cast the principal object to UserDetails
|
||||||
UserDetails userDetails = (UserDetails) principal;
|
UserDetails userDetails = (UserDetails) principal;
|
||||||
|
|
||||||
// Retrieve username and other attributes
|
// Retrieve username and other attributes
|
||||||
String username = userDetails.getUsername();
|
username = userDetails.getUsername();
|
||||||
|
|
||||||
|
// Add oAuth2 Login attributes to the model
|
||||||
|
model.addAttribute("oAuth2Login", false);
|
||||||
|
}
|
||||||
|
if (principal instanceof OAuth2User) {
|
||||||
|
// Cast the principal object to OAuth2User
|
||||||
|
OAuth2User userDetails = (OAuth2User) principal;
|
||||||
|
|
||||||
|
// Retrieve username and other attributes
|
||||||
|
username = userDetails.getAttribute("email");
|
||||||
|
|
||||||
|
// Add oAuth2 Login attributes to the model
|
||||||
|
model.addAttribute("oAuth2Login", true);
|
||||||
|
}
|
||||||
|
if (username != null) {
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user =
|
Optional<User> user =
|
||||||
userRepository.findByUsernameIgnoreCase(
|
userRepository.findByUsernameIgnoreCase(
|
||||||
|
|
|
@ -118,6 +118,7 @@ public class ApplicationProperties {
|
||||||
private Boolean enableLogin;
|
private Boolean enableLogin;
|
||||||
private Boolean csrfDisabled;
|
private Boolean csrfDisabled;
|
||||||
private InitialLogin initialLogin;
|
private InitialLogin initialLogin;
|
||||||
|
private OAUTH2 oauth2;
|
||||||
private int loginAttemptCount;
|
private int loginAttemptCount;
|
||||||
private long loginResetTimeMinutes;
|
private long loginResetTimeMinutes;
|
||||||
|
|
||||||
|
@ -145,6 +146,14 @@ public class ApplicationProperties {
|
||||||
this.initialLogin = initialLogin;
|
this.initialLogin = initialLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OAUTH2 getOAUTH2() {
|
||||||
|
return oauth2 != null ? oauth2 : new OAUTH2();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOAUTH2(OAUTH2 oauth2) {
|
||||||
|
this.oauth2 = oauth2;
|
||||||
|
}
|
||||||
|
|
||||||
public Boolean getEnableLogin() {
|
public Boolean getEnableLogin() {
|
||||||
return enableLogin;
|
return enableLogin;
|
||||||
}
|
}
|
||||||
|
@ -165,6 +174,8 @@ public class ApplicationProperties {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Security [enableLogin="
|
return "Security [enableLogin="
|
||||||
+ enableLogin
|
+ enableLogin
|
||||||
|
+ ", oauth2="
|
||||||
|
+ oauth2
|
||||||
+ ", initialLogin="
|
+ ", initialLogin="
|
||||||
+ initialLogin
|
+ initialLogin
|
||||||
+ ", csrfDisabled="
|
+ ", csrfDisabled="
|
||||||
|
@ -202,6 +213,70 @@ public class ApplicationProperties {
|
||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class OAUTH2 {
|
||||||
|
|
||||||
|
private boolean enabled;
|
||||||
|
private String issuer;
|
||||||
|
private String clientId;
|
||||||
|
private String clientSecret;
|
||||||
|
private boolean autoCreateUser;
|
||||||
|
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIssuer() {
|
||||||
|
return issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIssuer(String issuer) {
|
||||||
|
this.issuer = issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientSecret() {
|
||||||
|
return clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAutoCreateUser() {
|
||||||
|
return autoCreateUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoCreateUser(boolean autoCreateUser) {
|
||||||
|
this.autoCreateUser = autoCreateUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "OAUTH2 [enabled="
|
||||||
|
+ enabled
|
||||||
|
+ ", issuer="
|
||||||
|
+ issuer
|
||||||
|
+ ", clientId="
|
||||||
|
+ clientId
|
||||||
|
+ ", clientSecret="
|
||||||
|
+ (clientSecret!= null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||||
|
+ ", autoCreateUser="
|
||||||
|
+ autoCreateUser
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class System {
|
public static class System {
|
||||||
|
|
|
@ -437,6 +437,8 @@ login.rememberme=Remember me
|
||||||
login.invalid=Invalid username or password.
|
login.invalid=Invalid username or password.
|
||||||
login.locked=Your account has been locked.
|
login.locked=Your account has been locked.
|
||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
|
|
|
@ -437,6 +437,8 @@ login.rememberme=Remember me
|
||||||
login.invalid=Invalid username or password.
|
login.invalid=Invalid username or password.
|
||||||
login.locked=Your account has been locked.
|
login.locked=Your account has been locked.
|
||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
|
|
|
@ -7,6 +7,12 @@ security:
|
||||||
csrfDisabled: true
|
csrfDisabled: true
|
||||||
loginAttemptCount: 5 # lock user account after 5 tries
|
loginAttemptCount: 5 # lock user account after 5 tries
|
||||||
loginResetTimeMinutes : 120 # lock account for 2 hours after x attempts
|
loginResetTimeMinutes : 120 # lock account for 2 hours after x attempts
|
||||||
|
#oauth2:
|
||||||
|
# enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
||||||
|
# issuer: "" # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
||||||
|
# clientId: "" # Client ID from your provider
|
||||||
|
# clientSecret: "" # Client Secret from your provider
|
||||||
|
# autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
||||||
|
|
||||||
system:
|
system:
|
||||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
</div>
|
</div>
|
||||||
</th:block>
|
</th:block>
|
||||||
<!-- Change Username Form -->
|
<!-- Change Username Form -->
|
||||||
<form action="api/v1/user/change-username" method="post">
|
<form th:if="${!oAuth2Login}" action="api/v1/user/change-username" method="post">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="newUsername" th:text="#{account.changeUsername}">Change Username</label>
|
<label for="newUsername" th:text="#{account.changeUsername}">Change Username</label>
|
||||||
<input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
|
<input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
|
||||||
|
@ -59,8 +59,8 @@
|
||||||
<hr> <!-- Separator Line -->
|
<hr> <!-- Separator Line -->
|
||||||
|
|
||||||
<!-- Change Password Form -->
|
<!-- Change Password Form -->
|
||||||
<h4 th:text="#{account.changePassword}">Change Password?</h4>
|
<h4 th:if="${!oAuth2Login}" th:text="#{account.changePassword}">Change Password?</h4>
|
||||||
<form action="api/v1/user/change-password" method="post">
|
<form th:if="${!oAuth2Login}" action="api/v1/user/change-password" method="post">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
|
<label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
|
||||||
<input type="password" class="form-control" name="currentPassword" id="currentPasswordPassword" th:placeholder="#{account.oldPassword}">
|
<input type="password" class="form-control" name="currentPassword" id="currentPasswordPassword" th:placeholder="#{account.oldPassword}">
|
||||||
|
|
|
@ -139,6 +139,13 @@
|
||||||
<form th:action="@{login}" method="post">
|
<form th:action="@{login}" method="post">
|
||||||
<img class="mb-4" src="favicon.svg?v=2" alt="" width="144" height="144">
|
<img class="mb-4" src="favicon.svg?v=2" alt="" width="144" height="144">
|
||||||
<h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
|
<h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
|
||||||
|
<div th:if="${oAuth2Enabled}">
|
||||||
|
<a href="oauth2/authorization/oidc" class="w-100 btn btn-lg btn-primary" th:text="#{login.ssoSignIn}">Login Via SSO</a>
|
||||||
|
<div class="text-danger text-center">
|
||||||
|
<div th:if="${error == 'oauth2AutoCreateDisabled'}" th:text="#{login.oauth2AutoCreateDisabled}">OAUTH2 Auto-Create User Disabled.</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
<h2 class="h5 mb-3 fw-normal" th:text="#{login.signinTitle}">Please sign in</h2>
|
<h2 class="h5 mb-3 fw-normal" th:text="#{login.signinTitle}">Please sign in</h2>
|
||||||
|
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
|
|
Loading…
Reference in a new issue