shiro 流程大概总结:
JSP:
配置loginUrl,在此URL返回的视图里,会有个form(此form无须指明action,我想是因为配置了loginUrl的关系,但是配置了authc 为loginUrl),提交后,就会进入FormAuthenticationFilter,你可以重写他,处理自己的逻辑,如写response为JSON的,等等。这样如果登陆成功了,会根据之前访问的URL,和配置的SUCCEEURL作为备选,优先是去之前的URL,(有个疑问,如果之前的是login,那现在登陆成功后还是login?或者处理了下如果是login就返回备选URL,如果没有配successUrl呢,是不是应该有什么必须的东西再里面)。这样就访问到之前的URL了,一般都是/,就是首页了,controller里面定义下这个这个返回方法就行(可以写个mainController,用来处理首页的。有时候还可以把 / 配置成重定向到另外一个controler,不提了)
JSON:
上面都是JSP的,不适合EXT,同样需要配置loginUrl,登陆操作不是用form来了,用ajax提交的,同样配置了authc 为loginUrl,同样进FormAuthenticationFilter,这时候肯定要重新它了,让它返回JSON,自己在JS sucess里面回调重定向到首页(wecome/index/home 这种名字的)。
还有个注意的,所谓的shiro权限字符串(如 "admin:session:forceLogout"),这玩意没什么特别的地方,就是一个ID,一个有意义的ID,我之前一直以为shiro会怎么处理它,发现就是ID而已,甚至可以用 其他什么ID来代替它,这玩意就是用来唯一标识一个权限的。一般可以在controller里面方法上面 加入注解 @RequiresPermissions("admin:session:forceLogout")(也可以在页面上用shiro 标签 包裹某段代码,用以实现该段代码是否显示)。
(上面这段错了,还是有意义的,shiro会用通配符来匹配,两边进行equals,
@Override public boolean implies(Permission p) { if(!(p instanceof BitPermission)) { return false; } BitPermission other = (BitPermission) p; if(!("*".equals(this.resourceIdentify) || this.resourceIdentify.equals(other.resourceIdentify))) { return false; } if(!(this.permissionBit ==0 || (this.permissionBit & other.permissionBit) != 0)) { return false; } if(!("*".equals(this.instanceId) || this.instanceId.equals(other.instanceId))) { return false; } return true; }
,只有当是通配符,或者相等时候,返回 TRUE),就是分解,然后三个部分进行相等比较,三个部分表示
在spring-mvc配置文件里统一处理异常
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="java.lang.Exception">error/500</prop> <prop key="java.lang.RuntimeException">error/500</prop> <prop key="java.lang.Throwable">error/500</prop> <prop key="UnauthorizedException">error/noRealm</prop> </props> </property> <!-- 设置日志输出级别,不定义则默认不输出警告等错误日志信息 --> <property name="warnLogCategory" value="WARN"></property> <!-- 默认错误页面,当找不到上面mappings中指定的异常对应视图时,使用本默认配置 --> <property name="defaultErrorView" value="error/500"></property> <!-- 默认HTTP状态码 --> <property name="defaultStatusCode" value="500"></property> </bean>
这里可能JSON的就不是这么处理了。(如果权限粒度只到菜单 这个返回页面的 ,不到操作按钮的这种AJAX 返回JSON的,是可以满足的)。
总结,主要还是AuthorizingRealm这个类,和FormAuthenticationFilter这个类
public class ShiroDbRealm extends AuthorizingRealm { private static Logger logger = LoggerFactory.getLogger(ShiroDbRealm.class); @Autowired protected AccountService accountService; /** * 认证回调函数,登录时调用. */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { logger.info("doGetAuthenticationInfo----"); UsernamePasswordToken token = (UsernamePasswordToken) authcToken; User user = null; try { user = accountService.findUserByLoginName(token.getUsername()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } if (user != null) { byte[] salt = Encodes.decodeHex(user.getSalt());//16进制的 return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(salt), getName()); // return new SimpleAuthenticationInfo(); } else { return null; } } /** * 设定Password校验的Hash算法与迭代次数. */ @PostConstruct public void initCredentialsMatcher() { HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(AccountService.HASH_ALGORITHM); matcher.setHashIterations(AccountService.HASH_INTERATIONS); setCredentialsMatcher(matcher); } /** * 授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String)principals.getPrimaryPrincipal(); //Authorization 授权,即权限验证,验证某个已认证的用户是否拥有某个权限 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); try { User user = this.userService.getUserByName(username); Set<String> roles = new HashSet<String>(); //本系统设计为一个用户属于一个用户组,即用户组就是用户的角色(employee、finance、hr、boss..);每个用户组有不同的权限(资源) //其他系统中可以设置 一个用户有多个角色,一个角色有多个权限 //在本系统中 除了管理员是admin其他组都用user标识,除了老板,其他用户组的操作都和员工组一样的。 roles.add("admin".equals(user.getGroup().getType())?"admin":"user"); List<GroupAndResource> grList = this.grService.getResource(user.getGroup().getId()); Set<String> resources = new HashSet<String>(); for(GroupAndResource gr : grList){ Resource resource = this.resourceService.getPermissions(gr.getResourceId()); if(!BeanUtils.isBlank(resource)){ resources.add(resource.getPermission()); } } authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(resources); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); logger.error("realm 错误!"); } return authorizationInfo; } public void setAccountService(AccountService accountService) { this.accountService = accountService; }
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter { private static final Logger log = LoggerFactory .getLogger(CustomFormAuthenticationFilter.class); /** * 所有请求都会经过的方法。 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (isLoginRequest(request, response)) { if (isLoginSubmission(request, response)) { if (log.isTraceEnabled()) { log.trace("Login submission detected. Attempting to execute login."); } //验证码暂不处理 // if ("XMLHttpRequest" // .equalsIgnoreCase(((HttpServletRequest) request) // .getHeader("X-Requested-With"))) {// 不是ajax请求 // String vcode = request.getParameter("vcode"); // HttpServletRequest httpservletrequest = (HttpServletRequest) request; // String vvcode = (String) httpservletrequest // .getSession() // .getAttribute("com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY" // ); // if (vvcode == null || "".equals(vvcode) // || !vvcode.equals(vcode)) { // response.setCharacterEncoding("UTF-8"); // PrintWriter out = response.getWriter(); // out.println("{success:false,msg:'验证码错误'}"); // out.flush(); // out.close(); // return false; // } // } return executeLogin(request, response); } else { if (log.isTraceEnabled()) { log.trace("Login page view."); } // allow them to see the login page ;) return true; } } else { if (log.isTraceEnabled()) { log.trace("Attempting to access a path which requires authentication. Forwarding to the " + "Authentication url [" + getLoginUrl() + "]"); } if (!"XMLHttpRequest" .equalsIgnoreCase(((HttpServletRequest) request) .getHeader("X-Requested-With"))) {// 不是ajax请求 saveRequestAndRedirectToLogin(request, response); } else { response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); out.println("{msg:'login'}"); out.flush(); out.close(); } return false; } } /** * 覆盖默认实现,用sendRedirect直接跳出框架,以免造成js框架重复加载js出错。 * @param token * @param subject * @param request * @param response * @return * @throws Exception * @see org.apache.shiro.web.filter.authc.FormAuthenticationFilter#onLoginSuccess(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.subject.Subject, javax.servlet.ServletRequest, javax.servlet.ServletResponse) */ @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { //issueSuccessRedirect(request, response); //we handled the success redirect directly, prevent the chain from continuing: HttpServletRequest httpServletRequest = (HttpServletRequest)request; HttpServletResponse httpServletResponse = (HttpServletResponse)response; if (!"XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With")) && request.getParameter("ajax") == null) {// 不是ajax请求 让他重定向到页面 issueSuccessRedirect(request, response); } else {//在ajax里面,用不了shiro配置的successurl重定向功能,只能在回调函数里重定向 wj 2016-9-7 httpServletResponse.setCharacterEncoding("UTF-8"); PrintWriter out = httpServletResponse.getWriter(); out.println("{success:true,msg:'登入成功'}"); out.flush(); out.close(); } return false; } /** * 主要是处理登入失败的方法 */ @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { if (!"XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request) .getHeader("X-Requested-With"))) {// 不是ajax请求 setFailureAttribute(request, e); return true; } try { response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); String msg = e.getClass().getSimpleName(); if ("IncorrectCredentialsException".equals(msg)) { out.println("{success:false,msg:'密码错误'}"); } else if ("UnknownAccountException".equals(msg)) { out.println("{success:false,msg:'账号不存在'}"); } else if ("LockedAccountException".equals(msg)) { out.println("{success:false,msg:'账号被锁定'}"); } else { out.println("{success:false,msg:'未知错误'}"); } out.flush(); out.close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } return false; }
,