Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

Solucionado
(ver solução)
15
respostas

[Duvida] Segregação de acesso na API

Oi, implmentei na API uma tabela de roles, mas estou com problemas ao implementar este controle.

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http.csrf(csrf -> csrf.disable())
            .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(req -> {
                req.requestMatchers(HttpMethod.POST, "/login").permitAll();
                req.requestMatchers(HttpMethod.GET,"/teste").hasRole("ADMIN");
                req.anyRequest().authenticated();
            })
            .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
}

Tentei validar somente a Role ADMIN para este endpoint e não esta funcionando, todas as outras roles tem o acesso. E se coloco alguma role especifica no login o acesso é negado para todos.

trecho da classe usuario onde eu adicionei as roles

@Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userId;
    @Column(name="user_name")
    private String login;
    private String password;
    private boolean enabled;
    
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(
            name = "users_roles",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id")
            )
    private List<Role> roles = new ArrayList<>();
    
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
         
        roles.forEach(role -> authorities.add(new SimpleGrantedAuthority(role.getName())));
        authorities.forEach(a -> System.out.println("authorities "+a.getAuthority()));
        return authorities;
    
        //return List.of(new SimpleGrantedAuthority("ROLE_USER"));
    }

Preciso realizar alguma configuração diferente para restrigir acesso para alguns endpoints mesmo estando logado com o token?

15 respostas

Oi!

Seu método getAuthorities deveria apenas devolver o atributo roles:

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return roles;
}

Considerando que a sua classe Role está configurada certinha.

oi tentei colocar somente o

return roles;

mas a IDE gerou um erro pedindo o Cast ae ficou

return (Collection<? extends GrantedAuthority>) roles;

e gerou o erro abaixo

java.lang.ClassCastException: class br.com.capture.model.Role cannot be cast to class org.springframework.security.core.GrantedAuthority (br.com.capture.model.Role is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @dde1842; org.springframework.security.core.GrantedAuthority is in unnamed module of loader 'app') at org.springframework.security.authentication.AbstractAuthenticationToken.(AbstractAuthenticationToken.java:58) ~[spring-security-core-6.1.4.jar:6.1.4] at org.springframework.security.authentication.UsernamePasswordAuthenticationToken.(UsernamePasswordAuthenticationToken.java:69) ~[spring-security-core-6.1.4.jar:6.1.4] at org.springframework.security.authentication.UsernamePasswordAuthenticationToken.authenticated(UsernamePasswordAuthenticationToken.java:99) ~[spring-security-core-6.1.4.jar:6.1.4] at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.createSuccessAuthentication(AbstractUserDetailsAuthenticationProvider.java:196) ~[spring-security-core-6.1.4.jar:6.1.4] at org.springframework.security.authentication.dao.DaoAuthenticationProvider.createSuccessAuthentication(DaoAuthenticationProvider.java:132) ~[spring-security-core-6.1.4.jar:6.1.4] at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:168) ~[spring-security-core-6.1.4.jar:6.1.4] at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-6.1.4.jar:6.1.4] at br.com.capture.controller.AutenticacaoController.efetuaLogin(AutenticacaoController.java:33) ~[classes/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-6.0.12.jar:6.0.12] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-6.0.12.jar:6.0.12] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.0.12.jar:6.0.12] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[spring-webmvc-6.0.12.jar:6.0.12] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-6.0.12.jar:6.0.12] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.0.12.jar:6.0.12] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[spring-webmvc-6.0.12.jar:6.0.12] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.12.jar:6.0.12] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.12.jar:6.0.12] at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.0.12.jar:6.0.12] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[tomcat-embed-core-10.1.13.jar:6.0] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.0.12.jar:6.0.12] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.13.jar:6.0]

@Marcos Vieira, tenta dessa forma que vai dar certo.

  @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(
            name = "users_roles",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : getRoles()) {
            authorities.add(new SimpleGrantedAuthority(role.getName().trim()));
        }
        return authorities;
    }

A classe Role deve herdar de GrantedAuthority, assim como a classe Usuario precisou herdar de UserDetails.

Exemplo:

@Entity
public class Role extends GrantedAuthority {

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String nome;
    
    @Override
    public String getAuthority() {
        return this.nome;
    }
    
    public Long getId() {
        return this.id;
    }

}

