Versiones comparadas

Clave

  • Se ha añadido esta línea.
  • Se ha eliminado esta línea.
  • El formato se ha cambiado.

...

SecurityConfig

Nuestra clase de configuración de seguridad (la que tenemos con la anotación @EnableWebSecurity, se llame ConfiguraciónSeguridad, SecurityConfig, o como queramos) quedará así (no hay que duplicar esta clase, debemos tener una en el proyecto, si tenemos otras cosas metidas en nuestro SecurityConfig, como ModelMapper por ejemplo, se debe añadir lo que contiene esta):

Bloque de código
languagejava
themeEclipse
titleConfiguración de seguridad - SecurityConfig.java
linenumberstrue
collapsetrue
package es.um.atica.----.cas.config;
 
import static es.um.atica.----.Constants.SESSION_TOKEN_HEADER;
import static es.um.atica.----.Constants.LOCAL_ENV;
 
import java.util.Arrays;
 
import javax.servlet.http.HttpServletRequest;
 
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
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.CasAuthenticationFilter;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 
import es.um.atica.----.cas.handlers.CustomizedLogoutHandler;
import es.um.atica.----.cas.handlers.CustomizedUrlAuthenticationSuccessHandler;
 
@EnableWebSecurity
@EnableGlobalMethodSecurity( securedEnabled = true )
public class ConfiguracionSeguridad extends WebSecurityConfigurerAdapter {

	private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager
			.getLogger( ConfiguracionSeguridad.class );
 
    private final AuthenticationProvider authenticationProvider;
    private final LogoutFilter logoutFilter;
    private final SingleSignOutFilter singleSignOutFilter;
    private final AuthenticationEntryPoint authenticationEntryPoint;
    private final ServiceProperties serviceProperties;
	@Value( "${es.um.p15s.environment}" )
	private String environment;
 
    @Autowired
    public ConfiguracionSeguridad ( AuthenticationEntryPoint aep, LogoutFilter lF, SingleSignOutFilter ssF,
            CasAuthenticationProvider casAuthenticationProvider, ServiceProperties sp ) {
        authenticationProvider = casAuthenticationProvider;
        logoutFilter = lF;
        singleSignOutFilter = ssF;
        authenticationEntryPoint = aep;
        serviceProperties = sp;
    }
 
    // Autenticacion básica con usuario autogenerado
    @Override
    protected void configure( HttpSecurity http ) throws Exception {
 
        http.cors().and().csrf().disable().addFilter( casAuthenticationFilter( serviceProperties ) ).authorizeRequests()
        .regexMatchers( "^/entrada(\\/)?(\\?.+)?$" ).authenticated().and().authorizeRequests()
        .regexMatchers( "/public/*", "/private/*" ).permitAll().and().httpBasic()
        .authenticationEntryPoint( authenticationEntryPoint ).and().logout()
        .logoutSuccessHandler( customizedLogoutHandler() ).deleteCookies( SESSION_TOKEN_HEADER ).and()
        .addFilterBefore( singleSignOutFilter, CasAuthenticationFilter.class )
        .addFilterBefore( logoutFilter, LogoutFilter.class );
    }
 
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns( Arrays.asList( "*" ) );
        configuration.setAllowedMethods( Arrays.asList( "*" ) );
        configuration.setAllowedHeaders( Arrays.asList( "*" ) );
        configuration.setAllowCredentials( true );
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration( "/**", configuration );
        return source;
    }
 
    @Bean
    public CustomizedLogoutHandler customizedLogoutHandler() {
        return new CustomizedLogoutHandler();
    }
 
    @Override
    protected void configure( AuthenticationManagerBuilder auth ) throws Exception {
        auth.authenticationProvider( authenticationProvider );
    }
 
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return new ProviderManager( Arrays.asList( authenticationProvider ) );
    }
 
    @Bean
    AuthenticationDetailsSource<HttpServletRequest, ServiceAuthenticationDetails> dynamicServiceResolver() {
        return ( HttpServletRequest context ) -> {
            String urlProtocol = "http:";
            if ( !LOCAL_ENV.equals( environment ) ) {
                urlProtocol = "https:";
            }
            final String url = context.getRequestURL().toString().replace( "http:", urlProtocol );
            log.error( "-- ORIGINAL Dynamic URL: " + context.getRequestURL().toString() + " ---" );
            return new ServiceAuthenticationDetails() {

                private static final long serialVersionUID = 1L;

                @Override
                public String getServiceUrl() {
                    log.error( "-- Dynamic URL: " + url + " ---" );
                    return url;
                }
            };
        };
    }
 
    @Bean
    public CasAuthenticationFilter casAuthenticationFilter( ServiceProperties sP ) throws Exception {
        final CasAuthenticationFilter filter = new CasAuthenticationFilter();
        filter.setServiceProperties( sP );
        filter.setAuthenticationManager( authenticationManager() );
        filter.setFilterProcessesUrl( "/entrada/*" );
        filter.setAuthenticationDetailsSource( dynamicServiceResolver() );
        filter.setAuthenticationSuccessHandler( new CustomizedUrlAuthenticationSuccessHandler() );
        return filter;
    }
 
}
Bloque de código
languagejava

