Spring Security用户授权

用户认证在上一篇用户认证

用户授权

总体流程:
在这里插入图片描述
在这里插入图片描述

在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。判断当前用户是否拥有访问当前资源所需的权限。

SpringSecurity中的Authentication类:

public interface Authentication extends Principal, Serializable {//权限数据列表Collection<? extends GrantedAuthority> getAuthorities();Object getCredentials();Object getDetails();Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

前面登录时执行loadUserByUsername方法时,return new CustomUser(sysUser, Collections.emptyList());后面的空数据对接就是返回给Spring Security的权限数据。

在TokenAuthenticationFilter中怎么获取权限数据呢?登录时我们把权限数据保存到redis中(用户名为key,权限数据为value即可),这样通过token获取用户名即可拿到权限数据,这样就可构成出完整的Authentication对象。

1、修改loadUserByUsername接口方法

增加返回权限的功能:

   @Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名进行查询SysUser sysUser = sysUserService.getUserByUserName(username);if(null == sysUser) {throw new UsernameNotFoundException("用户名不存在!");}if(sysUser.getStatus().intValue() == 0) {throw new RuntimeException("账号已停用");}//根据userid查询用户操作权限数据List<String> userPermsList = sysMenuService.findUserPermsByUserId(sysUser.getId());//创建list集合,封装最终权限数据 权限是SimpleGrantedAuthority类型List<SimpleGrantedAuthority> authList = new ArrayList<>();//查询list集合遍历for (String perm : userPermsList) {authList.add(new SimpleGrantedAuthority(perm.trim()));}return new CustomUser(sysUser, authList);}
}

2、spring-security模块配置redis

添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3、修改TokenLoginFilter登录成功方法

