一、背景
其实很早的时候,就在项目中有使用到shiro做登陆认证,直到今天才又想起来这茬,自己抽空搭了一个spring+springmvc+mybatis和shiro进行集成的种子项目,当然里面还有很简单的测试。本文将讲述在maven下如何进行集成,希望对你有所帮助,喜欢请推荐。至于shiro相关的,最近也会写几篇介绍的,希望能够有一个主观的了解。
二、集成步骤
说明:关于spring+springmvc+mybatis的集成请移步另一篇博客:Spring+SpringMvc+Mybatis框架集成搭建教程
1.第一步引入shiro依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency>
2.在web.xml中引入shiro的filter
<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>
3.resources文件下的spring目录下新建spring-shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <description>Shiro Configuration</description> <!--custom myself realm--> <bean id="customRealm" class="com.hafiz.www.shiro.CustomRealm"/> <!--Shiro`s main business-tier object for web-enable applications--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm"/> </bean> <!--shiro filter--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.html"/> <property name="successUrl" value="/index.html"/> <property name="unauthorizedUrl" value="/unauthorized.html"/> <property name="filters"> <util:map> <entry key="auth"> <bean class="com.hafiz.www.filter.AuthorizeFilter"/> </entry> </util:map> </property> <property name="filterChainDefinitions"> <value> /login.json = anon /logout.json = anon /js/** = anon / = authc /** = auth </value> </property> </bean> </beans>
4.新建自定义的Realm,CustomRealm.java
package com.hafiz.www.shiro; import com.hafiz.www.po.UserEntity; import com.hafiz.www.service.UserService; import org.apache.commons.collections4.CollectionUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; 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.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Desc:自定义Realm * Created by hafiz.zhang on 2017/7/21. */ public class CustomRealm extends AuthorizingRealm{ private static final String _WILDCARD = "*"; private static final String _PATTERN_APPEND = "+.*"; @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 如果项目不需要授权,则该方法直接 return null; UserEntity operator = (UserEntity) principalCollection.getPrimaryPrincipal(); //获取该用户具有的所有的角色资源(把null换成findResourceUrlById()) List<String> resourceList = null; SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> allPermissions = new HashSet<>(resourceList); allPermissions.remove(""); allPermissions.remove(null); List<String> patternPermissions = new ArrayList<>(); //通配url,以*,或者.* if (CollectionUtils.isNotEmpty(allPermissions)) { for (String per : allPermissions) { if (per.endsWith(_WILDCARD)) { patternPermissions.add(per); } } } if (CollectionUtils.isNotEmpty(patternPermissions)) { allPermissions.removeAll(patternPermissions); for (String pat : patternPermissions) { if(pat.endsWith(_WILDCARD)){ info.addObjectPermission(new CustomRegexPermission(pat.substring(0,pat.length()-1)+_PATTERN_APPEND)); }else{ info.addObjectPermission(new CustomRegexPermission(pat+_PATTERN_APPEND)); } } } info.addStringPermissions(allPermissions); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); List<UserEntity> users = userService.getByUserName(username); if (CollectionUtils.isEmpty(users)) { throw new UnknownAccountException(); } if (users.size() != 1) { throw new LockedAccountException("用户名重复,请联系技术"); } UserEntity user = users.get(0); username = user.getUserName(); String password = user.getPassword(); // 第一个参数也可以放user对象,这样在doGetAuthorizationInfo()方法中可以直接使用 return new SimpleAuthenticationInfo(username, password, getName()); } }
说明:doGetAuthorizationInfo()是做授权,比如项目中有很多资源,指定角色的人员只有指定的资源,这种情况可以使用这个方法来做授权,doGetAuthenticationInfo()方法做认证,我们一般是用作用户登陆主逻辑,这个方法中我们只需要根据用户提供的用户名去数据库中查找对应的用户信息,然后用该信息返回一个SimpleAuthenticationInfo对象即可,不需要比较数据库中的密码和token中的密码是否一直,因为在登陆时shiro会帮我们做这件事,不匹配会抛出IncorrectCredentialsException来提示密码错误。
5.自定义AuthroizeFilter.java
package com.hafiz.www.filter; import com.alibaba.fastjson.JSON; import com.hafiz.www.consts.AppConst; import com.hafiz.www.vo.JsonResult; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.Writer; /** * Desc:认证验证过滤器 * Created by hafiz.zhang on 2017/7/21. */ public class AuthorizeFilter extends AuthorizationFilter { @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; Subject subject = getSubject(request, response); if (!subject.isAuthenticated()) { String requestURI = request.getRequestURI(); if (requestURI.endsWith(".json")) { JsonResult jr = new JsonResult(); jr.setCode(AppConst.UNAUTHORIZED); jr.setMsg("登陆超时,请重新登录"); response.setStatus(AppConst.UNAUTHORIZED); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=UTF-8"); Writer w = response.getWriter(); w.write(JSON.toJSONString(jr)); w.flush(); w.close(); } else { response.sendRedirect(request.getContextPath() + "/login.html"); } return false; } Boolean isAjax = isAjax(request); if (subject.getPrincipal() != null && isAjax) { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=UTF-8"); response.setStatus(AppConst.UNAUTHORIZED); Writer w = response.getWriter(); JsonResult jr = new JsonResult(); jr.setCode(AppConst.UNAUTHORIZED); jr.setMsg("无权限操作!"); w.write(JSON.toJSONString(jr)); w.flush(); w.close(); return false; } return super.onAccessDenied(servletRequest, servletResponse); } /** * 根据请求头判断是不是ajax请求 * * @param request 请求实体 * * @return */ private Boolean isAjax(HttpServletRequest request) { Boolean isAjax = request.getHeader("X-Requested-With") != null && "XMLHttpRequest".equals( request.getHeader("X-Requested-With").toString()); return isAjax; } /** * 判断用户是否可以访问请求的资源 * * @param request 用户请求 * * @param response 响应 * * @param mappedValue * * @return * * @throws Exception */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { // 登陆请求直接放行 if (isLoginRequest(request, response)) { return true; } // 获取用户认证信息 Subject subject = getSubject(request, response); if (!subject.isAuthenticated()) { HttpServletRequest httpServletRequest = (HttpServletRequest)request; HttpSession session = httpServletRequest.getSession(); String requestUrl = httpServletRequest.getRequestURL().toString(); session.setAttribute(AppConst.LAST_URL_KEY, requestUrl); return false; } // 判断请求资源是否授权(如果项目不需要授权,下面省略,直接return true,否则加上下面注释掉的代码,然后最后return false;) /*String resource = getPathWithinApplication(request); if (subject.isPermitted(resource)) { return true; }*/ return true; } }
6.自定义SessionUtils.java来管理shiro相关的session等
package com.hafiz.www.shiro; import com.hafiz.www.po.UserEntity; import com.hafiz.www.vo.JsonResult; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Desc: 用户Session工具类 * Created by hafiz.zhang on 2017/7/22. */ public class SessionUtils { private static final Logger LOGGER = LoggerFactory.getLogger(SessionUtils.class); /** * 获取登陆的key,即用户名 * * @return */ public static String getLoginKey() { Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { return (String)subject.getPrincipal(); } return null; } /** * 获取当前登陆用户 * * @return */ public static UserEntity getLoginUser() { Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { Session session = subject.getSession(); Object loginUser = session.getAttribute("loginUser"); return loginUser == null ? null : (UserEntity)loginUser; } return null; } /** * 获取当前登陆用户id * * @return */ public static Long getLoginUserId() { UserEntity user = getLoginUser(); if (user != null) { return user.getId(); } return null; } /** * 获取当前用户是否登陆 * * @return */ public static Boolean isLoggedIn() { boolean isLoggedIn = SecurityUtils.getSubject().isAuthenticated(); return isLoggedIn; } public static JsonResult login(String userName, String password) { Subject subject = SecurityUtils.getSubject(); AuthenticationToken token = new UsernamePasswordToken(userName, password); JsonResult jr = new JsonResult(); try { subject.login(token); } catch (UnknownAccountException ue) { LOGGER.error("用户不存在:{}", userName); jr.setSuccess(false); jr.setMsg("没有该账号"); } catch (LockedAccountException le) { LOGGER.error("用户名重复"); jr.setSuccess(false); jr.setMsg("用户名重复,请联系技术"); } catch (IncorrectCredentialsException ie) { LOGGER.error("密码错误"); jr.setSuccess(false); jr.setMsg("密码错误"); } catch (Exception e) { LOGGER.error("登陆出错:{}", e.getLocalizedMessage()); jr.setSuccess(false); jr.setMsg("登陆出错:" + e.getLocalizedMessage()); } return jr; } /** * 用户退出登陆 */ public static void logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); } }
经过上面这些步骤,我们就完成了spring和shiro的集成,关于简单的测试程序,不再贴出,在这里提供该种子项目的github地址:https://github.com/hafizzhang/spring-shiro.git
三、总结
通过本文,我们就完成了spring集成shiro做登陆的授权和认证,其实很简单,继续努力成长!