spring security(3)

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: spring security

spring security(2)https://developer.aliyun.com/article/1531011

三, 授权

主要是为了不同用户可以使用不同的功能

3.1 授权的基本流程

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

所以我们需要在项目中把登录用户的权限信息也存入Authentication

然后设置我们的资源所需的权限即可

3.2 授权访问

3.2.1 限制访问资源所需的权限

SpringSecurity为我们提供了基于注解的权限控制方案,这也是我们项目中主要采用的方式。我们可以使用注解去指定访问对应的资源所需的权限

第一步: 在SecurityConfig配置类添加如下,作用是开启相关配置

@EnableGlobalMethodSecurity(prePostEnabled = true)

第二步: 开启了相关配置之后,就能使用@PreAuthorize等注解了。在HelloController类的hello方法,添加如下注解,其中test表示自定义权限的名字

@PreAuthorize("hasAuthority('test')")
3.2.3 封装权限信息

登录的时候从数据库获取权限放入UserDetails然后验证后存到redis

@Component  
public class UserDetailsServiceImpl implements UserDetailsService {  
    @Autowired  
    UserMapper userMapper;  
    @Override  
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  
        //查询用户信息  
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();  
        lambdaQueryWrapper.eq(User::getUserName,username);  
        User user = userMapper.selectOne(lambdaQueryWrapper);  
        //查询权限信息  
        if (user == null){  
            throw new UsernameNotFoundException("用户名不存在");  
        }  
        LoginUser loginUser = new LoginUser();  
        loginUser.setUser(user);  
        List<String> roles = Arrays.asList("test");  
        return loginUser;  
    }  
}
package com.sucurity.domain;  
  
import lombok.Data;  
import org.springframework.security.core.GrantedAuthority;  
import org.springframework.security.core.authority.SimpleGrantedAuthority;  
import org.springframework.security.core.userdetails.UserDetails;  
  
import java.util.Collection;  
import java.util.List;  
import java.util.stream.Collectors;  
  
@Data  
public class LoginUser implements UserDetails {  
    private User user;  
 private List<String> permissions;  
@JSONField(serialize = false) //这个注解的作用是不让下面那行的成员变量序列化存入redis,避免redis不支持而报异常  
private List<SimpleGrantedAuthority> authorities;  
  
@Override  
  
public Collection<? extends GrantedAuthority> getAuthorities() {  
    if (permissions != null) {  
        return permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());  
    }  
    return null;  
}
    @Override  
    public String getPassword() {  
        return user.getPassword();  
    }  
  
    @Override  
    public String getUsername() {  
        return user.getUserName();  
    }  
  
    @Override  
    public boolean isAccountNonExpired() {  
        return true;  
    }  
  
    @Override  
    public boolean isAccountNonLocked() {  
        return true;  
    }  
  
    @Override  
    public boolean isCredentialsNonExpired() {  
        return true;  
    }  
  
    @Override  
    public boolean isEnabled() {  
        return true;  
    }  
}

访问其他的接口的时候经过的过滤器会从redis获取权限

//TODO 获取权限信息封装到Authentication中  
UsernamePasswordAuthenticationToken authenticationToken =  
        //第一个参数是LoginUser用户信息,第二个参数是凭证(null),第三个参数是权限信息(null)  
        new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());

完整版本

package com.sucurity.filter;  
  
