根据登录用户角色不同做出不同的响应。

huangapple go评论70阅读模式
英文:

Different response based on logged user role

问题

在我的RestController中,我有一个POST方法,根据用户角色返回不同的对象:

@PostMapping("getlocal")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public ResponseEntity<LocalDto> getLocal(@RequestBody LocalRequest localRequest) {
    return status(OK).body(localService.findLocalBylocalId(localRequest));
}

Service方法:

public LocalDto findLocalBylocalId(LocalRequest localRequest) {
    Role role = userRoleRepository.findByUsername(localRequest.getUsername());
    if (role == Role.ROLE_ADMIN) //为ADMIN返回localDto信息对象
    else if (role == Role.ROLE_USER) //为USER返回localDto信息对象
}

LocalRequest包含当前登录用户的用户名

问题在于,当用户经过身份验证并访问此端点时,他可以将管理员的用户名传递到RequestBody。在这种情况下,即使作为USER登录,他将能够访问管理员资源

如何避免这种情况并修改代码?

  1. 我应该创建两个不同的端点 - 一个仅对USER进行安全保护,另一个仅对ADMIN进行安全保护吗?
  2. 在POST方法中传递Principal对象,而不是传递用户名作为参数,是否可以?如果我正在使用JWT机制,我可以传递Principal对象吗?
英文:

In my RestController I have POST method which returns different object based on user role:

@PostMapping(&quot;getlocal&quot;)
@PreAuthorize(&quot;hasRole(&#39;ROLE_ADMIN&#39;) or hasRole(&#39;ROLE_USER&#39;)&quot;)
    public ResponseEntity&lt;LocalDto&gt; getLocal(@RequestBody LocalRequest localRequest){
        return status(OK).body(localService.findLocalBylocalId(localRequest));
    }

Service method:

public LocalDto findLocalBylocalId(LocalRequest localRequest) {
    Role role = userRoleRepository.findByUsername(localRequest.getUsername());
    if(role == Role.ROLE_ADMIN) //return localDto infromation object for ADMIN
        else if(role == Role.ROLE_USER) //return localDto information for USER       
}

LocalRequest contains username of current logged in user.

The problem is when user will authenticate and hit this endpoint he can pass to RequestBody admin's username. In this case he will get access to admin resources even if he is logged as USER.

How to avoid this situation and modify the code?

  1. Should I create two different endpoints - one secured only for USER, and one secured only for ADMIN?
  2. Should I pass Principal object instead passing username in POST method as parameter? Can I pass Princpal object if I am using jwt mechanism?

答案1

得分: 1

你可以通过将 Principal 指定为参数来访问当前经过身份验证的用户名。例如:

@PostMapping("getlocal")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public ResponseEntity<LocalDto> getLocal(Principal principal){
    return status(OK).body(localService.findLocalBylocalId(principal.getName()));
}

这是因为 Spring MVC 将会从 HttpServletRequest.getUserPrincipal() 方法中解析出 Principal 参数,而 Spring Security 会覆盖 该方法以与当前登录的用户保持一致。然后你需要更新你的 LocalDto,使其接受用户名而不是 Localrequest

另外,你也可以通过相同的方式解析整个 Spring Security 认证对象,因为 HttpServletRequest.getUserPrincipal() 将会返回一个 Authentication 对象。

@PostMapping("getlocal")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
public ResponseEntity<LocalDto> getLocal(Authentication authentication){
    return status(OK).body(localService.findLocalBylocalId(principal.getName()));
}

这样可以直接访问角色,而无需再次查找它们。这种方法的劣势是你现在直接依赖于 Spring Security 的 API。

你还可以考虑使用 @AuthenticationPrincipal 注解,以减少与 Spring Security 的耦合。如果你不仅需要访问用户名,这种方法是最好的,因为你仍然可以与 Spring Security 解耦,但这也需要更多的工作(例如,对于用户名/密码认证,你需要自定义的 UserDetailsService、自定义的 UserDetails)。由于涉及的工作/变量很多,在没有进一步细节的情况下,很难提供比链接到文档更多的指导。

1: http://@PostMapping("getlocal") @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')") public ResponseEntity getLocal(@RequestBody LocalRequest localRequest){ return status(OK).body(localService.findLocalBylocalId(localRequest)); }
2: https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getUserPrincipal()
3: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servletapi-user-principal
4: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-authentication-authentication
5: https://docs.spring.io/spring-security/site/docs/current/reference/html5/#mvc-authentication-principal

英文:

You can access the currently authenticated username by specifying Principal as an argument. For example:

@PostMapping(&quot;getlocal&quot;)
@PreAuthorize(&quot;hasRole(&#39;ROLE_ADMIN&#39;) or hasRole(&#39;ROLE_USER&#39;)&quot;)
public ResponseEntity&lt;LocalDto&gt; getLocal(Principal principal){
    return status(OK).body(localService.findLocalBylocalId(principal.getName()));
}

This works because Spring MVC will resolve the Principal argument from the HttpServletRequest.getUserPrincipal() method and Spring Security overrides the method to align with the currently logged in user. You would then need to update your LocalDto to accept a username instead of a Localrequest.

Alternatively, you can also resolve the entire Spring Security Authentication in the same way since the HttpServletRequest.getUserPrincipal() will be an Authentication.

@PostMapping(&quot;getlocal&quot;)
@PreAuthorize(&quot;hasRole(&#39;ROLE_ADMIN&#39;) or hasRole(&#39;ROLE_USER&#39;)&quot;)
public ResponseEntity&lt;LocalDto&gt; getLocal(Authentication authentication){
    return status(OK).body(localService.findLocalBylocalId(principal.getName()));
}

This gives you access to the roles without needing to look them up again. The disadvantage of this approach is that you are now relying on Spring Security's API directly.

You can also consider using the @AuthenticationPrincipal annotation to decouple yourself from Spring Security. This approach is the best if you need access to more than just the username because you can still be decoupled from Spring Security, but it also involves more work (i.e for username/password authentication you need a custom UserDetailsService, custom UserDetails). Because the amount of work/variables here, it is difficult to provide more guidance than a link to the documentation without further details.

huangapple
  • 本文由 发表于 2020年10月16日 03:14:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/64378312.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定