فهرست منبع

First version of CAS login

Drowsito 3 هفته پیش
والد
کامیت
5647bc1a16

+ 5 - 0
pom.xml

@@ -165,6 +165,11 @@
 	  	 -->
 
 		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-cas</artifactId>
+		</dependency>
+
+		<dependency>
 			<groupId>es.uv.saic.shared</groupId>
 		    <artifactId>UV_SAIC_SHARED</artifactId>
 		    <version>0.0.1-SNAPSHOT</version>

+ 10 - 2
src/main/java/es/uv/saic/config/ApplicationLocaleResolver.java

@@ -24,7 +24,7 @@ public class ApplicationLocaleResolver extends SessionLocaleResolver {
         SecurityContext securityContext = SecurityContextHolder.getContext();
         Locale userLocale = Locale.forLanguageTag("ca"); 
         if(!(securityContext.getAuthentication() instanceof AnonymousAuthenticationToken)) {
-        	Usuari usuari = ((Usuari)securityContext.getAuthentication().getPrincipal());
+            Usuari usuari = resolveUsuari(securityContext.getAuthentication().getPrincipal());
         	String locale = usuari.getLocale();
         	userLocale = locale == null ? userLocale : Locale.forLanguageTag(locale);
         }
@@ -38,9 +38,17 @@ public class ApplicationLocaleResolver extends SessionLocaleResolver {
 
         SecurityContext securityContext = SecurityContextHolder.getContext();
         if(!(securityContext.getAuthentication() instanceof AnonymousAuthenticationToken)) {
-        	Usuari usuari = ((Usuari)securityContext.getAuthentication().getPrincipal());
+            Usuari usuari = resolveUsuari(securityContext.getAuthentication().getPrincipal());
 	        usuari.setLocale(locale.toLanguageTag());
 	        us.save(usuari);
         }        
     }
+
+    private Usuari resolveUsuari(Object principal) {
+        if (principal instanceof Usuari) {
+            return (Usuari) principal;
+        }
+        String username = ((org.springframework.security.core.userdetails.UserDetails) principal).getUsername();
+        return us.findByUsername(username);
+    }
 }

+ 77 - 19
src/main/java/es/uv/saic/config/SecurityConfig.java

@@ -4,11 +4,18 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.apereo.cas.client.validation.Cas30ServiceTicketValidator;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.cas.ServiceProperties;
+import org.springframework.security.cas.authentication.CasAuthenticationProvider;
+import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
+import org.springframework.security.cas.web.CasAuthenticationFilter;
+import es.uv.saic.service.CasUserDetailService;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -21,6 +28,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
 import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
 import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
 import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
