1.web.xml里配置(ShiroFilter入口)
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
DelegatingFilterProxy作用是自动到spring容器查找名字为shiroFilter(filter-name)的bean并把所有Filter的操作委托给它。
2.将ShiroFilter配置到spring
<import resource="classpath:spring/shiro.xml" />
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"
default-lazy-init="true">
<description>Shiro Configuration</description>
<!-- Shiro's main business-tier object for web-enabled applications -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="shiroDbRealm" />
<property name="cacheManager" ref="shiroCacheManager" />
<!-- <property name="sessionManager" ref="sessionManager" /> -->
</bean>
<!-- 用来做登录用户验证 -->
<bean id="shiroDbRealm" class="com.impay.auth.ShiroDbRealm" />
<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/welcome" />
<property name="filters">
<map>
<entry key="authc" value-ref="authc" />
<!-- <entry key="kickout" value-ref="kickoutSessionControlFilter"/> -->
</map>
</property>
<property name="filterChainDefinitions">
<value>
/callback/** = anon <!-- 指定不需要拦截的路径 -->
/sms_check_file/** = anon
/login = authc
/logout = logout
/images/** = anon
/scripts/** = anon
/uploads/** = anon
/account/cashReturnData/** = anon
/thems/** = anon
/servlet/** = anon
/*.ico = anon
/hello = anon
/tfb* = anon
/veer.* = anon
/** = user
</value>
</property>
</bean>
<!-- 用户授权信息Cache -->
<bean id="shiroCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- AOP式方法级权限检查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
这里有必要说清楚”shiroFilter”这个bean里面的各个属性property的含义:
(1) securityManager:这个属性是必须的,没什么好说的,就这样配置就好。
(2) loginUrl:没有登录的用户请求需要登录的页面时自动跳转到登录页面.
(3) successUrl:登录成功默认跳转页面,不配置则跳转至”/”.
/admin=authc,roles[admin]表示用户必需已通过认证,并拥有admin角色才可以正常发起’/admin’请求
/edit=authc,perms[admin:edit] 表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起’/edit’请求
/home=user 表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起’/home’请求
3. login.jsp
<form action="<%=request.getContextPath()%>/login" method="post">
<input type="text" name="username">
input type="password" name="password">
<td valign="middle"><input type="text" id="captcha"
name="captcha" size="4" maxlength="4" class="required" /></td>
<td valign="middle" align="right"><img title="点击更换"
id="img_captcha" οnclick="javascript:refreshCaptcha();"
src="servlet/captchaCode"></td>
<input type="submit">
提交表单访问”/login”,但是由于配置了拦截,
/login =authc,访问需要认证,进入ShiroDbRealm验证
4.定义shiro拦截器ShiroDbRealm(认证doGetAuthenticationInfo,授权doGetAuthorizationInfo两个方法)
package com.impay.auth;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
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.credential.HashedCredentialsMatcher;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.impay.domain.SysAuth;
import com.impay.domain.SysUser;
import com.impay.service.SysUserService;
import com.impay.utils.UsernamePasswordCaptchaToken;
/**
* 登录系统后,对用户进行检验,包括严重和授权
*
* @author dj
*
*/
@Component
public class ShiroDbRealm extends AuthorizingRealm {
private static final Logger log = LoggerFactory
.getLogger(ShiroDbRealm.class);
@Autowired
private SysUserService userService;
// 设置密码加密方式为MD5
public ShiroDbRealm() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("MD5");
setCredentialsMatcher(matcher);
}
// 用户验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
UsernamePasswordCaptchaToken token2 = (UsernamePasswordCaptchaToken) token;
// 增加判断验证码逻辑
String captcha = token2.getCaptcha();
String exitCode = (String) SecurityUtils.getSubject().getSession()
.getAttribute("SE_KEY_MM_CODE");
if (null == captcha || !captcha.equalsIgnoreCase(exitCode)) {
throw new CaptchaException("验证码错误");
}
if (token.getPrincipal() == null)
return null;
log.info("User login: {}", token.getPrincipal());
SysUser user = null;
String username = (String) token.getPrincipal();
try {
user = userService.getByUserName((String) token.getPrincipal());
} catch (Exception e) {
log.error("query user exception", e);
}
if (user == null) {
return null;
}
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
if (user != null && Integer.parseInt(user.getStatus()) == 0) {
request.setAttribute("shiroLoginFailure", "UnknownAccountException");
request.setAttribute("USERNAME", username);
return null;
}
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
// 用户授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
SysUser ru = (SysUser) principals.fromRealm(getName()).iterator()
.next();
if (ru == null) {
return null;
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
List<SysAuth> ra;
try {
ra = userService.getAuthList(ru.getId());
for (SysAuth r : ra) {
info.addStringPermission(StringUtils.trim(r.getAuthCode()));
}
} catch (SQLException e) {
e.printStackTrace();
}
return info;
}
@Override
public boolean supports(AuthenticationToken token) {
return super.supports(token);
}
}
进入doGetAuthorizationInfo方法验证
(1)首先验证验证码,不匹配则抛出异常,之后进入Controller层,再回退login.jsp
(2)验证码通过,(实际上进入doGetAuthorizationInfo方法前进入了一个表单认证过滤器ShiroAuthFilter类,该类继承了FormAuthenticationFilter,通过该类的createToken方法把用户名,密码,验证码封装到了一个UsernamePasswordToken里,并把它作为参数传递给doGetAuthorizationInfo),根据用户名去数据库查找是否存在该用户,不存在进入Controller层,再回退login.jsp
(3)存在该用户,判断用户状态,用户停用进入Controller层,再回退login.jsp
(4) 用户状态正常,returnnewSimpleAuthenticationInfo(user, user.getPassword(), getName());登录成功!之后会进入表单认证过滤器ShiroAuthFilter(即配置文件中依赖的authc),进入重写的onLoginSuccess方法更新登录时间等操作,并根据我们在shiro.xml里配置的successUrl去重定向到index.jsp
(5) 如果在index.jsp页面有
<%@ taglibprefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:hasPermission name="auth1">
需要权限
</shiro:hasPermission>
那么重定向后还会调用ShiroDbRealm里的doGetAuthorizationInfo方法来给用户授权,之后去index.jsp,只显示有权限的菜单和按钮
5.ShiroAuthFilter
package com.impay.smsboss.auth;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.stereotype.Component;
import com.impay.smsboss.domain.SysUser;
import com.impay.smsboss.service.SysUserService;
import com.impay.smsboss.utils.DateUtils;
/**
* 表单认证过滤器,可以在这里记录登录成功后的日志记录等操作
*
* @author dj
*
*/
@Component("authc")
public class ShiroAuthcFilter extends FormAuthenticationFilter {
@Resource
private SysUserService userService;
public static final String DEFAULT_CAPTCHA_PARAM = "captcha";
private String captchaParam = DEFAULT_CAPTCHA_PARAM;
public String getCaptchaParam() {
return captchaParam;
}
protected String getCaptcha(ServletRequest request) {
return WebUtils.getCleanParam(request, getCaptchaParam());
}
protected AuthenticationToken createToken(ServletRequest request,
ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request) == null ? "" : getPassword(request);
String captcha = getCaptcha(request);
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
return new UsernamePasswordCaptchaToken(username,
password.toCharArray(), rememberMe, host, captcha);
}
// 登录成功操作,这里设置了代理商常用信息
@Override
protected boolean onLoginSuccess(AuthenticationToken token,
Subject subject, ServletRequest request, ServletResponse response)
throws Exception {
SysUser sysUser = (SysUser) SecurityUtils.getSubject().getPrincipal();
HttpSession session = WebUtils.toHttp(request).getSession(true);
session.setAttribute("user", sysUser);
// session.setAttribute("power", userService.checkPower(riskUser.getId()));
Map<String, String> param = new HashMap<String, String>();
WebUtils.issueRedirect(request, response, getSuccessUrl(), param, true);
// save log
String ip = request.getRemoteAddr();
userService.updateUserLoginTime(sysUser);
session.setAttribute("last_login_time", sysUser.getLastLoginTime()==null?null:DateUtils.formatDateTime(sysUser.getLastLoginTime()));
Map<String,Object> map = userService.findUserLastLoginLog(sysUser.getId()+"");
if(map!=null){
session.setAttribute("login_location", map.get("location"));
}
userService.saveLog(ip, sysUser);
return false;
}
}
6.shiro @RequiresPermissions不起作用
因为在正常情况下,如果在方法上面加了@RequiresPermissions(“XXXX”)注释,系统会直接进入doGetAuthorizationInfo方法进行权限验证,如果没有权限,那么就会抛出 org.apache.shiro.authz.UnauthorizedException: Subject does not have permission异常,但是有时候@RequiresPermissions没有效果,如果出现这种情况,那么就在springMvc中加入代码如下(注意:在系统会用到spring AOP技术,相关信息也必须配置):
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true" /> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
<shiro:hasPermission name="caidan">
菜单
</shiro:hasPermission>
那么也会默认拥有
<shiro:hasPermission name="caidan:add">
新增菜单
</shiro:hasPermission>
<shiro:hasPermission name="caidan:add:three">
3级菜单
</shiro:hasPermission>
<shiro:hasPermission name="caidan:del:asdasdasd">
乱写的:
</shiro:hasPermission>
等带
caidan:
的权限,但是不会有caidan_del这样带
caidan_
的权限,因为shiro中权限推荐字符是
:
,之后的字段会继承权限
@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。
@RequiresUser
验证用户是否被记忆,user有两种含义:
一种是成功登录的(subject.isAuthenticated() 结果为true);
另外一种是被记忆的(subject.isRemembered()结果为true)。
@RequiresGuest
验证是否是一个guest的请求,与@RequiresUser完全相反。
换言之,RequiresUser == !RequiresGuest。
此时subject.getPrincipal() 结果为null.
@RequiresRoles
例如:@RequiresRoles("aRoleName");
void someMethod();
如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。
@RequiresPermissions
例如: @RequiresPermissions({"file:read", "write:aFile.txt"} )
void someMethod();
要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException。
7.没有权限调用方法抛出异常问题
org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method:
在web.xml里定义
<error-page>
<error-code>500</error-code>
<location>/ccc</location>
</error-page>
@RequestMapping("/ccc")
@ResponseBody
public Msg ccc(){
return Msg.success().add("result", "没有权限!!");
}
返回给前台没问题,但是后台依然会抛出异常,这个有哪个小伙伴看到了帮我解决下?。。。
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();