Shiro与spirng整合 WEB项目实现

Shiro与spirng整合 WEB项目实现(jsp表单同步提交账户密码验证)

1.web.xml配置

<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>
  	<init-param>
  		<param-name>targetBeanName</param-name>
  		//这里的名字为配置spring容器中ShiroFilterFactoryBean的id名 必须相同
  		<param-value>shiroFilter</param-value>
  	</init-param>
  </filter>
  <filter-mapping>
  	<filter-name>shiroFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>
  

DelegatingFilterProxy 是spring框架里,配上为代理实现webSecurityManagerFilter
注意:targetBeanName设置的shiroFilter需要和spring容器中ShiroFilterFactoryBean的id名相同

2.applicationContext-shiro.xml --spring配置

总体层次(RemenberMe功能实现还有点小问题~~)-----后面分模块解析

	<!-- 这里的代理工厂与web.xml中的delegatingProxyFilter想对应,把shiro的filterbean纳入spring的filter管理 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="loginUrl" value="/login.action"/>
		<property name="successUrl" value="/index.action"/>
		<property name="unauthorizedUrl" value="/refuse.jsp"/>
		<!-- 核心运作的securityManager -->
		<property name="securityManager" ref="securityManager"/>
		<!-- 各种filter过滤器的配置 -->
		<property name="filterChainDefinitions">
			<value>
			<!--  -->
			<!-- 公开资源释放匿名访问过滤器anon -->
				/js/** = anon
				/locale/** = anon
				/plugins/** = anon
				/themes/** = anon
				<!-- 对验证码页面放行 -->
				/validatecode.jsp = anon
			<!-- 退出过滤器logout -->
				/logout.action= logout
				<!-- 	配置授权   **这玩意要放在 /**=authc前面,不然就值套用认证即可使用的资源
				/user/query.action = perms["user:query"] -->
			<!-- 需要通过认证过滤器 才能访问的资源-->
				/** = authc
			</value>
		</property>
		<property name="filters">
			<map>
				<entry key="authc" value-ref="customFormAuthenticationFilter"/>
			</map>
		</property>
	</bean>
	<!-- 配个自定义FormAuthenticationFilter -->
	<bean name="customFormAuthenticationFilter" class="com.sp.web.filter.CustomFormAuthenticationFilter">
		<!-- 如果没配自定义认证过滤器,其用户名默认值即username,密码默认值即password,报错后存入request作用域
		的Excoption Name默认值shiroLoginFailure -->
		<property name="usernameParam" value="username" />
		<property name="passwordParam" value="password" />
		<property name="failureKeyAttribute" value="shiroLoginFailure" />
	</bean>
	<!-- 配入securityManager支持filter工厂的工作 -->
	<bean name="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<!-- 需要一个自定义realm -->
		<property name="realm" ref="customRealm"/>
		<!-- 配置一个缓存,可以有效提高效率,当点击需要授权的链接是只需第一次差数据库,其他都会由本地缓存进行授权确认 -->
		<property name="cacheManager" ref="ehCacheManager"/>
		<!-- 配置记住我,即是网站常有的记住用户 -->
		<property name="rememberMeManager" ref="rememberMeManager"/>
	</bean>
		
	<!-- 配置一个缓存bean -->
	<bean name="ehCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManagerConfigFile" value="classpath:shiro/shiro-ehcache.xml"/>
	</bean>
	<!-- 配入自定义的realm -->
	<bean name="customRealm" class="com.sp.realm.CustomRealm">
		<!-- 配置凭证匹配器,MD5加盐算法 -->
		<property name="credentialsMatcher" ref="hashedCredentialsMatcher"/>
	</bean>
	<!-- 凭证匹配器 -->
	<bean name="hashedCredentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
		<property name="hashAlgorithmName" value="md5"/>
		<property name="hashIterations" value="1"/>
	</bean>
	<!-- 记住我功能实现的manager -->
	<bean name="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
		<property name="cookie" ref="simpleCookie"/>
	</bean>
	<!-- 配置记住我的需要的cookie -->
	<bean name="simpleCookie"  class="org.apache.shiro.web.servlet.SimpleCookie">
		<!-- 这个是配置cookie存活时间,记住7天 -->	
		<property name="maxAge" value="604800"/>
		<!-- 添加构造参数的值为登录页面中的记住我复选框的name属性值 -->
		<constructor-arg value="rememberMe"/>
	</bean>

模块(1)认证授权实现部分

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 如果没有认证过拦截后进入的url -->
		<property name="loginUrl" value="/login.action"/>
		 <!-- 认证通过后进入的url -->
		<property name="successUrl" value="/index.action"/>
		 <!-- 没有权限被拦截后进入的url -->
		<property name="unauthorizedUrl" value="/refuse.jsp"/>
		<!-- 核心运作的securityManager -->
		<property name="securityManager" ref="securityManager"/>
		<!-- 各种filter过滤器的配置 -->
		<property name="filterChainDefinitions">
			<value>
			<!--  -->
			<!-- 公开资源释放匿名访问过滤器anon -->
				/js/** = anon
				/locale/** = anon
				/plugins/** = anon
				/themes/** = anon
				<!-- 对验证码页面放行 -->
				/validatecode.jsp = anon
			<!-- 退出过滤器logout -->
				/logout.action= logout
				<!-- 	配置授权   **这玩意要放在 /**=authc前面,不然就值套用认证即可使用的资源
				/user/query.action = perms["user:query"] -->
			<!-- 需要通过认证过滤器 才能访问的资源-->
				/** = authc
			</value>
		</property>
	</bean>
	<!-- 配入securityManager支持filter工厂的工作 -->
	<bean name="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<!-- 需要一个自定义realm -->
		<property name="realm" ref="customRealm"/>
	</bean>
	<!-- 配入自定义的realm -->
	<bean name="customRealm" class="com.sp.realm.CustomRealm">
		<!-- 配置凭证匹配器,MD5加盐算法 -->
		<property name="credentialsMatcher" ref="hashedCredentialsMatcher"/>
	</bean>
	<!-- 凭证匹配器 -->
	<bean name="hashedCredentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
		<property name="hashAlgorithmName" value="md5"/>
		<property name="hashIterations" value="1"/>
	</bean>

这个是shirofilter的代理工厂,内含securityManager进行认证和授权的处理.
filterChainDefinitions这个属性则是定义各种类型shiroflter所管理的url,缩写详细见附录

securityManager必须要有个自定义Realm,只有自定义的Realm才能实现与数据库进行交互

customRealm自定义的realm继承AuthorizingRealm,重写认证和授权方法.

public class CustomRealm extends AuthorizingRealm {
	@Autowired
	private SysUserService sysUserService;
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		try {
			//授权操作,从主体中拿到用户进行授权,需要查找数据库总该用户所有的权限
			SysUser user = (SysUser) principals.getPrimaryPrincipal();
			List<SysPermission> Perssions = sysUserService.findPerssionsByUserId(user.getUsercode());
			SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
			Set<String> stringPermissions = new HashSet<>();
			for (SysPermission permisson : Perssions) {
				System.out.println(permisson.getPercode());
				stringPermissions.add(permisson.getPercode());
			}
			authorizationInfo.setStringPermissions(stringPermissions );
			return authorizationInfo;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		try {
			//通过token拿到前端输入的用户名再从数据库中查找
			String userCode = (String) token.getPrincipal();
			SysUser user = sysUserService.findUserByUsercode(userCode);
			//找不到用户则返回null,找到则需要赋予menu及找到密码 后续匹配密码;
			//这里的menu为自定义的url,用户拥有相应的url才会在页面显示相应的a标签(按需来写)
			if (user==null) return null;
			List<SysPermission> menus = sysUserService.getMenusByUsercode(userCode);
			user.setMenus(menus);
			String password = user.getPassword();
			String salt = user.getSalt();
			//把找到的密码和盐返回到上层进行匹配
			//这里注意返回的principal直接使用数据库查找到的用户对象
			return new SimpleAuthenticationInfo(user, password, ByteSource.Util.bytes(salt),"myRealm");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

}

----配置注解授权限制访问----在springmvc中配置(如果直接用perms过滤器则不需要)

<!-- requiredPermisson 注解配置1:需求代理确认? -->
	<aop:config proxy-target-class="true"></aop:config>
	<!-- requiredPermisson 注解配置2:类似拦截器要配一个? -->
	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
		<property name="securityManager" ref="securityManager"></property>
	</bean>
	<!-- requiredPermisson 注解配置3:因为这里会套用默认的报错方式,所以需要自定义视图 -->
	<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
		<property name="exceptionMappings">
			<props>
				<prop key="org.apache.shiro.authz.UnauthorizedException">anno-refuse</prop>
			</props>
		</property>
	</bean>
      @RequestMapping("/user/query")
 	 //下面的注解代表如果要访问/user/query.action这个资源,则必须当前用户在数据库中有user:queryList权限才行。
	@RequiresPermissions("user:queryList")
	public String query() {
		return "layout/user/query";
	
	}

模块(2)认证filter重写实现验证码登录

在ShiroFilterFactoryBean添加属性

<property name="filters">
			<map>
				<entry key="authc" value-ref="customFormAuthenticationFilter"/>
			</map>
		</property>
		<!-- 配个自定义FormAuthenticationFilter -->
	<bean name="customFormAuthenticationFilter" class="com.sp.web.filter.CustomFormAuthenticationFilter">
		<property name="usernameParam" value="username" />
		<property name="passwordParam" value="password" />
		<property name="failureKeyAttribute" value="shiroLoginFailure" />
	</bean>

自定义的过滤器继承AuthenticationFilter

public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {

	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
		//转型
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse rep = (HttpServletResponse) response;
		//这里是拿到jsp丢入到session里的正确验证码
		String vaCodeRight = (String) req.getSession().getAttribute("validateCode");
		//拿到用户输入的验证码
		String vaCodeIn = req.getParameter("validcode");
		//!!!!这里有个BUG 如果不输入验证码,只有账号密码也能登录,如果设定验证码为空进入下面方法也会导致直接匿名登录!!
		//所以在页面的表单提交需要通过不能为空验证,才能提交表单
		if (!StringUtils.isEmpty(vaCodeIn)&&!vaCodeIn.equals(vaCodeRight)){
			req.setAttribute("shiroLoginFailure", "validcodeError");
			return true;
		}
		return super.isAccessAllowed(request, response, mappedValue);
	}
}

验证码生成页面见附录

模块(3)缓存管理,提高性能,防止每次访问权限连接都要访问数据库

在securityManager中添加属性,并引入ehCacheManager的一个实现Bean

<!-- 配置一个缓存,可以有效提高效率,当点击需要授权的链接是只需第一次差数据库,其他都会由本地缓存进行授权确认 -->
		<property name="cacheManager" ref="ehCacheManager"/>
		<!-- 配置一个缓存bean -->
	<bean name="ehCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManagerConfigFile" value="classpath:shiro/shiro-ehcache.xml"/>
	</bean>

缓存的配置xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
	<!--diskStore:缓存数据持久化的目录 地址  -->
	<diskStore path="e:\develop\ehcache" />
	<!-- 
		“diskStore path=”java.io.tmpdir”:磁盘的存储目录; 
		“name=”xxx””:对要进行缓存的项进行一个标注; 
		“maxElementsInMemory=”2000”:可以缓存的最大的对象个数; 
		“eternal=”false”:是否允许自动失效(如果某一个对象长时间不使用); 
		“timeToIdleSeconds=”1800”:最小的失效时间,1800秒; 
		“timeToLiveSeconds=”0”:最大的保存时间,单位是秒; 
		“overflowToDisk=”false”:如果容量过多,可以将其保存在磁盘 
	 -->
	<defaultCache 
		maxElementsInMemory="1000" 
		maxElementsOnDisk="10000000"
		eternal="false" 
		overflowToDisk="false" 
		diskPersistent="false"
		timeToIdleSeconds="120"
		timeToLiveSeconds="120" 
		diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU">
	</defaultCache>
</ehcache>

模块(4)RememberMe功能
在SecurityManager添加属性

<!-- 配置记住我,即是网站常有的记住用户 -->
		<property name="rememberMeManager" ref="rememberMeManager"/>

<!-- 记住我功能实现的manager -->
	<bean name="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
		<property name="cookie" ref="simpleCookie"/>
	</bean>
	<!-- 配置记住我的需要的cookie -->
	<bean name="simpleCookie"  class="org.apache.shiro.web.servlet.SimpleCookie">
		<!-- 这个是配置cookie存活时间,记住7天 -->	
		<property name="maxAge" value="604800"/>
		<!-- 添加构造参数的值为登录页面中的记住我复选框的name属性值 -->
		<constructor-arg value="rememberMe"/>
	</bean>

需要配置user过滤器来实现

	<!-- 配置记住我及认证通过的用户可以访问的资源 -->
		/index.action = user
		/user/** = user
		/menu/** = user
		/student/** = user

3.controller

@Controller
public class UserController {

	@RequestMapping("/login")
	public String login(HttpServletRequest request,HttpServletResponse response) throws Exception{
		//当请求到来时,由FormAuthenticationFilter进行认证,由realm根据当前用户名查询用户信息,如果返回null,则
		//FormAuthenticationFilter对象将错误信息封装到request中,并以shiroLoginFailure为参数存放(自定义authc过滤
		//器之后改为errorMsg)
		String exceptionName = (String) request.getAttribute("shiroLoginFailure");
		System.out.println("===========>" + exceptionName);
		if(exceptionName != null){
			if(IncorrectCredentialsException.class.getName().equals(exceptionName)){
				throw new MyException("用户名/密码有误!");
			}else if (UnknownAccountException.class.getName().equals(exceptionName)){
				throw new MyException("账户异常!");
			}else if(UnauthorizedException.class.getName().equals(exceptionName)){
				throw new MyException("无权限访问异常!");
			}else if("validcodeError".equals(exceptionName)){
				throw new MyException("验证码错误!");
			}else{
				throw new Exception();
			}
		}
		//此方法只有在FormAuthenticationFilter认证失败时工作,认证成功返回页面就是上一次的页面
		return "login";
	}

附录

过滤器简称对应的java 类
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
anon:例子/admins/=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/
=authc 表示需要认证(登录)才能使用,FormAuthenticationFilter 是
表单认证,没有参数
roles:例子/admins/user/=roles[admin],参数可以写多个,多个时必须加上引号,并且参数
之间用逗号分割,当有多个参数时,例如admins/user/
=roles[“admin,guest”],每个参数通过
才算通过,相当于hasAllRoles()方法。
perms:例子/admins/user/=perms[user:add:*],参数可以写多个,多个时必须加上引号,并
且参数之间用逗号分割,例如/admins/user/
=perms[“user:add:,user:modify:”],当有多个
参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
user:例如/admins/user/**=user 没有参数表示必须存在用户, 身份认证通过或通过记住我认
证通过的可以访问,当登入操作时不做检查
注:
anon,authcBasic,auchc,user 是认证过滤器,
perms,roles,ssl,rest,port 是授权过滤器

验证码生成页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.util.Random"%>
<%@ page import="java.io.OutputStream"%>
<%@ page import="java.awt.Color"%>
<%@ page import="java.awt.Font"%>
<%@ page import="java.awt.Graphics"%>
<%@ page import="java.awt.image.BufferedImage"%>
<%@ page import="javax.imageio.ImageIO"%>
<%
	int width = 60;
	int height = 32;
	//create the image
	BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
	Graphics g = image.getGraphics();
	// set the background color
	g.setColor(new Color(0xDCDCDC));
	g.fillRect(0, 0, width, height);
	// draw the border
	g.setColor(Color.black);
	g.drawRect(0, 0, width - 1, height - 1);
	// create a random instance to generate the codes
	Random rdm = new Random();
	String hash1 = Integer.toHexString(rdm.nextInt());
	System.out.print(hash1);
	// make some confusion
	for (int i = 0; i < 50; i++) {
		int x = rdm.nextInt(width);
		int y = rdm.nextInt(height);
		g.drawOval(x, y, 0, 0);
	}
	// generate a random code
	String capstr = hash1.substring(0, 4);
	//将生成的验证码存入session
	session.setAttribute("validateCode", capstr);
	g.setColor(new Color(0, 100, 0));
	g.setFont(new Font("Candara", Font.BOLD, 24));
	g.drawString(capstr, 8, 24);
	g.dispose();
	//输出图片
	response.setContentType("image/jpeg");
	out.clear();
	out = pageContext.pushBody();
	OutputStream strm = response.getOutputStream();
	ImageIO.write(image, "jpeg", strm);
	strm.close();
%>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值