1 Shiro简介
Shiro是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。
Authentication:有时也简称为“登录”,这是一个证明用户他们是谁的行为。
Authorization:访问控制的过程,也就是决定“谁”去访问“什么”。
Session Management:管理用户特定的会话,即使在非 Web 或 EJB 应用程序。
Cryptography:通过使用加密算法保持数据安全同时易于使用。
2 初识Shiro。
一个官方简单的例子。代码说明均在注释中,不在重复。
具体代码:
public class Quickstart {
private static final transient Logger log = LoggerFactory
.getLogger(Quickstart.class);
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 获取当前用户
Subject currentUser = SecurityUtils.getSubject();
// 此处的Session是Shiro中的。不是web中的。
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// 判断当前用户是否通过认证
if (!currentUser.isAuthenticated()) {
// 当前用户没通过认证,使用UsernamePasswordToken封装用户和密码
UsernamePasswordToken token = new UsernamePasswordToken(
"lonestarr1", "vespa");
token.setRememberMe(true);
try {
// 执行认证操作,即登录,此处模拟的是遍历配置文件中各个组合。
currentUser.login(token);
} catch (UnknownAccountException uae) {
// token.getPrincipal()获取当前用户的用户名
log.info("There is no user with username of "
+ token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal()
+ " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal()
+ " is locked. "
+ "Please contact your administrator to unlock it.");
} catch (AuthenticationException ae) {
}
}
// 输出当前用户的用户名
log.info("User [" + currentUser.getPrincipal()
+ "] logged in successfully.");
// 检查当前用户是否为改角色
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
// 检测当前用户是否有指定操作的权限
if (currentUser.isPermitted("lightsaber:weild")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
// 检测某一类型的实例是否具有某一具体操作的权限
// goodguy = winnebago:drive:eagle5:表示当前用户可以对winnebago实体的eagle5实例做drive操作
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. "
+ "Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
// 退出
currentUser.logout();
}
}
Apache Shiro
简单、灵活
可脱离Spring
SpringSecurity
复杂、笨重
不可脱离Spring
整体架构
认证
创建SecurityManager-》主体提交认证-》SecurityManager认证-》Authenticator认证-》Realm认证
授权
创建SecurityManager-》主体授权-》SecurityManager授权-》Authorizer授权-》Realm获取角色权限数据。
提供了三种实现方式
- SimpleAccountRealm
//构造被校验的数据
SimpleAccountRealm simpleAccountRealm=new SimpleAccountRealm();
{
simpleAccountRealm.addAccount("t1","123","admin");
}
/**
* 认证
*/
@Test
public void authenticationTest(){
//构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm);
//主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken("t1","123");
subject.login(token);
logger.info("是否已认证:"+subject.isAuthenticated());
subject.logout();
logger.info("是否已认证:"+subject.isAuthenticated());
}
/**
* 授权
*/
@Test
public void authenticationTest02(){
//构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm);
//主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken("t1","123");
subject.login(token);
logger.info("是否认证通过:"+subject.isAuthenticated());
//校验是否有这样的权限
try{
subject.checkRole("admin");
logger.info("有权限");
}catch (AuthorizationException e){
logger.error("没有权限");
}
}
角色校验不通过,将会抛异常
2. IniRealm
创建user.ini文件
[users]
Mark=123456,admin
[roles]
admin=user:delete,user:update
[users]中设置用户名为Mark,密码为123456,角色为admin,
[roles]中设置admin有user:delete,user:update的权限
测试代码
@Test
public void testIniRealm(){
IniRealm iniRealm=new IniRealm("classpath:user.ini");
//构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
defaultSecurityManager.setRealm(iniRealm);
//主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken("Mark","123456");
subject.login(token);
logger.info("是否已认证:"+subject.isAuthenticated());
//是否具备角色
subject.checkRole("admin");
//是否具备指定操作的权限
subject.checkPermission("user:update");
}
- JDBCRealm
JDBCRealm采用默认的表结构,默认的sql执行语句,表结构如下:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for roles_permissions
-- ----------------------------
DROP TABLE IF EXISTS `roles_permissions`;
CREATE TABLE `roles_permissions` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`permission` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `idx_roles_permissions`(`role_name`, `permission`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of roles_permissions
-- ----------------------------
INSERT INTO `roles_permissions` VALUES (1, 'admin', 'user:select');
-- ----------------------------
-- Table structure for user_roles
-- ----------------------------
DROP TABLE IF EXISTS `user_roles`;
CREATE TABLE `user_roles` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`role_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `idx_user_roles`(`username`, `role_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of user_roles
-- ----------------------------
INSERT INTO `user_roles` VALUES (1, 'ship', 'admin');
INSERT INTO `user_roles` VALUES (2, 'ship', 'member');
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password_salt` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `idx_users_username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (1, 'ship', '123', NULL);
SET FOREIGN_KEY_CHECKS = 1;
测试代码
private static Logger logger = LoggerFactory.getLogger(JDBCRealmTest.class);
DruidDataSource dataSource=new DruidDataSource();
{
dataSource.setUrl("jdbc:mysql://192.168.209.101:3306/shiro");
dataSource.setUsername("root");
dataSource.setPassword("123123");
}
@Test
public void jdbcRealmTest(){
JdbcRealm jdbcRealm=new JdbcRealm();
jdbcRealm.setDataSource(dataSource);
//需要打开权限检查
jdbcRealm.setPermissionsLookupEnabled(true);
//自定义SQL认证
String authSql=" SELECT password FROM users WHERE username LIKE CONCAT('%',?,'%') ";
jdbcRealm.setAuthenticationQuery(authSql);
//自定义SQL角色校验
String roleSql=" SELECT role_name FROM user_roles WHERE username LIKE CONCAT('%',? ,'%') ";
jdbcRealm.setUserRolesQuery(roleSql);
//构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
defaultSecurityManager.setRealm(jdbcRealm);
//主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken("s","123");
subject.login(token);
logger.info("是否已认证:"+subject.isAuthenticated());
//校验是否为该角色
subject.checkRole("admin");
//校验是否有该操作的权限
subject.checkPermission("user:select");
}
需要特别注意的时,jdbcRealm需要打开权限校验,不然SQL不会执行。
SQL参考:https://www.jianshu.com/p/1c9a63ee6664
自定义Realm,继承AuthorizingRealm
public class CustomerRealm extends AuthorizingRealm {
Map<String,String> userMap=new HashMap<>();
{
userMap.put("t1","123");
super.setName("customerRealm");
}
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName=(String)principals.getPrimaryPrincipal();
//------------------------------
//模拟数据库通过用户名获取角色数据
Set<String> roles=new HashSet<>();
roles.add("admin");
roles.add("user");
//模拟通过用户名获取权限数据
Set<String> permissions=new HashSet<>();
permissions.add("user:delete");
permissions.add("user:add");
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从主体传过来的认证信息中,获取用户名
String userName=(String)authenticationToken.getPrincipal();
//模拟通过用户名到数据库中获取凭证
String password=userMap.get(userName);
if(password==null){
return null;
}
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo("t1",password,"customerRealm");
return authenticationInfo;
}
}
测试
public class CustomerRealmTest {
private static Logger logger = LoggerFactory.getLogger(CustomerRealmTest.class);
public static void main(String[] args){
CustomerRealm customerRealm=new CustomerRealm();
//构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
defaultSecurityManager.setRealm(customerRealm);
//主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken("t1","123");
subject.login(token);
logger.info("是否已认证:"+subject.isAuthenticated());
//验证是否为admin角色
subject.checkRole("admin");
}
}
加密采用shiro的加密方式
Customer部分修改
public class CustomerRealm extends AuthorizingRealm {
Map<String,String> userMap=new HashMap<>();
{
//userMap.put("t1","123");
//MD5加密后
userMap.put("t1","b38ac18016d255ee4e9a364fb6490ebf");
super.setName("customerRealm");
}
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName=(String)principals.getPrimaryPrincipal();
//------------------------------
//模拟数据库通过用户名获取角色数据
Set<String> roles=new HashSet<>();
roles.add("admin");
roles.add("user");
//模拟通过用户名获取权限数据
Set<String> permissions=new HashSet<>();
permissions.add("user:delete");
permissions.add("user:add");
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从主体传过来的认证信息中,获取用户名
String userName=(String)authenticationToken.getPrincipal();
//模拟通过用户名到数据库中获取凭证
String password=userMap.get(userName);
if(password==null){
return null;
}
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(userName,password,"customerRealm");
//----------------设置盐----------
//authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("Mark"));
return authenticationInfo;
}
public static void main(String[] args){
Md5Hash md5Hash=new Md5Hash("123","Mark");
System.out.println(md5Hash.toHex());
}
}
main函数生成加密后的密码,添加到map中。
测试
public class AlgorithRealm {
private static Logger logger = LoggerFactory.getLogger(CustomerRealmTest.class);
public static void main(String[] args){
CustomerRealm customerRealm=new CustomerRealm();
//---------------------设置密码加密方式------------------------
HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1);
customerRealm.setCredentialsMatcher(matcher);
//构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
defaultSecurityManager.setRealm(customerRealm);
//主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken("t1","1233");
subject.login(token);
logger.info("是否已认证:"+subject.isAuthenticated());
//验证是否为admin角色
//subject.checkRole("admin");
}
}
本文部分摘自官方法文档,详情请自行查阅官方文档。