Introdução ao Spring Boot Security

A proposta deste tutorial é demonstrar os passos para implantar em um sistema Web escrito utilizando o Spring Boot, algumas funcionalidades que a biblioteca do Spring Security trás para a aplicação.

O primeiro passo importante é alterar o arquivo do nosso projeto pom.xml para na seção dependencies, incluir a biblioteca do spring-boot-starter-security.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

httpBasic

O Basic Access Autentication (httpBasic) é um método de autenticação previsto no protocolo HTTP cujo funcionamento é bastante simples, e normalmente é um dos primeiros níveis de segurança implantado em qualquer aplicação (apesar de não ser suficiente para considerar a aplicação como totalmente segura). Seu funcionamento é bastante simples:

  • Toda vez que um cliente solicita ao servidor qualquer recurso, e no cabeçalho da requisição HTTP não for enviada as credenciais para autenticação do acesso, receberá um retorno do tipo 401 Unauthorized com uma propriedade no cabeçalho da resposta chamado WWW-Authenticate com o valor Basic
  • Com essa resposta, o cliente sabe que deve enviar novamente uma requisição, para o mesmo endereço, mas colocando no cabeçalho da requisição a propriedade Autorization: Basic e o login:senha do usuário do cliente encriptado no formato Base64
  • O retorno pode ser do tipo 200 OK, dando acesso ao recurso solicitado ou o retorno será do tipo 401

O simples fato de adicionar a biblioteca spring-boot-starter-security a nossa aplicação já habilita o httpBasic e também a autenticação por formulário. Observe o log da aplicação, durante o processo de carregamento a seguinte linha será apresentada no log:

Using generated security password: 461eb78b-0394-4544-ae94-1edef7f6f88f

Portanto ao tentar acessar a aplicação com http://localhost:8080 seremos redirecionados para um formulário http://localhost:8080/login onde podemos inserir o usuário: user e a senha: 461eb78b-0394-4544-ae94-1edef7f6f88f

IMPORTANTE: o usuário user será recriado toda vez que a aplicação for reiniciada, portanto sua senha será modificada.

Esse método de autenticação serve apenas como um testes inicial da biblioteca Spring Boot Security, agora vamos definir um usuário e senha padrão para nossa aplicação. Altere o conteúdo do arquivo application.properties para inserir em seu final as seguintes linhas:

spring.security.user.name=bob
spring.security.user.password=bob
spring.security.user.roles=ADMIN

Com esta alteração, ao acessar a aplicação podemos informar no formulário o usuário bob e a senha bob e teremos acesso a aplicação. Essa solução funciona bem para pequenas aplicações onde não é necessário um grande número de usuários. Agora vamos construir uma classe que será responsável por todo o processo de autenticação dos usuários da aplicação. Você antes de iniciar o próximo processo, deve colocar em comentário as três linhas que foram adicionadas no arquivo application.properties utilizando o símbolo #.

Clique bom o botão direito sobre o pacote br.com.faltoupontoevirgula.projetospring e selecione a opção New -> Class. Crie uma nova classe com o nome: WebSecurityConfig

Agora que a classe foi criada, vamos alterar seu código, primeiro faça com que a classe WebSecurityConfig extenda a classe WebSecurityConfigureAdapter conforme o exemplo abaixo, não esqueça de teclar control + shift + o para corrigir os imports do projeto.

package br.com.faltoupontoevirgula.projetospring;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

}

O próximo passo é sobrescrever dois métodos desta classe abstrata, ambos tem o mesmo nome configure, mas com assinaturas diferentes, para isso faça os seguintes passos:

  • Com o curso dentro do código fonte da classe
  • Clique com o botão direito e selecione no menu suspenso a opção Source -> Override/Implement Methods

Na próxima tela, selecione o método configure que recebe por parâmetro os objetos AuthenticationManagerBuilder e HttpSecurity, confirme no botão OK.

Para que esta nova classe seja considerada pelo Spring Boot, precisamos anotá-la com o @Configuration e o @EnableWebSecurity. O resultado deverá ser um código similar ao seguinte:

package br.com.faltoupontoevirgula.projetospring;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
super.configure(auth);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
super.configure(http);
}

}

Em seguida a primeira alteração que iremos realizar nesta classe para sobrescrever o código do método configure que recebe por parâmetro o objeto AuthenticationManagerBuilder é criar em memória dois usuários. Cada usuário deverá ter uma regra (ROLE) atribuído a ele. Por padrão as duas regras são: USER e ADMIN.

package br.com.faltoupontoevirgula.projetospring;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("user").roles("USER")
.and()
.withUser("admin").password("admin").roles("ADMIN");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
super.configure(http);
}

}

