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;
}
,