@[TOC]
Shiro框架介绍
Apache Shiro是一个强大且易用的Java安全框架,它执行身份验证、授权、密码和会话管理。Shiro框架通过其三个核心组件:Subject、SecurityManager和Realms,提供了一个通用的安全认证框架。
Shiro官方:http://shiro.apache.org
Shiro的核心架构图
Shiro 基本使用
认证流程:
授权流程:
SimpleAccountRealm
认证代码:
@Test
public void authen() {
//认证的发起者(subject), SecurityManager, Realm
//1. 准备Realm(基于内存存储用户信息)
SimpleAccountRealm realm = new SimpleAccountRealm();
realm.addAccount("admin", "admin", "超级管理员", "商家");
//2. 准备SecurityManager
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//3. SecurityManager和Realm建立连接
securityManager.setRealm(realm);
//4. subject和SecurityManager建立联系
SecurityUtils.setSecurityManager(securityManager);
//5. 声明subject
Subject subject = SecurityUtils.getSubject();
//6. 发起认证
subject.login(new UsernamePasswordToken("admin", "admin"));
// 如果认证时,用户名错误,抛出:org.apache.shiro.authc.UnknownAccountException异常
// 如果认证时,密码错误,抛出:org.apache.shiro.authc.IncorrectCredentialsException:
//7. 判断是否认证成功
System.out.println(subject.isAuthenticated());
//8. 退出登录后再判断
// subject.logout();
// System.out.println("logout方法执行后,认证的状态:" + subject.isAuthenticated());
//9. 授权是在认证成功之后的操作!!!
// SimpleAccountRealm只支持角色的授权
System.out.println("是否拥有超级管理员角色:" + subject.hasRole("超级管理员"));
subject.checkRole("商家");
// check方法校验角色时,如果没有指定角色,会抛出异常:org.apache.shiro.authz.UnauthorizedException: Subject does not have role [角色信息]
}
IniRealm
基于文件存储用户名、密码、角色等信息
准备一个.ini文件,存储用户信息,并且IniRealm支持权限校验
[users]
username=password,role1,role2
admin=admin,超级管理员,运营
[roles]
role1=perm1,perm2
超级管理员=user:add,user:update,user:delete
代码:
@Test
public void authen(){
//1. 构建IniRealm
IniRealm realm = new IniRealm("classpath:shiro.ini");
//2. 构建SecurityManager绑定Realm
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
//3. 基于SecurityUtils绑定SecurityManager并声明subject
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
//4. 认证操作
subject.login(new UsernamePasswordToken("admin","admin"));
//5. 角色校验
// 超级管理员
System.out.println(subject.hasRole("超级管理员"));
subject.checkRole("运营");
//6. 权限校验
System.out.println(subject.isPermitted("user:update"));
// 如果没有响应的权限,就抛出异常:UnauthorizedException: Subject does not have permission [user:select]
subject.checkPermission("user:delete");
}
JdbcRealm
基于数据库存储用户名、密码、角色等信息。
用户认证、授权时推荐的表结构设计,经典五张表!
代码:
@Test
public void authen(){
//1. 构建IniRealm
JdbcRealm realm = new JdbcRealm();
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///shiro");
dataSource.setUsername("root");
dataSource.setPassword("root");
realm.setDataSource(dataSource);
realm.setPermissionsLookupEnabled(true);
//2. 构建SecurityManager绑定Realm
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
//3. 基于SecurityUtils绑定SecurityManager并声明subject
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
//4. 认证操作
subject.login(new UsernamePasswordToken("admin","admin"));
//5. 授权操作(角色)
System.out.println(subject.hasRole("超级管1理员"));
//6. 授权操作(权限)
System.out.println(subject.isPermitted("user:add"));
}
SQL构建代码
DROP TABLE IF EXISTS `roles_permissions`;
CREATE TABLE `roles_permissions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`permission` varchar(128) NOT NULL,
`role_name` varchar(128) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of roles_permissions
-- ----------------------------
INSERT INTO `roles_permissions` VALUES ('1', 'user:add', '超级管理员');
INSERT INTO `roles_permissions` VALUES ('2', 'user:update', '超级管理员');
INSERT INTO `roles_permissions` VALUES ('3', 'user:select', '运营');
-- ----------------------------
-- Table structure for `users`
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password` varchar(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES ('1', 'admin', 'admin');
-- ----------------------------
-- Table structure for `user_roles`
-- ----------------------------
DROP TABLE IF EXISTS `user_roles`;
CREATE TABLE `user_roles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_name` varchar(128) NOT NULL,
`username` varchar(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of user_roles
-- ----------------------------
INSERT INTO `user_roles` VALUES ('1', '超级管理员', 'admin');
INSERT INTO `user_roles` VALUES ('2', '运营', 'admin');
CustomRealm(自定义Realm)
仿照JdbcRealm实现一个自定义的Realm对象
- 声明POJO类,继承AuthorizingRealm
public class CustomRealm extends AuthorizingRealm { …………………… }
重写doGetAuthenticationInfo方法(认证)
/** * 认证方法,只需要完成用户名校验即可,密码校验由Shiro内部完成 * @param token 用户传入的用户名和密码 * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //1. 基于Token获取用户名 String username = (String) token.getPrincipal(); //2. 判断用户名(非空) if(StringUtils.isEmpty(username)){ // 返回null,会默认抛出一个异常,org.apache.shiro.authc.UnknownAccountException return null; } //3. 如果用户名不为null,基于用户名查询用户信息 User user = this.findUserByUsername(username); //4. 判断user对象是否为null if(user == null){ return null; } //5. 声明AuthenticationInfo对象,并填充用户信息 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"CustomRealm!!"); //6. 返回info return info; } // 模拟数据库操作 private User findUserByUsername(String username) { if("admin".equals(username)){ User user = new User(); user.setId(1); user.setUsername("admin"); user.setPassword("admin"); return user; } return null; }
- 重写doGetAuthenticationInfo方法(密码加密加盐)
```java{ HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("MD5"); matcher.setHashIterations(1024); this.setCredentialsMatcher(matcher); }
/**
* 认证方法,只需要完成用户名校验即可,密码校验由Shiro内部完成
* @param token 用户传入的用户名和密码
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1. 基于Token获取用户名
String username = (String) token.getPrincipal();
//2. 判断用户名(非空)
if(StringUtils.isEmpty(username)){
// 返回null,会默认抛出一个异常,org.apache.shiro.authc.UnknownAccountException
return null;
}
//3. 如果用户名不为null,基于用户名查询用户信息
User user = this.findUserByUsername(username);
//4. 判断user对象是否为null
if(user == null){
return null;
}
//5. 声明AuthenticationInfo对象,并填充用户信息
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"CustomRealm!!");
// 设置盐!
info.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
//6. 返回info
return info;
}
// 模拟数据库操作
private User findUserByUsername(String username) {
if("admin".equals(username)){
User user = new User();
user.setId(1);
user.setUsername("admin");
user.setPassword("1ebc4dcaf1e21b814ece65f27531f1a9");
user.setSalt("weruiothergjkdfnbgjkdfngjkdf");
return user;
}
return null;
}
```
重写doGetAuthorizationInfo方法(授权)
// 授权方法,授权是在认证之后的操作 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //1. 获取认证用户的信息 User user = (User) principals.getPrimaryPrincipal(); //2. 基于用户信息获取当前用户拥有的角色。 Set<String> roleSet = this.findRolesByUser(); //3. 基于用户拥有的角色查询权限信息 Set<String> permSet = this.findPermsByRoleSet(roleSet); //4. 声明AuthorizationInfo对象作为返回值,传入角色信息和权限信息 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roleSet); info.setStringPermissions(permSet); //5. 返回 return info; } private Set<String> findPermsByRoleSet(Set<String> roleSet) { Set<String> set = new HashSet<>(); set.add("user:add"); set.add("user:update"); return set; } private Set<String> findRolesByUser() { Set<String> set = new HashSet<>(); set.add("超级管理员"); set.add("运营"); return set; }