Agora podemos configurar os comportamentos padrões do processo de autenticação. Vamos modificar o código do método configure que recebe por parâmetro o objeto HttpSecurity, conforme exemplo abaixo:

  • Da instância do objeto HttpSecurity que esta no parâmetro http, são chamados os seguintes métodos;
  • authorizeRequests() – defini a configuração de como será a autenticação das requisições;
  • anyRequest().fullyAuthenticated() – definimos que qualquer requisição deverá passar pelo processo de autenticação
  • and() – utilizamos esse método para adicionar mais regras ao nosso processo de autenticação
  • httpBasic() – habilita o método de autenticação HttpBasic
  • .formLogin().permitAll() – definimos que o processo de login deverá ser feito por meio de um formulário.
  • por fim criamos um método chamado passwordEncoder() que deverá retornar o objeto responsável por codificado a senha do usuário em memória.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("user").roles("USER")
.and()
.withUser("admin").password("admin").roles("ADMIN");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}

Importante verificar o objetivo de cada método na documentação do Spring: https://docs.spring.io/spring-security/site/docs/4.2.12.RELEASE/apidocs/org/springframework/security/config/annotation/web/builders/HttpSecurity.html

Salve as alterações e tente acessar a aplicação, utilize um dos usuários para realizar o login.

O próximo passo será adicionar o botão sair no menu do topo da aplicação, para isso vamos alterar o arquivo layout.html, para incluir um novo botão no canto do menu que deverá realizar um POST para a página /logout.

<nav th:fragment="menu" class="navbar navbar-expand-lg navbar-dark bg-dark">
            <a class="navbar-brand" href="#">Sistema</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarTogglerDemo02">
                <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
                  <li class="nav-item active">
                    <a class="nav-link" href="/">Home</a>
                  </li>
                  <li class="nav-item active">
                    <a class="nav-link" href="/cliente">Cliente</a>
                  </li>
                </ul>
                <form th:action="@{/logout}" method="post">
                    <input type="submit" value="Sair" class="btn"/>
                </form>
            </div>
        </nav>

O próximo passo é customizar a tela de login, para isso precisamos primeiro adicionar um arquivo de configuração em nosso pacote principal da aplicação br.com.faltoupontoevirgula.projetospring, incluindo uma classe com o nome MvcConfig. Essa classe irá implementar a interface WebMvcConfigurer, e receber a anotação @Configuration por esse motivo será executada automaticamente pelo framework Spring Boot, quando a aplicação for carregada. O método addViewControllers precisa ser implementado onde a rota /login será criada apontando para o arquivo login.html que deverá ser colocado dentro da pasta src/main/resources/templates

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}

}

O seguinte arquivo login.html deve ser adicionado dentro da pasta /src/main/resources/templates.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head th:include="layout :: htmlhead"></head>
    <body>
        <div class="container">
            <div id="login">
                <div id="login-row"
                    class="row justify-content-center align-items-center">
                    <div id="login-column" class="col-md-6">
                        <div id="login-box" class="col-md-12">
                            <form id="login-form" th:action="@{/login}" method="post">
                                <h3 class="text-center text-info">Login</h3>
                                <div class="form-group">
                                    <label for="username" class="text-info">Username:</label><br>
                                    <input type="text" name="username" id="username"
                                        class="form-control">
                                </div>
                                <div class="form-group">
                                    <label for="password" class="text-info">Password:</label><br>
                                    <input type="password" name="password" id="password"
                                        class="form-control">
                                </div>
                                <div class="form-group">
                                    <input type="submit" name="submit" class="btn btn-info btn-md"
                                        value="submit">
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div th:include="layout :: footer" id="footer">(footer)</div>
    </body>
</html>

Para finalizar vamos configurar nosso processo de login através da modificação do código da classe WebSecurityConfig incluindo o código no método configure (HttpSecurity http) o seguinte código:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
		.withUser("user").password("user").roles("USER")
		.and()
		.withUser("admin").password("admin").roles("ADMIN");
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http
		.csrf().disable()
		.authorizeRequests()
			.anyRequest().authenticated()
		.and()
			.formLogin()
            .loginPage("/login")
            .permitAll()
        .and()
	        .logout()
	        .logoutUrl("/logout")
	        .logoutSuccessUrl("/login")
	        .invalidateHttpSession(true)
	        .permitAll();
	}
	@Override
	public void configure(WebSecurity web) throws Exception {
		String[] resources = new String[]{
	            "/webjars/**","/include/**",
	            "/css/**","/icons/**","/image/**","/js/**","/layer/**","/api/**"
	    };
		web.ignoring().antMatchers(resources);
		web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
	}
	@Bean
	public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
	    StrictHttpFirewall firewall = new StrictHttpFirewall();
	    firewall.setAllowUrlEncodedSlash(true);
	    firewall.setAllowSemicolon(true);
	    return firewall;
	}
	@Bean
	public PasswordEncoder passwordEncoder() {
		return NoOpPasswordEncoder.getInstance();
	}

}

