shiro流程

FormAuthenticationFilter资料

Shiro登录成功之后跳到指定URL


 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容器查找名字为shiroFilterfilter-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不起作用

把shiro.xml中的这两个配置移到spring-mvc中

因为在正常情况下,如果在方法上面加了@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:readwrite: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", "没有权限!!");
	}

返回给前台没问题,但是后台依然会抛出异常,这个有哪个小伙伴看到了帮我解决下?。。。




最后给出我做的demo
-----------------------------------------符---------------------------------------
为了在任何地方(比如验证类中)都能获取到httpservletrequest对象,
<listener>
	   <listener-class>
			org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getRequest();



猜你喜欢

转载自blog.csdn.net/q975583865/article/details/70808024
今日推荐