spring security 在controller层 方法级别使用注解 @PreAuthorize("hasRole('ROLE_xxx')")设置权限拦截 ,无权限则返回403
1.前言
以前学习的时候使用权限的拦截,一般都是对路径进行拦截 ,要么用拦截器设置拦截信息,要么是在配置文件内设置拦截信息,
spring security 支持使用注解的形式 ,写在方法和接口上拦截 ,
分别支持 三种 :
@PreAuthorize("hasRole('ROLE_xxx')" )
@PostAuthorize("returnObject.type == authentication.name")
@Secured({ "ROLE_DBA", "ROLE_ADMIN" })
常用的是 @PreAuthorize 和 @Secured ,但是推荐使用 @PreAuthorize 因为支持Spring EL表达式 。
@PreAuthorize如果有多个权限设置,则使用如下:
//必须有全部的权限才可以访问
@PreAuthorize("hasRole('ROLE_admin') and hasAnyRole('ROLE_user')")
//至少有一个即可访问
@PreAuthorize("hasRole('ROLE_admin') or hasAnyRole('ROLE_user')")
重点 ,超级重点 !!!!坑了我好长时间: 使用 @PreAuthorize("hasRole('ROLE_admin'))
权限名字必须带前缀 ROLE_ 后面的随便写 比如 ROLE_admin , ROLE_user , ROLE_kk22 , ROLE_love 否则无法正确识别权限,
因为hasRole 就是定义角色的,转成权限会自动添加前缀ROLE_ ,因此,不怎么使用。
如果使用 @PreAuthorize("hasAuthority('admin')") 则不需要带前缀,是什么就是什么,不需要添加前缀,
因为 hasAuthority 就是定义权限的,写入名字即可。
经过测试: 路径拦截权限的名称必须与权限列表注册的一样,经过测试,使用hasRole的注解, 方法级别的注解权限需要
ROLE_前缀 ,因此,路径拦截权限的名称、注解权限名称、数据库存储的权限名称都要加
ROLE_前缀最好,避免出现错误, 如果数据库的权限名称不加ROLE_前缀,那么在注册权
限列表的时候记得拼接ROLE_前缀。
如果不想麻烦,可以使用 hasAuthority ,不需要添加前缀,
不论是那种方法,注解的限制效果和在security配置文件设置的效果是一样的。
2.操作
(1)准备一个建好的spring boot + spring security 工程 【具体操作这里不解释 ,我的其他随笔有具体记载】
目录结构
在security 配置类 开启 权限注解功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
注解 @EnableGlobalMethodSecurity 有3个参数,【默认是false ,需要开启时设为true】
prePostEnabled :确定 Spring Security 前置注释 [@PreAuthorize,@PostAuthorize,..] 是否应该启用;
secureEnabled : 确定 Spring Security 安全注释 [@Secured] 是否应该启用;
jsr250Enabled : 确定 JSR-250注释 [@RolesAllowed..] 是否应该启用;
完整的配置类


package com.example.security5500.securityConfig; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component; //这个加不加无所谓
@Configuration
//开启security自定义配置
@EnableWebSecurity
//开启 Controller层的访问方法权限,与注解@PreAuthorize("hasRole('ROLE_admin')")配合,会拦截注解了@PreAuthrize注解的配置
// 想要@PreAuthorize正确执行 ,权限关键字必须带前缀 ROLE_ ,后面的部分可以随便写!!!!靠,琢磨了4小时了 ,终于找到原因了
@EnableGlobalMethodSecurity(prePostEnabled = true)
//, securedEnabled = true
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //实例自定义登录校验接口 【内部有 数据库查询】
@Autowired
private DbUserDetailsService dbUserDetailsService; //忽略拦截的静态文件路径
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/js/**",
"/css/**",
"/img/**",
"/webjars/**");
} //拦截规则设置
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//允许基于使用HttpServletRequest限制访问
//即授权请求设置
.authorizeRequests()
//设置不拦截页面,可直接通过,路径访问 "/", "/index", 则不拦截,
.antMatchers("/", "/index", "/hhk/**")
//是允许所有的意思
.permitAll()
// //访问 /hai 需要admin权限 ,无权限则提示 403
// .antMatchers("/hai").hasAuthority("admin")
// //访问 /kk 需要admin或user权限 ,无权限则提示 403
// .antMatchers("/kk").hasAnyAuthority("admin", "user")
// //路径/admin/**所有的请求都需要admin权限 ,无权限则提示 403
// .antMatchers("/admin/**").hasAuthority("admin")
//其他页面都要拦截,【需要在最后设置这个】
.anyRequest().authenticated()
.and()
//设置自定义登录页面
//即开启登录设置
.formLogin()
//指定自定义登录页面的访问虚拟路径
.loginPage("/login")
.permitAll()
.and()
// 添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效
// 来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
//即开启登出设置
.logout()
// //指定的登出操作的虚拟路径,需要以post方式请求这个 http://localhost:5500/mylogout 才可以登出 ,也可以直接清除用户认证信息达到登出目的
// .logoutUrl("/mylogout")
//使httpsession失效
.invalidateHttpSession(true)
//清除认证信息
.clearAuthentication(true)
//登出请求匹配器,新建一个蚂蚁路径请求匹配器 ,与 .logoutUrl("/mylogout")效果一样
.logoutRequestMatcher(new AntPathRequestMatcher("/mylogout"))
//登出成功后访问的地址
.logoutSuccessUrl("/home")
.permitAll()
.and()
//开启记住我设置,用于自动登录
.rememberMe()
//密钥
.key("unique-and-secret")
//存在cookie的用户名[用于cookie名]
.rememberMeCookieName("remember-me-cookie-name")
//生命周期,单位毫秒
.tokenValiditySeconds(24 * 60 * 60);
//登陆后"选择记住我" ,会生成cookie ,登出则会自动删除该cookie , 只要不登出且未超出生命周期 ,那么关闭浏览器后再次访问将自动登录
// [name] [value] [domain] [path] [expires/max-age] [size] [httponly] [priority]
//remember-me-cookie-name eGk6MTU5MTIwODAzNDk5MTozZWUyN2FlMmEwMWQxNDczMDhhY2ZkYTAxZWQ5ZWQ5YQ localhost / 2020-06-03T18:13:54.992Z 89 ✓ Medium } /**
* 添加 UserDetailsService, 实现自定义登录校验,数据库查询
*/
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
//注入用户信息,每次登录都会来这查询一次信息,因此不建议每次都向mysql查询,应该使用redis
//密码加密
builder.userDetailsService(dbUserDetailsService);
// .passwordEncoder(passwordEncoder());
} /**
* BCryptPasswordEncoder相关知识:
* 用户表的密码通常使用MD5等不可逆算法加密后存储,为防止彩虹表破解更会先使用一个特定的字符串(如域名)加密,然后再使用一个随机的salt(盐值)加密。
* 特定字符串是程序代码中固定的,salt是每个密码单独随机,一般给用户表加一个字段单独存储,比较麻烦。
* BCrypt算法将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题。
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} // /**
// * 选择加密方式 ,密码不加密的时候选择 NoOpPasswordEncoder,不可缺少,否则报错
// * java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
// */
// @Bean
// public static PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();
// } }
(2)在需要权限拦截的方法上加入注解 @PreAuthorize("hasRole('ROLE_xxx')" )
有多个权限则使用
@PreAuthorize("hasRole('ROLE_xxx') AND hasRole('ROLE_xxx2')")
[必须具备两个权限才可以]
或
@PreAuthorize("hasRole('ROLE_xxx') or hasRole('ROLE_xxx2')")
【只要有其中一个权限即可】
@PreAuthorize("hasRole('ROLE_love1')")
意思是需要权限 ROLE_love1才可以访问这个方法 ,
(3)那么数据库中的权限字符串该怎么写?
图中是我测试的权限名称, 以 符号 ,来分隔多个权限 ,经过测试 ,只有ROLE_前缀的权限才可以被注解识别 【原因不清楚,找不到相关资料】
因此,不论是注解还是数据库存储的值,都需要加 ROLE_前缀 ,
//
当然 ,数据库中也可以不加,可以在注册权限到内存的时候再加 ,
但是容易遗忘等原因出错,因此不建议使用,如下图写法:
3.测试
(1)启动工程 ,端口5500
访问网址 http://localhost:5500/home
被拦截需要登录
使用一个 无 ROLE_love1 权限 的账户登录
username = xi
password = 11
点击登录显示403
控制台打印权限
(2)
换一个有ROLE_love1 权限 的账户登录
username = cen
password = 11
【因为我在配置类配置类登录页面路径
因此想要重新登录,需要访问 网址 http://localhost:5500/login 返回到登录页面 ,
注意 ,访问登录页面不会自动登出,但是 进入登录页面后,提交以此表单登录申请 后将自动登出 原本已经登录的账户 ,即便登录失败 ,照样会登出
】
点击登录,成功进入页面
控制台打印权限
------------------------------
参考博文原址 : https://blog.csdn.net/HiBoyljw/article/details/84032839
spring security 在controller层 方法级别使用注解 @PreAuthorize("hasRole('ROLE_xxx')")设置权限拦截 ,无权限则返回403的更多相关文章
- Spring Security教程之基于方法的权限控制(十二)
目录 1.1 intercept-methods定义方法权限控制 1.2 使用pointcut定义方法权限控制 1.3 使用注解定义方法权限控制 1.3.1 JSR-25 ...
- 【异常处理】Springboot对Controller层方法进行统一异常处理
Controller层方法,进行统一异常处理 提供两种不同的方案,如下: 方案1:使用 @@ControllerAdvice (或@RestControllerAdvice), @ExceptionH ...
- springMVC中controller层方法中使用private和public问题
楼主一直习惯使用public,偶尔手误也可能使用private,但是发觉也没啥区别,都能调用service层,注入bean. 后来做一个新项目时,发觉自己以前的写的部分功能报错,当时有点懵逼,,找了半 ...
- Springboot对Controller层方法进行统一异常处理
Controller层方法,进行统一异常处理 提供两种不同的方案,如下: 方案1:使用 @@ControllerAdvice (或@RestControllerAdvice), @ExceptionH ...
- 浅谈使用spring security中的BCryptPasswordEncoder方法对密码进行加密与密码匹配
浅谈使用springsecurity中的BCryptPasswordEncoder方法对密码进行加密(encode)与密码匹配(matches) spring security中的BCryptPass ...
- PowerMock+SpringMVC整合并测试Controller层方法
PowerMock扩展自Mockito,实现了Mockito不支持的模拟形式的单元测试.PowerMock实现了对静态方法.构造函数.私有方法以及final方法的模拟支持,对静态初始化过程的移除等强大 ...
- Junit mockito 测试Controller层方法有Pageable异常
1.问题 在使用MockMVC+Mockito模拟Service层返回的时候,当我们在Controller层中参数方法调用有Pageable对象的时候,我们会发现,我们没办法生成一个Pageable的 ...
- Spring Boot从Controller层进行单元测试
单元测试是程序员对代码的自测,一般公司都会严格要求单元测试,这是对自己代码的负责,也是对代码的敬畏. 一般单元测试都是测试Service层,下面我将演示从Controller层进行单元测试. 无参Co ...
- Spring boot异常统一处理方法:@ControllerAdvice注解的使用、全局异常捕获、自定义异常捕获
一.全局异常 1.首先创建异常处理包和类 2.使用@ControllerAdvice注解,全局捕获异常类,只要作用在@RequestMapping上,所有的异常都会被捕获 package com.ex ...
随机推荐
- Tableau预测指示器的运用
一.将订单日期拖拽两次到列,日期格式设置为年订单日期和月订单日期 二.将销售额拖拽至行,对应结果如下图所示 三.分析-趋势线-显示趋势线-显示选择整个视图 四.右键预测的任意位置,选择预测-描述预测- ...
- canvas绘制圣诞树
最近不知道咋的都玩起了用备忘录手绘圣诞树,作为万能的程序员,肯定也要跟上潮流,那用代码来实现圣诞树吧.哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈 实现效果如下: ...
- secret_file
拿到题目例行检查,进入main函数 这个逆向有些复杂,程序首先让我们像dest输入256个字符,我们可以看到关键的strcmp(v15,v17),若相等则执行poppen poppen这个函数有额外的 ...
- 初探Windows用户态调试机制
我们在感叹Onlydbg强大与便利的同时,是否考虑过它实现的原理呢? 作为一个技术人员知其然必知其所以然,这才是我们追求的本心. 最近在学习张银奎老师的<软件调试>,获益良多.熟悉Wind ...
- Flink的窗口处理机制(一)
一.为什么需要 window ? 在流处理应用中,数据是连续不断的,即数据是没有边界的,因此我们不可能等到所有数据都到了才开始处理.当然我们可以每来一个消息就处理一次,但是有时我们需要做一些聚合类的处 ...
- springboot 整合activemq
1.配置连接信息 spring: activemq: broker-url: tcp://192.168.1.28:61616 user: admin password: 123456 package ...
- JAVA导入(读取)Excel中的数据(支持xls与xlsx文件)
一.导入jar包 poi-3.7.jarpoi-scratchpad-3.7.jarpoi-examples-3.7.jarpoi-ooxml-3.7.jarpoi-ooxml-schemas-3.7 ...
- JAVA获取当前日期指定月份后(多少个月后)的日期
环境要求:使用jdk1.8 package com.date; import java.text.ParseException; import java.text.SimpleDateFormat; ...
- 【LeetCode】206. Reverse Linked List 解题报告(Python&C++&java)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 迭代 递归 日期 [LeetCode] 题目地址:h ...
- 【LeetCode】231. Power of Two 解题报告(Java & Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 二进制 位运算 判断是不是最大2的幂的因数 判断因子 ...