文章目录
- 1. SpringBoot集成shiro快速入门
- 1. shiro 用户认证
- 2. shiro用户授权
- 2. SpringBoot 使用IniRealm进行认证授权
- 3. Spring Boot 使用 JdbcRealm 进行认证授权
- 1. 数据库驱动
- 2. 数据库表结构
- 3. 创建 testJdbcRealm方法
- 4. 更改数据库表名
- 4. Spring Boot 使用自定义 Realm 进行认证授权
- 5. SpringBoot整合shiro之盐值加密认证详解
1. SpringBoot集成shiro快速入门
导入shiro依赖
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.1</version>
</dependency>
1. shiro 用户认证
@SpringBootTest
class ShiroApplicationTests {@Testvoid authentication() {//构建SecurityManager环境DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();//创建一个SimpleAccountRealm 域SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();//添加一个测试账号(后面可以做成读取动态读取数据库)simpleAccountRealm.addAccount("zhangsan","123456");//设置RealmdefaultSecurityManager.setRealm(simpleAccountRealm);SecurityUtils.setSecurityManager(defaultSecurityManager);//获取主体Subject subject = SecurityUtils.getSubject();//用户名和密码UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");try {// 进行登录,提交认证subject.login(token);}catch (IncorrectCredentialsException exception){System.out.println("用户名密码不匹配");}catch (LockedAccountException exception){System.out.println("账号已被锁定");}catch (DisabledAccountException exception){System.out.println("账号已被禁用");}catch (UnknownAccountException exception){System.out.println("用户不存在");}catch (UnauthorizedException ae ) {System.out.println("用户没有权限");}System.out.println("用户的认证状态:isAuthenticated="+subject.isAuthenticated());System.out.println("执行logout()方法");subject.logout();System.out.println("用户的认证状态:isAuthenticated="+subject.isAuthenticated());}
}
用户的认证状态:isAuthenticated=true
执行logout()方法
用户的认证状态:isAuthenticated=false
将密码改成:
simpleAccountRealm.addAccount("zhangsan","1234");
用户名密码不匹配
用户的认证状态:isAuthenticated=false
执行logout()方法
用户的认证状态:isAuthenticated=false
将用户名改成lisi:
simpleAccountRealm.addAccount("lisi","1234");
用户不存在
用户的认证状态:isAuthenticated=false
执行logout()方法
用户的认证状态:isAuthenticated=false
2. shiro用户授权
做权限管理的时候,分为两块,一个认证,一个是授权,认证是判断用户是否账号密 码正确,授权是判断用户登入以后有什么权限。
@SpringBootTest
class ShiroApplicationTests {@Testpublic void authentication1(){//构建SecurityManager环境DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();//创建一个SimpleAccountRealm 域SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();//添加一个测试账号、和所拥有的角色(后面可以做成读取动态读取数据库),user拥有admin角色simpleAccountRealm.addAccount("zhangsan","123456","admin","user");//设置realmdefaultSecurityManager.setRealm(simpleAccountRealm);SecurityUtils.setSecurityManager(defaultSecurityManager);//获取主体Subject subject = SecurityUtils.getSubject();//用户名和密码(用户输入的用户名密码)生成tokenUsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");try {// 登录,提交认证subject.login(token);//检测用户是否拥有传入的角色,即只要有一个不是用户所拥有的角色就会抛出异常。subject.checkRoles("admin","user");}catch (IncorrectCredentialsException exception){System.out.println("用户名密码不匹配");}catch (LockedAccountException exception){System.out.println("账号已被锁定");}catch (DisabledAccountException exception){System.out.println("账号已被禁用");}catch (UnknownAccountException exception){System.out.println("用户不存在");}catch ( UnauthorizedException ae ) {System.out.println("用户没有权限");}System.out.println("用户的认证状态:isAuthenticated="+subject.isAuthenticated());System.out.println("执行logout()方法");subject.logout();System.out.println("用户的认证状态:isAuthenticated="+subject.isAuthenticated());}
}
用户的认证状态:isAuthenticated=true
执行logout()方法
用户的认证状态:isAuthenticated=false
将用户改成test,看其是否有admin的权限:
subject.checkRoles("admin","test");
用户没有权限
用户的认证状态:isAuthenticated=true
执行logout()方法
用户的认证状态:isAuthenticated=false
2. SpringBoot 使用IniRealm进行认证授权
SimpleAccountRealm 在程序中写死了用户安全数据,接下来我们使用.ini
将数据移到配置文件中。IniRealm是Shiro提供一种Realm实现。用户、角色、权限等信息集中在一个.ini
文件那里。
在 resources 目录下创建一个 shiro.ini 文件
# 账号信息
# 账号=密码,角色
[users]
test=123456,test
admin=123456,admin# 角色信息
# 角色=权限1,权限2
[roles]
test=user:list,user:deleted,user:edit
# 拥有所有权限
admin= *
测试:
@SpringBootTest
class ShiroApplicationTests {@Testpublic void testIniRealm(){//配置文件中的用户权限信息,文件在类路径下IniRealm iniRealm = new IniRealm("classpath:shiro.ini");//构建SecurityManager环境DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();//设置realmdefaultSecurityManager.setRealm(iniRealm);SecurityUtils.setSecurityManager(defaultSecurityManager);//获取主体Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken("test", "123456");try {//主体提交认证请求subject.login(token);//检查是否有角色,判断该用户是否拥有 test 角色subject.checkRoles("test");//检查是否拥有权限,检查用户是否拥有 user:list 的权限subject.checkPermissions("user:list");}catch (IncorrectCredentialsException exception){System.out.println("用户名或密码错误");}catch (LockedAccountException exception){System.out.println("账号已被锁定");}catch (DisabledAccountException exception){System.out.println("账号已被禁用");}catch (UnknownAccountException exception){System.out.println("用户不存在");}catch ( UnauthorizedException ae ) {System.out.println("用户没有权限");}}
}
3. Spring Boot 使用 JdbcRealm 进行认证授权
把用户安全信息(相应的角色/权限)配置在 .ini 文件,使用 IniRealm 去读取 .ini 文件获得用户的安全信息。也是 有局限性的。因为我们得事先把所有用户信息配置在.ini 文件,这样显然是行不通的,我们的系统用户都是动态的不固定的,它的一些 用户信息权限信息都是变化的,所以固定在.ini 配置文件显然是行不通的。这些数据通常我们都是把它存入到DB 中,那shiro 有没有提 供直接从DB读取用户安全信息的域呢 ? (Realm)
shiro 作为一个优秀的开源框架,显然是可以的,即 JdbcRealm。
1. 数据库驱动
<!--数据库驱动-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version>
</dependency>
<!--数据源-->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version>
</dependency>
2. 数据库表结构
下面我们创建一个名为 shiro 的数据库、分别创建三张表 users、user_roles、roles_permissions
CREATE DATABASE IF NOT EXISTS shiro DEFAULT CHARSET utf8 COLLATE utf8_general_ci;CREATE TABLE `users` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(25) DEFAULT NULL,`password` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE `user_roles` (`id` int(11) NOT NULL AUTO_INCREMENT,`role_name` varchar(25) DEFAULT NULL,`username` varchar(25) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE `roles_permissions` (`id` int(11) NOT NULL AUTO_INCREMENT,`permission` varchar(255) DEFAULT NULL,`role_name` varchar(25) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;USE shiro;
INSERT INTO `shiro`.`users` (`id`, `username`, `password`) VALUES ('1', 'admin', '123456');
INSERT INTO `shiro`.`users` (`id`, `username`, `password`) VALUES ('2', 'test', '123456');
INSERT INTO `shiro`.`user_roles` (`id`, `role_name`, `username`) VALUES ('1', 'admin', 'admin');
INSERT INTO `shiro`.`user_roles` (`id`, `role_name`, `username`) VALUES ('2', 'test', 'test');
INSERT INTO `shiro`.`roles_permissions` (`id`, `permission`, `role_name`) VALUES ('1','user:deleted', 'test');
INSERT INTO `shiro`.`roles_permissions` (`id`, `permission`, `role_name`) VALUES ('2','user:list', 'test');
INSERT INTO `shiro`.`roles_permissions` (`id`, `permission`, `role_name`) VALUES ('3', '*','admin');
INSERT INTO `shiro`.`roles_permissions` (`id`, `permission`, `role_name`) VALUES ('4','user:edit', 'test');
一个用户对应多个权限,一个权限对应多个用户,多对多的关系
3. 创建 testJdbcRealm方法
@SpringBootTest
class ShiroApplicationTests {@Testpublic void testJdbcRealm(){//配置数据源DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl("jdbc:mysql://localhost:3306/shiro");dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUsername("root");dataSource.setPassword("root");//配置文件中的用户权限信息,文件在类路径下JdbcRealm jdbcRealm = new JdbcRealm();jdbcRealm.setDataSource(dataSource);//使用JdbcRealm下面的值需要为true,不然无法查询用户权限jdbcRealm.setPermissionsLookupEnabled(true);DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();defaultSecurityManager.setRealm(jdbcRealm);SecurityUtils.setSecurityManager(defaultSecurityManager);Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token);System.out.println("用户的认证状态:isAuthenticated="+subject.isAuthenticated());//检查是否拥有角色subject.checkRoles("admin");//检查是否拥有权限subject.checkPermissions("user:delete");}catch (IncorrectCredentialsException exception){System.out.println("用户名或密码错误");}catch (LockedAccountException exception){System.out.println("账号已被锁定");}catch (DisabledAccountException exception){System.out.println("账号已被禁用");}catch (UnknownAccountException exception){System.out.println("用户不存在");}catch ( UnauthorizedException ae ) {System.out.println("用户没有权限");}}
}
用户的认证状态:isAuthenticated=true
当用户 test 登录进来的时候:
UsernamePasswordToken token = new UsernamePasswordToken("test", "123456");
用户的认证状态:isAuthenticated=true
用户没有权限
通过源码可以看出JdbcRealm 已经帮我们写好查询语句了,所以我们就要在数据库创建与之对应的表结构,这样才能查出数据,但是这里只能使用他默认的 sql,在实际的开发中,我们不可能就简单的使用 JdbcRealm 默认的 sql 语句,而是自己自定义的 sql 语句,更多时候我们的数据库以及数据表都是根据业务需要自己创建的,而不是默认的数据库表。
4. 更改数据库表名
USE shiro;
ALTER TABLE users RENAME sys_users;
ALTER TABLE user_roles RENAME sys_user_roles;
ALTER TABLE roles_permissions RENAME sys_roles_permissions;
@SpringBootTest
class ShiroApplicationTests {@Testpublic void testJdbcRealm(){//配置数据源DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl("jdbc:mysql://localhost:3306/shiro");dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUsername("root");dataSource.setPassword("root");//配置文件中的用户权限信息,文件在类路径下JdbcRealm jdbcRealm = new JdbcRealm();jdbcRealm.setDataSource(dataSource);//使用JdbcRealm下面的值需要为true,不然无法查询用户权限jdbcRealm.setPermissionsLookupEnabled(true);//使用自定义sql查询String sql = "select password from sys_users where username=?";jdbcRealm.setAuthenticationQuery(sql);String roleSQl = "select role_name from sys_user_roles where username=?";jdbcRealm.setUserRolesQuery(roleSQl);String permissionSql = "select permission from sys_roles_permissions where role_name=?";jdbcRealm.setPermissionsQuery(permissionSql);DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();defaultSecurityManager.setRealm(jdbcRealm);SecurityUtils.setSecurityManager(defaultSecurityManager);Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token);System.out.println("用户的认证状态:isAuthenticated="+subject.isAuthenticated());//检查是否拥有角色subject.checkRoles("admin");//检查是否拥有权限subject.checkPermissions("user:delete");}catch (IncorrectCredentialsException exception){System.out.println("用户名或密码错误");}catch (LockedAccountException exception){System.out.println("账号已被锁定");}catch (DisabledAccountException exception){System.out.println("账号已被禁用");}catch (UnknownAccountException exception){System.out.println("用户不存在");}catch ( UnauthorizedException ae ) {System.out.println("用户没有权限");}}
}
4. Spring Boot 使用自定义 Realm 进行认证授权
虽然 jdbcRealm 已经实现了从数据库中获取用户的验证信息,但是 jdbcRealm灵活性也是稍差一些的,如果要实现自己的一些特殊应 用时将不能支持,这个时候可以通过自定义realm来实现身份的认证功能。
通常自定义Realm只需要继承:AuthorizingRealm重写 doGetAuthenticationInfo(用户认证)、doGetAuthorizationInfo(用户授权) 这 两个方法即可。
public class CustomRealm extends AuthorizingRealm {/*** 模拟数据库中的用户名和密码*/private Map<String, String> userMap =new HashMap<>();{userMap.put("admin","123456");userMap.put("test","123456");}/*** 获取用户认证信息:用户名+密码* @param authenticationToken* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {String username = (String)authenticationToken.getPrincipal();String password = getPasswordByUsername(username);SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,password,getName());return simpleAuthenticationInfo;}/*** 获取用户授权信息:用户+角色+权限* @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {String username = (String) principalCollection.getPrimaryPrincipal();//从数据库或者缓存中获取角色数据List<String> roles = getRolesByUsername(username);//从数据库或者缓存中获取权限数据List<String> permissions = getPermissionsByUsername(username);//创建AuthorizationInfo,并设置角色和权限信息SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();simpleAuthorizationInfo.addStringPermissions(permissions);simpleAuthorizationInfo.addRoles(roles);return simpleAuthorizationInfo;}/*** 通过数据库,根据用户名获取权限信息* @param username* @return*/private List<String> getPermissionsByUsername(String username) {List<String> permissions = new ArrayList<>();/*** 只有是 admin 用户才有 新增、删除权限*/if(username.equals("admin")){permissions.add("user:delete");permissions.add("user:add");}permissions.add("user:edit");permissions.add("user:list");return permissions;}/*** 通过数据库,根据用户名获取角色信息* @param username* @return*/List<String> getRolesByUsername(String username){List<String> roles = new ArrayList<>();if(username.equals("admin")){roles.add("admin");}roles.add("test");return roles;}private String getPasswordByUsername(String username) {return userMap.get(username);}
}
测试:
@SpringBootTest
class ShiroApplicationTests {@Testpublic void testCustomRealm(){DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();CustomRealm customRealm = new CustomRealm();defaultSecurityManager.setRealm(customRealm);SecurityUtils.setSecurityManager(defaultSecurityManager);Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token);System.out.println("用户的认证状态:isAuthenticated="+subject.isAuthenticated());//检查是否拥有角色subject.checkRoles("admin");//检查是否拥有权限subject.checkPermissions("user:delete");}catch (IncorrectCredentialsException exception){System.out.println("用户名或密码错误");}catch (LockedAccountException exception){System.out.println("账号已被锁定");}catch (DisabledAccountException exception){System.out.println("账号已被禁用");}catch (UnknownAccountException exception){System.out.println("用户不存在");}catch ( UnauthorizedException ae ) {System.out.println("用户没有权限");}}
}
shiro 更多的是帮助我们完成验证过程。我们需要从数据库查询当前用户的角色、权限,把这些信息告诉 shiro 框架。当我们执行用户认证的时候首先调用 doGetAuthenticationInfo 进行获取用户认证信息,当我们要校验权限的时候 就会执行 doGetAuthorizationInfo 进行获取用户授权信息。
5. SpringBoot整合shiro之盐值加密认证详解
自定义 Realm,里面的用户认证所使用的密码都是明文,这种方式是不可取的往往我们在实战开发中用户的密码 都是以密文形势进行存储,并且要求加密算法是不可逆的,著名的加密算法有MD5、SHA1等。
public class CustomRealm extends AuthorizingRealm {/*** 获取用户认证信息:用户名+密码* @param authenticationToken*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {String username = (String)authenticationToken.getPrincipal();String password = getPasswordByUsername(username);String matcherPassword = getPasswordMatcher(password);SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,matcherPassword,getName());return simpleAuthenticationInfo;}/*** 获取密文密码* @param currentPassword*/private String getPasswordMatcher(String currentPassword){return new Md5Hash(currentPassword, null,2).toString();}// 省略...
}
@Test
public void testCustomRealm(){DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();CustomRealm customRealm = new CustomRealm();// 加密HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();// 采用MD5加密matcher.setHashAlgorithmName("md5");// 设置加密次数matcher.setHashIterations(2);customRealm.setCredentialsMatcher(matcher);defaultSecurityManager.setRealm(customRealm);SecurityUtils.setSecurityManager(defaultSecurityManager);Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token);System.out.println("用户的认证状态:isAuthenticated="+subject.isAuthenticated());//检查是否拥有角色subject.checkRoles("admin");//检查是否拥有权限subject.checkPermissions("user:delete");}catch (IncorrectCredentialsException exception){System.out.println("用户名或密码错误");}catch (LockedAccountException exception){System.out.println("账号已被锁定");}catch (DisabledAccountException exception){System.out.println("账号已被禁用");}catch (UnknownAccountException exception){System.out.println("用户不存在");}catch ( UnauthorizedException ae ) {System.out.println("用户没有权限");}
}
当两个用户的密码相同时,单纯使用不加盐的MD5加密方式,会发现数据库中存在相同结构的密码,这样也是不安全的。我们希望即 便是两个人的原始密码一样,加密后的结果也不一样。如何做到呢?其实就好像炒菜一样,两道一样的鱼香肉丝,加的盐不一样,炒 出来的味道就不一样。MD5加密也是一样,需要进行盐值加密。
public class CustomRealm extends AuthorizingRealm {/*** 获取用户认证信息:用户名+密码* @param authenticationToken*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {String username = (String)authenticationToken.getPrincipal();String password = getPasswordByUsername(username);String salt = UUID.randomUUID().toString().substring(5);String matcherPassword = getPasswordMatcher(password,salt);SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,matcherPassword,getName());simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(salt));return simpleAuthenticationInfo;}/*** 获取密文密码* @param currentPassword* @param salt */private String getPasswordMatcher(String currentPassword,String salt){return new Md5Hash(currentPassword, salt).toString();}// 省略...
}
测试:
@Test
public void testCustomRealm(){DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();CustomRealm customRealm = new CustomRealm();// 加密HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();// 采用MD5加密matcher.setHashAlgorithmName("md5");// 设置加密次数matcher.setHashIterations(1);customRealm.setCredentialsMatcher(matcher);defaultSecurityManager.setRealm(customRealm);SecurityUtils.setSecurityManager(defaultSecurityManager);Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token);System.out.println("用户的认证状态:isAuthenticated="+subject.isAuthenticated());//检查是否拥有角色subject.checkRoles("admin");//检查是否拥有权限subject.checkPermissions("user:delete");}catch (IncorrectCredentialsException exception){System.out.println("用户名或密码错误");}catch (LockedAccountException exception){System.out.println("账号已被锁定");}catch (DisabledAccountException exception){System.out.println("账号已被禁用");}catch (UnknownAccountException exception){System.out.println("用户不存在");}catch ( UnauthorizedException ae ) {System.out.println("用户没有权限");}
}