基于Spring Security实现权限管理系统 - Go语言中文社区

基于Spring Security实现权限管理系统


基于Spring Security实现权限管理系统

稍微复杂一点的后台系统都会涉及到用户权限管理。何谓用户权限?我的理解就是,权限就是对数据(系统的实体类)和数据可进行的操作(增删查改)的集中管理。要构建一个可用的权限管理系统,涉及到三个核心类:一个是用户User,一个是角色Role,最后是权限Permission。接下来本文将介绍如何基于Spring Security 4.0一步一步构建起一个接口级别的权限管理系统。

1. 相关概念

  • 权限(Permission) = 资源(Resource) + 操作(Privilege)
  • 角色(Role) = 权限的集合(a set of low-level permissions)
  • 用户(User) = 角色的集合(high-level roles)

2. Spring Security的maven依赖

Spring Boot版本虽然已经到2.0了,但是之前使用的时候发现了一些坑,所以推荐还是暂时使用比较稳定的1.5版本。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xxx.xxx</groupId>
    <artifactId>api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>security-demo</name>
    <description>Demo project for spring security</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.7</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

3. 定义系统的权限集合

权限是资源以及可对资源进行的操作的一个集合。对于我们的系统来说,几乎所有实体类都可以看作一个资源,而常见的操作也就是增删查改四类,当然,根据我们实际的业务需要,可能还有其他的特殊操作,比如我们这里加了一个导入用户的操作。这里简单列举两个基本的权限集合:

[
  {
    "resourceId":"permission",
    "resourceName":"权限",
    "privileges": {
      "read":"查看",
      "write":"新增",
      "update":"更新",
      "delete":"删除"
    }
  },
  {
    "resourceId":"user",
    "resourceName":"用户",
    "privileges": {
      "read":"查看用户列表",
      "write":"新增用户",
      "import":"导入用户",
      "update":"修改用户信息",
      "delete":"删除用户"
    }
  }
]

在对权限的定义中,关键是resourceIdprivilegeskey,后续将使用这两者结合来对用户的权限进行判断。我这里使用resourceId-privilege这样的形式来唯一表示对某个资源进行的某个操作。

4. 角色相关的操作

资源与操作权限集合类定义JsonPermissions

@Data
public class JsonPermissions {

    private List<SimplePermission> permissions;

    @Data
    public static class SimplePermission {

        /**
         * 资源id
         */
        private String resourceId;

        /**
         * 资源名
         */
        private String resourceName;

        /**
         * 权限列表
         */
        private Map<String, String> privileges;

        /**
         * 是否被遗弃
         */
        private boolean abandon = false;
    }
}

角色类定义Role

import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;

@Document(collection = "role")
@Data
public class Role {

    @Id
    private String id;

    /**
     * 创建时间
     */
    private Long createdTime = System.currentTimeMillis();

    /**
     * 是否被移除
     */
    private Boolean isRemoved = false;

    /**
     * 角色名,用于权限校验
     */
    private String name;

    /**
     * 角色中文名,用于显示
     */
    private String nickname;

    /**
     * 角色描述信息
     */
    private String description;

    /**
     * 是否为内置
     */
    private boolean builtIn = false;

    /**
     * 角色状态,是否已禁用
     */
    private Boolean banned = false;

    /**
     * 角色可进行的操作列表
     */
    private List<JsonPermissions.SimplePermission> permissions;

    /**
     * 角色创建者
     */
    private String proposer;

    /**
     * Spring Security 4.0以上版本角色都默认以'ROLE_'开头
     * @param name
     */
    public void setName(String name) {
        if (name.indexOf("ROLE_") == -1) {
            this.name = "ROLE_" + name;
        } else {
            this.name = name;
        }
    }
}

5. 给用户赋予角色

Spring Security框架提供了一个基础用户接口UserDetails,该接口提供了基本的用户相关的操作,比如获取用户名/密码、用户账号是否过期和用户认证是否过期等,我们定义自己的User类时需要实现该接口。

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.*;

@Data
@NoArgsConstructor
public class User implements UserDetails {

    public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();

    @Id
    private String id;

    /**
     * 创建时间
     */
    private Long createdTime = System.currentTimeMillis();

    /**
     * 用户登录名
     */
    private String username;

    /**
     * 用户真实姓名
     */
    private String realName;

    /**
     * 用户登录密码,用户的密码不应该暴露给客户端
     */
    @JsonIgnore
    private String password;

