关于Shiro Shiro是Apache公司旗下产品,与Apache项目结合起来稳定可靠,使用灵活,可以独立于任何项目独立存在,粒度比较大,对于普通的web项目足够满足需求。与Shiro相对应的是Spring-security,Spring-security对Spring结合较好,如果项目用的springmvc,使用起来很方便。但是 如果项目中没有用到spring,那就不要考虑它了,而且粒度很细不适用于小型项目。
Shiro的总体架构:
Shiro主要组件包括:Subject,SecurityManager,Authenticator,Authorizer,SessionManager,CacheManager,Cryptography,Realms
Subject表示与系统交互的对象,一般是登录系统的操作用户,也可能是另外一个软件系统
SecurityManager是Shiro架构最核心的组件。实际上,SecurityManager就是Shiro框架的控制器,协调其他组件一起完成认证和授权
Authenticator用于认证,协调一个或者多个Realm,从Realm指定的数据源取得数据之后进行执行具体的认证# Maven项目中Shiro的使用
Authorizer用户访问控制授权,决定用户是否拥有执行指定操作的权限
Shiro与生俱来就支持会话管理,这在安全类框架中都是独一无二的功能。即便不存在web容器环境,shiro都可以使用自己的会话管理机制,提供相同的会话API
Shiro提供了一个加解密的命令行工具jar包,需要单独下载使用。 详见 https://shiro.apache.org/download.html
一个常见的登陆过程:
1)使用用户的登录信息创建令牌
Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassward());
token可以理解为用户令牌,登录的过程被抽象为Shiro验证令牌是否具有合法身份以及相关权限。
2)执行登陆操作
Shiro的核心部分是SecurityManager,它负责安全认证与授权。Shiro本身已经实现了所有的细节,用户可以完全把它当做一个黑盒来使用。SecurityUtils对象,本质上就是一个工厂类似Spring中的ApplicationContext。Subject是初学者比较难于理解的对象,很多人以为它可以等同于User,其实不然。Subject中文翻译:项目,而正确的理解也恰恰如此。它是你目前所设计的需要通过Shiro保护的项目的一个抽象概念。通过令牌(token)与项目(subject)的登陆(login)关系,Shiro保证了项目整体的安全。
3)检查角色
subject.checkRole("admin" );
4)检查权限
subject.checkPermission("user:delete" );
Maven项目中使用Shiro 在pom.xml中配置依赖包 <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <version > 1.4.0</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <version > 1.4.0</version > </dependency > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-web</artifactId > <version > 1.4.0</version > </dependency >
在Spring.xml中配置相关bean <bean id ="shiroFilter" class ="org.apache.shiro.spring.web.ShiroFilterFactoryBean" > <property name ="securityManager" ref ="securityManager" /> <property name ="loginUrl" value ="login.html" /> <property name ="unauthorizedUrl" value ="403.html" /> <property name ="filterChainDefinitions" > <value > /login.html = anon /subLogin = anon /* = authc </value > </property > </bean > <bean class ="filter.RolesOrFilter" id ="rolesOrFilter" />
<bean id ="securityManager" class ="org.apache.shiro.web.mgt.DefaultWebSecurityManager" > <property name ="realm" ref ="realm" /> </bean > <bean id ="realm" class ="realm.CustomRealm" > <property name ="credentialsMatcher" ref ="credentialsMatcher" /> </bean > <bean class ="org.apache.shiro.authc.credential.HashedCredentialsMatcher" id ="credentialsMatcher" > <property name ="hashAlgorithmName" value ="md5" /> <property name ="hashIterations" value ="1" /> </bean >
需要注意filterChainDefinitions过滤器中对于路径的配置是有顺序的,当找到匹配的条目之后容器不会再继续寻找。因此带有通配符的路径要放在后面。三条配置的含义是: /authc/admin需要用户有用admin权限、/authc/用户必须登录才能访问、/ 其他所有路径任何人都可以访问
spring-mvc.xml配置 <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 >
缓存机制ehcache.xml <?xml version="1.0" encoding="UTF-8"?> <ehcache name ="shirocache" > <diskStore path ="java.io.tmpdir" /> <cache name ="passwordRetryCache" maxEntriesLocalHeap ="2000" eternal ="false" timeToIdleSeconds ="1800" timeToLiveSeconds ="0" overflowToDisk ="false" statistics ="true" > </cache > </ehcache >
timeToLiveSeconds为缓存的最大生存时间,timeToIdleSeconds为缓存的最大空闲时间,当eternal为false时ttl和tti才可以生效
散列算法与加密算法 <bean class ="org.apache.shiro.authc.credential.HashedCredentialsMatcher" id ="credentialsMatcher" > <property name ="hashAlgorithmName" value ="md5" /> <property name ="hashIterations" value ="1" /> </bean >
web.xml <web-app xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns ="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation ="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id ="WebApp_ID" version ="3.0" > <!-将Shiro的配置文件交给Spring监听器初始化- > <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath*:spring/spring.xml</param-value > </context-param > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener > <listener > <listener-class > org.springframework.web.context.request.RequestContextListener</listener-class > </listener > <filter > <filter-name > shiroFilter</filter-name > <filter-class > org.springframework.web.filter.DelegatingFilterProxy</filter-class > </filter > <filter-mapping > <filter-name > shiroFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping > <servlet > <servlet-name > springDispatcherServlet</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > classpath*:spring/spring-mvc.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > springDispatcherServlet</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping > </web-app >
基于JDBC的自定义reaml public class CustomRealm extends AuthorizingRealm {@Resource private UserDao userDao;protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); Set<String> permissions = new HashSet<>(); Set<String> roles = getUserRolesByName(username); for (String s:roles){ Set<String> tem = getUserPermissionByName(s); permissions.addAll(tem); }
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setStringPermissions(permissions); authorizationInfo.setRoles(roles); return authorizationInfo; } private Set<String> getUserPermissionByName (String username) { List<String> list = userDao.getPermissionByName(username); Set<String> permission = new HashSet<String>(list); return permission; } private Set<String> getUserRolesByName (String username) { List<String> list = userDao.getRolesByName(username); Set<String> roles = new HashSet<String>(list); return roles; } protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); String passwd = getUserPasswdByName(username); if (passwd == null ){ return null ; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,passwd,"custom" ); return authenticationInfo; } private String getUserPasswdByName (String name) { User user = userDao.getUserByName(name); if (user != null ){ return user.getPassward(); } return null ; } public static void main (String[] args) { Md5Hash md5Hash = new Md5Hash("what" ,"what" ); System.out.println(md5Hash.toString()); } }
根据Shiro的设计思路,用户与角色之前的关系为多对多,角色与权限之间的关系也是多对多。在数据库中需要因此建立5张表,分别是用户表(存储用户名,密码,盐等)、角色表(角色名称,相关描述等)、权限表(权限名称,相关描述等)、用户-角色对应中间表(以用户ID和角色ID作为联合主键)、角色-权限对应中间表(以角色ID和权限ID作为联合主键)。具体dao与service的实现本文不提供。总之结论就是,Shiro需要根据用户名和密码首先判断登录的用户是否合法,然后再对合法用户授权。而这个过程就是Realm的实现过程。
Shiro会话管理 shiro实现了简单的会话管理,由于自带的会话管理需要多次查询数据库,在实际项目中对性能影响过大,所以结合redis 实现会话管理时比较好的选择,当然也可以实现一个二级缓存进一步提升性能。在shiro中配置和实现如下:
spring配置文件中配置自定义sessionManagement(可以用注解方式实现)
<!-自定义SessionDAO- > <bean class ="session.RedisSessionDao" id ="redisSessionDao" /> <!-自定义SessionManager- > <bean class ="session.CustomSessionManager" id ="sessionManager" > <property name ="sessionDAO" ref ="redisSessionDao" /> </bean >
自定义Management
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; } }
自定义Cache实现权限和角色数据缓存 虽然角色缓存和权限下缓存包含的数据可能并不多,但是有效的减少了对数据库的访问次数,而且速度更快。
实现Shiro 自带CacheManager 接口@Component public class RedisCacheManager <K , V > implements CacheManager { @Resource private RedisCache redisCache; @Override public <K, V> Cache<K, V> getCache (String s) throws CacheException { return redisCache; } }
spring 中配置如下:
<bean class ="cache.RedisCacheManager" id ="cacheManager" />
一个简单的DEMO 基于Spring的实现DemoDemo
为正常使用来必力评论功能请激活JavaScript