如何在Spring Security JWT身份验证中检查用户角色

时间:2020-06-30 10:39:11

标签: spring spring-boot spring-security jwt jwt-auth

我正在使用JWT身份验证进行Spring Security实现。我不确定如何在方法级别检查用户角色。我在互联网上看到了这个示例:

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

我是否需要从JWT令牌中提取用户类型?是否有其他方法可以实现此目的?在我看来,仅使用@Secured({"ROLE_ADMIN"})并不完整。

5 个答案:

答案 0 :(得分:1)

有很多方法可以使用注释以及基于端点的安全配置来设计基于权限的 API 访问。

注释:

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

为了使用注解,您需要启用如下安全配置

@Configuration
@EnableGlobalMethodSecurity(
  prePostEnabled = true, 
  securedEnabled = true, 
  jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
  • prePostEnabled 属性启用 Spring Security pre/post 注释
  • securedEnabled 属性确定是否应启用 @Secured 注释
  • jsr250Enabled 属性允许我们使用 @RoleAllowed 注释

@Secured & @RoleAllowed

具有给定角色的用户能够执行该方法。 @RoleAllowed 注释是 JSR-250 中 @Secured 注释的等效注释。

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

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

@PreAuthorize & @PostAuthorize

@PreAuthorize 批注在进入方法之前检查给定的表达式,而 @PostAuthorize 批注在方法执行后对其进行验证并可能改变结果。

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

@PreAuthorize & @PostAuthorize@Secured 的主要区别在于 @Secured 不支持 SpEL(Spring 表达式语言)。要查看更多差异,您可以阅读更多详细信息here

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

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

这里,只有当参数 username 的值与当前主体的用户名相同时,用户才能调用 methodX。您可以检查其他可能的 SpEL(Spring 表达式语言) 自定义 here

您可以从here

获得更多详细信息

使用 configure(HttpSecurity http)configure(WebSecurity web) 方法。

@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) 此方法中使用的端点会忽略 spring 安全过滤器,也忽略安全功能(安全标头、csrf 保护等),并且不会设置安全上下文并且无法保护端点以进行跨站点脚本、XSS 攻击、内容嗅探。< /p>

  • configure(HttpSecurity http) 此方法中使用的端点会忽略 antMatchers 中使用的端点的身份验证,其他安全功能将生效,例如安全标头、CSRF 保护等。

您可以将 hasRole()、hasAnyRole()、hasAuthority()、hasAnyAuthority() 方法与 configure(HttpSecurity http) 一起使用。请注意,对于 hasRole() 和 hasAnyRole() 方法,您不需要使用 ROLE_ 前缀,而对于其他两个方法,您必须使用 ROLE_

要了解区别和用法,您可以获取详细信息here


您还可以按如下方式创建 utils 方法,这可能会有所帮助。

   /**
     * 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());
    }

答案 1 :(得分:1)

我主要在我的 Web 应用程序中同时使用 JWT 身份验证和 spring 安全性。以下是我的常见做法:

  1. 验证 JWT 令牌(或从您的令牌商店查询)
 private Claims getClaimsFromToken(String token, String key) throws ServletException {
        return Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(token)
                .getBody();
  1. 获取要进行身份验证的用户及其拥有令牌的权限(或在您的情况下的角色)。

   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. 创建org.springframework.security.authentication.AbstractAuthenticationToken 与您的用户及其权限联系并注入 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. 使用具有所需权限的 @PreAuthorize 用户可以访问。
@PreAuthorize("hasAnyAuthority('READ')")

  1. 如果需要,从 SecurityContextHolder 获取用户。
User User= SecurityContextHolder.getContext().getAuthentication().getUser();

答案 2 :(得分:0)

您尝试过吗:

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

编辑: 要检查用户是否被分配了多个角色,请使用:

@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_MANAGER')")

答案 3 :(得分:0)

link解释了有关JWT身份验证的所有内容。 在下面,您可以看到一些示例可以用作修改代码的基础:

@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.";
  }
}

答案 4 :(得分:0)

您可以实现自己的 AbstractPreAuthenticatedProcessingFilter 并自己创建您的 principal

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {

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

        DecodedJWT jwt = JWT.decode(token);

        // TODO create principal

    }