Shiro 使用
hello shiro
-
首先创建Maven项目并引入最新的shiro依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.7.0</version> </dependency>
-
在项目的
resources
目录下新建shiro.ini文件,并在文件中配置用户名和密码[users] zhangsan=123
-
在
src
目录下新建类HelloShiro
public class HelloShiro { public static void main(String[] args) { // 1.创建默认的SecurityManager管理器 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); // 2. 加载Realm defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini")); // 3. 将SecurityManager交给SecurityUtils管理 SecurityUtils.setSecurityManager(defaultSecurityManager); // 4. 获取认证授权主体 Subject subject = SecurityUtils.getSubject(); // 5. 创建具有用户名和密码的认证授权token UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123"); try { // 6. 进行登录,如果登录失败则会进入异常 subject.login(token); System.out.println("是否登录成功:" + subject.isAuthenticated()); } catch (UnknownAccountException e) { System.out.println("帐号错误"); e.printStackTrace(); } catch (IncorrectCredentialsException e) { System.out.println("密码错误"); e.printStackTrace(); } } }
Hello Shiro的登录流程查看
自定义Realm实现认证授权
自定义Realm实现认证
-
Maven引入shiro依赖
-
自定义
CustomRealm
类并继承AuthorizingRealm
抽象类public class CustomRealm extends AuthorizingRealm { // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 获取认证帐号 String username = (String) token.getPrincipal(); // 其中“zhangsan”和“123”是从数据库中查出来的(这里省略了数据库查询操作) if (username != null && username.equals("zhangsan")) { return new SimpleAuthenticationInfo("zhangsan", "123", this.getName())'' } return null; } }
-
新建类进行登录,验证自定义的Realm。在该类中因为是自定义的Realm,所以在
SecurityManager
加载Realm与HelloShiro有所不同, 具体如下:public class ConstomRealmAuthentication { public static void main(String[] args) { DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); // 这里直接加载自定义的Realm defaultSecurityManager.setRealm(new CustomRealm()); SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); try { subject.login(new UsernamePasswordToken("zhangsan", "123")); System.out.println("登录是否成功:" + subject.isAuthenticated()); } catch (UnknownAccountException e) { System.out.println("账户错误"); e.printStackTrace(); } catch (IncorrectCredentialsException e) { System.out.println("密码错误"); e.printStackTrace(); } } }
自定义Realm实现授权
步骤大体与自定义Realm认证相同,只不过授权需要更改CustomRealm
类中的doGetAuthorizationInfo
的方法。其自定义Realm类如下:
public class CustomRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取登录名
String username = (String) principals.getPrimaryPrincipal();
// 这里的admin,user:update:01, order:*:*,字符串假设是从数剧库中查到的
if (username.equals("zhangsan")) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("admin");
info.addStringPermission("user:update:01");
info.addStringPermission("order:*:*");
return info;
}
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
if (username != null && username.equals("zhangsan")) {
return new SimpleAuthenticationInfo("zhangsan", "123", this.getName());
}
return null;
}
}
其中对用户角色和权限的验证代码如下:
try {
subject.login(new UsernamePasswordToken("zhangsan", "123"));
System.out.println("登录是否成功:" + subject.isAuthenticated());
boolean admin = subject.hasRole("admin");
System.out.println("用户具有admin角色:" + admin);
boolean permitted = subject.isPermitted("user:update:01");
System.out.println("用户具有对user模块的01实例进行update的权限:" + permitted);
boolean permitted1 = subject.isPermitted("order:update:*");
System.out.println("用户具有对order模块的所有实例进行update权限:" + permitted1);
} catch (UnknownAccountException e) {
System.out.println("账户错误");
e.printStackTrace();
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
e.printStackTrace();
}
在权限中使用user:update:01
是资源通配符,其规则是 资源标识符:操作:对象实例 ID ,表示为可对那个资源的那个实例进行什么操作。
加密
-
首先通过shiro中的
Md5Hash
类来加密,模拟从数据库中取出的已经加密的密码// 原密码:123456, salt:7G&;-,散列次数:1024 Md5Hash ceshi = new Md5Hash("123456", "7G&;-", 1024); System.out.println(ceshi.toHex()); // output:1e923b90f26d3078e0c6611427d1ace9
-
如果要实现加密认证,首先更改原本的自定义Realm中的认证方法
// 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); if (username != null && username.equals("zhangsan")) { // 这里的密码和salt是直接从数据库中查出来的 return new SimpleAuthenticationInfo("zhangsan", "1e923b90f26d3078e0c6611427d1ace9", ByteSource.Util.bytes("ceshi"), this.getName()); } return null; }
-
然后更改认证的类,在自定义realm中设置相应的密码匹配器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); CustomRealm realm = new CustomRealm(); // 这里设置认证凭证匹配器 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); // 设置加密方式 credentialsMatcher.setHashAlgorithmName("md5"); // 设置散列次数 credentialsMatcher.setHashIterations(1024); realm.setCredentialsMatcher(credentialsMatcher); defaultSecurityManager.setRealm(realm); SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); try { UsernamePasswordToken zhangsan = new UsernamePasswordToken("zhangsan", "123456"); subject.login(zhangsan); System.out.println("登录是否成功:" + subject.isAuthenticated()); } catch (UnknownAccountException e) { System.out.println("账户错误"); e.printStackTrace(); } catch (IncorrectCredentialsException e) { System.out.println("密码错误"); e.printStackTrace(); }
spring boot整合shiro
初始化数据库
首先创建数据库,数剧看中有5张表,分别是用户表,角色表, 用户角色关系表, 权限表,角色权限表
# 用户表
CREATE TABLE `t_users` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(40) DEFAULT NULL,
`password` varchar(45) DEFAULT NULL,
`salt` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
# 角色表
CREATE TABLE `t_roles` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
# 用户角色关系表
CREATE TABLE `t_user_role` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`role_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
# 权限表
CREATE TABLE `t_perms` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
`url` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
# 权限角色关系表
CREATE TABLE `t_role_perm` (
`id` int NOT NULL AUTO_INCREMENT,
`role_id` int NOT NULL,
`perm_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
接下来引入依赖
在pom文件中引入mysql数据库连接坐标,mybatis,shiro、spring boot的坐标
整合spring boot实现登录认证
spring 项目中使用shiro做认证授权时,会启动shiro的WebEnvironment
,然后通过WebEnvironment
中的ShiroFilter
对请求进行认证授权。
-
因为使用了Spring Boot,因此不能像之前那样直接在main方法中编写登录的认证,需要将shiro的SecurityManager注入到spring容器中,交给spring进行管理。shiro的配置如下:
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); // 配置请求的过滤策略 Map<String, String> map = new HashMap<>(); map.put("/register.jsp", "anon"); map.put("/login.jsp", "anon"); map.put("/user/login", "anon"); map.put("/user/register", "anon"); map.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } @Bean public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(realm); return defaultWebSecurityManager; } @Bean public Realm realm() { CustomerRealm customerRealm = new CustomerRealm(); HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("md5"); credentialsMatcher.setHashIterations(1024); customerRealm.setCredentialsMatcher(credentialsMatcher); return customerRealm; } }
在上传代码中有提到请求的过滤策略,其中
anon、authc
是DefaultFilter
枚举中的值(具体含义可以查看源码或shiro文档,在此不在赘述😂)。其可选值如下:
-
定义自定义realm
在自定义realm中需要根据用户账户查询数据库,因此需要用到一个工具类,该工具类能够获取Dao的bean。
-
根据名字获取bean的工具类
@Component public class ApplicationContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextUtil.applicationContext = applicationContext; } /** * 根据名字获取bean * @param name * @return */ public static Object getBean(String name) { return applicationContext.getBean(name); } }
-
自定义Realm
public class CustomerRealm extends AuthorizingRealm { /** * 授权 * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); UserService userService = (UserService) ApplicationContextUtil.getBean("userServiceImpl"); RoleService roleService = (RoleService) ApplicationContextUtil.getBean("roleServiceImpl"); User user = userService.selectUserAndRolesByUsername(username); if (user != null) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); for (Roles role: user.getRoles()) { info.addRole(role.getName()); // 获取权限 List<Roles> roles = roleService.selectRoleAndPermByRoleId(role.getId()); for (Roles r : roles) { r.getPerms().forEach(item -> info.addStringPermission(item.getName())); } } return info; } return null; } /** * 认证 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); UserService userService = (UserService) ApplicationContextUtil.getBean("userServiceImpl"); System.out.println("认证"); User user = userService.selectByUsername(username); if (user != null) { return new SimpleAuthenticationInfo(username, user.getPassword(), // ByteSource.Util.bytes(user.getSalt()), new MyByteSource(user.getSalt()), this.getName()); } return null; } }
-
-
编写Controller层程序
@RequestMapping("/login") public String login(String username, String password) { Subject subject = SecurityUtils.getSubject(); try { subject.login(new UsernamePasswordToken(username, password)); return "redirect:/index.jsp"; } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("用戶名錯誤"); return "redirect:/login.jsp"; } catch (IncorrectCredentialsException e) { e.printStackTrace(); System.out.println("密碼错误"); return "redirect:/login.jsp"; } }
其中关于授权的验证有多种方式,可以直接通过
subject.hasRole("admin")
这种方式,也可以通过@RequiresRoles(value = {"admin"})
这种方式进行判断,当然授权也是如此。具体使用可以查看官方文档。
spring boot整合shiro使用redis缓存
-
添加redis的依赖
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>6.0.1.RELEASE</version> </dependency>
-
实现Redis缓存类
在shiro缓存是通过
CacheManager
实现的,因此实现的连个Redis缓存类,一个需要实现CacheManager
, 另一个需要实现Cache
(因为CacheManager
中的getCache
方法需要返回的是实现Cache
接口的类)-
实现
CacheManager
的类public class RedisCacheManager implements CacheManager { @Override public <K, V> Cache<K, V> getCache(String s) throws CacheException { System.out.println("redisCacheManager:" + s); return new RedisCache<K, V>(s); } }
-
实现
Cache
的类public class RedisCache<K, V> implements Cache<K, V> { private String cacheName; public RedisCache() { } public RedisCache(String cacheName) { this.cacheName = cacheName; } @Override public V get(K k) throws CacheException { System.out.println("redisCache-get:" + k); RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); // redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return (V) redisTemplate.opsForHash().get(this.cacheName, k.toString()); } @Override public V put(K k, V v) throws CacheException { System.out.println("redisCanche-put:" + k); System.out.println("------------- :" + v); RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); // redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.opsForHash().put(this.cacheName, k.toString(), v); return null; } @Override public V remove(K k) throws CacheException { RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return (V) redisTemplate.opsForHash().delete(this.cacheName, k.toString()); } @Override public void clear() throws CacheException { RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.delete(this.cacheName); } @Override public int size() { RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate.opsForHash().size(this.cacheName).intValue(); } @Override public Set<K> keys() { RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate.opsForHash().keys(this.cacheName); } @Override public Collection<V> values() { RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate.opsForHash().values(this.cacheName); } }
整个spring boot集成shiro并使用redis缓存的代码
https://github.com/StudyRecording/shiro-learn
-