SpringBoot之SpringSecurity权限注解在方法上进行权限认证多种方式

kenx
• 阅读 1889

前言

Spring Security支持方法级别的权限控制。在此机制上,我们可以在任意层的任意方法上加入权限注解,加入注解的方法将自动被Spring Security保护起来,仅仅允许特定的用户访问,从而还到权限控制的目的, 当然如果现有的权限注解不满足我们也可以自定义

快速开始

  1. 首先加入security依赖如下
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.接着新建安全配置类

Spring Security默认是禁用注解的,要想开启注解,要在继承WebSecurityConfigurerAdapter的类加@EnableMethodSecurity注解,并在该类中将AuthenticationManager定义为Bean。

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(
  prePostEnabled = true, 
  securedEnabled = true, 
  jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

我们看到@EnableGlobalMethodSecurity 分别有prePostEnabled 、securedEnabled、jsr250Enabled三个字段,其中每个字段代码一种注解支持,默认为false,true为开启。那么我们就一一来说一下这三总注解支持。

prePostEnabled = true 的作用的是启用Spring Security的@PreAuthorize 以及@PostAuthorize 注解。

securedEnabled = true 的作用是启用Spring Security的@Secured 注解。

jsr250Enabled = true 的作用是启用@RoleAllowed 注解

更详细使用整合请参考我这两篇 轻松上手SpringBoot+SpringSecurity+JWT实RESTfulAPI权限控制实战

Spring Security核心接口用户权限获取,鉴权流程执行原理

在方法上设置权限认证

JSR-250注解

遵守了JSR-250标准注解 主要注解

  1. @DenyAll
  2. @RolesAllowed
  3. @PermitAll

这里面@DenyAll@PermitAll 相信就不用多说了 代表拒绝和通过。

@RolesAllowed 使用示例

@RolesAllowed("ROLE_VIEWER")
public String getUsername2() {
    //...
}

@RolesAllowed({ "USER", "ADMIN" })
public boolean isValidUsername2(String username) {
    //...
}

代表标注的方法只要具有USER, ADMIN任意一种权限就可以访问。这里可以省略前缀ROLE_,实际的权限可能是ROLE_ADMIN

在功能及使用方法上与 @Secured 完全相同

securedEnabled注解

主要注解

@Secured

  1. Spring Security的@Secured注解。注解规定了访问访方法的角色列表,在列表中最少指定一种角色

  2. @Secured在方法上指定安全性,要求 角色/权限等 只有对应 角色/权限 的用户才可以调用这些方法。 如果有人试图调用一个方法,但是不拥有所需的 角色/权限,那会将会拒绝访问将引发异常。

比如:

@Secured("ROLE_VIEWER")
public String getUsername() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return securityContext.getAuthentication().getName();
}

@Secured({ "ROLE_DBA", "ROLE_ADMIN" })
public String getUsername2() {
    //...
}

@Secured("ROLE_VIEWER") 表示只有拥有ROLE_VIEWER角色的用户,才能够访问getUsername()方法。

@Secured({ "ROLE_DBA", "ROLE_ADMIN" }) 表示用户拥有"ROLE_DBA", "ROLE_ADMIN" 两个角色中的任意一个角色,均可访问 getUsername2 方法。

还有一点就是@Secured,不支持Spring EL表达式

prePostEnabled注解

这个开启后支持Spring EL表达式 算是蛮厉害的。如果没有访问方法的权限,会抛出AccessDeniedException。

主要注解

  1. @PreAuthorize --适合进入方法之前验证授权

  2. @PostAuthorize --检查授权方法之后才被执行并且可以影响执行方法的返回值

  3. @PostFilter --在方法执行之后执行,而且这里可以调用方法的返回值,然后对返回值进行过滤或处理或修改并返回

  4. @PreFilter --在方法执行之前执行,而且这里可以调用方法的参数,然后对参数值进行过滤或处理或修改

@PreAuthorize注解使用

@PreAuthorize("hasRole('ROLE_VIEWER')")
public String getUsernameInUpperCase() {
    return getUsername().toUpperCase();
}

@PreAuthorize("hasRole('ROLE_VIEWER')") 相当于@Secured(“ROLE_VIEWER”)。

同样的@Secured({“ROLE_VIEWER”,”ROLE_EDITOR”}) 也可以替换为:@PreAuthorize(“hasRole(‘ROLE_VIEWER') or hasRole(‘ROLE_EDITOR')”)

除此以外,我们还可以在方法的参数上使用表达式:

在方法执行之前执行,这里可以调用方法的参数,也可以得到参数值,这里利用JAVA8的参数名反射特性,如果没有JAVA8,那么也可以利用Spring Secuirty的@P标注参数,或利用Spring Data的@Param标注参数。

//无java8
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority(‘ADMIN’)")
void changePassword(@P("userId") long userId ){}
//有java8
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority(‘ADMIN’)")
void changePassword(long userId ){}

这里表示在changePassword方法执行之前,判断方法参数userId的值是否等于principal中保存的当前用户的userId,或者当前用户是否具有ROLE_ADMIN权限,两种符合其一,就可以访问该 方法。

@PostAuthorize注解使用

在方法执行之后执行可,以获取到方法的返回值,并且可以根据该方法来决定最终的授权结果(是允许访问还是不允许访问):

@PostAuthorize
  ("returnObject.username == authentication.principal.nickName")
public CustomUser loadUserDetail(String username) {
    return userRoleRepository.loadUserByUserName(username);
}

上述代码中,仅当loadUserDetail方法的返回值中的username与当前登录用户的username相同时才被允许访问

注意如果EL为false,那么该方法也已经执行完了,可能会回滚。EL变量returnObject表示返回的对象。

@PreFilter以及@PostFilter注解使用

Spring Security提供了一个@PreFilter 注解来对传入的参数进行过滤:

@PreFilter("filterObject != authentication.principal.username")
public String joinUsernames(List<String> usernames) {
    return usernames.stream().collect(Collectors.joining(";"));
}

当usernames中的子项与当前登录用户的用户名不同时,则保留;当usernames中的子项与当前登录用户的用户名相同时,则移除。比如当前使用用户的用户名为zhangsan,此时usernames的值为{"zhangsan", "lisi", "wangwu"},则经@PreFilter过滤后,实际传入的usernames的值为{"lisi", "wangwu"}

如果执行方法中包含有多个类型为Collection的参数,filterObject 就不太清楚是对哪个Collection参数进行过滤了。此时,便需要加入 filterTarget 属性来指定具体的参数名称:

@PreFilter
  (value = "filterObject != authentication.principal.username",
  filterTarget = "usernames")
public String joinUsernamesAndRoles(
  List<String> usernames, List<String> roles) {

    return usernames.stream().collect(Collectors.joining(";")) 
      + ":" + roles.stream().collect(Collectors.joining(";"));
}

同样的我们还可以使用@PostFilter注解来过返回的Collection进行过滤:

@PostFilter("filterObject != authentication.principal.username")
public List<String> getAllUsernamesExceptCurrent() {
    return userRoleRepository.getAllUsernames();
}

此时 filterObject 代表返回值。如果按照上述代码则实现了:移除掉返回值中与当前登录用户的用户名相同的子项。

自定义元注解

如果我们需要在多个方法中使用相同的安全注解,则可以通过创建元注解的方式来提升项目的可维护性。

比如创建以下元注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ROLE_VIEWER')")
public @interface IsViewer {
}

然后可以直接将该注解添加到对应的方法上:

@IsViewer
public String getUsername4() {
    //...
}

在生产项目中,由于元注解分离了业务逻辑与安全框架,所以使用元注解是一个非常不错的选择。

类上使用安全注解

如果一个类中的所有的方法我们全部都是应用的同一个安全注解,那么此时则应该把安全注解提升到类的级别上:

@Service
@PreAuthorize("hasRole('ROLE_ADMIN')")
public class SystemService {

    public String getSystemYear(){
        //...
    }

    public String getSystemDate(){
        //...
    }
}

上述代码实现了:访问getSystemYear 以及getSystemDate 方法均需要ROLE_ADMIN权限。

方法上应用多个安全注解

在一个安全注解无法满足我们的需求时,还可以应用多个安全注解:

@PreAuthorize("#username == authentication.principal.username")
@PostAuthorize("returnObject.username == authentication.principal.nickName")
public CustomUser securedLoadUserDetail(String username) {
    return userRoleRepository.loadUserByUserName(username);
}

此时Spring Security将在执行方法前执行@PreAuthorize的安全策略,在执行方法后执行@PostAuthorize的安全策略。

总结

在此结合我们的使用经验,给出以下两点提示:

  1. 默认情况下,在方法中使用安全注解是由Spring AOP代理实现的,这意味着:如果我们在方法1中去调用同类中的使用安全注解的方法2,则方法2上的安全注解将失效。

  2. Spring Security上下文是线程绑定的,这意味着:安全上下文将不会传递给子线程。

public boolean isValidUsername4(String username) {
    // 以下的方法将会跳过安全认证
    this.getUsername();
    return true;
}
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Spring Security 5 集成 Authing OIDC 单点登录指南
本文以Spring生态中用于提供认证及访问权限控制的SpringSecurity5为例,详细介绍SpringSecurity5如何接入AuthingOIDC。SpringSecurity是一个提供安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了SpringIoC
Stella981 Stella981
3年前
Spring Security(3):配置与自动配置的介绍及源码分析
基于注解的配置(JavaConfiguration)从SpringSecurity3.2开始就已经支持,本篇基于Springboot注解的配置进行讲解,如果需要基于XML配置(SecurityNamespaceConfiguration),可查阅SpringSecurity官网:https://docs.spring.io/springse
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
3年前
Spring注解大全,汇总版
Spring使用的注解大全和解释注解解释@Controller组合注解(组合了@Component注解),应用在MVC层(控制层),DispatcherServlet会自动扫描注解了此注解的类,然后将web请求映射到注解了@RequestMapping的方法上。@Service组合注解(组合了@Component注解),应用在
Stella981 Stella981
3年前
Shiro权限相关配置文件
Shiro权限框架开发系统中,少不了权限,目前java里的权限框架有SpringSecurity和Shiro(以前叫做jsecurity),对于SpringSecurity:功能太过强大以至于功能比较分散,使用起来也比较复杂,跟Spring结合的比较好。对于初学SpringSecurity者来说,曲线还是较大,需要深入学习其源码和框架,配置起
Stella981 Stella981
3年前
Spring Boot使用aop进行权限控制
项目中需要用到权限控制,简单的方案记录下。自定义一个注解@CheckPermission。/检查权限@authoryanyimin/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Do
Wesley13 Wesley13
3年前
@Secured()、 @PreAuthorize() 、 @RolesAllowed()
在Springsecurity的使用中,为了对方法进行权限控制,通常采用的三个注解,就是@Secured()、@PreAuthorize()、@RolesAllowed()。示例,修改用户密码必须是ADMIN权限,可以用三种方法:@Secured({"ROLE_ADMIN"})publicvoidchangePassword(