Shiro Apache Shiro 是一个功能强大且灵活的开源安全框架,主要功能包括用户认证、授权、会话管理以及加密。
Shiro简单灵活
四大功能:
其他功能支持:
Web支持:Shiro的Web支持API可帮助轻松保护Web应用程序。
缓存:缓存是Apache Shiro API的第一层公民,可确保安全操作保持快速有效。
并发性:Apache Shiro的并发功能支持多线程应用程序。
测试:测试支持可以帮助您编写单元测试和集成测试,并确保您的代码将按预期进行保护。
“运行方式”:一种功能,允许用户采用其他用户的身份(如果允许),有时在管理方案中很有用。
“记住我”:在各个会话中记住用户的身份,因此他们仅在必要时登录。
Shiro组件
Subject:把当前用户作为一个主体,通过subject进行授权认证
SecurityManager:安全管理器,对全部的subject进行安全管理
Authenticator:认证
Authorizer:授权
Realm:领域,相当于数据源
Shiro过滤器
SpringBoot整合Shiro
整合了授权认证、MD5盐值加密、thymeleaf、EhCache、Redis
Yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 spring: application: name: springboot-shiro datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/shiro?useSSL=true username: root password: root redis: port: 6379 host: password: '070409' mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl mapper-locations: classpath:/mapper/* type-aliases-package: cn.this52.springbootshiro.pojo
ShiroConfig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 package cn.this52.springbootshiro.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;import cn.this52.springbootshiro.realm.UserRealm;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.cache.ehcache.EhCacheManager;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.LinkedHashMap;import java.util.Map;@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean (DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiro = new ShiroFilterFactoryBean(); shiro.setSecurityManager(securityManager); Map<String, String> auth = new LinkedHashMap<>(); auth.put("/main" , "authc" ); auth.put("/manager" , "perms[manager]" ); auth.put("/admin" , "roles[admin]" ); shiro.setLoginUrl("/login" ); shiro.setUnauthorizedUrl("/unAuth" ); shiro.setFilterChainDefinitionMap(auth); return shiro; } @Bean public DefaultWebSecurityManager securityManager (UserRealm userRealm) { return new DefaultWebSecurityManager(userRealm); } @Bean public UserRealm userRealm () { UserRealm userRealm = new UserRealm(); HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher("md5" ); hashedCredentialsMatcher.setHashIterations(1024 ); userRealm.setCredentialsMatcher(hashedCredentialsMatcher); userRealm.setCacheManager(new EhCacheManager()); userRealm.setCachingEnabled(true ); userRealm.setAuthenticationCachingEnabled(true ); userRealm.setAuthorizationCachingEnabled(true ); userRealm.setAuthenticationCacheName("authentication" ); userRealm.setAuthorizationCacheName("authorization" ); return userRealm; } @Bean public ShiroDialect shiroDialect () { return new ShiroDialect(); } }
Controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package cn.this52.springbootshiro.controller;import cn.this52.springbootshiro.pojo.User;import cn.this52.springbootshiro.service.UserService;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.subject.Subject;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller public class UserController { @Autowired private UserService userService; @GetMapping("/{url}") public String forward (@PathVariable String url) { return url; } @PostMapping("/login") public String login (User user, Model model) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); try { subject.login(token); subject.getSession().setAttribute("user" , subject.getPrincipal()); return "redirect:/index" ; } catch (UnknownAccountException e) { e.printStackTrace(); model.addAttribute("msg" , "用户不存在" ); return "login" ; } catch (IncorrectCredentialsException e) { e.printStackTrace(); model.addAttribute("msg" , "密码错误" ); return "login" ; } } @GetMapping("/unAuth") @ResponseBody public String unAuth () { return "未授权无法访问!" ; } @GetMapping("/logout") public String logout () { SecurityUtils.getSubject().logout(); return "login" ; } @PostMapping("/register") public String register (User user, Model model) { int register = userService.register(user); model.addAttribute("msg" , register == 1 ? "注册成功!" : "注册失败!" ); return register == 1 ? "login" : "register" ; } }
Mapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package cn.this52.springbootshiro.mapper;import cn.this52.springbootshiro.pojo.Permission;import cn.this52.springbootshiro.pojo.User;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import org.apache.ibatis.annotations.ResultMap;import org.apache.ibatis.annotations.Select;import java.util.List;public interface UserMapper extends BaseMapper <User > { @ResultMap("userMap") @Select("select uid,username,rid,r_name\n" + "from user\n" + " left join user_role ur on uid = ur.user_id\n" + " left join role on ur.role_id = role.rid where uid =#{uid}") User findRolesByUid (int uid) ; @Select("select p_name\n" + "from role\n" + " left join role_perm rp on role.rid = rp.role_id\n" + " left join permission p on rp.perm_id = p.pid\n" + "where rid = 1;") List<Permission> findPermsByRid (int rid) ; }
Pojo 1 2 3 4 5 6 7 8 9 @Data @Accessors(chain = true) @AllArgsConstructor @NoArgsConstructor public class Permission implements Serializable { private static final long serialVersionUID = -5891141512288426298L ; private Integer pid; private String pName; }
1 2 3 4 5 6 7 8 9 10 11 12 @Data @Accessors(chain = true) @AllArgsConstructor @NoArgsConstructor public class Role implements Serializable { private static final long serialVersionUID = -4021452759620212876L ; private Integer rid; private String rName; @TableField(exist = false) List<Permission> permissions; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Data @Accessors(chain = true) @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -265042359335069131L ; private Integer uid; private String username; public String password; private String salt; @TableField(exist = false) private List<Role> roles; }
Realm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 package cn.this52.springbootshiro.shiro.realm;import cn.this52.springbootshiro.pojo.Permission;import cn.this52.springbootshiro.pojo.Role;import cn.this52.springbootshiro.pojo.User;import cn.this52.springbootshiro.service.UserService;import cn.this52.springbootshiro.shiro.salt.MyByteSource;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.CollectionUtils;import org.springframework.beans.factory.annotation.Autowired;import java.util.List;import java.util.stream.Collectors;public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { Subject subject = SecurityUtils.getSubject(); User user = (User) subject.getPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User rolesByUid = userService.findRolesByUid(user.getUid()); List<Role> roles = rolesByUid.getRoles(); if (!CollectionUtils.isEmpty(roles)) { roles.forEach(role -> { authorizationInfo.addRole(role.getRName()); authorizationInfo.addStringPermissions( userService.findPermsByRid(role.getRid()) .stream() .map(Permission::getPName) .collect(Collectors.toList())); }); } return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("=>> 认证" ); String username = (String) authenticationToken.getPrincipal(); User user = userService.findUser(username); if (user == null ) return null ; return new SimpleAuthenticationInfo( user, user.getPassword(), new MyByteSource(user.getSalt()), getName()); } }
Service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package cn.this52.springbootshiro.service;import cn.this52.springbootshiro.pojo.Permission;import cn.this52.springbootshiro.pojo.User;import java.util.List;public interface UserService { User findUser (String username) ; User findRolesByUid (int uid) ; List<Permission> findPermsByRid (int rid) ; int register (User user) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package cn.this52.springbootshiro.service;import cn.hutool.core.util.RandomUtil;import cn.this52.springbootshiro.mapper.UserMapper;import cn.this52.springbootshiro.pojo.Permission;import cn.this52.springbootshiro.pojo.User;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import org.apache.shiro.crypto.hash.Md5Hash;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.util.List;@Service @Transactional public class UserServiceImpl implements UserService { @Resource private UserMapper userMapper; @Override public User findUser (String username) { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("username" , username); return userMapper.selectOne(wrapper); } @Override public User findRolesByUid (int uid) { return userMapper.findRolesByUid(uid); } @Override public List<Permission> findPermsByRid (int rid) { return userMapper.findPermsByRid(rid); } @Override public int register (User user) { if (findUser(user.getUsername()) != null ) { return 0 ; } String salt = RandomUtil.randomString(10 ); user.setPassword(new Md5Hash(user.getPassword(), salt, 1024 ).toHex()); user.setSalt(salt); return userMapper.insert(user); } }
CustomRedisTemplate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package cn.this52.springbootshiro.redis;import org.springframework.context.annotation.Bean;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.StringRedisSerializer;import org.springframework.stereotype.Component;@Component public class CustomRedisTemplate { @Bean public static RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate; } }
RedisCache 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package cn.this52.springbootshiro.shiro.cache;import cn.this52.springbootshiro.utils.ApplicationContextUtil;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheException;import org.springframework.data.redis.core.RedisTemplate;import java.util.Collection;import java.util.Set;@SuppressWarnings("all") 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("get k=>>>>>>>>>>>>>>>>>>>>" + k); Object o = redisTemplate().opsForHash().get(cacheName, k.toString()); System.out.println("get v=>>>>>>>>>>>>>>>>>>>>" + o); return (V) o; } @Override public V put (K k, V v) throws CacheException { System.out.println("put k=>>>>>>>>>>>>>>>>>>>>" + k); System.out.println("put v=>>>>>>>>>>>>>>>>>>>>" + v); redisTemplate().opsForHash().put(cacheName, k.toString(), v); return null ; } @Override public V remove (K k) throws CacheException { return null ; } @Override public void clear () throws CacheException { redisTemplate().delete(cacheName); } @Override public int size () { return redisTemplate().opsForHash().size(cacheName).intValue(); } @Override public Set<K> keys () { return (Set<K>) redisTemplate().opsForHash().keys(cacheName); } @Override public Collection<V> values () { return (Collection<V>) redisTemplate().opsForHash().values(cacheName); } public RedisTemplate<String, Object> redisTemplate () { return (RedisTemplate<String, Object>) ApplicationContextUtil.getBean("redisTemplate" ); } }
RedisCacheManger 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package cn.this52.springbootshiro.shiro.cache;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheException;import org.apache.shiro.cache.CacheManager;public class RedisCacheManger implements CacheManager { @Override public <K, V> Cache<K, V> getCache (String cacheName) throws CacheException { return new RedisCache<K, V>(cacheName); } }
MybyteSource 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 package cn.this52.springbootshiro.shiro.salt;import org.apache.shiro.codec.Base64;import org.apache.shiro.codec.CodecSupport;import org.apache.shiro.codec.Hex;import org.apache.shiro.util.ByteSource;import java.io.File;import java.io.InputStream;import java.io.Serializable;import java.util.Arrays;public class MyByteSource implements ByteSource , Serializable { private static final long serialVersionUID = -256381558320083357L ; private byte [] bytes; private String cachedHex; private String cachedBase64; public MyByteSource () { } public MyByteSource (byte [] bytes) { this .bytes = bytes; } public MyByteSource (char [] chars) { this .bytes = CodecSupport.toBytes(chars); } public MyByteSource (String string) { this .bytes = CodecSupport.toBytes(string); } public MyByteSource (ByteSource source) { this .bytes = source.getBytes(); } public MyByteSource (File file) { this .bytes = (new MyByteSource.BytesHelper()).getBytes(file); } public MyByteSource (InputStream stream) { this .bytes = (new MyByteSource.BytesHelper()).getBytes(stream); } public static boolean isCompatible (Object o) { return o instanceof byte [] || o instanceof char [] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream; } public byte [] getBytes() { return this .bytes; } public boolean isEmpty () { return this .bytes == null || this .bytes.length == 0 ; } public String toHex () { if (this .cachedHex == null ) { this .cachedHex = Hex.encodeToString(this .getBytes()); } return this .cachedHex; } public String toBase64 () { if (this .cachedBase64 == null ) { this .cachedBase64 = Base64.encodeToString(this .getBytes()); } return this .cachedBase64; } public String toString () { return this .toBase64(); } public int hashCode () { return this .bytes != null && this .bytes.length != 0 ? Arrays.hashCode(this .bytes) : 0 ; } public boolean equals (Object o) { if (o == this ) { return true ; } else if (o instanceof ByteSource) { ByteSource bs = (ByteSource) o; return Arrays.equals(this .getBytes(), bs.getBytes()); } else { return false ; } } private static final class BytesHelper extends CodecSupport { private BytesHelper () { } public byte [] getBytes(File file) { return this .toBytes(file); } public byte [] getBytes(InputStream stream) { return this .toBytes(stream); } } }
Utils 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package cn.this52.springbootshiro.utils;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;@Component public class ApplicationContextUtil implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { context = applicationContext; } public static Object getBean (String beanName) { return context.getBean(beanName); } public static <T> T getBean (Class<T> clazz) { return context.getBean(clazz); } }
Pom依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > 5.4.0</version > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.3.0</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.49</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <version > 1.5.1</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-ehcache</artifactId > <version > 1.5.3</version > </dependency > <dependency > <groupId > com.github.theborakompanioni</groupId > <artifactId > thymeleaf-extras-shiro</artifactId > <version > 2.0.0</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
错误
配置缓存后使用devtools
热部署会重复创建CacheManager
,此时的CacheManager
是单例的。
2.配置Shiro缓存后,Realm里面的盐值反序列化失败
原因:
解决方案:
取消authenticationCache ,就不会序列化盐值,自然不会反序列化
自定义ByteSource 实现类,可以继承SimpleByteSource ,然后实现序列化接口
然后会在序列化的时候没问题了,但是反序列化的时候会发现一个新的错误
那是因为SimpleByteSource 没有无参构造,子类也无法写无参构造‘,就会抛出异常
解决方案:
复制SimpleByteSource 所有信息到自定以ByteSource 中,并实现ByteSource 接口以及序列化接口,还得生成无参构造。