0

I'm working on Spring Security implementation with JWT authentication. I'm not sure how to check the user role and get authenticated user at method level. I saw this example on Internet:

@PostMapping("{id}")
@Secured({"ROLE_ADMIN"})
public ResponseEntity<?> save(Authentication authentication, Principal principal, @PathVariable Integer id, @RequestBody UserNewDTO dto) {
    ........
}

Do I need to extract the user type from the JWT token is there is other way to implement this? Using only @Secured({"ROLE_ADMIN"}) looks to me uncomplete.

Looks like this code is used to get user if session type is used, I get NPE. Do you know for JWT how I can the the user?

Github Full source: https://github.com/rcbandit111/OAuth2/blob/master/src/main/java/org/engine/rest/DataPagesController.java

Peter Penzov
  • 1,126
  • 134
  • 430
  • 808
  • Search into the Spring manual. To check an `User` credentials, I would say 80% of the cases can be handled by : `@Secured, @PreAuthorize @PostAuthorize @PostFilter @PreFilter` and some mix of the `El` expression like : `@PreAutorize("hasAnyRole('ADMIN', 'MODO') and hasPermission(...)")`. Also, have a look into the `SecurityExpressionRoot` class. – Zorglube Jun 30 '20 at 11:28

5 Answers5

1

This link explains everything about JWT authentication. Below you can see some examples could be used as base to adapt your code:

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/test")
public class TestController {
  @GetMapping("/all")
  public String allAccess() {
    return "Public Content.";
  }

  @GetMapping("/user")
  @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
  public String userAccess() {
    return "User Content.";
  }

  @GetMapping("/mod")
  @PreAuthorize("hasRole('MODERATOR')")
  public String moderatorAccess() {
    return "Moderator Board.";
  }

  @GetMapping("/admin")
  @PreAuthorize("hasRole('ADMIN')")
  public String adminAccess() {
    return "Admin Board.";
  }
}
doctore
  • 3,855
  • 2
  • 29
  • 45
1

There are many ways to design authority-based access to the APIs using annotations as well as security configurations based on the endpoints.

Annotations:

  • @Secured
  • @PreAuthorize
  • @PostAuthorize
  • @RolesAllowed
  • @PreFilter
  • @PostFilter

In order to use the annotations you need to enable the security configurations as follow

@Configuration
@EnableGlobalMethodSecurity(
  prePostEnabled = true, 
  securedEnabled = true, 
  jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
  • The prePostEnabled property enables Spring Security pre/post annotations
  • The securedEnabled property determines if the @Secured annotation should be enabled
  • The jsr250Enabled property allows us to use the @RoleAllowed annotation

@Secured & @RoleAllowed

Users who have the given role are able to execute the method. The @RoleAllowed annotation is the JSR-250’s equivalent annotation of the @Secured annotation.

@Secured({ "ROLE_ADMIN", "ROLE_SUPERADMIN" })
public ResponseEntity<?> save(...) {
    ...
}

@RolesAllowed({ "ROLE_ADMIN", "ROLE_SUPERADMIN" })
public ResponseEntity<?> save(...) {
    ...
}

@PreAuthorize & @PostAuthorize

The @PreAuthorize annotation checks the given expression before entering the method, whereas, the @PostAuthorize annotation verifies it after the execution of the method and could alter the result.

@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public ResponseEntity<?> save(...) {
    ...
}

The major difference between @PreAuthorize & @PostAuthorize and @Secured is that @Secured does not support the SpEL (Spring Expression Language). To check more difference you may read more details here

@PreAuthorize("#username == authentication.principal.username")
public String methodX(String username) {
    //...
}

@PostAuthorize("#username == authentication.principal.username")
public String methodY(String username) {
    //...
}

Here, a user can invoke the methodX only if the value of the argument username is the same as the current principal's username. You can check the other possible SpEL (Spring Expression Language) customization here

You can get the more details from the here


Using configure(HttpSecurity http) and configure(WebSecurity web) method.

@EnableWebSecurity
@EnableGlobalMethodSecurity(  
   prePostEnabled = true, 
   securedEnabled = true, 
   jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) {
        web
            .ignoring()
            .antMatchers("/app/**/*.{js,html}")
            .antMatchers("/i18n/**")
            .antMatchers("/content/**")
            .antMatchers("/swagger-ui/**")
            .antMatchers("/test/**");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .csrf()
            .disable()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .authorizeRequests()
            .antMatchers("/api/public/**").permitAll()
            .antMatchers("/api/**").hasAuthority(AuthoritiesConstants.USER)
            .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN);
        // @formatter:on
    }
  
}
  • configure(WebSecurity web) Endpoint used in this method ignores the spring security filters, security features (secure headers, csrf protection etc) are also ignored and no security context will be set and can not protect endpoints for Cross-Site Scripting, XSS attacks, content-sniffing.

  • configure(HttpSecurity http) Endpoint used in this method ignores the authentication for endpoints used in antMatchers and other security features will be in effect such as secure headers, CSRF protection, etc.

You can use the hasRole(), hasAnyRole(), hasAuthority(), hasAnyAuthority() methods with the configure(HttpSecurity http). Note that with the hasRole(), hasAnyRole() method you don't need to use the ROLE_ prefix while with other two you have to use the ROLE_

To get the difference and usage you may get the details here


