社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
Spring Security
实现权限管理系统稍微复杂一点的后台系统都会涉及到用户权限管理。何谓用户权限?我的理解就是,权限就是对数据(系统的实体类
)和数据可进行的操作(增删查改
)的集中管理。要构建一个可用的权限管理系统,涉及到三个核心类:一个是用户User
,一个是角色Role
,最后是权限Permission
。接下来本文将介绍如何基于Spring Security 4.0
一步一步构建起一个接口级别的权限管理系统。
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>
权限是资源以及可对资源进行的操作的一个集合。对于我们的系统来说,几乎所有实体类都可以看作一个资源,而常见的操作也就是增删查改
四类,当然,根据我们实际的业务需要,可能还有其他的特殊操作,比如我们这里加了一个导入用户
的操作。这里简单列举两个基本的权限集合:
[
{
"resourceId":"permission",
"resourceName":"权限",
"privileges": {
"read":"查看",
"write":"新增",
"update":"更新",
"delete":"删除"
}
},
{
"resourceId":"user",
"resourceName":"用户",
"privileges": {
"read":"查看用户列表",
"write":"新增用户",
"import":"导入用户",
"update":"修改用户信息",
"delete":"删除用户"
}
}
]
在对权限的定义中,关键是resourceId
和privileges
的key
,后续将使用这两者结合来对用户的权限进行判断。我这里使用resourceId-privilege
这样的形式来唯一表示对某个资源进行的某个操作。
资源与操作权限集合类定义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;
}
}
}
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;
}
}
如果我们对系统的所有接口都加上了访问限制,那么由谁来作为初始用户登录系统并创建其他用户呢?所以我们需要定义系统的初始角色和初始用户,并在系统启动时将初始角色和初始用户自动录入系统,然后再使用初始用户登录系统去创建其他业务相关的用户。定义系统的超级管理员角色: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"]
}
]
在系统部署时,需要将系统的初始化角色和用户自动加载到数据库中,这样才能正常登录使用。使用@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;
}
}
在UserDetailService
中自定义加载用户信息,并将用户角色role
相关的所有Permissions
设置到Authentication
的authorities
中以供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
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
-
发表于 2020-03-01 22:16:21
- 阅读 ( 1156 )
- 分类:研发管理
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!