Oi Joelson, obrigado pelo retorno, mas desta forma ainda continua gerando o erro inicial que postei no forum, consigo fazer o login normalmente, mas não consigo segregar o acesso de acordo com a Role de cada usuario, desta forma todas as roles estão passando mesmo eu fixando no codigo desta maneira

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.csrf(csr -> csr.disable())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> {
                                                    authorize.requestMatchers(HttpMethod.POST,"/login").permitAll();
                                                    authorize.requestMatchers(HttpMethod.GET,"/teste").hasRole("ADMIN");
                                                    authorize.anyRequest().authenticated();})
                /*.authorizeHttpRequests((authz) -> authz
                           .requestMatchers(HttpMethod.POST,"/login").permitAll()
                            //.anyRequest().authenticated()
                       
                    )*/
                .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
       	} 
    

A duvida seria se é possivel segregar acesso a determinado endpoint com as roles. caso queira ver o projeto todo, este é o link do github https://github.com/marcosMatias/Capture

oi Rodrigo, tentei fazer como você postou mas não aparece na IDE a opção GrantedAuthority para estender a classe, somente estas aqui

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Opa, erro meu. Na verdade não é extends e sim implements, pois é uma interface do Spring Security.

Realizei as implementações abaixo

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
        @JoinTable(
                name = "users_roles",
                joinColumns = @JoinColumn(name = "user_id"),
                inverseJoinColumns = @JoinColumn(name = "role_id")
        )
        private Set<Role> roles = new HashSet<>();

        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {

            /*List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for (Role role : getRoles()) {
                authorities.add(new SimpleGrantedAuthority(role.getName().trim()));
            }
            return authorities;*/
        	return roles;
        }
@Entity
@Table(name = "roles")
@Getter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of="roleId")
public class Role implements GrantedAuthority {
    
    
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long roleId;    
    private String name;
    
    private static final long serialVersionUID = 1L;
    
    @Override
    public String getAuthority() {
        
        return this.name;
    }
    
@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.csrf(csr -> csr.disable())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> {
                                                    authorize.requestMatchers(HttpMethod.POST,"/login").permitAll();
                                                    authorize.requestMatchers(HttpMethod.GET,"/teste").hasRole("ADMIN");
                                                    authorize.anyRequest().authenticated();})
                /*.authorizeHttpRequests((authz) -> authz
                           .requestMatchers(HttpMethod.POST,"/login").permitAll()
                            //.anyRequest().authenticated()
                       
                    )*/
                .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
       	} 

Porém ao logar com um usuario que não tem a role ADMIN, e tento acessar o endpoint /teste ele deixa acessar, fica liberado para todas as Roles. O correto não seria somente quem tem a Role ADMIN poder acessar este endpoint?

Você também precisa criar a tabela de roles no banco de dados e preencher.

create table roles(
    role_id bigint not null auto_increment,
    name varchar(100) not null,
    
    primary key(role_id)
);

insert into roles(role_id, name) values(1, 'ROLE_ADMIN');
insert into roles(role_id, name) values(2, 'ROLE_USER');

E precisa popular a tabela de join, que no seu caso é users_roles com os ids dos usuarios e seus respectivos roles.

Exemplo:

insert into users_roles(user_id, role_id) values(1, 1);
insert into users_roles(user_id, role_id) values(2, 2);

No exemplo anterior, o usuário de id 1 vai ter a role ADMIN e o usuário de id 2 terá a role USER.

As tabelas ja estavam criadas no banco com as roles selecionadas para cada usuario, a unica diferença foi que no nome da role não havia salvo com o prefixo 'ROLE_' antes, vou tentar ajustar isso no banco e ver se vai dar certo. Pois vi em uma documentação que a partir da versão 6 do spring não seria necessário passar o prefixo "ROLE_'" ele ja entenderia isso.

Rodrigo, realizei a alteração no nome das Roles no banco , mas não surtiu efeito. Tentei também no endponit /teste adicionar a anotação @PreAuthorize("hasRole('ROLE_ADMIN')") e desta outra forma @PreAuthorize("hasRole('ADMIN')") mas ele ainda deixa passar todas as roles para este metodo. Não estou conseguindo fazer essa segregação de acesso com as roles.

Consegue compartilhar seu projeto?

Oi Rodrigo, sim , segue o link do github

https://github.com/marcosMatias/Capture

solução!

O problema é que você configurou a url errada:

authorize.requestMatchers(HttpMethod.GET, "/teste").hasRole("ADMIN");

Está configurando /teste, mas no seu controller a url completa é: /helios/v1/teste

oi Rodrigo, não havia me atentado a isso, realizei um teste agora com a URL completa e o problema foi resolvido. Obrigado!