Autenticação baseada em entidades do banco de dados

O próximo passo é alterar nosso código para permitir que o usuário e a senha estejam armazenadas no banco de dados, utilizando uma classe de entidade para representar os usuários do sistema. No pacote model, vamos criar uma nova classe chamada Usuario. Observe que esta classe possui os atributos utilizados no código da classe WebSecurityConfig para representar um usuário.

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Usuario {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String username;
private String password;
private String role;

public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

Em seguida precisamos criar um repositório para esse novo model, esse repositório será responsável por realizar a busca do usuário no banco de dados e recuperar todos os seus dados. Para isso dentro do pacote br.com.faltoupontoevirgula.projetospring.repository crie uma interface com o nome UsuarioRepository. Nesta interface vamos criar a assinatura do método chamado findByUsername(String username) que será implementado de forma automática pelo Spring Data para fazer a busca pelo nome do usuário.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import br.univille.dsi2019.model.Usuario;

@Repository
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
	Usuario findByUsername(String username);
}

Agora precisamos criar um serviço que será utilizado pela classe de autenticação do Spring Secutiry. Como o nome já diz, um serviço é uma classe que realiza um trabalho muito específico dentro da aplicação, por isso vamos separar o código em um novo pacote. Para isso clique com o botão direito sobre o pacote br.com.faltoupontoevirgula.projetospring e selecione a opção new -> package:

Na próxima caixa de diálogo, no campo name digite br.com.faltoupontoevirgula.projetospring.service e confirme no botão finish.

Dentro deste novo pacote de service, vamos criar uma nova classe chamada MyUserDetailService, essa classe recebe a anotação @Service e irá possuir o método loadUserByUsername. Esse método será responsável por recuperar do banco de dados os dados da classe Usuario e repassar os atributos para uma instância da classe User do pacote org.springframework.security.core.userdetails.User, que será gerenciada pelo Spring Security.

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import br.univille.dsi2019.model.Usuario;
import br.univille.dsi2019.repository.UsuarioRepository;

@Service
public class MyUserDetailsService implements UserDetailsService {
 
    @Autowired
    private UsuarioRepository usuarioRepository;
 
    @Override
    public User loadUserByUsername(String username) {
        Usuario user = usuarioRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        Collection<SimpleGrantedAuthority> listGrants = new ArrayList<>();
        listGrants.add(new SimpleGrantedAuthority(user.getRole()));
        return new User(user.getUsername(),user.getPassword(),listGrants);
    }
}

Por fim devemos realizar três alterações no código da classe WebSecurityConfig:

  • Criamos um atributo com o nome userDetailsService, e com a anotação @Autowired, será injetado automaticamente a instância do nosso serviço
  • Criamos o método authenticationProvider que será responsável por retornar a instância da classe DaoAuthenticationProvider para configurar o Spring Security para utilizar o acesso ao banco de dados para buscar o usuário e a senha
  • Por fim modificamos o código do método configure(AuthenticationManagerBuilder auth), para não mais criar o usuário de forma fixa no código mas sim utilizar a instância da classe DaoAuthenticationProvider como provedor de autenticação
package br.univille.dsi2019;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;

import br.univille.dsi2019.service.impl.MyUserDetailsService;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	private MyUserDetailsService userDetailsService;

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		/*auth.inMemoryAuthentication()
		.withUser("user").password("user").roles("USER")
		.and()
		.withUser("admin").password("admin").roles("ADMIN");*/
		auth.authenticationProvider(authenticationProvider());
	}

	@Bean
	public DaoAuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider authProvider
		= new DaoAuthenticationProvider();
		authProvider.setUserDetailsService(userDetailsService);
		authProvider.setPasswordEncoder(passwordEncoder());
		return authProvider;
	}
	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http
		.csrf().disable()
		.authorizeRequests()
		.anyRequest().authenticated()
		.and()
		.formLogin()
		.loginPage("/login")
		.permitAll()
		.and()
		.logout()
		.logoutUrl("/logout")
		.logoutSuccessUrl("/login")
		.invalidateHttpSession(true)
		.permitAll();
	}
	@Override
	public void configure(WebSecurity web) throws Exception {
		String[] resources = new String[]{
				"/webjars/**","/include/**",
				"/css/**","/icons/**","/image/**","/js/**","/layer/**","/api/**"
		};
		web.ignoring().antMatchers(resources);
		web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
	}
	@Bean
	public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
		StrictHttpFirewall firewall = new StrictHttpFirewall();
		firewall.setAllowUrlEncodedSlash(true);
		firewall.setAllowSemicolon(true);
		return firewall;
	}
	@Bean
	public PasswordEncoder passwordEncoder() {
		return NoOpPasswordEncoder.getInstance();
	}

}

