Home > front end >  How can I make my spring security custom roles permission aware?
How can I make my spring security custom roles permission aware?

Time:09-08

I am building a springboot application, and currently trying to implement permission based authorisation. In my securityConfig file, role based authorisation works, but permission based authorisation fails. As seen in the SecurityConfig file below, passing a role in hasAnyRole function in antMatchers works, but passing permissions to hasAnyAuthority does not work, i.e., it does not authorise.

This is my SecurityConfig file

@Override
    protected void configure(HttpSecurity http) throws Exception {
        /*
         * Setting custom login endpoint
         * */
        CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean());
        customAuthenticationFilter.setFilterProcessesUrl("/api/login");
        http.csrf().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.authorizeRequests()
                .antMatchers("/api/login/**", "/api/token/refresh/**").permitAll()
                .antMatchers("/api/users/save/**").permitAll()
                .antMatchers(GET, "/api/users/**").hasAnyAuthority(CAN_ADD_MEMBER_TO_CHAMA.getPermission())
                .antMatchers(GET, "/api/users/**").hasAnyRole(PLATFORM_SUPER_ADMIN.name())
                .anyRequest()
                .authenticated()
                .and();

        http.addFilter(customAuthenticationFilter);
        http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

Roles

public enum ApplicationUserRole {
    PLATFORM_SUPER_ADMIN(
            Sets.newHashSet(
                    CAN_CREATE_CHAMA, CAN_DELETE_CHAMA,
                    CAN_UPDATE_CHAMA, CAN_INVITE_NEW_USER)),
    PLATFORM_USER(Sets.newHashSet(CAN_CREATE_CHAMA)),

    CHAMA_MEMBER(Sets.newHashSet(CAN_MAKE_CONTRIBUTION, CAN_INVITE_NEW_USER));


    private final Set<ApplicationUserPermission> permissions;

    ApplicationUserRole(Set<ApplicationUserPermission> permissions) {
        this.permissions = permissions;
    }

    public Set<ApplicationUserPermission> getPermissions() {
        return permissions;
    }

    public Set<SimpleGrantedAuthority> getGrantedAuthorities() {
        Set<SimpleGrantedAuthority> permissions = getPermissions().stream()
                .map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
                .collect(Collectors.toSet());
        permissions.add(new SimpleGrantedAuthority("ROLE_"   this.name()));
        return permissions;
    }
}

Permissions

package com.chama.chamaservice.config;

public enum ApplicationUserPermission {
    CAN_CREATE_CHAMA("CAN_CREATE_CHAMA"),
    CAN_DELETE_CHAMA("CAN_DELETE_CHAMA"),
    CAN_UPDATE_CHAMA("CAN_UPDATE_CHAMA"),

    private final String permission;

    ApplicationUserPermission(String permission) {
        this.permission = permission;
    }

    public String getPermission() {
        return permission;
    }
}

CustomAuthorizationFilter

public class CustomAuthorizationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (request.getServletPath().equals("/api/login") || request.getServletPath().equals("/api/refresh/token")) {
            filterChain.doFilter(request, response);
        } else {
            // Generate new access token from refresh token
            String authorizationHeader = request.getHeader(AUTHORIZATION);
            if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
                try {
                    String token = authorizationHeader.substring("Bearer ".length());
                    Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
                    JWTVerifier verifier = JWT.require(algorithm).build();
                    DecodedJWT decodedJWT = verifier.verify(token);
                    String phoneNumber = decodedJWT.getSubject();

                    String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
                    Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
                    stream(roles).forEach(role -> {
                        authorities.add(new SimpleGrantedAuthority("ROLE_"   role)); // Populate ROLES
                    });

                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(phoneNumber, null, authorities);
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    filterChain.doFilter(request, response);
                } catch (Exception e) {
                    log.error("Error logging in: {}", e.getMessage());
                    response.setHeader("error", e.getMessage());
                    response.setStatus(FORBIDDEN.value());
                    Map<String, String> error = new HashMap<>();
                    error.put("message", e.getMessage());
                    response.setContentType(APPLICATION_JSON_VALUE);
                    new ObjectMapper().writeValue(response.getOutputStream(), error);
                }
            } else {
                filterChain.doFilter(request, response);
            }
        }
    }
}

CodePudding user response:

In your custom filter you parse "roles" JWT claims only to roles as String, so your SecurityContext doesn't know anything about permissions (authorities).
Instead of this try to utilize your ApplicationUserRole enum .getGrantedAuthorities() method, for example like this:

String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = Arrays.stream(roles)
                .map(ApplicationUserRole::valueOf) // <- parsing every String to ApplicationUserRole
                .map(ApplicationUserRole::getGrantedAuthorities) // <- converting every ApplicationUserRole to a set of GrantedAuthority
                .flatMap(Collection::stream) // <- converting stream of sets to a stream of GrantedAuthority
                .collect(Collectors.toList());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(phoneNumber, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);

If "roles" claim cannot be parsed to an array of ApplicationUserRole, then .valueOf() method will throw IllegalArgumentException, and your catch block will convert it to 403 http status response.

  • Related