spring+shiro 配置使用(完整代码篇)
2016-09-07 12:37
369 查看
web安全框架,shiro相比spring security,更轻量级,配置简单易懂,小巧灵活,功能强大,和spring完美结合,shiro上手超级简单,一看就懂,但如果需求较为复杂,仍需要细细研究其中原理,灵活配置。本人才疏学浅,本文仅涉及登录验证,动态权限验证,后面有机会再慢慢研究。
zhangsan登录时,验证信息(自己写service判断),登录成功后,shiro会将登录信息存放到认证信息类中,并缓存起来,再次访问/auth/test时,shiro就不会拦截,直接访问资源。
注意:缓存的前提是,我们配置了
项目启动时会初始化资源权限信息并缓存起来, 比如/test1 对应a, /test2对应c
zhangsan登录时会查询他对应的权限,并缓存起来,比如 zhangsan对应a,b
当zhangsan访问/test1资源时,shiro会把前面两个存储的权限(a),(a,b)进行对比,zhangsan有a这个权限,就可以访问。
当shangsan访问/test2资源时,shiro会把前面两个存储的权限(c),(a,b)进行对比,zhangsan没有c这个权限,就不能访问,跳转到spring-shiro配置的
注意:权限认证之前会先进行登录认证,就算/test1不是/auth/test1,也会进行登录验证
引入Shiro的Maven依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>1.3.0</version> </dependency>
web.xml配置
<!-- 配置shiro的核心拦截器 --> <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>
spring-shiro.xml 配置
<!-- 配置权限管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- ref对应我们写的realm JuJinShiro --> <property name="realm" ref="JuJinShiro"/> <!-- 使用下面配置的缓存管理器 --> <property name="cacheManager" ref="cacheManager" /> </bean> <bean id="JuJinShiro" class="com.hd.security.JuJinShiro"></bean> <bean id="chainDefinitionSectionMetaSource" class="com.hd.security.ChainDefinitionSectionMetaSource"> <property name="filterChainDefinitions"> <value> <!-- anon表示此地址不需要任何权限即可访问 --> /css/**=anon /imgs/**=anon /js/**=anon /auth/**=authc </value> </property> </bean> <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 调用我们配置的权限管理器 --> <property name="securityManager" ref="securityManager" /> <!-- 配置我们的登录请求地址 --> <property name="loginUrl" value="/auth/silencelogin" /> <!-- 配置我们在登录页登录成功后的跳转地址 --> <property name="successUrl" value="/auth/auth" /> <!-- 如果您请求的资源不再您的权限范围,则跳转到无权限地址 --> <property name="unauthorizedUrl" value="/auth/unauthorized" /> <!-- 权限配置,动态的可以读取数据库 --> <property name="filterChainDefinitionMap" ref="chainDefinitionSectionMetaSource" /> </bean> <!-- 会话管理器 --> <bean id="sessionManager" class="org.apach 14e53 e.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="globalSessionTimeout" value="1800" /> <property name="deleteInvalidSessions" value="true" /> <property name="sessionValidationSchedulerEnabled" value="true" /> </bean> <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" /> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
自定义realm配置
package com.hd.security; import java.util.List; 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.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.hd.common.Constants; import com.hd.dao.mappers.security.PermissionDao; import com.hd.entity.login.LoginBean; import com.hd.service.login.LoginService; @Service @Transactional public class JuJinShiro extends AuthorizingRealm{ protected static Logger logger = LoggerFactory.getLogger(JuJinShiro.class); @Autowired private LoginService loginService; @Autowired private PermissionDao permissionDao; /** * 权限认证(仅使用了permission) */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission) SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); String userId = (String) principalCollection.fromRealm(getName()).iterator().next(); // 查询用户所对应的资源ID List<String> resources = permissionDao.getResourceForUser(userId); info.addStringPermissions(resources); return info; } /** * 登录认证 */ protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authenticationToken) throws AuthenticationException { //UsernamePasswordToken对象用来存放提交的登录信息 UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken; char[] password = token.getPassword(); //查询用户 LoginBean user = new LoginBean(); try { // 验证登录信息 user = loginService.loginCheck(token.getUsername(),String.valueOf(password)); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } //注入session Session shiroSession = SecurityUtils.getSubject().getSession(); shiroSession.setAttribute(Constants.USER_KEY, user); shiroSession.setAttribute(Constants.USERNAME_KEY, user.getUser_id()); //将此用户存放到登录认证info中 return new SimpleAuthenticationInfo(user.getUser_id(), String.valueOf(password), getName()); } }
package com.hd.security; import org.apache.shiro.config.Ini; import org.apache.shiro.config.Ini.Section; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; import com.hd.service.security.PermissionService; public class ChainDefinitionSectionMetaSource implements FactoryBean<Ini.Section>{ @Autowired private PermissionService permissionService; public static String filterChainDefinitions; /** * 默认permission字符串 */ public static final String PREMISSION_STRING ="perms[\"{0}\"]"; @Override public Section getObject() throws Exception { // 初始化权限 return permissionService.initPermission(filterChainDefinitions); } /** * 通过filterChainDefinitions对默认的url过滤定义 * * @param filterChainDefinitions 默认的url过滤定义 */ public void setFilterChainDefinitions(String filterChainDefinitions) { this.filterChainDefinitions = filterChainDefinitions; } @Override public Class<?> getObjectType() { // TODO Auto-generated method stub return this.getClass(); } @Override public boolean isSingleton() { // TODO Auto-generated method stub return false; } }
权限操作
package com.hd.service.security; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.shiro.config.Ini; import org.apache.shiro.config.Ini.Section; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager; import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; import org.apache.shiro.web.servlet.AbstractShiroFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.hd.dao.mappers.security.PermissionDao; import com.hd.service.BaseService; import com.hd.utils.ShiroUtils; @Service public class PermissionService extends BaseService{ @Autowired private ShiroFilterFactoryBean shiroFilterFactoryBean; @Autowired private PermissionDao permissionDao; /** * 默认permission字符串 */ public static final String PREMISSION_STRING="perms[\"{0}\"]"; protected static final Logger logger = LoggerFactory.getLogger(PermissionService.class); /** * 初始化框架权限资源配置 */ public Section initPermission(String filterChainDefinitions) { //shiroFilterFactoryBean.setFilterChainDefinitionMap(obtainPermission(filterChainDefinitions)); Section section = obtainPermission(filterChainDefinitions); logger.info("初始化shiro权限成功..."); return section; } /** * 更新权限资源配置 (强制线程同步) */ public void updatePermission(String filterChainDefinitions) { synchronized (shiroFilterFactoryBean) { AbstractShiroFilter shiroFilter = null; try { shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject(); } catch (Exception e) { logger.error(e.getMessage()); } // 获取过滤管理器 PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter .getFilterChainResolver(); DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager(); // 清空初始权限配置 manager.getFilterChains().clear(); shiroFilterFactoryBean.getFilterChainDefinitionMap().clear(); // 重新构建生成 shiroFilterFactoryBean.setFilterChainDefinitions(filterChainDefinitions); Map<String, String> chains = obtainPermission(filterChainDefinitions); for (Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue().trim().replace(" ", ""); manager.createChain(url, chainDefinition); } //清除用户授权信息 ShiroUtils.clearAllCachedAuthorizationInfo(); logger.info("更新shiro权限成功..."); } } /** * 读取配置资源和第三方资源 * */ private Section obtainPermission(String filterChainDefinitions) { Ini ini = new Ini(); ini.load(filterChainDefinitions); // 加载资源文件节点串 Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME); // 使用默认节点 Map<String, String> permissionMap = initOtherPermission(); if (permissionMap != null && !permissionMap.isEmpty()) { section.putAll(permissionMap); } return section; } /** * 读取第三方资源 * @return */ public Map<String, String> initOtherPermission() { Map<String,String> permissionMap = new HashMap<>(); // 获取所有需要权限的接口资源 List<Map<String, Object>> resources = permissionDao.getAllResources(); for (Map<String,Object> resource : resources) { String code = resource.get("code").toString(); String url = resource.get("url").toString(); permissionMap.put("/**/"+url+"/**", MessageFormat.format(PREMISSION_STRING,code)); } //permissionMap.put("/**/auth/**", "authc"); return permissionMap; } }
登录controller
package com.hd.controller.login; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.util.SavedRequest; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.hd.controller.BaseController; import com.hd.controller.MakeVerifyCodeService; import com.hd.entity.login.LoginBean; import com.hd.redis.RedisUtil; import com.hd.security.ChainDefinitionSectionMetaSource; import com.hd.service.login.LoginService; import com.hd.service.security.PermissionService; import com.hd.utils.CookieTool; import com.jujin.common.ExceptionHelper; import com.jujin.common.OpResult; @Controller @RequestMapping(value = "") public class loginControler extends BaseController{ protected static final Logger logger = LoggerFactory.getLogger(loginControler.class); String verifyCode = "verifyCode"; @Autowired private PermissionService permissionService; @Autowired private LoginService loginService; /** * 本系统用户登陆 * @param userid * @param password * @return */ @RequestMapping(value = "/login",method = RequestMethod.POST) @ResponseBody public OpResult login(HttpEntity<LoginBean> model,HttpServletRequest request, HttpServletResponse response) { OpResult result = new OpResult(); LoginBean login = model.getBody(); String userId = login.getUser_id(); String password = login.getPassword(); if(StringUtils.isEmpty(userId) || StringUtils.isEmpty(password) ){ result.setMsg("输入参数不完整"); result.setStatus(false); return result; } try { Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); String code = login.getCode(); if ("1".equals(session.getAttribute("codeIsShow"))) { String sessionCode = (String) session.getAttribute(verifyCode); if(code == null){ result.setMsg("请输入验证码"); result.setStatus(false); return result; } if (!StringUtils.isEmpty(code) && !code.equals(sessionCode)) { result.setMsg("验证码错误,请重新输入"); result.setStatus(false); return result; } } UsernamePasswordToken token = new UsernamePasswordToken(userId, password); //登陆认证 subject.login(token); //登陆日志 logger.info("用户" + userId + "在" + sysdate() + "登陆"); result.setStatus(true); result.setLoginstatus(1); // 更新用户权限 permissionService.updatePermission(ChainDefinitionSectionMetaSource.filterChainDefinitions); logger.info("更新用户[" + this.getLoginUserId(request) + "]权限成功"); } catch (Exception e) { result.setStatus(false); result.setLoginstatus(0); result.setMsg(e.getCause().getMessage()); } if (supportJsonp(request)) { jsonpWrapper(request, response, result); return null; } else { return result; } } // 普通图片验证码 @RequestMapping(value = "/verify", method = RequestMethod.GET) public @ResponseBody Object getVerifyImage(HttpServletRequest request, HttpServletResponse response) { try { response.setContentType("textml;charset=UTF-8"); // 将请求、响应的编码均设置为UTF-8(防止中文乱码) request.setCharacterEncoding("UTF-8"); response.setContentType("image/jpg"); OutputStream os = response.getOutputStream(); MakeVerifyCodeService service = MakeVerifyCodeService .getVerifyCode(60, 20); String code = service.getCode(); os.write(service.getImage()); logger.info("生成的验证码:" + code); Session shiroSession = SecurityUtils.getSubject().getSession(); if (shiroSession != null) { shiroSession.setAttribute(verifyCode, code); } String wx_token = request.getHeader("wx-token"); if (!StringUtils.isEmpty(wx_token)) { RedisUtil.setString("VERIFY_" + wx_token, code, 600); } Cookie c = CookieTool.getCookieByName(request, "wx_token"); if (c != null) { RedisUtil.setString("VERIFY_" + c.getValue(), code, 600); } else { String token = UUID.randomUUID().toString().toLowerCase() .replace("-", ""); RedisUtil.setString("VERIFY_" + token, code, 600); Cookie cookie = new Cookie("wx_token", token); cookie.setPath("/"); response.addCookie(cookie); } os.flush(); os.close(); } catch (Exception e) { logger.error(ExceptionHelper.getExceptionDetail(e)); } return null; } /** * 系统时间 * @return */ protected String sysdate() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); } /** * 本系统用户注销 * @return */ @RequestMapping(value = "/logout") @ResponseBody public OpResult loginout(HttpServletRequest request, HttpServletResponse response) { OpResult result = new OpResult(); try { Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { logger.info("用户" + this.getLoginUserId(request) + "在" + this.sysdate() + "注销"); subject.logout(); } result.setStatus(true); result.setLoginstatus(0); } catch (Exception e) { result.setStatus(false); result.setMsg(e.getMessage()); } return result; } /** * * * @param request */ @RequestMapping(value = "/silencelogin") public String loginSilence(HttpServletRequest request) { Subject subject = SecurityUtils.getSubject(); SavedRequest savedRequest = WebUtils.getSavedRequest(request); //获取原始请求URL String originalUrl = savedRequest.getRequestUrl(); String contextPath = request.getContextPath(); if (originalUrl.startsWith(contextPath)) { originalUrl = originalUrl.substring(contextPath.length(), originalUrl.length()); } if (subject.isAuthenticated()) { return "forward:" + originalUrl; } else { //未登录,提示用户先登录 if (originalUrl.contains("callback")) { originalUrl = originalUrl.replace(originalUrl.substring(0, originalUrl.indexOf("?")), "/needlogin"); return "forward:" + originalUrl; } else { return "forward:/needlogin"; } } } /** * 提示用户需要登录 * @return */ @RequestMapping(value = "/needlogin") @ResponseBody public OpResult needLogin(HttpServletRequest request, HttpServletResponse response) { OpResult op = new OpResult(); op.setStatus(true); op.setMsg("请登录"); op.setLoginstatus(0); if (supportJsonp(request)) { jsonpWrapper(request, response, op); return null; } else { return op; } } /** * 提示用户需要权限 * @return */ @RequestMapping(value = "/unauthorized") @ResponseBody public OpResult unauthorized(HttpServletRequest request, HttpServletResponse response) { OpResult op = new OpResult(); op.setStatus(false); op.setMsg("没有权限访问"); op.setLoginstatus(1); if (supportJsonp(request)) { jsonpWrapper(request, response, op); return null; } else { return op; } } }
登录认证业务例子:
比如我们配置了/auth/**=authc,表示所有带/auth的资源都会被拦截进行登录验证,现在zhangsan未登录首次访问资源/auth/test,shiro拦截到以后会跳转到我们配置的
<property name="loginUrl" value="/silencelogin" />,具体看loginController。
zhangsan登录时,验证信息(自己写service判断),登录成功后,shiro会将登录信息存放到认证信息类中,并缓存起来,再次访问/auth/test时,shiro就不会拦截,直接访问资源。
注意:缓存的前提是,我们配置了
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
权限认证业务例子:
比如用户shangsan,拥有角色管理员 1,管理员对应的权限为a,b,资源(接口)/test1 对应的权限为a,/test2对应的权限为c
项目启动时会初始化资源权限信息并缓存起来, 比如/test1 对应a, /test2对应c
zhangsan登录时会查询他对应的权限,并缓存起来,比如 zhangsan对应a,b
当zhangsan访问/test1资源时,shiro会把前面两个存储的权限(a),(a,b)进行对比,zhangsan有a这个权限,就可以访问。
当shangsan访问/test2资源时,shiro会把前面两个存储的权限(c),(a,b)进行对比,zhangsan没有c这个权限,就不能访问,跳转到spring-shiro配置的
<property name="unauthorizedUrl" value="/unauthorized" />
注意:权限认证之前会先进行登录认证,就算/test1不是/auth/test1,也会进行登录验证
相关文章推荐
- Apache shiro+springmvc+springdata+jpa+swagger(零配置文件使用)
- 使用Spring配置shiro时,自定义Realm中属性无法使用注解注入解决办法
- ehcache配置:使用Spring+SpringMVC+Mybatis或者有shiro【转】
- ehcache配置:使用Spring+SpringMVC+Mybatis或者有shiro【转】
- 使用Spring配置shiro时,自定义Realm中属性无法使用注解注入解决办法
- java 使用memcached以及spring 配置memcached完整实例代码
- ehcache配置:使用Spring+SpringMVC+Mybatis或者有shiro
- 使用Spring配置shiro时,自定义Realm中属性无法使用注解注入解决办法
- 简单两步快速实现shiro的配置和使用,包含登录验证、角色验证、权限验证以及shiro登录注销流程(基于spring的方式,使用maven构建)
- 使用Spring配置shiro时,自定义Realm中属性无法使用注解注入解决办法
- spring整合shiro使用注解方式配置
- 第三部分:shiro集成spring使用cas单点登录配置
- ehcache配置:使用Spring+SpringMVC+Mybatis或者有shiro【转】
- 使用Spring配置shiro时,自定义Realm中属性无法使用注解注入解决办法
- Apache shiro配置与使用(Spring整合)
- shiro集成spring使用cas单点登录配置
- (原创)使用SPRING配置LDAP认证服务
- 知识积累(十六)——使用spring和hibernate配置ehcache和query cache
- Spring学习-webcontex的自动配置和事件传播机制及使用
- Spring2.0简明手册(系列之二 Resource的配置及使用)