前言
什么是权限管理?
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,
权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问
而且只能访问自己被授权的资源。权限管理包括用户身份认证和授权两部分,简称认证授权。
对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
什么是身份认证?
身份认证,就是判断一个用户是否为合法用户的处理过程。
最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,
看其是否与系统中存储的该用户的用户名和口令一致, 来判断用户身份是否正确。
对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
什么是授权?
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,
对于某些资源没有权限是无法访问的
什么是Shiro?
Shiro是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。
使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序
从最小的移动应用程序到最大的web和企业应用程序。
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,
实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
Shiro的核心架构
Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为
当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。
Subject在shiro中是一 个接口,接口中定义了很多认证授相关的方法,外部程序通过subjec进行认证授,
而subject是通过SecurityManager安全 管理器进行认证授权
Shiro中的认证
Shiro 中认证的关键对象
Subject: 主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
Principal: 身份信息
是主体(subject) 进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,
一个主体可以有多个身份,但是必须有一个主身份(Primary Principal) .
Credential: 凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
认证流程
Shiro 入门
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
</dependencies>
Shiro.ini
[users]
vivi=123
bingbing=234
public static void main(String[] args) {
// 创建安全管理器对象
SessionsSecurityManager manager = new DefaultSecurityManager();
// 给安全管理器对象设置 Realm
manager.setRealm(new IniRealm("classpath:shiro.ini"));
// 给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(manager);
// 关键对象 Subject 主体
Subject subject = SecurityUtils.getSubject();
// 创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("vivi", "1233");
try {
// 用户认证
subject.login(token);
// 用户认证状态
System.out.println(subject.isAuthenticated());
System.out.println("ok");
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
}
}
认证
// 自定义 Realm
public class CustomerRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo
(PrincipalCollection principals) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// 获取用户名
String username = (String) token.getPrincipal();
if ("vivi".equals(username)) {
return new SimpleAuthenticationInfo(username,
"3416387f60900122a999d4e05c104450", this.getName());
}
return null;
}
}
public class TestCustomerRealm {
public static void main(String[] args) {
// 创建安全管理器
DefaultSecurityManager manager = new DefaultSecurityManager();
// 设置自定义 Realm
manager.setRealm(new CustomerRealm());
// 设置安全管理器
SecurityUtils.setSecurityManager(manager);
// 获得主体对象
Subject subject = SecurityUtils.getSubject();
// 创建 token
String password = new Md5Hash("123", "%^&(*(()", 1024).toHex();
UsernamePasswordToken token = new UsernamePasswordToken("vivi", password);
try {
// 认证
subject.login(token);
System.out.println("ok");
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,
对于某些资源没有权限是无法访问的。
关键对象
授权可简单理解为who对what(which)进行How操作:
Who,即主体(Subject) .主体需要访问系统中的资源。
What,即资源(Resource), 如系统菜单、页面、按钮、类方法、系统商品信息等。
资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,
编号为001的商品信息也属于资源实例。
How,权限/许可(Permission) ,规定了主体对资源的操作许可,权限离开资源没有意义,
如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,
通过权限可知主体对哪些资源都有哪些操作许可。
授权流程
授权方式
基于角色的访问控制
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
基于资源的访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制
权限字符串
权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,
“.”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
用户创建权限: user:create, 或user:create:*
用户修改实例001的权限: user:update:001
用户实例001的所有权限: user:*: 001
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
{
String username = (String) principals.getPrimaryPrincipal();
// 查询角色信息
SimpleAuthorizationInfo autho = new SimpleAuthorizationInfo();
// 配置查询的角色信息
autho.addRole("admin");
// 配置查询权限字符串
autho.addStringPermission("user:*:*");
return autho;
}
// 基于角色
System.out.println(subject.hasRole("admin"));
// 基于权限字符串
System.out.println(subject.isPermitted("user:*:create"));
Shiro整合SpringBoot
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version><!--$NO-MVN-MAN-VER$ -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
</dependencies>
server.port=80
server.servlet.context-path=/dev
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///data?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
// jpa的正向工程
spring.jpa.hibernate.ddl-auto=update
// 显示sq1
spring.jpa.show-sql=true
// 开启SQL格式化
spring.jpa.properties.hibernate.format_sql=true
// 指定数据库类型
spring.jpa.database=mysql
// 数据库方言:支持的特有语法
spring.jpa.database-platform=org.hibernate.dialect.MySQL55Dialect
// 开启懒加载,不开启在实体类中使用 FetchType.LAZY 会报错
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
ShiroConfig.java
@Configuration
public class ShiroConfig {
// 自定义ShiroFilter
@Bean
public ShiroFilterFactoryBean getFilterFactoryBean(DefaultWebSecurityManager manager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(manager);
Map<String,String> map = new HashMap<>();
// authc 表示认证 anon 不需要认证授权
map.put("/users/**", "anon");
map.put("/ui/**", "anon");
map.put("/**", "authc");
factoryBean.setFilterChainDefinitionMap(map);
// 修改用户认证的路径
factoryBean.setLoginUrl("/ui/login");
return factoryBean;
}
@Bean
public DefaultWebSecurityManager getsSecurityManager(@Qualifier("viRealm") Realm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
SecurityUtils.setSecurityManager(securityManager);
securityManager.setRealm(realm);
return securityManager;
}
@Bean("viRealm")
public Realm getRealm() {
CustomerRealm realm = new CustomerRealm();
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("MD5");
matcher.setHashIterations(1024);
realm.setCredentialsMatcher(matcher);
return realm;
}
}
CustomerRealm.java
public class CustomerRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
{
String username = (String) principals.getPrimaryPrincipal();
if (Objects.isNull(username)) {
return null;
}
SimpleAuthorizationInfo autho = new SimpleAuthorizationInfo();
autho.addRole("admin");
autho.addStringPermission("user:*:*");
return autho;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
String username = (String) token.getPrincipal();
User user = userService.findByUsername(username);
if (!Objects.isNull(user)) {
return new SimpleAuthenticationInfo( //
user.getUsername(), //
user.getPassword(), //
ByteSource.Util.bytes(user.getSalt()), //
this.getName());
}
return null;
}
}
UserController.java
@RestController
@RequestMapping("users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("login")
public String login(String username, String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (UnknownAccountException e) {
System.out.println(e.getMessage());
return "login err username is error";
} catch (IncorrectCredentialsException e) {
System.out.println(e.getMessage());
return "login err password is error";
}
return "login ok";
}
@PostMapping("register")
public String register(String username, String password) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
userService.save(user);
return "ok";
}
@GetMapping("logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "logout ok..";
}
@DeleteMapping
// TODO
@RequiresRoles({"user", "admin"})
@RequiresPermissions("user:*:*")
public String deleteUser() {
return "delete ok";
}
}