SpringBoot集成Spring Security(5)——权限控制

本文详细介绍了如何在 Spring Boot 项目中实现基于角色的权限控制,包括创建权限表、设计 POJO、Mapper 和 Service 层,以及自定义 PermissionEvaluator 实现细粒度的权限检查。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在第一篇中,我们说过,用户<–>角色<–>权限三层中,暂时不考虑权限,在这一篇,是时候把它完成了。

为了方便演示,这里的权限只是对角色赋予权限,也就是说同一个角色的用户,权限是一样的。当然了,你也可以精细化到为每一个用户设置权限,但是这不在本篇的探讨范围,有兴趣可以自己实验,原理都是一样的。

目录

一、数据准备

1.1 创建sys_permission表

1.2 创建POJO、Mapper、Service

1.3 修改接口

二、PermissionEvaluator

三、运行程序


一、数据准备

1.1 创建sys_permission表

让我们先创建一张权限表,名为sys_permission

CREATE TABLE `sys_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  `permission` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_roleId` (`role_id`),
  CONSTRAINT `fk_roleId` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

内容就是两条数据,url+role_id+permission唯一标识了一个角色访问某一url时的权限,其中权限暂定为c、r、u、d,即增删改查。

1.2 创建POJO、Mapper、Service

(1)pojo

package com.chwx.springbootspringsecurity.pojo;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

@Table(name = "sys_permission")
@Data
public class SysPermission implements Serializable {
    static final long serialVersionUID = 1L;

    @Id
    private Integer id;

    private String url;

    @Column(name = "role_id")
    private Integer roleId;

    private String permission;

    @Transient
    private List permissions;

    // 省略除permissions外的getter/setter

    public List<String> getPermissions() {
        return Arrays.asList(this.permission.trim().split(","));
    }

    public void setPermissions(List permissions) {
        this.permissions = permissions;
    }
}


这里需要注意的时相比于数据库,多了一个permissions属性,该字段将permission按逗号分割为了list。

(2)mapper

package com.chwx.springbootspringsecurity.dao;

import com.chwx.springbootspringsecurity.pojo.SysPermission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @author ZY Wei
 * @date 2018/11/20 16:49
 */
@Mapper
public interface SysPermissionMapper {
    @Select("select * from sys_permission where role_id = #{roleId}")
    List<SysPermission> listByRoleId(Integer roleId);
}

(3)Service

package com.chwx.springbootspringsecurity.service;

import com.chwx.springbootspringsecurity.pojo.SysPermission;
import com.chwx.springbootspringsecurity.dao.SysPermissionMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author ZY Wei
 * @date 2018/11/20 16:50
 */
@Service
public class SysPermissionService {
    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    /**
     * 获取指定角色所有权限
     * @param roleId
     * @return
     */
    public List<SysPermission> listByRoleId(Integer roleId){
        return sysPermissionMapper.listByRoleId(roleId);
    }
}

Service中有一个方法,根据roleId获取所有的SysPermission

1.3 修改接口

@Controller
public class LoginController {
    ...

    @RequestMapping("/admin")
    @ResponseBody
    @PreAuthorize("hasPermission('/admin','r')")
    public String printAdminR() {
        return "如果你看见这句话,说明你访问/admin路径具有r权限";
    }

    @RequestMapping("/admin/c")
    @ResponseBody
    @PreAuthorize("hasPermission('/admin','c')")
    public String printAdminC() {
        return "如果你看见这句话,说明你访问/admin路径具有c权限";
    }
}

让我们修改下我们要访问的接口,重点是printAdmin()方法,@PreAuthorize("hasPermission('/admin','r')")是关键,它第一个参数指明了访问该接口需要的url,第二个参数指明了访问该接口需要的权限。

二、PermissionEvaluator

我们需要自定义对hasPermission()方法的处理,就需要自定义PermissionEvaluator,创建类CustomPermissionEvaluator,实现PermissionEvaluator接口。

package com.chwx.springbootspringsecurity.common;