    /**
     * 用户类型
     */
    private String type;

    /**
     * 该用户关联的企业/区块id
     */
    private Map<String, Object> associatedResources = new HashMap<>();

    /**
     * 用户关注的企业列表
     */
    private List<String> favourite = new ArrayList<>();

    /**
     * 用户在系统中的角色列表,将根据角色对用户操作权限进行限制
     */
    private List<String> roles = new ArrayList<>();
    
    public void setPassword(String password) {
        this.password = PASSWORD_ENCODER.encode(password);
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

6. 创建系统的初始角色和超级管理员

如果我们对系统的所有接口都加上了访问限制,那么由谁来作为初始用户登录系统并创建其他用户呢?所以我们需要定义系统的初始角色和初始用户,并在系统启动时将初始角色和初始用户自动录入系统,然后再使用初始用户登录系统去创建其他业务相关的用户。定义系统的超级管理员角色:roles.json

[
  {
    "name":"ROLE_ADMINISTRATOR",
    "nickname":"管理员",
    "description":"系统超级管理员,不允许用户更改",
    "banned":false,
    "state":"normal",
    "permissions":[
        {
            "resourceId":"permission",
            "resourceName":"权限",
            "privileges": {
            "read":"查看",
            "write":"新增",
            "update":"更新",
            "delete":"删除"
            }
        },
        {
            "resourceId":"user",
            "resourceName":"用户",
            "privileges": {
            "read":"查看用户列表",
            "write":"新增用户",
            "import":"导入用户",
            "update":"修改用户信息",
            "delete":"删除用户"
            }
        }
    ]
  }
]

定义系统的初始管理员用户:users.json

[
  {
    "username":"admin",
    "realName":"超超超级管理员",
    "password":"$2a$10$GhI1umKcTHysip4iSFXPXOQG1x9U.4eCWMEFwF/h3LBAt98K4o1B.",
    "number":"admin",
    "type":"system",
    "activated":true,
    "roles":["ROLE_ADMINISTRATOR"]
  }
]

7. 加载系统初始化角色和用户数据

在系统部署时,需要将系统的初始化角色和用户自动加载到数据库中,这样才能正常登录使用。使用@Component@PostConstruct注解在系统启动时自动导入初始化角色和用户。

import com.google.gson.reflect.TypeToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;

/**
 * 系统初始化配置类,主要用于加载内置数据到目标数据库上
 */
@Component
public class SystemInitializer {

	@Value("${initialzation.file.users:users.json}") private String userFileName;

	@Value("${initialzation.file.roles:roles.json}") private String roleFileName;

	@Autowired
    private UserRepository userRepository;

	@Autowired private RoleRepository roleRepository;

	@PostConstruct
	public boolean initialize() throws Exception {
		try {
			InputStream userInputStream = getClass().getClassLoader().getResourceAsStream(userFileName);
			if(userInputStream == null){
				throw new Exception("initialzation user file not found: " + userFileName);
			}

			InputStream roleInputStream = getClass().getClassLoader().getResourceAsStream(roleFileName);
			if(roleInputStream == null){
				throw new Exception("initialzation role file not found: " + roleFileName);
			}

			//导入初始的系统超级管理员角色
			Type roleTokenType = new TypeToken<ArrayList<Role>>(){}.getType();
			ArrayList<Role> roles = CommonGsonBuilder.create().fromJson(new InputStreamReader(roleInputStream, StandardCharsets.UTF_8), roleTokenType);
			for (Role role: roles) {
				if (roleRepository.findByName(role.getName()) == null) {
					roleRepository.save(role);
				}
			}

			//导入初始的系统管理员用户
			Type teacherTokenType = new TypeToken<ArrayList<User>>(){}.getType();
			ArrayList<User> users = CommonGsonBuilder.create().fromJson(new InputStreamReader(userInputStream, StandardCharsets.UTF_8), teacherTokenType);
			for (User user : users) {
				if (userRepository.findByUsername(user.getUsername()) == null) {
                    userRepository.save(user);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return true;
	}
}

8. 实现自己的UserDetailsService

UserDetailService中自定义加载用户信息,并将用户角色role相关的所有Permissions设置到Authenticationauthorities中以供PermissionEvaluator对用户权限进行判断。注意这里使用了resourceId-privilege的形式进行了拼接后存放。我这里用户信息是存放在MongoDB数据库中的,也可以换成其他的数据库。

import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.security.core
                        
                        
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/xiaoyu90520/article/details/83790111
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