Shiro整体架构
Authenticator:认证器,管理登录登出;
Authorizer:授权器;
SessionManager:Session管理器,可以不借助web容器使用session;
SessionDAO:session的增删改查;
CacheManager:缓存管理机制,缓存角色数据和权限数据;
Realm:Shiro和数据库数据源之间的桥梁,shiro通过Realm获取角色数据,权限数据;
cryptography:加密。
主体提交请求->调用Authenticator->通过Realm获取认证信息;
Shiro基本概念 如上文说明,认证(Authentication)和授权(Authorization)特别像的两个词,我自己的理解是认证简单说就是证明王大锤是王大锤,授权就是说明王大锤的职能,比如砸墙。如有不当之处,请通过评论或公众号等联系方式联系笔者纠正,谢谢。
Shiro认证 认证过程:
创建SecurityManager;
主体提交认证;
SecurityManager认证;
Authenticator认证;
Realm验证;
Shiro授权
创建SecurityManager;
主体授权;
SecurityManager授权;
Authorizer授权;
Realm获取角色权限数据;
内置Realm
内置Realm: a. IniRealm b. JdbcRealm * 在不设置查询语句的时候,默认有查询语句; * 权限查询开关默认为关闭,需要打开;
自定义Realm 参考下文的具体代码
Shiro加密
Shiro散列配置
HashedCredentialsMatcher
自定义Realm中使用散列
盐的使用
Shiro集成Spring Shiro基本配置与用户登录 一、添加shiro相关依赖 pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <shiro.version > 1.3.2</shiro.version > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <version > ${shiro.version}</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-ehcache</artifactId > <version > ${shiro.version}</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-web</artifactId > <version > ${shiro.version}</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <version > ${shiro.version}</version > </dependency >
二、配置Shiro过滤器 web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <filter > <description > shiro 权限拦截</description > <filter-name > shiroFilter</filter-name > <filter-class > org.springframework.web.filter.DelegatingFilterProxy</filter-class > <init-param > <param-name > targetFilterLifecycle</param-name > <param-value > true</param-value > </init-param > </filter > <filter-mapping > <filter-name > shiroFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping >
三、添加Shiro的配置
配置shiroFilter过滤器shiroFilter;
自定义的表单过滤器、角色、权限过滤器sysRolesFilter;
配置权限管理器securityManager
自定义的userRealm
spring-shiro.xml
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 <description > Shiro配置</description > <bean id ="shiroFilter" class ="org.apache.shiro.spring.web.ShiroFilterFactoryBean" > <property name ="securityManager" ref ="securityManager" /> <property name ="loginUrl" value ="/index" /> <property name ="successUrl" value ="/sys/home" /> <property name ="unauthorizedUrl" value ="/error403" /> <property name ="filters" > <map > <entry key ="authc" value-ref ="formAuthenticationFilter" /> <entry key ="roles" value-ref ="sysRolesFilter" /> </map > </property > <property name ="filterChainDefinitionMap" ref ="chainDefinitionSectionMetaSource" > </property > </bean > <bean class ="com.weyoung.platform.shiro.filter.SysRolesFilter" id ="sysRolesFilter" /> <bean id ="chainDefinitionSectionMetaSource" class ="com.weyoung.platform.shiro.service.ChainDefinitionSectionMetaSource" > <property name ="filterChainDefinitions" > <value > /=anon /assets/**=anon /webservice/**=anon /view/system/**=anon /view/templates/**=anon /sys/login=anon /error403=anon /swagger-ui.html=anon /webjars/**=anon /druid/**=authc /errorException=anon /sys/logout=anon /sys/home=authc /sys/home=roles["admin"] /**=authc </value > </property > </bean > <bean id ="shiroCacheManager" class ="org.apache.shiro.cache.ehcache.EhCacheManager" > <property name ="cacheManager" ref ="ehCacheManager" /> </bean > <bean id ="securityManager" class ="org.apache.shiro.web.mgt.DefaultWebSecurityManager" > <property name ="realm" ref ="userRealm" /> <property name ="cacheManager" ref ="shiroCacheManager" /> </bean > <bean id ="userRealm" class ="com.weyoung.platform.shiro.realm.SystemAuthRealm" > <property name ="authorizationCacheName" value ="authCache" > </property > </bean > <bean id ="formAuthenticationFilter" class ="com.weyoung.platform.shiro.filter.MyFormAuthenticationFilter" > <property name ="loginUrl" value ="/" /> </bean >
spring-mvc.xml
1 2 3 4 5 6 7 8 9 10 11 12 <bean id ="lifecycleBeanPostProcessor" class ="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <bean class ="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on ="lifecycleBeanPostProcessor" > <property name ="proxyTargetClass" value ="true" /> </bean > <bean class ="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor" > <property name ="securityManager" ref ="securityManager" /> </bean >
四、自定义Realm 主要用于从数据库中动态加载角色、权限信息 shiro并不是在认证之后就马上对用户授权 ,而是在用户认证通过之后 , 接下来要访问的资源或者目标方法需要权限的时候才会调用doGetAuthorizationInfo()方法,进行授权.
Realm:域,是Shiro和应用程序之间的连接器。Shiro从Realm获取权限数据(如用户、角色、权限及其之间的关系),SecurityManager验证用户身份时,需要使用Realm的认证器确定用户身份合法,使用Realm的授权器获取用户的角色和权限。
在前端发起登录请求时,执行subject.login(token);
代码时,
本文使用示例。
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 @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { logger.debug("------------Shiro开始验证------------" ); String userName = (String) token.getPrincipal(); UserLogin userLogin = loginDao.getUserLoginByUserName(userName); if (userLogin == null ) { throw new UnknownAccountException (LOGIN_PASS_ERROR_MSG); } else { password = userLogin.getPassword(); } String credentials = password; String realmName = getName(); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo (userName, credentials, realmName); logger.debug("------------Shiro完成验证------------" ); return info; } @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { logger.debug("------------Shiro开始授权------------" ); String userName = (String) principalCollection.getPrimaryPrincipal(); List<SysRolePermission> rolePermissions = this .getRolesByUserName(userName); List<String> roleKeys = rolePermissions.stream().map(SysRolePermission::getRoleKey).distinct().collect(Collectors.toList()); List<String> permissions = rolePermissions.stream().map(SysRolePermission::getPerms).distinct().collect(Collectors.toList()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo (); info.addStringPermissions(permissions); info.addRoles(roleKeys); logger.debug("------------Shiro完成授权------------" ); return info; }
五、登录实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @RequestMapping(value = "/sys/login", method = RequestMethod.POST) public String systemLogin (HttpServletRequest request, HttpServletResponse response) throws Exception { String userName = request.getParameter("userName" ); String password = request.getParameter("password" ); Subject currentUser = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken (userName, password); String viewName = REDIRECT_VIEW_URL_INDEX; try { currentUser.login(token); } catch (AuthenticationException e) { if (e instanceof IncorrectCredentialsException) { logger.error("登录失败!{}" , LOGIN_PASS_ERROR_MSG); } return viewName; } UserInfo userInfo = loginService.getSysUserByUserName(userName); currentUser.getSession().setAttribute(SESSION_DEFAULT, userInfo); return REDIRECT_VIEW_URL_HOME; }
六、Shiro过滤器
Shiro内置过滤器
认证相关的过滤器:anon:不需要任何认证,authBasic:,authc:需要认证后访问,user,logout
perms,roles,ssl,port
添加过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class RolesOrFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed (ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { Subject subject = getSubject(servletRequest, servletResponse); String[] roles = (String[]) o; if (roles == null || roles.length == 0 ) { return true ; } for (String role : roles) { if (subject.hasRole(role)) { return true ; } } return false ; } }
七、通过注解方式进行授权
@RequiresRoles:都可以传入多个参数
@RequiresPermissions:
pom中引入
1 2 3 4 5 <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.8.13</version > </dependency >
如果需要实现通过注解配置授权,需要在spring-mvc.xml中添加如下代码。注意:以下的注解代码尽量不要和springMVC的配置分开,否则会不生效
1 2 3 4 5 <aop:config proxy-target-class ="true" > <bean class ="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <bean class ="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor" > <property name ="securityManager" ref ="securityManager" /> </bean >
controller中添加示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RequiresRoles("admin") @RequestMapping(value = "/testRole", method = RequestMethod.GET) @ResponseBody public String testRole () { System.out.println("testRole" ); return "testRole success" ; } @RequiresPermissions("admin1") @RequestMapping(value = "/testRole1", method = RequestMethod.GET) @ResponseBody public String testRole1 () { System.out.println("testRole1" ); return "testRole1 success" ; }
Shiro数据库表结构设计
Shiro会话管理和缓存管理 Shiro会话(Session)管理
shiro session 管理
SessionManager:session管理器、SessionDAO:session增删改查
通过Redis实现session共享
Redis实现session共享存在的问题
pom中添加:
1 2 3 4 5 <dependency > <groupId > redis.clients</groupId > <artifactId > jedis</artifactId > <version > 2.9.0</version > </dependency >
添加RedisSessionDao
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 public class RedisSessionDao extends AbstractSessionDAO { @Resource private JedisUtil jedisUtil; private final String SHIRO_SESSION_PREFIX = "lucifer-session:" ; private byte [] getKey(String key) { return (SHIRO_SESSION_PREFIX + key).getBytes(); } private void saveSession (Session session) { if (session != null && session.getId() != null ) { byte [] key = getKey(session.getId().toString()); byte [] value = SerializationUtils.serialize(session); jedisUtil.set(key, value); jedisUtil.expire(key, 600 ); } } @Override protected Serializable doCreate (Session session) { Serializable sessionId = generateSessionId(session); assignSessionId(session, sessionId); saveSession(session); return sessionId; } @Override protected Session doReadSession (Serializable sessionId) { System.out.println("doReadSession" ); if (sessionId == null ) { return null ; } byte [] key = getKey(sessionId.toString()); byte [] value = jedisUtil.get(key); return (Session) SerializationUtils.deserialize(value); } @Override public void update (Session session) throws UnknownSessionException { saveSession(session); } @Override public void delete (Session session) { if (session == null || session.getId() == null ) { return ; } byte [] key = getKey(session.getId().toString()); jedisUtil.del(key); } @Override public Collection<Session> getActiveSessions () { Set<byte []> keys = jedisUtil.keys(SHIRO_SESSION_PREFIX); Set<Session> sessions = new HashSet <>(); if (CollectionUtils.isEmpty(keys)) { return sessions; } for (byte [] key : keys ) { Session session = (Session) SerializationUtils.deserialize(jedisUtil.get(key)); sessions.add(session); } return sessions; } }
添加spring-redis.xml文件,内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean class ="redis.clients.jedis.JedisPool" id ="jedisPool" > <constructor-arg ref ="jedisPoolConfig" /> <constructor-arg value ="127.0.0.1" /> <constructor-arg value ="6379" /> </bean > <bean class ="redis.clients.jedis.JedisPoolConfig" id ="jedisPoolConfig" /> </beans >
spring.xml中添加如下代码;
1 2 3 4 5 6 7 8 9 10 11 12 <bean class ="com.lucifer.session.CustomSessionManager" id ="sessionManager" > <property name ="sessionDAO" ref ="redisSessionDao" /> </bean > <bean class ="com.lucifer.session.RedisSessionDao" id ="redisSessionDao" /> <bean id ="securityManager" class ="org.apache.shiro.web.mgt.DefaultWebSecurityManager" > ....添加如下代码.... <property name ="sessionManager" ref ="sessionManager" /> </bean >
解决Redis实现session共享存在的问题: 多次访问redis; CustomSessionManager.java中的代码
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 public class CustomSessionManager extends DefaultWebSessionManager { @Override protected Session retrieveSession (SessionKey sessionKey) throws UnknownSessionException { Serializable sessionId = getSessionId(sessionKey); ServletRequest request = null ; if (sessionKey instanceof WebSessionKey) { request = ((WebSessionKey) sessionKey).getServletRequest(); } if (request != null && sessionId != null ) { Session session = (Session) request.getAttribute(sessionId.toString()); if (session != null ){ return session; } } Session session = super .retrieveSession(sessionKey); if (request != null && sessionId != null ) { request.setAttribute(sessionId.toString(), session); } return session; } }
Shiro使用Redis实现缓存管理
CacheManeger、Cache 可以使用echache或者shiro实现。
Redis实现CacheManager
添加RedisCacheManager.java:
1 2 3 4 5 6 7 8 9 public class RedisCacheManager implements CacheManager { @Resource private RedisCache redisCache; @Override public <K, V> Cache<K, V> getCache (String s) throws CacheException { return redisCache; } }
添加RedisCache.java:
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 @Component public class RedisCache <K, V> implements Cache <K, V> { @Resource private JedisUtil jedisUtil; private final String CACHE_PREFIX = "lucifer-cache:" ; private byte [] getKey(K k) { if (k instanceof String) { return (CACHE_PREFIX + k).getBytes(); } return SerializationUtils.serialize(k); } @Override public V get (K k) throws CacheException { System.out.println("从redis中获取数据" ); byte [] value = jedisUtil.get(getKey(k)); if (value != null ) { return (V) SerializationUtils.deserialize(value); } return null ; } @Override public V put (K k, V v) throws CacheException { byte [] key = getKey(k); byte [] value = SerializationUtils.serialize(v); jedisUtil.set(key, value); jedisUtil.expire(key, 600 ); return v; } @Override public V remove (K k) throws CacheException { byte [] key = getKey(k); byte [] value = jedisUtil.get(key); jedisUtil.del(key); if (value != null ) { return (V) SerializationUtils.deserialize(value); } return null ; } @Override public void clear () throws CacheException { } @Override public int size () { return 0 ; } @Override public Set<K> keys () { return null ; } @Override public Collection<V> values () { return null ; } }
spring.xml中添加配置
1 2 3 4 5 6 7 <bean class ="com.lucifer.cache.RedisCacheManager" id ="cacheManager" /> <bean id ="securityManager" class ="org.apache.shiro.web.mgt.DefaultWebSecurityManager" > ....省略.... <property name ="cacheManager" ref ="cacheManager" /> </bean >
Shiro自动登录
Shiro RememberMe
spring.xml中添加
1 2 3 4 5 6 7 <bean class ="org.apache.shiro.web.mgt.CookieRememberMeManager" id ="cookieRememberMeManager" > <property name ="cookie" ref ="cookie" /> </bean > <bean class ="org.apache.shiro.web.servlet.SimpleCookie" id ="cookie" > <constructor-arg value ="rememberMe" /> <property name ="maxAge" value ="200000" /> </bean >
controller中的示例代码:
1 2 3 4 5 6 7 @RequestMapping(value = "/subLogin", method = RequestMethod.POST, produces = "application/json;charset=utf-8") @ResponseBody public String subLogin (User user) {........ token.setRememberMe(user.isRememberMe()); ........ }
遇到问题 问题一
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘securityManager’ defined in class path resource [spring/spring-shiro.xml]: Cannot resolve reference to bean ‘shiroCacheManager’ while setting bean property ‘cacheManager’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘shiroCacheManager’ defined in class path resource [spring/spring-shiro.xml]: Initialization of bean failed; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type [org.springframework.cache.ehcache.EhCacheCacheManager] to required type [net.sf.ehcache.CacheManager] for property ‘cacheManager’; nested exception is java.lang.IllegalStateException: Cannot convert value of type [org.springframework.cache.ehcache.EhCacheCacheManager] to required type [net.sf.ehcache.CacheManager] for property ‘cacheManager’: no matching editors or conversion strategy found
处理方式: spring-ehcache.xml 从
1 2 3 4 5 6 7 8 9 10 11 12 13 <cache:annotation-driven cache-manager ="ehCacheManager" /> <bean id ="ehcache" class ="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" > <property name ="configLocation" value ="classpath:ehcache.xml" /> <property name ="shared" value ="true" /> </bean > <bean id ="ehCacheManager" class ="org.springframework.cache.ehcache.EhCacheCacheManager" > <property name ="cacheManager" ref ="ehcache" /> </bean >
修改为
1 2 3 4 5 6 <bean id ="ehCacheManager" class ="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" > <property name ="configLocation" value ="classpath:ehcache.xml" /> <property name ="shared" value ="true" /> </bean >
spring-shiro.xml
1 2 3 4 5 6 7 8 9 10 <bean id ="shiroCacheManager" class ="org.apache.shiro.cache.ehcache.EhCacheManager" > <property name ="cacheManager" ref ="ehCacheManager" /> </bean > <bean id ="securityManager" class ="org.apache.shiro.web.mgt.DefaultWebSecurityManager" > <property name ="realm" ref ="userRealm" /> <property name ="cacheManager" ref ="shiroCacheManager" /> </bean >
问题二
2019-04-11 23:16:31,395 ERROR [RMI TCP Connection(4)-127.0.0.1] org.springframework.web.context.ContextLoader - Context initialization failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘shiroFilter’ defined in class path resource [spring/spring-shiro.xml]: Cannot resolve reference to bean ‘formAuthenticationFilter’ while setting bean property ‘filters’ with key [TypedStringValue: value [authc], target type [null]]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘formAuthenticationFilter’ defined in class path resource [spring/spring-shiro.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property ‘loginUrl’ of bean class [com.weyoung.platform.shiro.filter.FormAuthenticationFilter]: Bean property ‘loginUrl’ is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
相关