Tutorial Spring REST 3/9 – Configurar OAUTH2 en un proyecto REST de Spring 4

1426

En este artículo explicamos la configuración de OAUTH2 con Spring Security en un servicio REST, míra nuestro anterior artículo para ver la configuración de un servicio REST con Spring 4, con más detalle.

El objetivo de asegurar servicios REST con Spring Security y OAUTH2 es proteger nuestra aplicación contra los ataques más comunes en Internet, además de autenticar y dar acceso a nuestros usuarios mediante tokens caducables con el protocolo OAUTH2.

Dependencias de Maven

        <!-- Spring Security -->
	<dependency>
	    <groupId>org.springframework</groupId>
	    <artifactId>spring-context</artifactId>
	</dependency>	
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-config</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-taglibs</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.security.oauth</groupId>
		<artifactId>spring-security-oauth2</artifactId>
        </dependency>

Configuración de seguridad

Finalmente, revisemos las clases de configuración para la seguridad web y autenticación (mediante tokens) de nuestro proyecto, de acuerdo al protocolo OAUTH2.

Primeramente revisemos la configuración del Servidor de Autorización, en el cual especificaremos los permisos otorgados a un cliente de confianza y el tiempo que durará activo cada uno de los tokens (en este demo, los clientes se especifican en memoria pero se pueden verificar contra una base de datos).

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
	
	@Autowired
	private TokenStore tokenStore;

	@Autowired
	private UserApprovalHandler userApprovalHandler;

	@Autowired
	@Qualifier("authenticationManagerBean")
	private AuthenticationManager authenticationManager;

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.inMemory()
	        .withClient("client")
            .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
            .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
            .scopes("read", "write", "trust")
            .secret("client")
            .accessTokenValiditySeconds(600).
            refreshTokenValiditySeconds(900);
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)
				.authenticationManager(authenticationManager);
	}
	
	@Override
	public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
		oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess(
				"hasAuthority('ROLE_TRUSTED_CLIENT')");
	}
}

En esta clase de configuración, habilitaremos el servidor de recursos, en el cual especificaremos las URIs que queremos proteger y qué roles de usuario tendrán acceso.

@Configuration
@EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {

	private static final String RESOURCE_ID = "rest_api";
	
	@Override
	public void configure(ResourceServerSecurityConfigurer resources) {
		resources.resourceId(RESOURCE_ID).stateless(false);
	}

	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.
		anonymous().disable()
		.requestMatchers().antMatchers("/user/**")
		.and().authorizeRequests()
		.antMatchers("/user/**").access("hasRole('ADMIN')")
		.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
	}
}

Finalmente, habilitamos la seguridad web en nuestro proyecto y definimos la forma en la que la aplicación validará las credenciales recibidas de los usuarios (en este caso, contra una base de datos SQL). También, especificamos el algoritmo de cifrado para los passwords (en este caso BCrypt).

@Configuration
@EnableWebSecurity
public class Oauth2Security extends WebSecurityConfigurerAdapter {

	@Autowired
	private ClientDetailsService clientDetailsService;	
	@Autowired
	private DataSource dataSource;
	
	@Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
        	.dataSource(dataSource)
        	.passwordEncoder(passwordEncoder())
        	.usersByUsernameQuery("SELECT USERNAME, PASSWORD, ACTIVE FROM USER WHERE USERNAME = ?")
        	.authoritiesByUsernameQuery("SELECT USERNAME, ROLE FROM USER WHERE USERNAME = ?");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	http.csrf().disable()
		.anonymous().disable()
	  	.authorizeRequests()
	  	.antMatchers("/oauth/token").permitAll();		
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

	@Bean
	public TokenStore tokenStore() {
		return new InMemoryTokenStore();
	}

	@Bean
	@Autowired
	public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
		TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
		handler.setTokenStore(tokenStore);
		handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
		handler.setClientDetailsService(clientDetailsService);
		return handler;
	}
	
	@Bean
	@Autowired
	public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
		TokenApprovalStore store = new TokenApprovalStore();
		store.setTokenStore(tokenStore);
		return store;
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}	
}

Configuración de JPA

Como pudiste notar en la sección anterior, la autenticación se hace contra la base de datos, por lo que requerimos de una conexión que en este caso haremos mediante JPA y Hibernate, de la siguiente manera (este bean es inyectado a la configuración de seguridad):

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("org.codesolt.repository")
@PropertySource({ "classpath:persistance.properties" })
public class JDBCConfiguration {

	@Autowired
    private Environment env;
	
	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource manager = new DriverManagerDataSource();
		manager.setDriverClassName(env.getProperty("jdbc.driver"));
		manager.setUrl(env.getProperty("jdbc.url"));
		manager.setUsername(EncodeUtil.decodeBase64String(env.getProperty("jdbc.user")));
		manager.setPassword(EncodeUtil.decodeBase64String(env.getProperty("jdbc.pass")));
		manager.setConnectionProperties(jpaProperties()); 	
		return manager;
	}
	
	 @Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
		 LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
	     em.setDataSource(dataSource());
	     em.setPackagesToScan(new String[] { "org.codesolt.model" });
	 
	     JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
	     em.setJpaVendorAdapter(vendorAdapter);
	     em.setJpaProperties(jpaProperties());
	     
	     return em;
	}
	 
	@Bean
	public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
		JpaTransactionManager transactionManager = new JpaTransactionManager();
	    transactionManager.setEntityManagerFactory(emf);
	 
	    return transactionManager;
	}
	 
	@Bean
	public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
		return new PersistenceExceptionTranslationPostProcessor();
	}
	   
	@Bean
	public Properties jpaProperties() { 
		Properties jpaProperties = new Properties();
		jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
		jpaProperties.put("hibernate.hbm2ddl.auto", "validate");
		jpaProperties.put("hibernate.show_sql", "false");
		jpaProperties.put("hibernate.format_sql", "true");
		return jpaProperties;
	}
}

Para indagar más en el tema de JPA con Spring 4, puedes consultar nuestro artículo que habla específicamente sobre este tema. 🙂

Acceso por roles dentro del código

Adicionalmente a la seguridad Web y autenticación con tokens revisadas, puedes utilizar validaciones de seguridad dentro de tu código en Java con las anotaciones de Spring Security en orden de garantizar acceso a partir de roles de usuarios a nivel no solo de URIs sino de métodos de Java, de la siguiente forma.

@PreAuthorize("hasRole('ROLE_ADMIN')")
public UserList updateUser(User user) { ... }

Continua con el tutorial

Recuerda que esta serie cuenta con 9 tutoriales, puedes revisar el siguiente sobre la configuraciónde Hibernate Validator para validar los objetos recibidos en los request hacia nuestro servicio REST.

Tutorial Spring REST 4/9 – Configuración de Hibernate Validator con Spring 4

Repositorio en Github

Nota que no todo el código del proyecto fue incluido en este artículo, puedes encontrar el código completo en nuestro repositorio de Github: https://github.com/chuucks/SPRING-REST-API/

Recuerda darnos una estrellita 😉


La configuración de Spring Security y OAUTH2 mostrada en este artículo para un servicio REST, es solo una forma propuesta de hacerlo y tienen fines únicamente ilustrativos para nuestra la comunidad de Codesolt.

Comments

comments