@@ -42,25 +50,33 @@ public class SecurityConfig {
 	private String validIp;
 	
 	@Bean
-	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+	public SecurityFilterChain filterChain(HttpSecurity http, CasAuthenticationFilter casFilter, CasAuthenticationEntryPoint casEntryPoint) throws Exception {
 		http.authorizeHttpRequests((auth) -> auth
 	        	.requestMatchers("/", "/css/**", "/js/**", "/img/**", "/logos/*", "/logos/**").permitAll()
-	        	.requestMatchers("/login**").permitAll()
+	        	.requestMatchers("/login").permitAll()
+				.requestMatchers("/login/cas").authenticated()
 	        	.requestMatchers("/keepalive").permitAll()
 				.requestMatchers("/public/**").permitAll()
 	        	.requestMatchers("/actuator/**").access(new WebExpressionAuthorizationManager("hasIpAddress('" + this.validIp + "')"))
 				.requestMatchers("/actuator/**").access(new WebExpressionAuthorizationManager("hasIpAddress('127.0.0.1')"))
+				.anyRequest().authenticated()
 	        )
-	        .authorizeHttpRequests((auth)-> auth
-	            .anyRequest().authenticated()
-				//.anyRequest().permitAll()
-	        )
+			.addFilter(casFilter)
+			.exceptionHandling(ex -> ex
+				.defaultAuthenticationEntryPointFor(
+					casEntryPoint,
+					request -> request.getServletPath().startsWith("/login/cas")
+				)
+				.defaultAuthenticationEntryPointFor(
+					new LoginUrlAuthenticationEntryPoint("/login"),
+					request -> !request.getServletPath().startsWith("/login/cas")
+				)
+			)
 	        .formLogin((form) -> form
 	            .loginPage("/login")
 	            .defaultSuccessUrl("/procedures?_new=1",true)
 	            .failureUrl("/login?error=noauth")
 	            .successHandler(new AuthSuccessHandler())
-	            .permitAll()
 	        )
 	        .logout((logout) -> logout
 	        	.logoutSuccessUrl("/login")
@@ -87,24 +103,16 @@ public class SecurityConfig {
 	}
 	    
     @Bean
-    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
-        return http.getSharedObject(AuthenticationManagerBuilder.class)
-        		   .authenticationProvider(authProvider)
-        		   .build();
-    }
-    
+	public AuthenticationManager authenticationManager(AuthProvider authProvider, CasAuthenticationProvider casAuthProvider) {
+		return new ProviderManager(authProvider, casAuthProvider);
+	}
+
     @Bean
     public SessionRegistry sessionRegistry() {
         return new SessionRegistryImpl();
     }
     
     @Bean
-    public DefaultSpringSecurityContextSource contextSource() {
-        return  new DefaultSpringSecurityContextSource(
-                Collections.singletonList("ldap://ldap.uv.es"), "dc=uv,dc=es");
-    }
-    
-    @Bean
     public PasswordEncoder passwordEncoder() {
         return new BCryptPasswordEncoder();
     }
@@ -114,6 +122,56 @@ public class SecurityConfig {
         return new HttpSessionEventPublisher();
     }
      
+
+	@Bean
+    public ServiceProperties serviceProperties() {
+        ServiceProperties sp = new ServiceProperties();
+        sp.setService("http://localhost:8080/login/cas"); 
+        return sp;
+    }
+
+    @Bean
+    public CasAuthenticationEntryPoint casAuthenticationEntryPoint(ServiceProperties sp) {
+        CasAuthenticationEntryPoint cp = new CasAuthenticationEntryPoint();
+        cp.setLoginUrl("http://localhost:8085/cas/login");
+        cp.setServiceProperties(sp);
+        return cp;
+    }
+
+	@Bean
+	public CasAuthenticationFilter casAuthenticationFilter(
+			AuthenticationManager authManager, 
+			ServiceProperties sp,
+			CasAuthenticationEntryPoint casEntryPoint) {
+		
+		CasAuthenticationFilter filter = new CasAuthenticationFilter();
+		filter.setAuthenticationManager(authManager);
+		filter.setServiceProperties(sp);
+		filter.setFilterProcessesUrl("/login/cas");
+		
+		filter.setAuthenticationFailureHandler((request, response, exception) -> {
+			casEntryPoint.commence(request, response, exception);
+		});
+		
+		return filter;
+	}
+
+	@Bean
+	public CasAuthenticationProvider casAuthenticationProvider(
+			ServiceProperties sp, 
+			CasUserDetailService userDetailsService) {
+		
+		CasAuthenticationProvider provider = new CasAuthenticationProvider();
+		provider.setAuthenticationUserDetailsService(userDetailsService);
+		provider.setServiceProperties(sp);
+		
+		provider.setTicketValidator(new Cas30ServiceTicketValidator("http://localhost:8085/cas"));
+		
+		provider.setKey("CAS_PROVIDER_SAIC_LOCAL");
+		
+		return provider;
+	}
+
     @Bean
     public CompositeSessionAuthenticationStrategy concurrentSession() {
 

+ 43 - 0
src/main/java/es/uv/saic/service/CasUserDetailService.java

@@ -0,0 +1,43 @@
+package es.uv.saic.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import es.uv.saic.shared.domain.Usuari;
+
+@Service
+public class CasUserDetailService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
+    @Autowired
+    private UsuariService us;
+    @Autowired 
+    private UsuarisRolService urs;
+
+    @Override
+    public Usuari loadUserDetails(CasAssertionAuthenticationToken token) {
+        String username = token.getName();
+        Usuari u = this.us.findByUsername(username);
+
+        if (u == null) throw new UsernameNotFoundException("Usuario no encontrado");
+
+        // Igual que en AuthProvider
+        u.setGranted(this.urs.isGrantedUser(u));
+        u.setAdmin(this.urs.isAdminUser(u));
+        u.setDataTest(this.urs.isDataTestUser(u));
+
+        List<SimpleGrantedAuthority> auths = new ArrayList<>();
+        auths.add(new SimpleGrantedAuthority("ROLE_USER"));
+        if (u.isAdmin())   auths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
+        if (u.isGranted()) auths.add(new SimpleGrantedAuthority("ROLE_MANAGER"));
+        if (u.isDataTest()) auths.add(new SimpleGrantedAuthority("ROLE_TESTER"));
+
+        u.setAuthorities(auths);
+        return u;
+    }
+}

+ 2 - 0
src/main/resources/application-local.properties

@@ -1,3 +1,5 @@
+server.port = 8080
+
 # Urls
 saic.url.domain = http://127.0.0.1
 saic.url.public = ${saic.url.domain}/public

+ 60 - 39
src/main/resources/templates/login.html

@@ -22,47 +22,63 @@
 							<span class="uv-login-title" th:text="#{login.h2}"></span>
 						</div>
 					</div>
-					<div class="row">
-						<div class="row">
-							<form enctype='multipart/form-data' method="POST" action="/login" id="loginForm">
-								<div class="form-group uv-login-form-group">
-									<input type="text" id="username" class="uv-login-input" name="username" th:placeholder="#{login.placeholder.username}">
-								</div>
-								<div class="form-group uv-login-form-group">
-									<input type="password" id="passwd" class="uv-login-input" name="password" th:placeholder="#{login.placeholder.passwd}">
-								</div>
-								<div class="form-group">
-									<input type="submit" class="uv-login-button" th:value="#{login.button}" id="loginButton">
-								</div>
-						    </form>
-					    </div>
-					</div>
-					<div class="row uv-mar-t-20" th:if="${error}">
-						<span class="alert alert-danger alert-dismissible fade show" role="alert">
-							<span th:text="#{login.error}"></span>
-							<button type="button" class="close" data-dismiss="alert" aria-label="Close">
-						    	<span aria-hidden="true">&times;</span>
-						  	</button>
-						</span>
-					</div>
-					<div class="row uv-mar-t-20" th:if="${restricted}">
-						<span class="alert alert-warning alert-dismissible fade show" role="alert">
-							<span th:text="#{login.restricted}"></span>
-							<button type="button" class="close" data-dismiss="alert" aria-label="Close">
-						    	<span aria-hidden="true">&times;</span>
-						  	</button>
-						</span>
-					</div>
-					<div class="row uv-mar-t-20" th:if="${expired}">
-						<span class="alert alert-danger alert-dismissible fade show" role="alert">
-							<span th:text="#{login.expired}"></span>
-							<button type="button" class="close" data-dismiss="alert" aria-label="Close">
-						    	<span aria-hidden="true">&times;</span>
-						  	</button>
-						</span>
+					<div class="row" id="choiceButtons">
+						<div class="row uv-login-row">
+							<a href="/login/cas" class="btn btn-primary btn-block">
+								<i class="fas fa-university"></i> Usuario Universidad
+							</a>
+							<button type="button" id="btnExternal" class="btn btn-secondary btn-block">
+								<i class="fas fa-user-external"></i> Usuario Externo
+							</button>
+						</div>
 					</div>
-					<div class="row uv-mar-t-20" style="display:none;" id="incompatible">
+					<div id="externalLoginForm" style="display:none;" class="mt-3">
+						<div class="row">
+							<div class="row">
+								<form enctype='multipart/form-data' method="POST" action="/login" id="loginForm">
+									<div class="form-group uv-login-form-group">
+										<input type="text" id="username" class="uv-login-input" name="username" th:placeholder="#{login.placeholder.username}">
+									</div>
+									<div class="form-group uv-login-form-group">
+										<input type="password" id="passwd" class="uv-login-input" name="password" th:placeholder="#{login.placeholder.passwd}">
+									</div>
+									<div class="form-group">
+										<input type="submit" class="uv-login-button" th:value="#{login.button}" id="loginButton">
+										<button type="button" onclick="$('#externalLoginForm').hide(); $('#choiceButtons').show();" class="uv-login-button">Volver</button>
+									</div>
+									<div class="form-group">
 
+									</div>
+								</form>
+							</div>
+						</div>
+						<div class="row uv-mar-t-20" th:if="${error}">
+							<span class="alert alert-danger alert-dismissible fade show" role="alert">
+								<span th:text="#{login.error}"></span>
+								<button type="button" class="close" data-dismiss="alert" aria-label="Close">
+									<span aria-hidden="true">&times;</span>
+								</button>
+							</span>
+						</div>
+						<div class="row uv-mar-t-20" th:if="${restricted}">
+							<span class="alert alert-warning alert-dismissible fade show" role="alert">
+								<span th:text="#{login.restricted}"></span>
+								<button type="button" class="close" data-dismiss="alert" aria-label="Close">
+									<span aria-hidden="true">&times;</span>
+								</button>
+							</span>
+						</div>
+						<div class="row uv-mar-t-20" th:if="${expired}">
+							<span class="alert alert-danger alert-dismissible fade show" role="alert">
+								<span th:text="#{login.expired}"></span>
+								<button type="button" class="close" data-dismiss="alert" aria-label="Close">
+									<span aria-hidden="true">&times;</span>
+								</button>
+							</span>
+						</div>
+						<div class="row uv-mar-t-20" style="display:none;" id="incompatible">
+
+						</div>
 					</div>
 					<div class="row" style="display:none;">
 						<div class="row uv-notice-container">
@@ -115,6 +131,11 @@
 							+"<span>Recomendamos el uso de Chrome, Firefox o Edge.</span>")
 				.appendTo($('#incompatible'));	  
 		}
+
+		$("#btnExternal").click(function() {
+			$("#choiceButtons").hide();
+			$("#externalLoginForm").show();
+		});
 	</script>
 
 </body>