欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RBAC基于角色的权限访问控制

程序员文章站 2024-03-19 14:05:58
...

一 Shiro简介
Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障。

Shiro的具体功能点如下:
(1)身份认证/登录,验证用户是不是拥有相应的身份;
(2)授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
(3)会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
(4)加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
(5)Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
(6)shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
(7)提供测试支持;
(8)允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
(9)记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

二 身份认证Authentication
1 工作流程

2 认证主体 Subject
访问系统的用户,进行认证的都称为主体;
认证主体包含两个信息:
Principals:身份。可以是用户名,邮件,手机号码等等,用来标识一个登录主体身份;
Credentials:凭证。是只有主体自己知道的安全信息,常见有密码,数字证书等等

3 realm
realm就是一个域,Shiro就是从realm中获取验证数据的,比如,上一节中提到的.ini文件,realm有很多种,如text realm、jdbc realm、jndi realm、自定义realm等。

三 授权Authorization
权限认证,也叫访问控制,即在应用中控制谁能访问哪些资源

1 权限认证的要素
最核心的三个要素是:权限,角色和用户
权限(permission):即操作资源的权利,比如访问某个页面,以及对某个模块的数据的添加,修改,删除,查看的权利;
角色(role):指的是用户担任的的角色,一个角色可以有多个权限;
用户(user):在Shiro 中,代表访问系统的用户。

2 访问控制
1)基于角色的访问控制
授权过程是通过判断角色来完成的,哪个角色可以做这件事

2)基于资源(权限)的访问控制

四 与spring boot整合
1 引入jar

org.apache.shiro
shiro-spring
1.3.2

2 自定义realm实现认证和授权
1) 建立表
用户表、角色表、资源表、用户角色对应表、角色资源对应表

2) 自定义realm
自定义realm需要继承AuthorizingRealm类
重写如下两个方法:
doGetAuthenticationInfo()方法:用来验证当前登录的用户,获取认证信息
doGetAuthorizationInfo()方法:用来为当前登陆成功的用户授予权限和角色(已经登陆成功了)
public class MyRealm extends AuthorizingRealm {

// 从数据库获取权限信息,并设置
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    String username = (String) SecurityUtils.getSubject().getPrincipal();
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    Set<String> stringSet = new HashSet<>();
    stringSet.add("user:show");
    stringSet.add("user:admin");
    info.setStringPermissions(stringSet);
    return info;
}

// 从数据库获取身份信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    String userName = (String) authenticationToken.getPrincipal();
    String userPwd = new String((char[]) authenticationToken.getCredentials());
    //根据用户名从数据库获取密码
    // ......
    String password = "123";
    if (userName == null) {
        throw new AccountException("用户名不正确");
    } else if (!userPwd.equals(password )) {
        throw new AccountException("密码不正确");
    }
    return new SimpleAuthenticationInfo(userName, password, getName());
}

}

3 shiro的配置类
@Configuration
public class ShiroConfig {

// shiro资源过滤配置
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    // 未登陆情况下,访问需要登陆后才能访问资源时,跳转到指定资源(比如登陆页面)
    shiroFilterFactoryBean.setLoginUrl("/login");
    // 当没有权限访问某些资源时,跳转到的资源
    shiroFilterFactoryBean.setUnauthorizedUrl("/notPermision");
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    // authc:必须认证通过才可以访问;
    // anon: 匿名访问
    filterChainDefinitionMap.put("/js/**", "anon");
    filterChainDefinitionMap.put("/css/**", "anon");
    filterChainDefinitionMap.put("/login", "anon");

    filterChainDefinitionMap.put("/admin/**", "authc");
    filterChainDefinitionMap.put("/user/**", "authc");

    //必须放在所有权限设置的最后,匹配的是不满足前面匹配条件的资源
    filterChainDefinitionMap.put("/**", "authc");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;

}

// 安全管理对象
@Bean
public SecurityManager securityManager() {
    DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
    defaultSecurityManager.setRealm(customRealm());
    return defaultSecurityManager;
}