import com.sucurity.domain.LoginUser;  
import com.sucurity.utils.JwtUtil;  
import com.sucurity.utils.RedisCache;  
import io.jsonwebtoken.Claims;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;  
import org.springframework.security.core.GrantedAuthority;  
import org.springframework.security.core.context.SecurityContextHolder;  
import org.springframework.stereotype.Component;  
import org.springframework.util.StringUtils;  
import org.springframework.web.filter.OncePerRequestFilter;  
import javax.servlet.FilterChain;  
import javax.servlet.ServletException;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.IOException;  
import java.util.Collection;  
import java.util.Objects;  
  
  
@Component  
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {  
  
    @Autowired  
    private RedisCache redisCache;  
  
    @Override  
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {  
        //获取token,指定你要获取的请求头叫什么  
        String xxtoken = request.getHeader("token");  
        //判空,不一定所有的请求都有请求头,所以上面那行的xxtoken可能为空  
        //!StringUtils.hasText()方法用于检查给定的字符串是否为空或仅包含空格字符  
        if (!StringUtils.hasText(xxtoken)) {  
            //如果请求没有携带token,那么就不需要解析token,不需要获取用户信息,直接放行就可以  
            filterChain.doFilter(request, response);  
            //return之后,就不会走下面那些代码  
            return;  
        }  
        //解析token  
        String userid; //把userid定义在外面,才能同时用于下面的46行和52行  
        try {  
            Claims claims = JwtUtil.parseJWT(xxtoken);  
            userid = claims.getSubject();  
        } catch (Exception e) {  
            e.printStackTrace();  
            throw new RuntimeException("token非法");  
        }  
        //从redis中获取用户信息  
        String redisKey = "login:" + userid;  
        //LoginUser是我们在domain目录写的实体类  
  
        LoginUser loginUser = redisCache.getCacheObject(redisKey);  
  
        //判断获取到的用户信息是否为空,因为redis里面可能并不存在这个用户信息,例如缓存过期了  
        if(Objects.isNull(loginUser)){  
            //抛出一个异常  
            throw new RuntimeException("用户未登录");  
        }  
  
        //把最终的LoginUser用户信息,通过setAuthentication方法,存入SecurityContextHolder  
        //TODO 获取权限信息封装到Authentication中  
        UsernamePasswordAuthenticationToken authenticationToken =  
                //第一个参数是LoginUser用户信息,第二个参数是凭证(null),第三个参数是权限信息(null)  
                new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());  
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);  
  
        //全部做完之后,就放行  
        filterChain.doFilter(request, response);  
    }  
}
3.3.3 从数据库查询权限信息
3.3.3.1 RABC 权限模型

这个是基于角色的权限模型

用户 ,角色,权限

用户 1:多 角色

角色 1: 多 权限

(assets/Pasted%20image%2020240229174251.png)

sql

use security;
CREATE TABLE `sys_menu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '菜单名',
  `path` varchar(200) DEFAULT NULL COMMENT '路由地址',
  `component` varchar(255) DEFAULT NULL COMMENT '组件路径',
  `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
  `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
  `perms` varchar(100) DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
  `create_by` bigint(20) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_by` bigint(20) DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
