MyException - 我的异常网
当前位置:我的异常网» VC/MFC » SpringMVC调整Shiro

SpringMVC调整Shiro

www.MyException.Cn  网友分享于:2013-10-02  浏览:35次
SpringMVC整合Shiro

这里用的是SpringMVC-3.2.4和Shiro-1.2.2,示例代码如下


首先是web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<!-- 指定Spring的配置文件 -->
	<!-- 否则Spring会默认从WEB-INF下寻找配置文件,contextConfigLocation属性是Spring内部固定的 -->
	<!-- 通过ContextLoaderListener的父类ContextLoader的第120行发现CONFIG_LOCATION_PARAM固定为contextConfigLocation -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>

	<!-- 防止发生java.beans.Introspector内存泄露,应将它配置在ContextLoaderListener的前面 -->
	<!-- 详细描述见http://blog.csdn.net/jadyer/article/details/11991457 -->
	<listener>
		<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
	</listener>
	
	<!-- 实例化Spring容器 -->
	<!-- 应用启动时,该监听器被执行,它会读取Spring相关配置文件,其默认会到WEB-INF中查找applicationContext.xml -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- 解决乱码问题 -->
	<filter>
		<filter-name>SpringEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>SpringEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 -->
	<!-- 这里filter-name必须对应applicationContext.xml中定义的<bean id="shiroFilter"/> -->
	<!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过Shiro的过滤 -->
	<!-- 通常会将此filter-mapping放置到最前面(即其他filter-mapping前面),以保证它是过滤器链中第一个起作用的 -->
	<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>

	<!-- SpringMVC核心分发器 -->
	<servlet>
		<servlet-name>SpringMVC</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:applicationContext.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>SpringMVC</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

	<!-- 默认欢迎页 -->
	<!-- Servlet2.5中可直接在此处执行Servlet应用,如<welcome-file>servlet/InitSystemParamServlet</welcome-file> -->
	<!-- 这里使用了SpringMVC提供的<mvc:view-controller>标签,实现了首页隐藏的目的,详见applicationContext.xml -->
	<!-- 
	<welcome-file-list>
		<welcome-file>login.jsp</welcome-file>
	</welcome-file-list>
	 -->
	
	<error-page>
		<error-code>405</error-code>
		<location>/WEB-INF/405.html</location>
	</error-page>
	<error-page>
		<error-code>404</error-code>
		<location>/WEB-INF/404.html</location>
	</error-page>
	<error-page>
		<error-code>500</error-code>
		<location>/WEB-INF/500.html</location>
	</error-page>
	<error-page>
		<error-code>javax.servle.ServletException</error-code>
		<location>/WEB-INF/error.html</location>
	</error-page>
	<error-page>
		<error-code>java.lang.NullPointerException</error-code>
		<location>/WEB-INF/error.html</location>
	</error-page>
</web-app>
下面是用于显示Request method 'GET' not supported的//WebRoot//WEB-INF//405.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
	<head>
		<title>405.html</title>
		<meta http-equiv="content-type" content="text/html; charset=UTF-8">
	</head>
	<body>
		<font color="blue">
			Request method 'GET' not supported
			<br/><br/>
			The specified HTTP method is not allowed for the requested resource.
		</font>
	</body>
</html>
下面是允许匿名用户访问的//WebRoot//login.jsp

<%@ page language="java" pageEncoding="UTF-8"%>

<script type="text/javascript">
<!--
function reloadVerifyCode(){
	document.getElementById('verifyCodeImage').setAttribute('src', '${pageContext.request.contextPath}/mydemo/getVerifyCodeImage');
}
//-->
</script>

<div style="color:red; font-size:22px;">${message_login}</div>

<form action="<%=request.getContextPath()%>/mydemo/login" method="POST">
	姓名:<input type="text" name="username"/><br/>
	密码:<input type="text" name="password"/><br/>
	验证:<input type="text" name="verifyCode"/>
		 &nbsp;&nbsp;
		 <img id="verifyCodeImage" onclick="reloadVerifyCode()" src="<%=request.getContextPath()%>/mydemo/getVerifyCodeImage"/><br/>
	<input type="submit" value="确认"/>
