【问题标题】:How check user role and get authenticated username in Spring Security JWT authentication如何在 Spring Security JWT 身份验证中检查用户角色并获取经过身份验证的用户名
【发布时间】:2020-10-20 15:30:20
【问题描述】:

我正在使用 JWT 身份验证实现 Spring Security。我不确定如何检查用户角色并在方法级别获取经过身份验证的用户。我在网上看到了这个例子:

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

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

看起来如果使用会话类型,此代码用于获取用户,我得到 NPE。你知道对于 JWT 来说我可以如何访问用户吗?

Github 完整源码:https://github.com/rcbandit111/OAuth2/blob/master/src/main/java/org/engine/rest/DataPagesController.java

【问题讨论】:

  • 搜索 Spring 手册。要检查 User 凭据,我想说 80% 的案例可以通过:@Secured, @PreAuthorize @PostAuthorize @PostFilter @PreFilter 和一些 El 表达式的组合来处理,例如:@PreAutorize("hasAnyRole('ADMIN', 'MODO') and hasPermission(...)")。另外,请查看SecurityExpressionRoot 类。

标签: spring spring-boot spring-security jwt


【解决方案1】:

你试过了吗:

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

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      有许多方法可以使用注释以及基于端点的安全配置来设计对 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 &amp; @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 攻击、内容嗅探。

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

      您可以在configure(HttpSecurity http) 中使用hasRole()、hasAnyRole()、hasAuthority()、hasAnyAuthority() 方法。请注意,对于 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());
          }
      

      更新

      @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)); 
              }
          }
      

      在安全配置中,我们可以使用configure(HttpSecurity http) 如下,它将调用isPermitted() 方法。

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

      或者使用如下注解:

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

      您可以从herefrom this blog 找到更多详细信息

      【讨论】:

      • 你能建议吗?
      • 在“java.lang.String”类型的对象上找不到属性或字段“用户名” - 可能不是公共的或无效的? 您尚未指定用户名在方法中输入字符串。您可能需要添加它。
      • 如果只是检查用户的权限,则不需要使用"#username == authentication.principal.username"。可以通过@PreAuthorize("hasRole('ROLE_ADMIN')"进行管理
      • 我需要获取用户名
      • 您是否只想为具有特定用户名的特定用户调用保存方法,否则保存操作不应该执行,对吗?
      【解决方案4】:

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

          @Override
          protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
      
              final String token = request.getHeader("YOUR_HEADER");
      
              DecodedJWT jwt = JWT.decode(token);
      
              // TODO create principal
      
          }
      

      【讨论】:

        【解决方案5】:

        我主要在我的 Web 应用程序中同时使用 JWT 身份验证和 Spring Security。这是我的常见做法:

        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();
        
        

        【讨论】:

          猜你喜欢
          • 2010-11-13
          • 2015-08-09
          • 1970-01-01
          • 2013-04-23
          • 2020-09-21
          • 1970-01-01
          • 2014-01-12
          • 2022-01-15
          • 1970-01-01
          相关资源
          最近更新 更多