登录成功我们将权限数据保存到reids

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private RedisTemplate redisTemplate;public TokenLoginFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate) {this.setAuthenticationManager(authenticationManager);this.setPostOnly(false);//指定登录接口及提交方式,可以指定任意路径this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));this.redisTemplate = redisTemplate;}//登录认证//获取输入的用户名和密码,调用方法认证public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response)throws AuthenticationException {try {//获取用户信息  loginVo(用户名 密码)LoginVo loginVo = new ObjectMapper().readValue(request.getInputStream(), LoginVo.class);//将用户信息封装成对象AuthenticationAuthentication authenticationToken =new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());//调用方法 校验(loadUserByUsername方法已经被重写 从数据库中查询用户信息)return this.getAuthenticationManager().authenticate(authenticationToken);} catch (IOException e) {throw new RuntimeException(e);}}//认证成功调用方法protected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain,Authentication auth)throws IOException, ServletException {//获取当前用户CustomUser customUser = (CustomUser)auth.getPrincipal();//生成tokenString token = JwtHelper.createToken(customUser.getSysUser().getId(),customUser.getSysUser().getUsername());//获取当前用户权限数据,放到Redis里面 key:username   value:权限数据redisTemplate.opsForValue().set(customUser.getUsername(),JSON.toJSONString(customUser.getAuthorities()));//返回Map<String,Object> map = new HashMap<>();map.put("token",token);ResponseUtil.out(response, Result.ok(map));}//认证失败调用方法protected void unsuccessfulAuthentication(HttpServletRequest request,HttpServletResponse response,AuthenticationException failed)throws IOException, ServletException {ResponseUtil.out(response,Result.build(null, ResultCodeEnum.LOGIN_ERROR));}

4、修改TokenAuthenticationFilter

认证是从redis里面获取权限数据

完整代码:


public class TokenAuthenticationFilter extends OncePerRequestFilter {private RedisTemplate redisTemplate;public TokenAuthenticationFilter(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain chain)throws ServletException, IOException {//如果是登录接口,直接放行 不登录哪来token呢?if("/admin/system/index/login".equals(request.getRequestURI())) {chain.doFilter(request, response);return;}//请求头中是否有tokenUsernamePasswordAuthenticationToken authentication = getAuthentication(request);if(null != authentication) {//authentication不为空 放入SecurityContex中SecurityContextHolder.getContext().setAuthentication(authentication);chain.doFilter(request, response);} else {ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_ERROR));}}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {//请求头是否有tokenString token = request.getHeader("token");//token不为空if(!StringUtils.isEmpty(token)) {String username = JwtHelper.getUsername(token);if(!StringUtils.isEmpty(username)) { //username不为空 则封装对象返回//当前用户信息放到ThreadLocal里面LoginUserInfoHelper.setUserId(JwtHelper.getUserId(token));LoginUserInfoHelper.setUsername(username);//通过username从redis获取权限数据String authString = (String)redisTemplate.opsForValue().get(username);//把redis获取字符串权限数据转换要求集合类型 List<SimpleGrantedAuthority>if(!StringUtils.isEmpty(authString)) {List<Map> maplist = JSON.parseArray(authString, Map.class);System.out.println(maplist);List<SimpleGrantedAuthority> authList = new ArrayList<>();for (Map map:maplist) {String authority = (String)map.get("authority");authList.add(new SimpleGrantedAuthority(authority));}return new UsernamePasswordAuthenticationToken(username,null, authList);} else {return new UsernamePasswordAuthenticationToken(username,null, new ArrayList<>());}}}return null;}
}

5、修改配置类

修改WebSecurityConfig类

配置类添加注解:

开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认

@EnableGlobalMethodSecurity(prePostEnabled = true)

添加注入bean:

@Autowired
private RedisTemplate redisTemplate;

添加参数:

连个fillter添加redisTemplate参数

完整代码如下:

@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate CustomMd5PasswordEncoder customMd5PasswordEncoder;@Bean@Overrideprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护http//关闭csrf跨站请求伪造.csrf().disable()// 开启跨域以便前端调用接口.cors().and().authorizeRequests()// 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的//.antMatchers("/admin/system/index/login").permitAll()// 这里意思是其它所有接口需要认证才能访问.anyRequest().authenticated().and()//TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,//这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。.addFilterBefore(new TokenAuthenticationFilter(redisTemplate),UsernamePasswordAuthenticationFilter.class).addFilter(new TokenLoginFilter(authenticationManager(),redisTemplate));//禁用sessionhttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 指定UserDetailService和加密器auth.userDetailsService(userDetailsService).passwordEncoder(customMd5PasswordEncoder);}/*** 配置哪些请求不拦截* 排除swagger相关请求* @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/admin/modeler/**","/diagram-viewer/**","/editor-app/**","/*.html","/admin/processImage/**","/admin/wechat/authorize","/admin/wechat/userInfo","/admin/wechat/bindPhone","/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");}
}

6、service-oa模块添加redis配置

application-dev.yml配文件

spring:redis:host: localhostport: 6379database: 0timeout: 1800000password:jedis:pool:max-active: 20 #最大连接数max-wait: -1    #最大阻塞等待时间(负数表示没限制)max-idle: 5    #最大空闲min-idle: 0     #最小空闲

7、控制controller层接口权限

Spring Security默认是禁用注解的,要想开启注解,需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,来判断用户对某个控制层的方法是否具有访问权限

通过@PreAuthorize标签控制controller层接口权限

public class SysRoleController {@Autowiredprivate SysRoleService sysRoleService;@PreAuthorize("hasAuthority('bnt.sysRole.list')")@ApiOperation(value = "获取分页列表")@GetMapping("{page}/{limit}")public Result index(@ApiParam(name = "page", value = "当前页码", required = true)@PathVariable Long page,@ApiParam(name = "limit", value = "每页记录数", required = true)@PathVariable Long limit,@ApiParam(name = "roleQueryVo", value = "查询对象", required = false)SysRoleQueryVo roleQueryVo) {Page<SysRole> pageParam = new Page<>(page, limit);IPage<SysRole> pageModel = sysRoleService.selectPage(pageParam, roleQueryVo);return Result.ok(pageModel);}@PreAuthorize("hasAuthority('bnt.sysRole.list')")@ApiOperation(value = "获取")@GetMapping("get/{id}")public Result get(@PathVariable Long id) {SysRole role = sysRoleService.getById(id);return Result.ok(role);}@PreAuthorize("hasAuthority('bnt.sysRole.add')")@ApiOperation(value = "新增角色")@PostMapping("save")public Result save(@RequestBody @Validated SysRole role) {sysRoleService.save(role);return Result.ok();}@PreAuthorize("hasAuthority('bnt.sysRole.update')")@ApiOperation(value = "修改角色")@PutMapping("update")public Result updateById(@RequestBody SysRole role) {sysRoleService.updateById(role);return Result.ok();}@PreAuthorize("hasAuthority('bnt.sysRole.remove')")@ApiOperation(value = "删除角色")@DeleteMapping("remove/{id}")public Result remove(@PathVariable Long id) {sysRoleService.removeById(id);return Result.ok();}@PreAuthorize("hasAuthority('bnt.sysRole.remove')")@ApiOperation(value = "根据id列表删除")@DeleteMapping("batchRemove")public Result batchRemove(@RequestBody List<Long> idList) {sysRoleService.removeByIds(idList);return Result.ok();}...
}

9、异常处理

异常处理有2种方式:

​ 1、扩展Spring Security异常处理类:AccessDeniedHandler、AuthenticationEntryPoint

​ 2、在spring boot全局异常统一处理

第一种方案说明:如果系统实现了全局异常处理,那么全局异常首先会获取AccessDeniedException异常,要想Spring Security扩展异常生效,必须在全局异常再次抛出该异常。

我们使用第二种方案。

全局异常添加处理

操作模块:service-util

/*** spring security异常* @param e* @return*/
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public Result error(AccessDeniedException e) throws AccessDeniedException {return Result.build(null, ResultCodeEnum.PERMISSION);
}

AccessDeniedException需要引入依赖,Spring Security对应的异常

在service-util模块引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><scope>provided</scope>
</dependency>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/68894.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

idea中Maven报错Unable to import maven project: See logs for details问题的解决方法

idea中Maven报错Unable to import maven project: See logs for details问题的解决方法。 在查看maven的环境配置和idea的maven配置后&#xff0c;发现是idea 2020版本和maven 3.9.3版本的兼容性问题。在更改为Idea自带的maven 3.6.1版本后问题解决&#xff0c;能成功下载jar包…

word 应用 打不开 显示一直是正在启动中

word打开来显示一直正在启动中&#xff0c;其他调用word的应用也打不开&#xff0c;网上查了下以后进程关闭spoolsv.exe,就可以正常打开word了

AI绘画 | 一文学会Midjourney绘画,创作自己的AI作品(快速入门+参数介绍)

一、生成第一个AI图片 首先&#xff0c;生成将中文描述词翻译成英文 然后在输入端输入&#xff1a;/imagine prompt:Bravely running boy in Q version, cute head portrait 最后&#xff0c;稍等一会即可输出效果 说明&#xff1a; 下面的U1、U2、U3、U4代表的第一张、第二张…

AI Chat 设计模式:15. 桥接模式

本文是该系列的第十五篇&#xff0c;采用问答式的方式展开&#xff0c;问题由我提出&#xff0c;答案由 Chat AI 作出&#xff0c;灰色背景的文字则主要是我的一些思考和补充。 问题列表 Q.1 如果你是第一次接触桥接模式&#xff0c;那么你会有哪些疑问呢&#xff1f;A.1Q.2 什…

JavaScript(JavaEE初阶系列13)

目录 前言&#xff1a; 1.初识JavaScript 2.JavaScript的书写形式 2.1行内式 2.2内嵌式 2.3外部式 2.4注释 2.5输入输出 3.语法 3.1变量的使用 3.2基本数据类型 3.3运算符 3.4条件语句 3.5循环语句 3.6数组 3.7函数 3.8对象 3.8.1 对象的创建 4.案例演示 4…

广告ROI可洞察到订单转化率啦

toB广告营销人的一日三问&#xff1a; 如何实现线索增长&#xff1f;如何获取更多高质量线索&#xff1f;如何能用更少的钱拿到更多高质量的线索&#xff1f; < 广告营销的终极目标&#xff0c;就是提升ROI > 从ROI公式中&#xff0c;可以找到提升广告营销ROI的路径&…

爬楼梯(一次爬1或2层)

一&#xff0c;题目描述 二&#xff0c;解题思路 动态规划 动规五部曲&#xff1a; 1. 确认dp数组以及下标含义 2. 推导递推公式 3. 确认dp数组如何初始化 4. 确认遍历顺序 5. 打印dp数组 dp数组含义&#xff1a;到第i层的方法数目 下标含义&#xff1a;层数 递推公式&…

WebRTC | SDP详解

目录 一、SDP标准规范 1. SDP结构 2. SDP内容及type类型 二、WebRTC中的SDP结构 1. 媒体信息描述 &#xff08;1&#xff09;SDP中媒体信息格式 i. “artpmap”属性 ii. “afmtp”属性 &#xff08;2&#xff09;SSRC与CNAME &#xff08;3&#xff09;举个例子 &…

前后端分离------后端创建笔记(10)用户修改

本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论&#xff0c;如有侵权请联系 源码&#xff1a;https://gitee.com/green_vegetables/x-admin-project.git 素材&#xff1a;https://pan.baidu.com/s/…

React源码解析18(7)------ 实现事件机制(onClick事件)

摘要 在上一篇中&#xff0c;我们实现了useState的hook&#xff0c;但由于没有实现事件机制&#xff0c;所以我们只能将setState挂载在window上。 而这一篇主要就是来实现事件系统&#xff0c;从而实现通过点击事件进行setState。 而在React中&#xff0c;虽然我们是将事件绑…

3.利用matlab计算不定积分和定积分(matlab程序 )

1.简述 不定积分 首先&#xff0c;通过符号变量创建一个符号函数&#xff0c;然后调用积分命令来计算函数的积分&#xff0c;示例如下&#xff1a; 注意&#xff1a;matlab中计算的不定积分结果中没有写上常数C&#xff0c;读者需要自己在使用的时候记得加上常数部分。 通常情…

【设计模式】装饰器模式

装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其结构。这种类型的设计模式属于结构型模式&#xff0c;它是作为现有的类的一个包装。 装饰器模式通过将对象包装在装饰器类中&#xff0c;以便动态地修改其行为…