看过那么多框架、教程,大部分shiro的文章或教程是我见过思路最糟糕的。做者完不清楚想要表达什么起到什么做用,把大段大段的理论讲一通。你见过哪一个java教程上来就给你讲一堆基础类库,讲虚拟机的。或者hibernate教程上来就给讲他有的设计有多精妙,管理的东西有多庞大的。html
而后我曾经硬着头看了1周的所谓shiro教程,看完发现本身仍是什么都不会,什么也作不出来。倍受打击。当年初学时看think in java都没这么失落过。前端
后来想一想不对,就直接去找spring整合shiro的教程。折腾了一周总算作出来一个能够项目实用的东西了。但中间走过很多坑,其中有些多是做者漏了,还有些是由于我也是整合shiro的要适应项目里的各个东西,适合本身项目的用法(这里吐槽一下,shiro会乱的缘由就是配置的方式太多种了,并且好多文章都力求全面讲,对于一个项目真不须要全用到)。 不要跟我讲什么使用文本管理配置权限,什么写根据角色控制访问,哪一个能用的项目会这么搞。浪费lz时间。 说什么从简单入手,你这个简单没鸟用,我后要改为从数据库读权限列表,读角色,根本就不可能在你这个简单的例子上逐渐改造,这还不是浪费时间仍是什么,并且会用到你这框架的人,本身没几个项目拿来练手么。这不是浪费时间是什么。java
另外各文章或教程,shiro的运行原理或者方式,只字未提,极力各类介绍概念。喂,咱们不是搞学术的。后面看有些文章会把各个过程当中加入做用说明和本身的理解的话,这个还挺不错的。但一直内心有一个疑问困扰着我,shiro是基于session(sessionId),仍是基于tokken(每次访问都要传),由于我一直不知道前端要传什么参数,登陆功能完成后,一直调不通登陆以后的接口,初学时也不少东西不知道。讽刺的是这个答案要等本身调通了才知道。答案:shiro是基于session(sessionId),至少默认是这样的,tokken方式我没研究过(也不是个人菜,总感受性能太差了,心生厌恶。流量不大,小项目仍是session好用)。 真·心累。写这篇文章就是为了解救像我以前同样迷茫的同窗。web
搭建一个项目能够用的shiro。 集成如下内容,使用相同内容的同窗能够直接搬过去用了:ajax
WEB-INF/web.xml,其余不相关内容省略算法
<web-app> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 设置true由servlet容器控制filter的生命周期 --> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <!-- 设置spring容器filter的bean id,若是不设置则找与filter-name一致的bean--> <init-param> <param-name>targetBeanName</param-name> <param-value>shiroFilter</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
applicationContext-shiro.xml,独立的文件引入到spring的配置中,能够在web.xml中引入也能够在总的applicationContext.xml importspring
<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-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"> <!-- id属性值要对应 web.xml中shiro的filter对应的bean --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"></property> <property name="filters"> <util:map> <entry key="authc" value-ref="shiroLoginFilter" /> </util:map> </property> <!-- 未登陆跳转页面,请求地址将由formAuthenticationFilter进行表单认证 --> <!-- 本项目经过ajax访问,由ShiroLoginFilter中处理返回json信息 --> <!--<property name="loginUrl" value="/notLogin"></property>--> <!-- 认证成功统一跳转到页面,建议不配置,shiro认证成功会默认跳转到上一个请求路径 --> <!-- 本项目经过ajax访问,loginController#loging中直接返回json信息 --> <!-- <property name="successUrl" value="/first.action"></property> --> <!-- 经过unauthorizedUrl指定没有权限操做时跳转页面,这个位置会拦截不到,下面有给出解决方法 --> <!-- 本项目经过ajax访问,由BaseController中@ExceptionHandler捕获异常处理 --> <!--<property name="unauthorizedUrl" value="/refuse"></property>--> <!-- 过滤器定义,从上到下执行,通常将/**放在最下面 --> <property name="filterChainDefinitions"> <value> <!-- 对静态资源设置匿名访问 --> /assets/** = anon <!--开放登录接口--> /api/sys/login = anon /api/sys/logout = anon /login.html = anon <!-- /**=authc 全部的url都必须经过认证才能够访问 --> /** = authc <!-- /**=anon 全部的url均可以匿名访问,不能配置在最后一排,否则全部的请求都不会拦截 --> </value> </property> </bean> <!--使用ajax访问,自定义未登陆返回信息--> <bean id="shiroLoginFilter" class="com.hammer.acl.shiro.ShiroLoginFilter"></bean> <!-- 解决shiro配置的没有权限访问时,unauthorizedUrl不跳转到指定路径的问题 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop> </props> </property> </bean> <!-- securityManager安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"></property> </bean> <!-- 配置自定义Realm --> <bean id="myRealm" class="com.hammer.acl.shiro.MyRealm"> <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 --> <property name="credentialsMatcher" ref="credentialsMatcher"></property> </bean> <!-- 凭证匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!-- 加密算法 --> <property name="hashAlgorithmName" value="md5"></property> <!-- 迭代次数 --> <property name="hashIterations" value="1"></property> </bean> </beans>
MyRealm.java数据库
/** * 自定义的Realm */ public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private LoginService loginService; // 设置realm的名称 @Override public void setName(String name) { super.setName("customRealm"); } /** * 认证的方法,登陆时执行 * * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //System.out.println("————身份认证方法————"); // token是用户输入的用户名和密码 // 第一步从token中取出用户名 final String loginId = (String) token.getPrincipal(); String password = null; final Object credentials = token.getCredentials(); if (credentials instanceof char[]) { password = new String((char[]) credentials); } // 第二步:根据用户输入从数据库查询用户信息 User user = loginService.getUse4Login(loginId, password); if (user == null) { throw new UnknownAccountException("帐号或密码错误"); } // 从数据库查询到密码 //配合shiro配置的mc5加密(应该能够配置为不加密) if (password != null) { password = DigestUtils.md5Hex(password); } //加密的盐 //String salt = user.getSalt(); final HashMap<String, Object> principal = new HashMap<>(); principal.put("user", user); return new SimpleAuthenticationInfo(principal, password, this.getName()); } /** * 受权的方法,每次访问须要权限的接口都会执行 * * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //System.out.println("————权限认证————"); //从principals获取主身份信息 //将getPrimaryPrincipal方法返回值转为真实身份类型(在上边doGetAuthenticationInfo认证经过填充到SimpleAuthenticationInfo中的身份类型) //如下方法等效SecurityUtils.getSubject().getPrincipal() principals.getPrimaryPrincipal() //Map principal = (Map) SecurityUtils.getSubject().getPrincipal(); Map principal = (Map) principals.getPrimaryPrincipal(); User user = (User) principal.get("user"); List<String> permissions = (List<String>) principal.get("permissions"); //查到权限数据,返回受权信息(要包括上边的permissions) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addStringPermissions(permissions);//这里添加用户有的权限列表 simpleAuthorizationInfo.addRole(user.getRoleId());//这里添加用户所拥有的角色 return simpleAuthorizationInfo; } }
ShiroLoginFilter.javaapache
public class ShiroLoginFilter extends FormAuthenticationFilter { private static final Logger log = LoggerFactory.getLogger(ShiroLoginFilter.class); /** * 若是isAccessAllowed返回false 则执行onAccessDenied * * @param request * @param response * @param mappedValue * @return */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { boolean isAllowed = false; //前端(某些框架)测试接口(OPTIONS)直接放行 if (request instanceof HttpServletRequest) { if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) { isAllowed = true; } } isAllowed = super.isAccessAllowed(request, response, mappedValue); if (isAllowed) { //登陆状态,做一些日志记录 } return isAllowed; } /** * 未登陆时的处理 * * @param request * @param response * @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其余过滤器 * @throws Exception */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { log.info("用户未登陆"); final HttpServletRequest request2 = (HttpServletRequest) request; final HttpServletResponse response2 = (HttpServletResponse) response; //ajax访问接口返回数据结构 if (WebUtil.isAjax(request2)) {// ajax接口 //这里是个坑,若是不设置的接受的访问源,那么前端都会报跨域错误,由于这里还没到corsConfig里面 response2.setHeader("Access-Control-Allow-Origin", request2.getHeader("Origin")); response2.setHeader("Access-Control-Allow-Credentials", "true"); response2.setCharacterEncoding("UTF-8"); response2.setContentType("application/json"); Map responseData = new HashMap(); responseData.put("state", "unauthorized"); responseData.put("code", 401); responseData.put("msg", "用户未登陆"); String result = Json.toJson(responseData); PrintWriter out; try { out = response2.getWriter(); out.print(result.toString()); out.flush(); } catch (IOException e) { log.error("返回数据失败!", e); } } else { //其余状况 //shiro处理 super.onAccessDenied(request, response); //其余处理方式 // 页面,直接跳转登陆页面 //redirect("login.html", request2, response2); //web.xml处理 //response2.setStatus(401);// 客户试图未经受权访问受密码保护的页面。 } return false; } }
Login2Controller.javajson
/** * shiro登陆 */ @Slf4j @RestController @RequestMapping("/api/sys") public class Login2Controller{ @Autowired private LoginService service; @Autowired private LoginService loginService; /** * 登录 * * @param loginId 登陆帐号 * @param password 密码 */ @RequestMapping(value = "/login") public RespObject login(String loginId, String password, HttpServletRequest req) { final String host = req.getRemoteHost(); // 在认证提交前准备 token(令牌) UsernamePasswordToken token = new UsernamePasswordToken(loginId, password, host); try { // 从SecurityUtils里边建立一个 subject Subject subject = SecurityUtils.getSubject(); // 执行认证登录 subject.login(token); //set session attribute final Map principal = (Map) subject.getPrincipal(); User user = (User) principal.get("user"); // loginService.buildSessionAttr方法生成了包含 List<String> permissions,key为"permissions"; final Map sessionAttrs = loginService.buildSessionAttr(user); principal.putAll(sessionAttrs); } catch (UnknownAccountException e) { final String message = e.getMessage(); log.info(String.format("%s[%s/%s]", message, loginId, password)); throw new FailException(message); } catch (AuthenticationException e) { throw new FailException(e); } return RespObject.success(null, "登陆成功"); } /** * 退出 * * @return */ @RequestMapping(value = "/logout", method = RequestMethod.GET) public RespObject logout() { Subject subject = SecurityUtils.getSubject(); //注销 subject.logout(); return RespObject.success(null, "成功注销!"); } /** * 当前session属性 * * @param attr * @return */ @RequestMapping(value = "/current") public RespObject getCurrentAttr(String attr) { Map sessionAttrs = service.readSessionAttr(); if (Strings.isEmpty(attr)) { return RespObject.success(sessionAttrs); } else { return RespObject.success(sessionAttrs.get(attr)); } }
MyControllerAdvice.java,统一处理spring MVC异常,代码里有些调用别地的经常使用处理方法,根据实际状况修改。
/** * controller 加强器,应用到全部@RequestMapping注解方法 */ @ControllerAdvice public class MyControllerAdvice { private static final Logger log = LoggerFactory.getLogger(MyControllerAdvice.class); @ExceptionHandler @ResponseBody public Object errorHandler(HttpServletRequest request, Exception e, HttpServletResponse response) { //记录日志 if (e instanceof UnauthorizedException) { //没有权限 String uri = request.getServletPath(); final String queryString = request.getQueryString(); if (null != queryString && queryString.trim().length() > 0) { uri = uri + "?" + queryString; } log.info(String.format("%s, [uri = %s]", e.getMessage(), uri)); } else { if (e instanceof BaseException) { log.error(e.getMessage()); } else { log.error("异常错误", e); } } Throwable e2 = WebUtil.deepestException(e); try { // 是否ajax调用 boolean isAjax = true; if (WebUtil.isAjax(request)) { RespObject respObject; if (e instanceof UnauthorizedException) { respObject = RespObject.forbidden(); } else if (e instanceof FailException) { respObject = RespObject.fail(RespObject.getExceptionMessage(e2)); } else if (e instanceof ErrorException) { respObject = RespObject.error(e2); } else { respObject = RespObject.exception(e2); } respObject.setExtra(e2.getMessage()); return respObject; } else { // 添加本身的异常处理逻辑,如日志记录 request.setAttribute("exceptionMessage", e.getMessage()); return "common/error"; } } catch (Exception e3) { log.error("返回数据失败!", e3); } return "common/error"; } }
UserController.java,测试接口调用
@RestController @RequestMapping("/api/base/user") public class UserController extends BaseController<User, String> { @RequiresPermissions(value = {"user"}, logical = Logical.OR)//执行此方法须要权限 @RequestMapping(value = "/search") public RespObject search(PageParam pageParam, User bean) { return super.search(pageParam, bean); } }
spring相关的包怎么引的随便找,使用shiro这里aop,确定要
<java.version>1.8</java.version> <spring.version>5.1.7.RELEASE</spring.version> <hibernate.version>5.4.2.Final</hibernate.version>
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>