Solucionado (ver solução)
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!