apache shiro整合spring(一)
apache shiro整合spring
maven引入jar包
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency>
将shiro配置文件整合到spring体系中
方式一:直接在spring的配置文件中import shiro的配置文件
web.xml中配置spring
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
在spring配置文件中引入shiro配置文件
<import resource="applicationContext-shiro.xml"/>
方式二:直接在web.xml中配置shiro的配置文件
<!-- Spring ApplicationContext 配置文件的路径,可使用通配符,多个路径用,号分隔 此参数用于后面的Spring Context Loader --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:/applicationContext-shiro.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
在web.xml中定义shiro的过滤器
<!-- 这里的filter-name 要和spring 的applicationContext-shiro.xml 里的 org.apache.shiro.spring.web.ShiroFilterFactoryBean 的bean name 相同 --> <filter> <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>
1.shiro中定义的filtername bean是要被wen.xml引用的,所以这里的filtername要和shiro中的filtername相同
2.shiro的filter要定义在Struts2的filter之前,否则action无法创建
shiro.xml配置文件(配置校验的策略,哪些校验,哪些放行)
<!-- 安全管理 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- Single realm app. If you have multiple realms, use the 'realms' property instead. --> <property name="realm" ref="authRealm"/><!-- 引用自定义的realm --> <!-- 二级缓存 --> <property name="cacheManager" ref="shiroEhcacheManager"/> </bean> <!-- 自定义权限认证 --> <bean id="authRealm" class="com.fyh.www.shiro.AuthRealm"> <!-- <property name="userService" ref="userService"/> --> <!-- 自定义密码加密算法 --> <property name="credentialsMatcher" ref="passwordMatcher"/> </bean> <!-- 设置密码加密策略 md5hash --> <bean id="passwordMatcher" class="com.fyh.www.shiro.CustomCredentialsMatcher"/> <!-- 此bean要被web.xml引用,和web.xml中的filtername同名 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/index.jsp" /> <!-- 没有认证返回地址 --> <property name="unauthorizedUrl" value="/index2.jsp" /> <!-- 没有授权返回地址 --> <property name="filterChainDefinitions"> <value> <!-- **代表任意子目录 --> /login/** = anon /user/** = authc,roles[admin] /test/** = authc,perms[测试用的lkkk] </value> </property> </bean> <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 --> <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/> </bean> <!-- 保证实现了Shiro内部lifecycle函数的bean执行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 生成代理,通过代理进行控制 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> </bean> <!-- 安全管理器 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
shiro缓存(ehcache支持ehcache-shiro.xml)
<ehcache updateCheck="false" name="shiroCache"> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> </ehcache>
java代码实现
自定义密码认证
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher { public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { UsernamePasswordToken usertoken = (UsernamePasswordToken) token; //注意token.getPassword()拿到的是一个char[],不能直接用toString(),它底层实现不是我们想的直接字符串,只能强转 Object tokenCredentials = Encrypt.md5(String.valueOf(usertoken.getPassword()),usertoken.getUsername()); Object accountCredentials = getCredentials(info); //将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false return equals(tokenCredentials, accountCredentials); } }
util中定义shiro的MD5加密方法
public class Encrypt { /* * 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据, * 常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”, * 产生的散列值是“21232f297a57a5a743894a0e4a801fc3”, * 可以到一些md5解密网站很容易的通过散列值得到密码“admin”, * 即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据, * 如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。 */ //高强度加密算法,不可逆 public static String md5(String password, String salt){ return new Md5Hash(password,salt,2).toString(); } public static void main(String[] args) { System.out.println(new Md5Hash("123456","tony",2).toString()); } }
自定义realm AuthRealm
在认证、授权内部实现机制中都有提到,最终处理都将交给Realm进行处理。
因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。
通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
public class AuthRealm extends AuthorizingRealm{ private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } //授权 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("授权"); //获取当前用户 User user = (User)principals.fromRealm(getName()).iterator().next(); //得到权限字符串 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<Role> roles = user.getRoles(); List<String> list = new ArrayList(); for(Role role :roles){ Set<Module> modules = role.getModules(); for(Module m:modules){ if(m.getCtype()==0){ //说明是主菜单 list.add(m.getCpermission()); } } } info.addStringPermissions(list); return info; } //认证 登录 protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { System.out.println("认证"); UsernamePasswordToken upToken = (UsernamePasswordToken)token; User user = userService.findUserByName(upToken.getUsername()); if(user==null){ return null; }else{ AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName()); return info; } } }
修改传统登录为shiro登录
public class LoginAction extends BaseAction { private static final long serialVersionUID = 1L; private String username; private String password; private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } public String login() throws Exception { /* * shiro登录方式:根据用户名获取密码,密码为null非法用户;有密码检查是否用户填写的密码 * 登录成功后无需往httpsession中存放当前用户,这样就跟web容器绑定,关联太紧密;它自己创建 * subject对象,实现自己的session。这个跟web容器脱离,实现松耦合。 */ //调用shiro判断当前用户是否是系统用户 Subject subject = SecurityUtils.getSubject(); //得到当前用户 //shiro是将用户录入的登录名和密码(未加密)封装到token对象中 UsernamePasswordToken token = new UsernamePasswordToken(userName,password); try{ subject.login(token); //自动调用AuthRealm.doGetAuthenticationInfo //写seesion,保存当前user对象 User curUser = (User)subject.getPrincipal(); //从shiro中获取当前用户 System.out.println(curUser.getDept().getDeptName()); //让懒加载变成立即加载 Set<Role> roles = curUser.getRoles(); for(Role role :roles){ Set<Module> moduless = role.getModules(); for(Module m :moduless) System.out.println(m.getName()); } session.put(SysConstant.CURRENT_USER_INFO, curUser); //Principal 当前用户对象 }catch(Exception ex){ super.put("errorInfo","用户名密码错误,请重新填写!"); ex.printStackTrace(); return "login"; } return SUCCESS; } public String logout(){ session.remove(SysConstant.CURRENT_USER_INFO); //删除session return "logout"; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
页面使用shiro标签
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%> <shiro:hasPermission name="sysadmin"> <span id="topmenu" onclick="toModule('sysadmin');">系统管理</span> </shiro:hasPermission>