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" onclick="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();