// 自定义realm对象
@Bean
public MyRealm customRealm() {
    MyRealm customRealm = new MyRealm();
    return customRealm;
}

}
4 内置的过滤器
过滤器名 对应的类
anon(匿名) org.apache.shiro.web.filter.authc.AnonymousFilter
authc(身份验证) org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic(http基本验证) org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout(退出) org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation(不创建session) org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms(许可验证) org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port(端口验证) org.apache.shiro.web.filter.authz.PortFilter
rest(rest方面) org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles(权限验证) org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl(ssl方面) org.apache.shiro.web.filter.authz.SslFilter
user(用户方面) org.apache.shiro.web.filter.authc.UserFilter

5 shiro注解
@RequiresPermissions :要求当前Subject在执行被注解的方法时具备一个或多个对应的权限。
@RequiresRoles :要求当前Subject在执行被注解的方法时具备所有的角色,否则将抛出AuthorizationException异常。
@RequiresAuthentication:要求在访问或调用被注解的类/实例/方法时,Subject在当前的session中已经被验证。
使用注解时,需要在配置类中增加:
@Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

@Bean
@DependsOn({“lifecycleBeanPostProcessor”})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}

针对使用注解的情况,如果验证不通过,可以使用全局异常类进行处理
6 加密处理
配置类中,增加如下内容
@Bean(name = “credentialsMatcher”)
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName(“md5”);
// 散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(1);
// storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}

创建自定义realm对象时
// 自定义realm对象
@Bean
public MyRealm customRealm() {
MyRealm customRealm = new MyRealm();
// 设置密码匹配器
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}

生成md5处理后的密码可以使用如下代码:
String md5Pwd = new SimpleHash(“MD5”, pwd, ByteSource.Util.bytes(“salt123”), 1).toHex();

自定义realm的类中,返回认证信息对象时,使用如下代码:
info = new SimpleAuthenticationInfo(userName, user.getPassword(), ByteSource.Util.bytes(“haha”), this.getName());

7 缓存
本例使用redis作为缓存,缓存用户权限相关信息
导入jar,注意版本

org.crazycake shiro-redis 2.4.2.1-RELEASE

@Value("${spring.redis.host}")
private String host;

@Value("${spring.redis.port}")
private int port;

@Value("${spring.redis.password}")
private String password;

public RedisManager redisManager() {
    RedisManager redisManager = new RedisManager();
    System.out.println(host);
    redisManager.setHost("106.12.48.175");
    redisManager.setPort(6379);
    redisManager.setPassword("redis");
    return redisManager;
}

public RedisCacheManager cacheManager() {
    RedisCacheManager redisCacheManager = new RedisCacheManager();
    redisCacheManager.setRedisManager(redisManager());
    // 配置过期时间

// redisCacheManager.setExpire(1800);
return redisCacheManager;
}

@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(customRealm());
// 自定义缓存实现 使用redis
defaultSecurityManager.setCacheManager(cacheManager());

    return defaultSecurityManager;
}

8 退出功能
一般需要自定义退出时跳转的页面,所以可以重写shiro的logoutFilter。这种情况下,不需要写退出的控制器资源。
package com.rr.shiro;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.LogoutFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class MyShiroLogoutFilter extends LogoutFilter {

@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    // 获取主体对象
    Subject subject = getSubject(request,response);

    //登出
    subject.logout();

    //获取登出后重定向到的地址
    String redirectUrl = getRedirectUrl(request,response,subject);
    //重定向
    issueRedirect(request,response,redirectUrl);
    return false;
}

}

在shiroFiter中增加
// 存放自定义的filter
LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
//配置自定义登出 覆盖 logout 之前默认的LogoutFilter
filtersMap.put(“logout”, shiroLogoutFilter());
shiroFilterFactoryBean.setFilters(filtersMap);

filterChainDefinitionMap.put("/logout", “logout”);