You can also create the utils method as follows which might be helpful.

   /**
     * Get the login of the current user.
     *
     * @return the login of the current user.
     */
    public static Optional<String> getCurrentUserLogin() {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        return Optional.ofNullable(securityContext.getAuthentication())
            .map(authentication -> {
                if (authentication.getPrincipal() instanceof UserDetails) {
                    UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
                    return springSecurityUser.getUsername();
                } else if (authentication.getPrincipal() instanceof String) {
                    return (String) authentication.getPrincipal();
                }
                return null;
            });
    }

    /**
     * Check if a user is authenticated.
     *
     * @return true if the user is authenticated, false otherwise.
     */
    public static boolean isAuthenticated() {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        return Optional.ofNullable(securityContext.getAuthentication())
            .map(authentication -> {
                List<GrantedAuthority> authorities = new ArrayList<>();
                    authorities.addAll(authentication.getAuthorities());
                return authorities.stream()
                    .noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS));
            })
            .orElse(false);
    }

    /**
     * If the current user has a specific authority (security role).
     * <p>
     * The name of this method comes from the {@code isUserInRole()} method in the Servlet API.
     *
     * @param authority the authority to check.
     * @return true if the current user has the authority, false otherwise.
     */
    public static boolean isCurrentUserInRole(String authority) {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        return Optional.ofNullable(securityContext.getAuthentication())
            .map(authentication -> {
                List<GrantedAuthority> authorities = new ArrayList<>();
                    authorities.addAll(authentication.getAuthorities());
                return authorities.stream()
                    .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority));
            })
            .orElse(false);
    }

    public static Optional<Authentication> getAuthenticatedCurrentUser() {
        log.debug("Request to get authentication for current user");
        SecurityContext securityContext = SecurityContextHolder.getContext();
        return Optional.ofNullable(securityContext.getAuthentication());
    }

UPDATE

@Component("userVerifier")
    public class UserVerifier {
         public boolean isPermitted(Authentication authentication) {
            String PERMITTED_USERNAME = Arrays.asList("abc", "xyz");
            return PERMITTED_USERNAME.stream.anyMatch(username -> authentication.getName().equals(username)); 
        }
    }

In security configurations we can use configure(HttpSecurity http) as follow which will invoke the isPermitted() method.

  http
     .authorizeRequests()
     .antMatchers("/your-endpoint/{id}")
     .access("@userVerifier.isPermitted(authentication)")
     ...

OR using the annotation as follows:

@PreAuthorize("@userVerifier.isPermitted(authentication)")
@PostMapping("{id}")
public ResponseEntity<?> save(Authentication authentication, Principal principal, @PathVariable Integer id, @RequestBody UserNewDTO dto) {
    ........
}

You may find more details from here and from this blog

Romil Patel
  • 12,879
  • 7
  • 47
  • 76
  • Can you advise? – Peter Penzov Jul 02 '21 at 12:50
  • *Property or field 'username' cannot be found on object of type 'java.lang.String' - maybe not public or not valid?* You have not specified the username of type String in the method. You may need to add it. – Romil Patel Jul 02 '21 at 13:38
  • if the requirement is just to check the authority of the user then you don't need to use the `"#username == authentication.principal.username"`. It can be managed by the `@PreAuthorize("hasRole('ROLE_ADMIN')"` – Romil Patel Jul 02 '21 at 13:50
  • I need to get the username – Peter Penzov Jul 02 '21 at 14:06
  • Do you want to invoke the save method only for specific users with certain usernames else the save operation should not perform, am I correct? – Romil Patel Jul 02 '21 at 14:39
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/234452/discussion-between-patel-romil-and-peter-penzov). – Romil Patel Jul 02 '21 at 16:34
1

I am mostly using JWT authentication and spring security together in my web applications. Here is my common practice shortly:

  1. Verify JWT token(or query from your token store)
 private Claims getClaimsFromToken(String token, String key) throws ServletException {
        return Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(token)
                .getBody();
  1. Fetch user to be authenticated and its authorities(or role in your case) who owns the token.

   User user = getUserFromToken(token);
   List<GrantedAuthority> authorities = getGrantedAuthorities(user);

public List<GrantedAuthority> getGrantedAuthorities(User user) {
        List<GrantedAuthority> result = new ArrayList<>();

        for (String privilegeName : user.getAuthorities()){ // e.g. ["READ", "WRITE"]
            result.add(new SimpleGrantedAuthority(privilegeName));
        }
        return result; 
}

  1. Create org.springframework.security.authentication.AbstractAuthenticationToken with your user and its authorities and inject into SecurityContextHolder.

AuthenticationFilter.java:

JWTAuthenticationToken jwtAuthenticationToken = new JWTAuthenticationToken(user,
                    authorities);

JWTAuthenticationToken.java

public class JWTAuthenticationToken extends AbstractAuthenticationToken {
    
    private User user;

    public JWTAuthenticationToken(User user, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.user = user;

    }

  1. Use @PreAuthorize with required authority for user can access.
@PreAuthorize("hasAnyAuthority('READ')")

  1. Get user from SecurityContextHolder if necesseray.
User User= SecurityContextHolder.getContext().getAuthentication().getUser();

fukit0
  • 924
  • 1
  • 11
  • 35
0

Have you tried:

@PreAuthorize ("hasRole('ROLE_ADMIN')")

Edit: To check if the user is assigned to more than one role use:

@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')")
Vertigo
  • 107
  • 7
0

You can implement your own AbstractPreAuthenticatedProcessingFilter and create your principal yourself.

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {

        final String token = request.getHeader("YOUR_HEADER");

        DecodedJWT jwt = JWT.decode(token);

        // TODO create principal

    }
Derrops
  • 7,651
  • 5
  • 30
  • 60