import com.chwx.springbootspringsecurity.pojo.SysPermission;
import com.chwx.springbootspringsecurity.service.SysPermissionService;
import com.chwx.springbootspringsecurity.service.SysRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;

/**
 * @author ZY Wei
 * @date 2018/11/20 16:57
 */
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

    @Autowired
    private SysPermissionService sysPermissionService;
    @Autowired
    private SysRoleService sysRoleService;

    @Override
    public boolean hasPermission(Authentication authentication, Object o, Object o1) {
        //获取loadUserByUsername()方法结果
        User user = (User) authentication.getPrincipal();
        //获取loadUserByUsername()中注入的角色
        Collection<GrantedAuthority> authorities = user.getAuthorities();

        //遍历所有的角色
        for (GrantedAuthority authority : authorities){
            String roleName = authority.getAuthority();
            Integer roleId = sysRoleService.selectByName(roleName);
            //得到角色所有的权限
            List<SysPermission> permissionList = sysPermissionService.listByRoleId(roleId);

            //遍历permissionList
            for(SysPermission sysPermission : permissionList){
                //获取权限集
                List permissions = sysPermission.getPermissions();
                //如果访问的url和权限用户符合的话,返回true
                if(o.equals(sysPermission.getUrl()) && permissions.contains(o1)) {
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
        return false;
    }
}

hasPermission()方法中,参数1代表用户的权限身份,参数2参数3分别和@PreAuthorize("hasPermission('/admin','r')")中的参数对应,即访问url和权限。

思路如下:

通过Authentication取出登录用户的所有Role

遍历每一个Role,获取到每个Role的所有Permission

遍历每一个Permission,只要有一个Permission的url和传入的url相同,且该Permission中包含传入的权限,返回true

如果遍历都结束,还没有找到,返回false

下面就是在WebSecurityConfig中注册CustomPermissionEvaluator
 

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    ...
    /**
     * 注入自定义PermissionEvaluator
     */
    @Bean
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new CustomPermissionEvaluator());
        return handler;
    }
    ...
}

三、运行程序

当我使用角色为ROLE_USER的用户仍然能访问,因为该用户访问/admin路径具有r权限:

### 整合 Spring Boot 和 Spring Security 以及 Vue.js 实现前后端分离的安全认证 #### 后端部分 在后端,Spring Boot 提供了RESTful API 的实现和业务逻辑处理[^1]。为了保护这些API免受未授权访问的影响,集成Spring Security来负责身份验证和授权管理。 对于基于Token的身份验证机制,如JSON Web Token (JWT),可以通过自定义`JwtAuthenticationFilter`来进行拦截和解析令牌操作[^2]。此过滤器会检查HTTP头部携带的JWT有效性,并将其转换成可识别的身份信息以便后续权限校验流程继续执行下去。 另外,在配置类中通过重载`configure(HttpSecurity http)`方法设置不同URL模式对应的访问控制策略;例如,“/api/auth/**”路径下的资源允许匿名用户直接获取(通常用于登录注册等功能),而其余所有请求则需经过合法的身份验证过程才能成功调用相应服务接口。 ```java @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() // 不需要认证即可访问 .anyRequest().authenticated(); // 所有其他请求均需认证 http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } ``` #### 前端部分 前端采用Vue.js框架创建项目。当涉及到与后端交互时,特别是涉及敏感数据的操作(比如提交表单、查询个人信息等),应当始终附带有效的JWT到服务器端以证明当前用户的合法性。这一般是在发起Ajax请求前将token加入到headers字段里完成: ```javascript axios.interceptors.request.use(config => { const token = localStorage.getItem('jwt'); if(token){ config.headers['Authorization'] = `Bearer ${token}`; } return config; }); ``` 此外,考虑到用户体验方面的需求,还需要设计一套完整的页面跳转逻辑——即当检测到session过期或不存在有效凭证的情况下自动导向至登陆界面等待重新输入账号密码进行新一轮的身份确认工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值