Para concluir, vamos criar um EventListener que ficará responsável por sempre criar um registro na tabela usuário, quando o sistema for inciado pela primeira vez. Para isso no pacote br.com.faltoupontoevirgula.projetospring crie o seguinte código da nova classe StartupEventListenerBean:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import br.univille.dsi2019.model.Usuario;
import br.univille.dsi2019.repository.UsuarioRepository;

@Component
public class StartupEventListenerBean {
	@Autowired
	private UsuarioRepository usuarioRepository;

	@EventListener
	public void onApplicationEvent(ContextRefreshedEvent event) {
		if(usuarioRepository.findByUsername("user") == null) {
			Usuario user = new Usuario();
			user.setUsername("user");
			user.setPassword("user");
			user.setRole("ROLE_USER");
			usuarioRepository.save(user);
		}

	}
}

Permissões de acesso por controlador ou método

Agora que temos nosso mecanismo de autenticação funcionando, podemos utiliza-lo para definir quais funcionalidades do sistema cada usuário poderá acessar. Para simplificar o modelo vamos utilizar as ROLES como base para definir quais páginas cada usuário irá poder acessar.

O primeiro passo é modificar o código da nossa classeWebSecurityConfig, no início do código existe uma anotação chamada @EnableGlobalMethodSecutiry que já estava com a propriedade securedEnabled = true, vamos adicionar a nova propriedade chamada prePostEnable=true.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	private MyUserDetailsService userDetailsService;

Com isso feito, a anotação @PreAuthorize pode ser colocada sobre cada classe controller definindo quais ROLES um usuário precisa ter para ter acesso a uma determinada funcionalidade. Por exemplo, vamos configurar nosso controlador paciente para que apenas usuários com a ROLE ADMIN possam acessar essa funcionalidade. Para isso basta alterar a classe PacienteController adicionando a anotação @PreAuthorize(“hasRole(‘ROLE_ADMIN’)”).

@Controller
@RequestMapping("/paciente")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public class PacienteController {

@Autowired
private PacienteRepository pacienteRepository;

Após realizar esta modificação e o servidor ser recarregado, ao efetuar o login no sistema com um usuário que não possua a ROLE ADMIN e tentar acessar a funcionalidade de Pacientes, vamos receber a seguinte tela de erro, portanto o usuário não terá acesso a esta funcionalidade do sistema caso não tenha permissão para isso.

Podemos construir regras mais elaboradas, por exemplo considerar que o usuário possua uma de duas permissões:

@Controller
@RequestMapping("/paciente")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public class PacienteController {

@Autowired
private PacienteRepository pacienteRepository;

Para maiores detalhes de como construir esses regras acesse o seguinte site.

Para concluir precisamos customizar a página de erro apresentada pelo Spring, para uma versão mais amigável para o usuário final. Para isso crie dentro da pasta templates um novo arquivo chamado error.html com o seguinte conteúdo:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<head th:include="layout :: htmlhead"></head>
	<body>
		<div class="container">
			<nav th:replace="layout :: menu">(menu)</nav>
			<h1 th:text="${status}"></h1>
			<h2 th:text="${message}"></h2>
			<a href="/">Home</a>
			<nav th:replace="layout :: menubottom">(menubottom)</nav>
		</div>
		<div th:include="layout :: footer" id="footer">(footer)</div>
	</body>
</html>

Agora dentro do pacote de controladores da aplicação vamos criar uma nova classe com o nome MyErrorController, essa classe deverá possuir o código similar a este a seguir com o objetivo de direcionar todos os erros para essa página customizada.

import java.util.HashMap;

import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MyErrorController implements ErrorController  {
 
    @RequestMapping("/error")
    public ModelAndView handleError(HttpServletRequest request) {
    	Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
    	HashMap<String, Object> data = new HashMap<String, Object>();
    	data.put("status", statusCode);
    	switch (statusCode) {
		    case 403:
		    	data.put("message", "Desculpe você não tem permissão!");
			
			break;
		}
        return new ModelAndView("error",data);
    }
 
    @Override
    public String getErrorPath() {
        return "/error";
    }
}

Agora ao tentar acessar a página da aplicação que você não tem permissão, receberá a seguinte mensagem de erro: