首页IT科技springsecurity权限控制的原理(Spring Security 权限控制)

springsecurity权限控制的原理(Spring Security 权限控制)

时间2025-05-04 02:55:02分类IT科技浏览3993
导读:日积月累,水滴石穿 😄 前言 项目 版本 Boot 2.3.12.RELEASE Security 5.3.9.RELEASE...

日积月累             ,水滴石穿 😄

前言

项目 版本 Boot 2.3.12.RELEASE Security 5.3.9.RELEASE

官网文档

在前面的文章中                   ,所有的接口只需要登录就能访问            。并没有对每个接口进行权限限制                    。 在正式的系统中       ,一个用户会拥有一个或者多个角色      ,而不同的角色会拥有不同的接口权限       。如果要实现这些功能                   ,需要重写WebSecurityConfigurerAdapter 中的configure(HttpSecurity http)            。HttpSecurity 用于构建一个安全过滤器链 SecurityFilterChain             ,可以通过它来进行自定义安全访问策略                   。

配置如下:

@Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("cxyxj") .password("123").roles("admin", "user") .and() .withUser("security") .password("security").roles("user"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //开启配置 .antMatchers("/cxyxj/**").hasRole("admin") //访问/cxyxj/**下的路径      ,必须具备admin身份 .antMatchers("/security/**").hasRole("user") //访问/security/**下的路径                   ,必须具备user身份 .antMatchers("/permitAll").permitAll() // 访问/permitAll路径             ,不需要登录 .anyRequest() //其他请求 .authenticated()//验证 表示其他请求只需要登录就能访问 .and() .formLogin(); // 开启表单登陆 } 复制代码

上述配置含义如下:

antMatchers("/cxyxj/**").hasRole("admin"):表示访问/cxyxj/路径的必须要有 admin 角色       。 antMatchers("/security/**").hasRole("user"):表示访问/security/路径的必须要有 user 角色      。 .antMatchers("/permitAll").permitAll():表示访问/permitAll 路径不需要认证 .anyRequest().authenticated():表示除了前面定义的url,其余url访问都得认证后才能访问(登录) and:表示结束当前标签                   ,回到上下文 HttpSecurity                   ,开启新一轮的配置 formLogin:开启表单登陆

根据上述的配置,我们新增三个接口             ,代码如下:

@GetMapping("/hello") public String hello(){ return "你好 Spring Security"; } @GetMapping("/cxyxj/hello") public String cxyxj() { return "cxyxj 你好 Spring Security"; } @GetMapping("/security/hello") public String user() { return "user 你好 Spring Security"; } @GetMapping("/permitAll") public String permitAll() { return "permitAll 你好 Spring Security"; } 复制代码

按照我们的配置                   ,permitAll接口不需要登录就能访问       ,cxyxj接口只有admin角色才能访问             ,security接口admin角色和user角色都能访问                   ,而 hello接口登录就能访问!

这里就不演示了       ,各位可以将上述代码 copy 到本地运行一下                   。

授权方式

授权的方式包括 web授权方法授权      ,web授权是通过 url 拦截进行授权                   ,方法授权是通过方法拦截进行授权             。他们都会使用 AccessDecisionManager接口进行授权决策      。若为web授权则拦截器为 FilterSecurityInterceptor;若为方法授权则拦截器为MethodSecurityInterceptor                   。如果同时使用 web 授权和方法授权             ,则先执行web授权      ,再执行方法授权                   ,最后决策都通过             ,则允许访问资源,否则将禁止访问             。 文章开头使用的方式就是 web授权方式。

FilterSecurityInterceptor:底层是 Filter                   。 MethodSecurityInterceptor:底层是 AOP                    。

web授权

Spring Security 可以通过 http.authorizeRequests() 开启对 web 请求进行授权保护。

http.formLogin(); http.authorizeRequests() .antMatchers("/permitAll").permitAll() // 访问/permitAll路径                   ,不需要认证 .anyRequest() //其他请求 .authenticated(); //需要认证才能访问 复制代码

url匹配

antMatchers()

方法定义如下:

antMatchers(String... antPatterns) {} 复制代码

参数是可变长参数                   ,每个参数是一个 ant 表达式,用于匹配 URL规则            。

ANT通配符有三种:

通配符 说明 ? 匹配任何单字符 * 匹配0个或者任意数量的字符 ** 匹配0个或者任意目录 // 访问 /cxyxj/** 路径             ,任意目录下 .js 文件 可以直接访问 .antMatchers("/cxyxj/**","/**/*.js").permitAll() 复制代码

使用 antMatchers 方法需要注意配置规则的顺序                   ,配置顺序会影响授权的效果       ,越是具体的应该放在前面             ,越是笼统的应该放到后面                    。

如下错误示例:

.antMatchers("/cxyxj/**").hasRole("ADMIN") .antMatchers("/cxyxj/login").permitAll() 复制代码

如上配置会导致访问/cxyxj/login接口时需要拥有ADMIN角色才能访问       。

regexMatchers

使用正则表达式进行匹配            。

//所有以.js 结尾的文件都被放行 .regexMatchers( ".+[.]js").permitAll() 复制代码

无论是 antMatchers() 还是 regexMatchers() 都具有两个参数的方法                   ,其中第一个参数都是 HttpMethod       ,表示请求方式      ,当设置了 HttpMethod 后表示只有设定的请求方式才执行对应的权限验证                   。

.antMatchers(HttpMethod.GET,"/cxyxj/hello").permitAll() .regexMatchers(HttpMethod.GET,".+[.]jpg").permitAll() 复制代码

anyRequest

匹配所有的请求       。该方法一般会放在最后      。结合 authenticated使用                   ,表示所有请求需要认证才能访问                   。

.anyRequest().authenticated() 复制代码

mvcMatchers

适用于配置了 mvcServletPath 的情况             。 mvcServletPath 就是所有的 URL 的统一前缀      。在 application.properties 中添加下面内容                   。

spring.mvc.servlet.path= /role 复制代码 正例 .mvcMatchers("/cxyxj/**").servletPath("/role").permitAll() 复制代码 反例 .mvcMatchers("/role/cxyxj/**").permitAll() 复制代码

如果不想使用 mvcMatchers() 也可以使用 antMatchers()             。

.antMatchers("/role/cxyxj/**").permitAll() 复制代码

如果你在项目中还配置了项目根路径。

server.servlet.context-path=/ctx-path 复制代码

在SecurityConfig中不需要理会             ,你只需要修改你的请求接口路径                   。

配置 .mvcMatchers("/cxyxj/**").servletPath("/role").permitAll() 复制代码 请求接口路径 http://localhost:8080/ctx-path/role/cxyxj/hello 复制代码

调试完成之后请将properties文件的配置进行注释!以下示例代码不使用配置文件                    。

RequestMatcher接口

上述的几种匹配方式都是 RequestMatcher 接口的子实现。 接口定义了matches方法      ,方法如果返回 true 表示提供的请求与提供的匹配规则匹配                   ,如果返回的是 false 则不匹配            。 Spring Security 内置提供了一些 RequestMatcher 实现类:

内置操作

上文只是将请求接口路径与配置的规则进行匹配             ,那匹配成功之后应该进行什么操作呢?Spring Security 内置了一些控制操作                    。

permitAll() 方法,所有用户可访问       。 denyAll() 方法                   ,所有用户不可访问            。 authenticated() 方法                   ,登录用户可访问                   。 anonymous() 方法,匿名用户可访问       。 rememberMe() 方法             ,通过 remember me 登录的用户可访问      。 fullyAuthenticated() 方法                   ,非 remember me 登录的用户可访问                   。 hasIpAddress(String ipaddressExpression) 方法       ,来自指定 IP 表达式的用户可访问             。 hasRole(String role) 方法             , 拥有指定角色的用户可访问                   ,传入的角色将被自动增加 “ROLE_             ” 前缀      。 hasAnyRole(String... roles) 方法       ,拥有指定任意角色的用户可访问                   。传入的角色将被自动增加 “ROLE_                   ” 前缀             。 hasAuthority(String authority) 方法      ,拥有指定权限( authority )的用户可访问。 hasAuthority(String... authorities) 方法                   ,拥有指定任意权限( authority )的用户可访问                   。 access(String attribute) 方法             ,上面所有方法的底层实现      ,当 Spring EL 表达式的执行结果为 true 时                   ,可以访问                    。 使用用法如下: // 如果用户具备 admin 权限             ,就允许访问。 .antMatchers("/cxyxj/**").hasAuthority("admin") // 如果用户具备给定权限中某一个,就允许访问            。 .antMatchers("/admin/demo").hasAnyAuthority("admin","System") // 如果用户具备 user 权限                   ,就允许访问                    。注意不需要手动写 ROLE_ 前缀                   ,写了会报错 .antMatchers("/security/**").hasRole("user") //如果请求是指定的 IP 就允许访问       。 .antMatchers("/admin/demo").hasIpAddress("192.168.64.5") 复制代码

这里单独介绍一下 access(表达式)            。表达式的基类是SecurityExpressionRoot,提供了一些在web和方法安全性中都可用的通用表达式             ,如下:

表达式 描述 hasRole(String role) 当前用户是否拥有指定角色                   。拥有则可以访问       。 hasAnyRole(String... roles) 当前用户是否拥有指定角色中的任意一个                   ,拥有则可以访问      。 hasAuthority(String authority) 拥有指定权限( authority )的用户可访问                   。 hasAnyAuthority(String... authorities) 拥有指定任一权限( authority )的用户可访问             。 getPrincipal 获得当前用户       ,可能是一个用户名             ,也可能是一个用户对象      。 getAuthentication 获取当前Authentication对象(认证对象)                   。 permitAll 总是返回true                   ,表示允许所有用户访问             。 denyAll 总是返回false       ,表示拒绝所有用户访问。 isAnonymous() 是否是一个匿名用户      ,如果是则允许访问                   。 isRememberMe() 用户是否是通过Remember-Me自动登录                   ,如果是则允许访问                    。 isAuthenticated() 用户是否登录认证成功             ,如果是则允许访问。 isFullyAuthenticated() 如果当前用户不是一个匿名用户      ,又不是通过Remember-Me自动登录的                   ,则允许访问            。(手动输入帐户信息认证)                    。

可以使用 access() 实现相同的功能       。

.antMatchers("/cxyxj/**").access("hasAuthority(admin)") .antMatchers("/permitAll").access("isAnonymous") 复制代码

自定义方法(重要)

虽然内置了很多的表达式             ,但是在实际项目中,用的很少                   ,而且很有可能不能满足需求            。所以需要自定义逻辑的情况                   。比如判断当前登录用户是否具有访问当前 URL 的权限!

import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.HashSet; import java.util.Set; @Component public class MyAccess { // 只需要登录就能访问的接口地址 private static final Set<String> URL = new HashSet<>(); // 需要区分角色的接口地址 用户名称:接口地址 private static final HashMap<String,Set<String>> URL_MAP = new HashMap(); static { URL.add("/hello"); Set<String> cxyxjSet = new HashSet<>(); cxyxjSet.add("/cxyxj/hello"); cxyxjSet.add("/security/hello"); URL_MAP.put("cxyxj",cxyxjSet); Set<String> securitySet = new HashSet<>(); securitySet.add("/security/hello"); URL_MAP.put("security",securitySet); } public boolean hasPermit(HttpServletRequest req, Authentication auth){ Object principal = auth.getPrincipal(); String servletPath = req.getRequestURI(); AntPathMatcher matcher = new AntPathMatcher(); // 有一些接口是不需要权限                   ,只要登录就能访问的,比如一些省市区接口 boolean result = URL.stream().anyMatch(url -> matcher.match(url, servletPath)); if(result){ return true; } //这里使用的是定义在内存的用户信息 if(principal instanceof User){ User user = (User) principal; // 可以根据用户id或者用户名从redis中获得用户拥有的菜单权限url String username = user.getUsername(); Set<String> urlSet = URL_MAP.get(username); return urlSet.stream().anyMatch(u -> matcher.match(u, servletPath)); } return false; } } 复制代码 配置 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //开启配置 .antMatchers("/permitAll").permitAll() // 访问/permitAll路径             ,不需要登录 .anyRequest().access("@myAccess.hasPermit(request,authentication)") //.anyRequest() //其他请求 //.authenticated()//验证 表示其他请求只需要登录就能访问 .and() .formLogin(); } 复制代码

使用这种方式                   ,authenticated()       ,就不需要加了       。access表达式的写法为 @符号 + Bean名称.方法(参数...)

增加方法 @GetMapping("/denyAll") public String denyAll() { return "denyAll 你好 Spring Security"; } 复制代码 测试 登录 cxyxj用户:可以访问/cxyxj/hello             、/security/hello                   、hello       、permitAll接口      。不能访问 denyAll接口                   。

登录 security用户:可以访问/security/hello      、hello                   、permitAll接口             。不能访问 denyAll             、/cxyxj/hello接口      。

方法授权

Spring Security在方法的权限控制上支持三种类型的注解             ,JSR-250注解      、@Secured注解                   、支持表达式注解                   。这三种注解默认都是没有启用的                   ,需要使用@EnableGlobalMethodSecurity来进行启用             。

1             、JSR250E

在 @EnableGlobalMethodSecurity 设置 jsr250Enabled 为 true       ,就开启了以下三个安全注解:

@RolesAllowed:表示访问对应方法时应该具备所指定的角色。示例:@RolesAllowed({"user", "admin"})      ,表示该方法只要具有"user", "admin"任意一种权限就可以访问                   。可以省略前缀ROLE_不写                    。该注解可以标注在类上                   ,也可以标注在方法上             ,当标注在类上时表示类中所有方法的执行都需要对应的角色      ,当标注在方法上表示执行该方法时所需要的角色                   ,当方法和类上都标注了@RolesAllowed             ,则方法上的@RolesAllowed将覆盖类上的@RolesAllowed。

@PermitAll 表示允许所有的角色进行访问            。@PermitAll可以标注在方法上也可以标注在类上,当标注在方法上时则只对对应方法不进行权限控制                   ,而标注在类上时表示对类里面所有的方法都不进行权限控制                    。

(1)当 @PermitAll 标注在类上                   ,而 @RolesAllowed 标注在方法上时,@RolesAllowed 将覆盖 @PermitAll             ,即需要 @RolesAllowed 对应的角色才能访问       。 (2)当 @RolesAllowed 标注在类上                   ,而 @PermitAll 标注在方法上时则对应的方法不进行权限控制            。 (3)当在类和方法上同时使用了@PermitAll 和 @RolesAllowed 时       ,先定义的将发生作用                   。

@DenyAll 表示什么角色都不能访问       。@DenyAll可以标注在方法上也可以标注在类上             ,当标注在方法上时则只对对应方法进行权限控制                   ,而标注在类上时表示对类里面所有的方法都进行权限控制      。

提供测试接口

@GetMapping("/hello") public String hello(){ return "你好 Spring Security"; } @GetMapping("/cxyxj/hello") @RolesAllowed({"admin"}) public String cxyxj() { return "cxyxj 你好 Spring Security"; } @GetMapping("/security/hello") @RolesAllowed({"user"}) public String user() { return "user 你好 Spring Security"; } @GetMapping("/denyAll") @DenyAll public String denyAll() { return "denyAll 你好 Spring Security"; } @GetMapping("/permitAll") @PermitAll public String permitAll() { return "permitAll 你好 Spring Security"; } 复制代码

SecurityConfig

@Configuration @EnableGlobalMethodSecurity(jsr250Enabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("cxyxj") .password("123").roles("admin", "user") .and() .withUser("security") .password("security").roles("user"); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin(); } } 复制代码

hello接口登录就能访问                   。/cxyxj/hello接口需要有 admin角色才能访问       ,/security/hello接口需要有 user角色才能访问,denyAll接口不允许访问      ,permitAll 接口所有人都能访问             。

各位是不是发现 configure(HttpSecurity http)方法中只配置了 http.formLogin()      。我为什么没有配置其他的呢?这是因为当配置 .authorizeRequests().anyRequest().authenticated()之后                   ,所有方法需要认证之后才能访问                   。该配置与 @PermitAll注解发生了冲突             。

开篇提到过:若为web授权则拦截器为 FilterSecurityInterceptor;若为方法授权则拦截器为MethodSecurityInterceptor。如果同时使用web 授权和方法授权             ,则先执行web授权      ,再执行方法授权                   ,最后决策通过             ,则允许访问资源,否则将禁止访问                   。 当配置.authorizeRequests().anyRequest().authenticated()之后                   ,相当于同时使用 web 授权和方法授权                   ,然后被 web 授权拦截了,所以 @PermitAll 并没有生效                    。当然实际项目中             , @PermitAll一般不会使用!

2、secured

在 @EnableGlobalMethodSecurity 设置 securedEnabled 为 true                    ,就开启了以下注解:

@Secured 是由 Spring Security 定义的       ,用来支持方法权限控制的注解。@Secured 专门用于判断是否具有指定角色的             ,可以写在方法或类上            。参数需要手动指定 "ROLE_" 前缀                    。

SecurityConfig

@EnableGlobalMethodSecurity(jsr250Enabled = true,securedEnabled = true) 复制代码

提供测试接口

@GetMapping("/secured") @Secured({"admin"}) public String secured() { return "secured 你好 Spring Security"; } 复制代码

3                   、表达式

Spring Security 中定义了四个支持使用表达式的注解                   ,分别是 @PreAuthorize                   、@PostAuthorize、 @PreFilter 和 @PostFilter       。其中前两者可以用来在方法调用前或者调用后进行权限检查       ,后两者可以用来对集合类型的参数或者返回值进行过滤            。 接下来演示一下使用方式      ,首先我们先来开启注解                   ,在 @EnableGlobalMethodSecurity 设置 prePostEnabled 为 true                    。

SecurityConfig

@EnableGlobalMethodSecurity(jsr250Enabled = true,securedEnabled = true,prePostEnabled = true) 复制代码

@PreAuthorize

最被常用的注解为@PreAuthorize       。在方法调用前进行权限检查             ,结果为 true 则可以执行!可以在类或者方法上进行标注      。注解参数与 access方法参数一致      ,也就是说可以使用内置方法和Spring-EL表达式                   。

// 只有角色为 admin 或者 user才能访问 @PreAuthorize("hasRole(admin) or hasRole(user)") @GetMapping("/preAuthorize") public String preAuthorize(){ return "PreAuthorize 你好 Spring Security"; } //Id大于1才能查询 // 按名称访问任何方法参数作为表达式变量 @PreAuthorize("#id>1") @GetMapping("/findById/{id}") public Integer findById(@PathVariable("id") Integer id) { return id; } // 限制只能查询自己的信息 // principal 值通常是 UserDetails 实例 @PreAuthorize("principal.username.equals(#username)") @GetMapping("/findByName/{username}") public String findByName(@PathVariable("username") String username) { return username; } //限制只能新增用户名称为abc的用户 @PreAuthorize("#username.equals(abc)") @GetMapping("/add/{username}") public String add(@PathVariable("username") String username) { return username; } 复制代码

@PostAuthorize

这个注解使用的很少                   ,当然可能某些需求需要使用             。比如需要校验该方法的返回值             ,这可以使用 @PostAuthorize 注解来实现      。要访问方法的返回值,请使用内置表达式 returnObject                   。

@PostAuthorize("returnObject.id%2==0") @GetMapping("/find/{id}") public SysUser find(@PathVariable("id") Integer id) { SysUser sysUser = new SysUser(); sysUser.setId(id); User principal = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); sysUser.setUsername(principal.getUsername()); return sysUser; } 复制代码

在方法调用完成后进行权限检查                   ,如果返回值的id是偶数则表示校验通过                   ,否则表示校验失败,将 抛出AccessDeniedException             。@PostAuthorize是在方法调用完成后进行权限检查             ,它不能控制方法是否能被调用                   ,只能在方法调用完成后       ,检查权限然后决定是否要抛出AccessDeniedException异常。

@PreFilter

对集合             、数组类型的请求参数进行过滤             ,移除结果为false的元素                   。该过程发生在接口接收参数之前,可以使用内置表达式 filterObjec 进行数据过滤                   ,filterObjec 表示集合中的当前对象                    。 如果有多个集合参数需要通过 filterTarget=<参数名> 来指定过滤的集合。

// id 大于 1 的才进行查询 @PreFilter("filterObject > 1") @PostMapping("/find") public List<Integer> batchGetInfo(@RequestParam("ids") ArrayList<Integer> ids) { return ids; } 复制代码

多个集合参数需要通过 filterTarget=<参数名> 来指定            。如下:

// id 大于 1 的才进行查询 @PreFilter(filterTarget = "ids",value = "filterObject > 1") @PostMapping("/batchGetInfo") public List<Integer> batchGetInfo(@RequestParam("ids") ArrayList<Integer> ids,@RequestParam("ids2")ArrayList<Integer> ids2) { return ids; } 复制代码

@PostFilter

@PostFilter可以对集合                   、数组类型的返回值进行过滤!

// 将结果集为偶数的值进行返回 @PostFilter("filterObject % 2 == 0") @GetMapping("/findAll") public List<Integer> findAll() { List<Integer> userList = new ArrayList<>(); for (int i=0; i<10; i++) { userList.add(i); } return userList; }

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
python merge inner(python merge()的连接) npm切换淘宝镜像指令(NPM 如何换源?)