</form>
下面是用户登录后显示的//WebRoot//main.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
普通用户可访问<a href="<%=request.getContextPath()%>/mydemo/getUserInfo" target="_blank">用户信息页面</a>
<br/>
<br/>
管理员可访问<a href="<%=request.getContextPath()%>/admin/listUser.jsp" target="_blank">用户列表页面</a>
<br/>
<br/>
<a href="<%=request.getContextPath()%>/mydemo/logout" target="_blank">Logout</a>
下面是只有管理员才允许访问的//WebRoot//admin//listUser.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
This is listUser.jsp
<br/>
<br/>
<a href="<%=request.getContextPath()%>/mydemo/logout" target="_blank">Logout</a>
下面是普通的登录用户所允许访问的//WebRoot//user//info.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
当前登录的用户为${currUser}
<br/>
<br/>
<a href="<%=request.getContextPath()%>/mydemo/logout" target="_blank">Logout</a>
下面是//src//log4j.properties

#use Root for GobalConfig
log4j.rootLogger=DEBUG,CONSOLE

log4j.logger.java.sql=DEBUG
log4j.logger.org.apache.shiro=DEBUG
log4j.logger.org.apache.commons=DEBUG
log4j.logger.org.springframework=DEBUG

#use ConsoleAppender for ConsoleOut
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=DEBUG
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%d{yyyyMMdd HH:mm:ss}][%t][%C{1}.%M]%m%n
下面是//src//applicationContext.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"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
						http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
						http://www.springframework.org/schema/mvc
						http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
						http://www.springframework.org/schema/context
						http://www.springframework.org/schema/context/spring-context-3.2.xsd">
	<!-- 它背后注册了很多用于解析注解的处理器,其中就包括<context:annotation-config/>配置的注解所使用的处理器 -->
	<!-- 所以配置了<context:component-scan base-package="">之后,便无需再配置<context:annotation-config> -->
	<context:component-scan base-package="com.jadyer"/>
	
	<!-- 启用SpringMVC的注解功能,它会自动注册HandlerMapping、HandlerAdapter、ExceptionResolver的相关实例 -->
	<mvc:annotation-driven/>

	<!-- 配置SpringMVC的视图解析器 -->
	<!-- 其viewClass属性的默认值就是org.springframework.web.servlet.view.JstlView -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/"/>
		<property name="suffix" value=".jsp"/>
	</bean>

	<!-- 默认访问跳转到登录页面 -->
	<mvc:view-controller path="/" view-name="forward:/login.jsp"/>

	<!-- 由于web.xml中设置是:由SpringMVC拦截所有请求,于是在读取静态资源文件的时候就会受到影响(说白了就是读不到) -->
	<!-- 经过下面的配置,该标签的作用就是:所有页面中引用"/js/**"的资源,都会从"/resources/js/"里面进行查找 -->
	<!-- 我们可以访问http://IP:8080/xxx/js/my.css和http://IP:8080/xxx/resources/js/my.css对比出来 -->
	<mvc:resources mapping="/js/**" location="/resources/js/"/>
	<mvc:resources mapping="/css/**" location="/resources/css/"/>
	<mvc:resources mapping="/WEB-INF/**" location="/WEB-INF/"/>

	<!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ShiroDbRealm.java -->
	<bean id="myRealm" class="com.jadyer.realm.MyRealm"/>

	<!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->
	<!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 -->
	<!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="myRealm"/>
	</bean>

	<!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->
	<!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<!-- Shiro的核心安全接口,这个属性是必须的 -->
		<property name="securityManager" ref="securityManager"/>
		<!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->
		<property name="loginUrl" value="/"/>
		<!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp了) -->
		<!-- <property name="successUrl" value="/system/main"/> -->
		<!-- 用户访问未对其授权的资源时,所显示的连接 -->
		<property name="unauthorizedUrl" value="/"/>
		<!-- Shiro连接约束配置,即过滤链的定义 -->
		<!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 -->
		<!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 -->
		<!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 -->
		<!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter -->
		<property name="filterChainDefinitions">
			<value>
				/mydemo/login=anon
				/mydemo/getVerifyCodeImage=anon
				/main**=authc
				/user/info**=authc
				/admin/listUser**=authc,perms[admin:manage]
			</value>
		</property>
	</bean>

	<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

	<!-- 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 -->
	<!-- 配置以下两个bean即可实现此功能 -->
	<!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run -->
	<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
		<property name="securityManager" ref="securityManager"/>
	</bean>