Resto de clases

Descargar clases necesarias

Podemos descargar estas clases, y el El resto de clases se pueden descargar necesarias aquí: Clases cliente CAS.rar.

Se  Se incluyen las entidades correspondientes a base de datos, y sus correspondientes repositorios. La estructura de paquetes del proyecto, incluyendo el cliente cas, quedaría así:

...

Bloque de código
languagejava
themeEclipse
firstlineCómo comprobar el token
linenumberstrue
collapsetrue
import static es.um.atica.----.Constants.AUTHORIZATION_HEADER;

@GetMapping("/titulaciones")
public ResponseEntity<List<TitulacionDTO>> titulaciones(@RequestHeader(value=AUTHORIZATION_HEADER, required = true) String tokenCodificado) {
	// Comprobamos el token
	try {
		jwtTokenUtil.getClaim( tokenCodificado );
	} catch( final ServicioException e ) {
		log.error( e.getLocalizedMessage() );
		return new ResponseEntity<>( e.getMessage(), HttpStatuse.FORBIDDENgetStatus() );
	}
}

En el método getClaim se comprueba si el token es correcto, y si lo es devuelve los datos que contiene. Este método se incluye en la clase JwtTokenUtil que viene en el cliente CAS, y si lo hacemos con el Portal de Servicios podemos incluirla así:

Bloque de código
languagejava
themeEclipse
titleJwtTokenUtil
linenumberstrue
collapsetrue
@Log4j2
@Component
public class JwtTokenUtil {

	@Value( "${es.um.jwt.secret}" )
	private String secret;

	// In minutes
	@Value( "${es.um.jwt.expires}" )
	private long expires;

	/**
	 * Get the claim from the token If token expirated throw TokenExpiredException If token is invalid throw
	 * UnauthorizedException
	 */
	public Claims getClaim( String token ) {
		final String[] tokenSplits = token.split( " " );
		if ( !tokenSplits[0].equals( "Bearer" ) ) {
			throw new UnauthorizedException();
		} else {
			token = tokenSplits[1];
			try {
				final Claims claims = Jwts.parser().setSigningKey( TextCodec.BASE64.encode( secret ) )
						.parseClaimsJws( token ).getBody();
				log.debug( "El claims es: " + claims.toString() );
				log.debug( "El subject es: " + claims.getSubject() );

				final Date expiration = claims.getExpiration();
				if ( Instant.now().isAfter( expiration.toInstant() ) ) {
					log.error( "Expiration Date: " + expiration.toString() );
					log.error( "Current Instant: " + Instant.now().toString() );
					log.error( "Error el token " + token + " está caducado" );
					throw new TokenExpiredException();
				}

				return claims;

			} catch ( final ExpiredJwtException e ) {
				log.error( "Error el token está caducado" );
				throw new TokenExpiredException();
			} catch ( final JwtException e ) {
				log.error( "Error filtering:" + e );
				throw new UnauthorizedException();
			}
		}
	}
}

...

Bloque de código
languagejava
themeEclipse
titleServicioException
linenumberstrue
collapsetrue
/**
 * ServicioNotFoundException
 */
public class ServicioException extends RuntimeException {
 
	private static final long serialVersionUID = 1L;
 
	private static final HttpStatus status = HttpStatus.BAD_REQUEST;
 
	protected ServicioException( String msg ) {
		super( msg );
	}
 
	public HttpStatus getStatus() {
		return ServicioException.status;
	}
 
}

...

Bloque de código
languagejava
themeEclipse
titleTokenExpiredException
linenumberstrue
collapsetrue
/**
 * ServicioNotFoundException
 */
public class TokenExpiredException extends ServicioException {
 
	private static final long serialVersionUID = 1L;
 
	private static final HttpStatus status = HttpStatus.UNAUTHORIZED;
 
	public TokenExpiredException() {
		super( "Token_expired" );
	}
 
	@Override
	public HttpStatus getStatus() {
		return TokenExpiredException.status;
	}
 
}

...

Bloque de código
languagejava
themeEclipse
titleUnauthorizedException
linenumberstrue
collapsetrue
/**
 * ServicioNotFoundException
 */
public class UnauthorizedException extends ServicioException {
 
	private static final long serialVersionUID = 1L;
 
	private static final HttpStatus status = HttpStatus.UNAUTHORIZED;
 
	public UnauthorizedException() {
		super( "No tiene acceso para solicitar este servicio " );
	}
 
	@Override
	public HttpStatus getStatus() {
		return UnauthorizedException.status;
	}
 
}

...