一 权限管理
1 概述
权限管理,一般指根据系统设置的安全规则或者安全策略,用户只可以访问自己被授权的资源。在权限管理中使用最多的还是功能权限管理中的基于角色访问控制(RBAC,用户➡角色➡权限)。当项目中需要使用权限管理的时候,我们可以选择使用第三方实现好的框架去实现。实现权限管理系统必备的功能:
1.权限管理(自定义权限注解/加载权限)
2.角色管理(新增/编辑/删除/关联权限)
3.用户管理(新增/编辑/删除/关联用户)
4.登录功能(定义登录拦截器/登录逻辑实现/登出功能)
5.权限拦截(定义权限拦截器/拦截逻辑实现)
其中框架可以帮我们做的有:登录功能(密码加密、验证码、记住我),权限拦截(内置很多的拦截器、提供标签/注解/编程方式进行权限认证),其他功能(缓存、会话管理等)
2 常见的权限管理框架
① Apache Shiro
Apache Shiro 是一个强大且易用的 Java 安全框架,它可实现身份验证、授权、密码和会话管理等功能。
②Spring Security
Spring Security 也是目前较为流行的一个安全权限管理框架,它与 Spring 紧密结合在一起。
③ 二者对比
Shiro 比 Spring Security更容易上手使用和理解,Shiro 可以不跟任何的框架或者容器绑定,可独立运行,而Spring Security 则必须要有Spring环境, Shiro 可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西。
二 Shiro
1 Shiro 作用
Shiro 可以帮助我们完成:认证(登录拦截)、授权(权限拦截)、加密、会话管理、与 Web 集成、缓存等。
2 Shiro 架构
Shiro 主要组件包括:Subject,SecurityManager,Authenticator,Authorizer,SessionManager,CacheManager,Cryptography,Realms。
① Subject(用户): 访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体; Subject 一词是一个专业术语,其基本意思是“当前的操作用户”。 在程序任意位置可使用:Subject currentUser = SecurityUtils.getSubject() 获取到subject主体对象,类似 Employee user = UserContext.getUser()
② SecurityManager(安全管理器):它是 shiro 功能实现的核心,负责与后边介绍的其他组件(认证器/授权器/缓存控制器)进行交互,实现 subject 委托的各种功能。有点类似于SpringMVC 中的 DispatcherServlet 前端控制器,负责进行分发调度。
③ Realms(数据源): Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。;可以把Realm 看成 DataSource,即安全数据源。执行认证(登录)和授权(访问控制)时,Shiro 会从应用配置的 Realm 中查找相关的比对数据。以确认用户是否合法,操作是否合理。
④ Authenticator(认证器): 用于认证,从 Realm 数据源取得数据之后进行执行认证流程处理。
⑤ Authorizer(授权器):用户访问控制授权,决定用户是否拥有执行指定操作的权限。
⑥ SessionManager (会话管理器):Shiro 与生俱来就支持会话管理,这在安全类框架中都是独一无二的功能。即便不存在 web 容器环境,shiro 都可以使用自己的会话管理机制,提供相同的会话 API。
⑦ CacheManager (缓存管理器):用于缓存认证授权信息等。
⑧ Cryptography(加密组件): Shiro 提供了一个用于加密解密的工具包。
具体的登录流程为:1. 将帐号和密码封装到Subject中。2.找到SecurityManager(安全管理器)。3.需要认证时安全管理器会找到 Authenticator(认证器)。4.认证器会带着帐号密码找到Realms(数据源),Realms中会有从数据库中查询到的帐号密码,二者比对一致登录成功。(认证器完成比对工作)
具体的授权流程为:1. 将帐号和密码封装到Subject中。2.找到SecurityManager(安全管理器)。3.需要授权时安全管理器会找到Authorizer(授权器)。4.授权器会带着帐号密码找到Realms(数据源),Realms中会有从数据库中查询当前用户的数据,查询当前用户是否有权限,有则放行,无则拦截。(授权器完成放行拦截工作)

三 Shiro 认证
1 环境部署
认证的过程即用户的身份确认过程(登录验证),用户输入账号和密码提交到后台,后台通过访问数据库执行账号密码的正确性校验