</beans>
下面是自定义的Realm类----MyRealm.java

package com.jadyer.realm;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
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.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

/**
 * 自定义的指定Shiro验证用户登录的类
 * @see 在本例中定义了2个用户:jadyer和玄玉,jadyer具有admin角色和admin:manage权限,玄玉不具有任何角色和权限
 * @create Sep 29, 2013 3:15:31 PM
 * @author 玄玉<http://blog.csdn.net/jadyer>
 */
public class MyRealm extends AuthorizingRealm {
	/**
	 * 为当前登录的Subject授予角色和权限
	 * @see 经测试:本例中该方法的调用时机为需授权资源被访问时
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
		//获取当前登录的用户名,等价于(String)principals.fromRealm(this.getName()).iterator().next()
		String currentUsername = (String)super.getAvailablePrincipal(principals);
//		List<String> roleList = new ArrayList<String>();
//		List<String> permissionList = new ArrayList<String>();
//		//从数据库中获取当前登录用户的详细信息
//		User user = userService.getByUsername(currentUsername);
//		if(null != user){
//			//实体类User中包含有用户角色的实体类信息
//			if(null!=user.getRoles() && user.getRoles().size()>0){
//				//获取当前登录用户的角色
//				for(Role role : user.getRoles()){
//					roleList.add(role.getName());
//					//实体类Role中包含有角色权限的实体类信息
//					if(null!=role.getPermissions() && role.getPermissions().size()>0){
//						//获取权限
//						for(Permission pmss : role.getPermissions()){
//							if(!StringUtils.isEmpty(pmss.getPermission())){
//								permissionList.add(pmss.getPermission());
//							}
//						}
//					}
//				}
//			}
//		}else{
//			throw new AuthorizationException();
//		}
//		//为当前用户设置角色和权限
//		SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
//		simpleAuthorInfo.addRoles(roleList);
//		simpleAuthorInfo.addStringPermissions(permissionList);
		SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
		//实际中可能会像上面注释的那样从数据库取得
		if(null!=currentUsername && "jadyer".equals(currentUsername)){
			//添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色  
			simpleAuthorInfo.addRole("admin");
			//添加权限
			simpleAuthorInfo.addStringPermission("admin:manage");
			System.out.println("已为用户[jadyer]赋予了[admin]角色和[admin:manage]权限");
			return simpleAuthorInfo;
		}else if(null!=currentUsername && "玄玉".equals(currentUsername)){
			System.out.println("当前用户[玄玉]无授权");
			return simpleAuthorInfo;
		}
		//若该方法什么都不做直接返回null的话,就会导致任何用户访问/admin/listUser.jsp时都会自动跳转到unauthorizedUrl指定的地址
		//详见applicationContext.xml中的<bean id="shiroFilter">的配置
		return null;
	}

	
	/**
	 * 验证当前登录的Subject
	 * @see 经测试:本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()时
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
		//获取基于用户名和密码的令牌
		//实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
		//两个token的引用都是一样的,本例中是org.apache.shiro.authc.UsernamePasswordToken@33799a1e
		UsernamePasswordToken token = (UsernamePasswordToken)authcToken;
		System.out.println("验证当前Subject时获取到token为" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
//		User user = userService.getByUsername(token.getUsername());
//		if(null != user){
//			AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), user.getNickname());
//			this.setSession("currentUser", user);
//			return authcInfo;
//		}else{
//			return null;
//		}
		//此处无需比对,比对的逻辑Shiro会做,我们只需返回一个和令牌相关的正确的验证信息
		//说白了就是第一个参数填登录用户名,第二个参数填合法的登录密码(可以是从数据库中取到的,本例中为了演示就硬编码了)
		//这样一来,在随后的登录页面上就只有这里指定的用户和密码才能通过验证
		if("jadyer".equals(token.getUsername())){
			AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("jadyer", "jadyer", this.getName());
			this.setSession("currentUser", "jadyer");
			return authcInfo;
		}else if("玄玉".equals(token.getUsername())){
			AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("玄玉", "xuanyu", this.getName());
			this.setSession("currentUser", "玄玉");
			return authcInfo;
		}
		//没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
		return null;
	}
	
	
	/**
	 * 将一些数据放到ShiroSession中,以便于其它地方使用
	 * @see 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到
	 */
	private void setSession(Object key, Object value){
		Subject currentUser = SecurityUtils.getSubject();
		if(null != currentUser){
			Session session = currentUser.getSession();
			if(null != session){
				session.setAttribute(key, value);
			}
		}
	}
}
下面是处理用户登录的LoginController.java