CREATE TABLE `sys_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT NULL,
  `role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串',
  `status` char(1) DEFAULT '0' COMMENT '角色状态(0正常 1停用)',
  `del_flag` int(1) DEFAULT '0' COMMENT 'del_flag',
  `create_by` bigint(200) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_by` bigint(200) DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
CREATE TABLE `sys_role_menu` (
  `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '菜单id',
  PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `sys_user_role` (
  `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '角色id',
  PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into sys_user_role values (2,1);
insert into sys_role values
(1,'经理','ceo',0,0,default,default,default,default,default),
(2,'程序员','coder',0,0,default,default,default,default,default);
insert into sys_role_menu values (1,1),(1,2);
insert into sys_menu values
(1,'部门管理','dept','system/dept/index',0,0,'system:dept:list','#',default,default,default,default,default,default),
(2,'测试','test','system/test/index',0,0,'system:test:list','#',default,default,default,default,default,default)

第二步: 测试SQL语句,也就是确认一下你的建表、插入数据是否达到要求

# 通过用户id去查询这个用户具有的权限列表。也就是根据userid查询perms,并且限制条件为role和menu都必须正常状态么也就是等于0
SELECT 
  DISTINCT m.`perms`
FROM
  sys_user_role ur
  LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
  LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
  LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
WHERE
  user_id = 2
  AND r.`status` = 0
  AND m.`status` = 0

对应的实体类

package com.sucurity.domain;  
  
import com.baomidou.mybatisplus.annotation.TableId;  
import com.baomidou.mybatisplus.annotation.TableName;  
import com.fasterxml.jackson.annotation.JsonInclude;  
import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  
import java.io.Serializable;  
import java.util.Date;  
  
  
//权限表(也叫菜单表)的实体类  
@TableName(value="sys_menu") //指定表名,避免等下mybatisplus的影响  
@Data  
@AllArgsConstructor  
@NoArgsConstructor  
@JsonInclude(JsonInclude.Include.NON_NULL)  
//Serializable是官方提供的,作用是将对象转化为字节序列  
public class Menu implements Serializable {  
    private static final long serialVersionUID = -54979041104113736L;  
  
    @TableId  
    private Long id;  
    /**  
     * 菜单名  
     */  
    private String menuName;  
    /**  
     * 路由地址  
     */  
    private String path;  
    /**  
     * 组件路径  
     */  
    private String component;  
    /**  
     * 菜单状态(0显示 1隐藏)  
     */  
    private String visible;  
    /**  
     * 菜单状态(0正常 1停用)  
     */  
    private String status;  
    /**  
     * 权限标识  
     */  
    private String perms;  
    /**  
     * 菜单图标  
     */  
    private String icon;  
  
    private Long createBy;  
  
    private Date createTime;  
  
    private Long updateBy;  
  
    private Date updateTime;  
    /**  
     * 是否删除(0未删除 1已删除)  
     */  
    private Integer delFlag;  
    /**  
     * 备注  
     */  
    private String remark;  
}


spring security(4)https://developer.aliyun.com/article/1531015

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3天前
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
21 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
7天前
|
存储 安全 Java
Spring Security 6.x OAuth2登录认证源码分析
上一篇介绍了Spring Security框架中身份认证的架构设计,本篇就OAuth2客户端登录认证的实现源码做一些分析。
31 2
Spring Security 6.x OAuth2登录认证源码分析
|
10天前
|
安全 Java 数据安全/隐私保护
Spring Security 6.x 一文快速搞懂配置原理
本文主要对整个Spring Security配置过程做一定的剖析,希望可以对学习Spring Sercurity框架的同学所有帮助。
41 5
Spring Security 6.x 一文快速搞懂配置原理
|
8天前
|
安全 Java API
Spring Security 6.x 图解身份认证的架构设计
【6月更文挑战第1天】本文主要介绍了Spring Security在身份认证方面的架构设计,以及主要业务流程,及核心代码的实现
17 1
Spring Security 6.x 图解身份认证的架构设计
|
8天前
|
安全 前端开发 Java
CSRF 攻击以及如何使用 Spring Security 预防攻击
【6月更文挑战第15天】CSRF 是指跨站请求伪造,是 Cross-site request forgery 的简称,有些地方也简写为 XSRF。
356 1
|
9天前
|
安全 前端开发 Java
Spring Security 6.x 过滤器链SecurityFilterChain是如何工作的
上一篇主要介绍了Spring Secuirty中的过滤器链SecurityFilterChain是如何配置的,那么在配置完成之后,SecurityFilterChain是如何在应用程序中调用各个Filter,从而起到安全防护的作用,本文主要围绕SecurityFilterChain的工作原理做详细的介绍。
47 0
Spring Security 6.x 过滤器链SecurityFilterChain是如何工作的
|
15天前
|
安全 Java 测试技术
Spring Security应用中的部分代码示例2
【6月更文挑战第12天】Spring Security应用中的部分代码示例2
24 5
|
13天前
|
存储 安全 Java
详解 Spring Security:全面保护 Java 应用程序的安全框架
详解 Spring Security:全面保护 Java 应用程序的安全框架
31 1
|
15天前
|
安全 Java 数据库
Spring Security应用代码示例
【6月更文挑战第12天】Spring Security应用代码示例
25 3
|
20天前
|
JSON 安全 Java
Java一分钟之-Spring Security:身份验证与授权
【6月更文挑战第7天】本文介绍了Spring Security的常见问题及解决方案,包括配置启动、身份验证、授权、无状态JWT和异常处理。通过`@EnableWebSecurity`启动安全框架,自定义登录页面和登录逻辑,使用`http.authorizeRequests()`设置访问规则。对于JWT,需添加解析器并注册过滤器。此外,处理Spring Security异常,创建自定义的`AccessDeniedHandler`和`AuthenticationEntryPoint`。理解核心概念并按业务需求定制,是确保应用安全的关键。
25 1