导入依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
<scope>provided</scope>
</dependency>
2 基于ini的认证
resources目录下,编写 ini 配置文件:shiro-authc.ini,shiro默认支持的是ini配置的方式(项目中还是会选择xml)如果输入的身份和凭证和 ini 文件中配置的能够匹配,那么登录成功,登录状态为true,反之登录状态为false。
登录失败一般存在两种情况:1.账号错误 org.apache.shiro.authc.UnknownAccountException;2.密码错误 org.apache.shiro.authc.IncorrectCredentialsException
#用户的身份、凭据
[users]
dahuang=666
xiaohuang=888
测试
public class ShiroDemo {
@Test
public void testLoginByIni(){
// 1. 创建安全管理器(Shiro的)
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2. 创建 Realm(数据源)对象,读取资源目录下的 shiro-authc.ini,然后将信息封装成一个 Map 对象
IniRealm realm = new IniRealm("classpath:shiro-authc.ini");
// 3. 将 Realm 对象设置给安全管理器(Realm在安全管理器中(架构))
securityManager.setRealm(realm);
// 4. 将安全管理器设置到上下文对象中(整个的Shiro环境中)
// SecurityUtils中含有一个静态变量securityManager接收传递的值
SecurityUtils.setSecurityManager(securityManager);
// 5. 从上下文对象中获取 Subject 对象
// securityManager与Subject都是从SecurityUtils中获取的
Subject subject = SecurityUtils.getSubject();
// *****验证目前是否已经登录*****
System.out.println("当前用户状态:" + subject.isAuthenticated());
// 6. 封装账号密码到令牌中(此处帐号密码错误会抛异常)
// UnknownAccountException(用户名异常)IncorrectCredentialsException(密码异常)
UsernamePasswordToken token = new UsernamePasswordToken("qg","8887");
// 7. 进行登录操作(携带用户名密码)
subject.login(token);
// *****验证目前是否已经登录*****
System.out.println("当前用户状态:" + subject.isAuthenticated());
// 8. 进行登出操作
subject.logout();
// *****验证目前是否已经登录*****
System.out.println("当前用户状态:" + subject.isAuthenticated());
}
}

0.初始化IniRealm,内部会读取shiro-authc.ini文件并把用户信息封装到Map<String,SimpleAccount>集合中
0.初始化SecurityManager,把InitRealm交给SecurityManager管理
0.将SecurityManager绑定到当前的环境中
1.通过SecurityUtils工具类获取Subject主体对象
2.Subject对象将Token作为参数,调用login方法完成登录
3.Subject.login方法会将请求分发给SecurityManager中的Authenticator认证器
3.Authenticator认证器会获配置好的Realm,然后调用里面获取用户认证信息SimpleAccount的方法.
3.如果这个过程返回的是NULL,则会抛出UnknowAccountException
4.如果SimpleAccount返回不为空,则会拿到Subject传入的token,然后获取其凭证(密码).
4.获取SimpleAccount中的凭证(密码)
4.将UsernamePasswordToken中的凭证(密码)和SimpleAccount中的凭证(密码)做比对.
4.如果一致则登录成功,否则就会抛出IncorrectCredentialsException异常
3 基于自定义Realm的认证
我们需要使用的账户信息通常来自程序或者数据库中, 而不是前面使用到的 ini 文件的配置,因此要做到想去哪里查,就去那里查。此处使用 DataMapper 作为一组假数据模拟数据库
public class DataMapper {
private static Map<String, User> userData = new HashMap<String, User>();
static{
//初始化数据
User u1 = new User("dahuang","666");
User u2 = new User("xiaohuang","888");
userData.put(u1.getUsername(),u1);
userData.put(u2.getUsername(),u2);
}
//提供静态方法,模拟数据库返回数据
public static User getUserByName(String username){
return userData.get(username);
}
}
实体类
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private String username;//用户名
private String password;//密码
}
UserRealm
// 继承Realm下的接口 实现两个方法 一般来说do开头的方法就是模板设计模式
// Realm是数据源 只查数据即可 将数据提交给认证器 认证器去做比对
public class UserRealm extends AuthorizingRealm {
// 授权功能
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取当前登录用户
User user = (User) principals.getPrimaryPrincipal(); //获取在 doGetAuthenticationInfo 中存放在上下文中的对象。
String username = user.getUsername();
// 当前登录用户的角色集合
List<String> roles = DataMapper.getRoleByName(username);
// 当前登录用户的权限集合
List<String> permissions = DataMapper.getPermissionByName(username);
// 需要将当前登录用户的权限和角色集合与返回的SimpleAuthorizationInfo 绑定关系
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roles);
info.addStringPermissions(permissions);
return info;
}
// 认证功能
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 如何获取数据? 返回当前用户名(map的key)
// String username = (String) token.getPrincipal();
// 此处使用它的子类获取token
UsernamePasswordToken token1 = (UsernamePasswordToken) token;
// 通过token获取用户名
String username = token1.getUsername();
// 根据用户名去mapper中查询数据
User user = DataMapper.getUserByName(username);
if(user == null){
return null;
}
// 需要返回一个 SimpleAuthenticationInfo 对象
// 3个参数 (需要存储到上下文环境中的对象(登录成功后向session中存对象),密码信息,Realm名字(这是父类的方法))
return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
}
}
测试
public class ShiroDemo {
@Test
public void testLoginByUserRealm(){
// 1. 创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2. 创建自定义 UserRealm 对象。
UserRealm realm = new UserRealm();
// 3. 将 Realm 对象设置给安全管理器
securityManager.setRealm(realm);
// 4. 将安全管理器设置到上下文对象中
SecurityUtils.setSecurityManager(securityManager);
// 5. 从上下文对象中获取 Subject 对象
Subject subject = SecurityUtils.getSubject();
// 6. 封装账号密码
UsernamePasswordToken token = new UsernamePasswordToken("qg","abc");
// 7. 进行登录操作
subject.login(token);
// *****验证目前是否已经登录*****
System.out.println("当前用户状态:" + subject.isAuthenticated());
// 获取当前登录对象
System.out.println("当前登录用户为:" + subject.getPrincipal());
}
}


1177

被折叠的 条评论
为什么被折叠?