package com.jadyer.controller;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import com.jadyer.util.VerifyCodeUtil;

/**
 * 本例中用到的jar文件如下
 * @see aopalliance.jar
 * @see commons-lang3-3.1.jar
 * @see commons-logging-1.1.2.jar
 * @see log4j-1.2.17.jar
 * @see shiro-all-1.2.2.jar
 * @see slf4j-api-1.7.5.jar
 * @see slf4j-log4j12-1.7.5.jar
 * @see spring-aop-3.2.4.RELEASE.jar
 * @see spring-beans-3.2.4.RELEASE.jar
 * @see spring-context-3.2.4.RELEASE.jar
 * @see spring-core-3.2.4.RELEASE.jar
 * @see spring-expression-3.2.4.RELEASE.jar
 * @see spring-jdbc-3.2.4.RELEASE.jar
 * @see spring-oxm-3.2.4.RELEASE.jar
 * @see spring-tx-3.2.4.RELEASE.jar
 * @see spring-web-3.2.4.RELEASE.jar
 * @see spring-webmvc-3.2.4.RELEASE.jar
 * @create Sep 30, 2013 11:10:06 PM
 * @author 玄玉<http://blog.csdn.net/jadyer>
 */
@Controller
@RequestMapping("mydemo")
public class LoginController {
	/**
	 * 获取验证码图片和文本(验证码文本会保存在HttpSession中)
	 */
	@RequestMapping("/getVerifyCodeImage")
	public void getVerifyCodeImage(HttpServletRequest request, HttpServletResponse response) throws IOException {
		//设置页面不缓存
		response.setHeader("Pragma", "no-cache");
		response.setHeader("Cache-Control", "no-cache");
		response.setDateHeader("Expires", 0);
		String verifyCode = VerifyCodeUtil.generateTextCode(VerifyCodeUtil.TYPE_NUM_ONLY, 4, null);
		//将验证码放到HttpSession里面
		request.getSession().setAttribute("verifyCode", verifyCode);
		System.out.println("本次生成的验证码为[" + verifyCode + "],已存放到HttpSession中");
		//设置输出的内容的类型为JPEG图像
		response.setContentType("image/jpeg");
		BufferedImage bufferedImage = VerifyCodeUtil.generateImageCode(verifyCode, 90, 30, 3, true, Color.WHITE, Color.BLACK, null);
		//写给浏览器
		ImageIO.write(bufferedImage, "JPEG", response.getOutputStream());
	}
	
	
	/**
	 * 用户登录
	 */
	@RequestMapping(value="/login", method=RequestMethod.POST)
	public String login(HttpServletRequest request){
		String resultPageURL = InternalResourceViewResolver.FORWARD_URL_PREFIX + "/";
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		//获取HttpSession中的验证码
		String verifyCode = (String)request.getSession().getAttribute("verifyCode");
		//获取用户请求表单中输入的验证码
		String submitCode = WebUtils.getCleanParam(request, "verifyCode");
		System.out.println("用户[" + username + "]登录时输入的验证码为[" + submitCode + "],HttpSession中的验证码为[" + verifyCode + "]");
		if (StringUtils.isEmpty(submitCode) || !StringUtils.equals(verifyCode, submitCode.toLowerCase())){
			request.setAttribute("message_login", "验证码不正确");
			return resultPageURL;
		}
		UsernamePasswordToken token = new UsernamePasswordToken(username, password);
		token.setRememberMe(true);
		System.out.println("为了验证登录用户而封装的token为" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
		//获取当前的Subject
		Subject currentUser = SecurityUtils.getSubject();
		try {
			//在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
			//每个Realm都能在必要时对提交的AuthenticationTokens作出反应
			//所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
			System.out.println("对用户[" + username + "]进行登录验证..验证开始");
			currentUser.login(token);
			System.out.println("对用户[" + username + "]进行登录验证..验证通过");
			resultPageURL = "main";
		}catch(UnknownAccountException uae){
			System.out.println("对用户[" + username + "]进行登录验证..验证未通过,未知账户");
			request.setAttribute("message_login", "未知账户");
		}catch(IncorrectCredentialsException ice){
			System.out.println("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
			request.setAttribute("message_login", "密码不正确");
		}catch(LockedAccountException lae){
			System.out.println("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
			request.setAttribute("message_login", "账户已锁定");
		}catch(ExcessiveAttemptsException eae){
			System.out.println("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
			request.setAttribute("message_login", "用户名或密码错误次数过多");
		}catch(AuthenticationException ae){
			//通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
			System.out.println("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");
			ae.printStackTrace();
			request.setAttribute("message_login", "用户名或密码不正确");
		}
		//验证是否登录成功
		if(currentUser.isAuthenticated()){
			System.out.println("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
		}else{
			token.clear();
		}
		return resultPageURL;
	}
	
	
	/**
	 * 用户登出
	 */
	@RequestMapping("/logout")
	public String logout(HttpServletRequest request){
		 SecurityUtils.getSubject().logout();
		 return InternalResourceViewResolver.REDIRECT_URL_PREFIX + "/";
	}
}
下面是处理普通用户访问的UserController.java

package com.jadyer.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("mydemo")
public class UserController {
	@RequestMapping(value="/getUserInfo")
	public String getUserInfo(HttpServletRequest request){
		String currentUser = (String)request.getSession().getAttribute("currentUser");
		System.out.println("当前登录的用户为[" + currentUser + "]");
		request.setAttribute("currUser", currentUser);
		return "/user/info";
	}
}
最后是用于生成登录验证码的VerifyCodeUtil.java

package com.jadyer.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * 验证码生成器
 * @see --------------------------------------------------------------------------
 * @see 可生成数字、大写、小写字母及三者混合类型的验证码
 * @see 支持自定义验证码字符数量,支持自定义验证码图片的大小,支持自定义需排除的特殊字符,支持自定义干扰线的数量,支持自定义验证码图文颜色
 * @see --------------------------------------------------------------------------
 * @see 另外,给Shiro加入验证码有多种方式,也可以通过继承修改FormAuthenticationFilter类,通过Shiro去验证验证码
 * @see 而这里既然使用了SpringMVC,也为了简化操作,就使用此工具生成验证码,并在Controller中处理验证码的校验
 * @see --------------------------------------------------------------------------
 * @create Sep 29, 2013 4:23:13 PM
 * @author 玄玉<http://blog.csdn.net/jadyer>
 */
public class VerifyCodeUtil {
	/**
	 * 验证码类型为仅数字,即0~9
	 */
	public static final int TYPE_NUM_ONLY = 0;

	/**
	 * 验证码类型为仅字母,即大小写字母混合
	 */
	public static final int TYPE_LETTER_ONLY = 1;

	/**
	 * 验证码类型为数字和大小写字母混合
	 */
	public static final int TYPE_ALL_MIXED = 2;

	/**
	 * 验证码类型为数字和大写字母混合
	 */
	public static final int TYPE_NUM_UPPER = 3;

	/**
	 * 验证码类型为数字和小写字母混合
	 */
	public static final int TYPE_NUM_LOWER = 4;

	/**
	 * 验证码类型为仅大写字母
	 */
	public static final int TYPE_UPPER_ONLY = 5;

	/**
	 * 验证码类型为仅小写字母
	 */
	public static final int TYPE_LOWER_ONLY = 6;

	private VerifyCodeUtil(){}
	
	/**
	 * 生成随机颜色
	 */
	private static Color generateRandomColor() {
		Random random = new Random();
		return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
	}
	
	
	/**
	 * 生成图片验证码
	 * @param type           验证码类型,参见本类的静态属性
	 * @param length         验证码字符长度,要求大于0的整数
	 * @param excludeString  需排除的特殊字符
	 * @param width          图片宽度(注意此宽度若过小,容易造成验证码文本显示不全,如4个字符的文本可使用85到90的宽度)
	 * @param height         图片高度
	 * @param interLine      图片中干扰线的条数
	 * @param randomLocation 每个字符的高低位置是否随机
	 * @param backColor      图片颜色,若为null则表示采用随机颜色
	 * @param foreColor      字体颜色,若为null则表示采用随机颜色
	 * @param lineColor      干扰线颜色,若为null则表示采用随机颜色
	 * @return 图片缓存对象
	 */
	public static BufferedImage generateImageCode(int type, int length, String excludeString, int width, int height, int interLine, boolean randomLocation, Color backColor, Color foreColor, Color lineColor){
		String textCode = generateTextCode(type, length, excludeString);
		return generateImageCode(textCode, width, height, interLine, randomLocation, backColor, foreColor, lineColor);
	}
	

	/**
	 * 生成验证码字符串
	 * @param type          验证码类型,参见本类的静态属性
	 * @param length        验证码长度,要求大于0的整数
	 * @param excludeString 需排除的特殊字符(无需排除则为null)
	 * @return 验证码字符串
	 */
	public static String generateTextCode(int type, int length, String excludeString){
		if(length <= 0){
			return "";
		}
		StringBuffer verifyCode = new StringBuffer();
		int i = 0;
		Random random = new Random();
		switch(type){
			case TYPE_NUM_ONLY:
				while(i < length){
					int t = random.nextInt(10);
					//排除特殊字符
					if(null==excludeString || excludeString.indexOf(t+"")<0) {
						verifyCode.append(t);
						i++;
					}
				}
			break;
			case TYPE_LETTER_ONLY:
				while(i < length){
					int t = random.nextInt(123);
					if((t>=97 || (t>=65&&t<=90)) && (null==excludeString||excludeString.indexOf((char)t)<0)){
						verifyCode.append((char)t);
						i++;
					}
				}
			break;
			case TYPE_ALL_MIXED:
				while(i < length){
					int t = random.nextInt(123);
					if((t>=97 || (t>=65&&t<=90) || (t>=48&&t<=57)) && (null==excludeString||excludeString.indexOf((char)t)<0)){
						verifyCode.append((char)t);
						i++;
					}
				}
			break;
			case TYPE_NUM_UPPER:
				while(i < length){
					int t = random.nextInt(91);
					if((t>=65 || (t>=48&&t<=57)) && (null==excludeString || excludeString.indexOf((char)t)<0)){
						verifyCode.append((char)t);
						i++;
					}
				}
			break;
			case TYPE_NUM_LOWER:
				while(i < length){
					int t = random.nextInt(123);
					if((t>=97 || (t>=48&&t<=57)) && (null==excludeString || excludeString.indexOf((char)t)<0)){
						verifyCode.append((char)t);
						i++;
					}
				}
			break;
			case TYPE_UPPER_ONLY:
				while(i < length){
					int t = random.nextInt(91);
					if((t >= 65) && (null==excludeString||excludeString.indexOf((char)t)<0)){
						verifyCode.append((char)t);
						i++;
					}
				}
			break;
			case TYPE_LOWER_ONLY:
				while(i < length){
					int t = random.nextInt(123);
					if((t>=97) && (null==excludeString||excludeString.indexOf((char)t)<0)){
						verifyCode.append((char)t);
						i++;
					}
				}
			break;
		}
		return verifyCode.toString();
	}

	/**
	 * 已有验证码,生成验证码图片
	 * @param textCode       文本验证码
	 * @param width          图片宽度(注意此宽度若过小,容易造成验证码文本显示不全,如4个字符的文本可使用85到90的宽度)
	 * @param height         图片高度
	 * @param interLine      图片中干扰线的条数
	 * @param randomLocation 每个字符的高低位置是否随机
	 * @param backColor      图片颜色,若为null则表示采用随机颜色
	 * @param foreColor      字体颜色,若为null则表示采用随机颜色
	 * @param lineColor      干扰线颜色,若为null则表示采用随机颜色
	 * @return 图片缓存对象
	 */
	public static BufferedImage generateImageCode(String textCode, int width, int height, int interLine, boolean randomLocation, Color backColor, Color foreColor, Color lineColor){
		//创建内存图像
		BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		//获取图形上下文
		Graphics graphics = bufferedImage.getGraphics();
		//画背景图
		graphics.setColor(null==backColor ? generateRandomColor() : backColor);
		graphics.fillRect(0, 0, width, height);
		//画干扰线
		Random random = new Random();
		if(interLine > 0){
			int x = 0, y = 0, x1 = width, y1 = 0;
			for(int i=0; i<interLine; i++){
				graphics.setColor(null==lineColor ? generateRandomColor() : lineColor);
				y = random.nextInt(height);
				y1 = random.nextInt(height);
				graphics.drawLine(x, y, x1, y1);
			}
		}
		//字体大小为图片高度的80%
		int fsize = (int)(height * 0.8);
		int fx = height - fsize;
		int fy = fsize;
		//设定字体
		graphics.setFont(new Font("Default", Font.PLAIN, fsize));
		//写验证码字符
		for(int i=0; i<textCode.length(); i++){
			fy = randomLocation ? (int)((Math.random()*0.3+0.6)*height) : fy;
			graphics.setColor(null==foreColor ? generateRandomColor() : foreColor);
			//将验证码字符显示到图象中
			graphics.drawString(textCode.charAt(i)+"", fx, fy);
			fx += fsize * 0.9;
		}
		graphics.dispose();
		return bufferedImage;
	}
}

文章评论

中美印日四国程序员比较
中美印日四国程序员比较
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
漫画:程序员的工作
漫画:程序员的工作
程序员应该关注的一些事儿
程序员应该关注的一些事儿
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
10个调试和排错的小建议
10个调试和排错的小建议
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
一个程序员的时间管理
一个程序员的时间管理
我是如何打败拖延症的
我是如何打败拖延症的
我的丈夫是个程序员
我的丈夫是个程序员
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
程序员和编码员之间的区别
程序员和编码员之间的区别
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
老程序员的下场
老程序员的下场
总结2014中国互联网十大段子
总结2014中国互联网十大段子
代码女神横空出世
代码女神横空出世
程序员必看的十大电影
程序员必看的十大电影
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
编程语言是女人
编程语言是女人
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
旅行,写作,编程
旅行,写作,编程
 程序员的样子
程序员的样子
如何成为一名黑客
如何成为一名黑客
程序员的鄙视链
程序员的鄙视链
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
那些争议最大的编程观点
那些争议最大的编程观点
鲜为人知的编程真相
鲜为人知的编程真相
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
为什么程序员都是夜猫子
为什么程序员都是夜猫子
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有