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/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><!-- Web容器加载顺序ServletContext--context-param--listener--filter--servlet --><!-- 指定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 --><!-- http://starscream.iteye.com/blog/1107036 --><!-- http://www.davenkin.me/post/2012-10-18/40039948363 --><!-- WebApplicationContextUtils.getWebApplicationContext() --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- 解决乱码问题 --><!-- forceEncoding默认为false,此时效果可大致理解为request.setCharacterEncoding("UTF-8") --><!-- forceEncoding=true后,可大致理解为request.setCharacterEncoding("UTF-8")和response.setCharacterEncoding("UTF-8") --><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><!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 --><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></servlet><servlet-mapping><servlet-name>SpringMVC</servlet-name><url-pattern>/</url-pattern></servlet-mapping><!-- Session超时30分钟(零或负数表示会话永不超时) --><!-- <session-config><session-timeout>30</session-timeout></session-config>--><!-- 默认欢迎页 --><!-- 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.jsp</location></error-page><error-page><error-code>500</error-code><location>/WEB-INF/500.jsp</location></error-page><error-page><exception-type>java.lang.Throwable</exception-type><location>/WEB-INF/500.jsp</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"/><img id="verifyCodeImage" οnclick="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,CONSOLElog4j.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/beanshttp://www.springframework.org/schema/beans/spring-beans-3.2.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.2.xsdhttp://www.springframework.org/schema/contexthttp://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><!-- 默认访问跳转到登录页面(即定义无需Controller的url<->view直接映射) --><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/"/><!-- SpringMVC在超出上传文件限制时,会抛出org.springframework.web.multipart.MaxUploadSizeExceededException --><!-- 该异常是SpringMVC在检查上传的文件信息时抛出来的,而且此时还没有进入到Controller方法中 --><bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="exceptionMappings"><props><!-- 遇到MaxUploadSizeExceededException异常时,自动跳转到/WEB-INF/error_fileupload.jsp页面 --><prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">WEB-INF/error_fileupload</prop><!-- 处理其它异常(包括Controller抛出的) --><prop key="java.lang.Throwable">WEB-INF/500</prop></props></property></bean><!-- 继承自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"/> --><!-- 用户访问未对其授权的资源时,所显示的连接 --><!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp --><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 --><!-- 由于本例中并未使用Shiro注解,故注释掉这两个bean(个人觉得将权限通过注解的方式硬编码在程序中,查看起来不是很方便,没必要使用) --><!-- <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 经测试:本例中该方法的调用时机为需授权资源被访问时* @see 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache* @see 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache* @see 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache*/@Overrideprotected 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()时*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {//获取基于用户名和密码的令牌//实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的//两个token的引用都是一样的,本例中是org.apache.shiro.authc.UsernamePasswordToken@33799a1eUsernamePasswordToken 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();System.out.println("Session默认超时时间为[" + session.getTimeout() + "]毫秒");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));//获取当前的SubjectSubject 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;}
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295468.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

这个发热鞋垫厉害了,有它冬天再也不怕脚冷

▲ 点击查看冬天一到&#xff0c;小爆发现身边的“抖友”又开始上线了&#xff01;至于为什么会抖脚&#xff1f;有盆友说&#xff0c;当然不是真的想抖&#xff0c;而是因为脚太冷冷冷了&#xff01;有时候穿了棉袜厚鞋&#xff0c;脚都是冷冰冰的&#xff0c;感觉就像踩在冰窟…

.NET 6新特性试用 | 热重载

前言在以前的开发模式下&#xff0c;我们修改代码后必须重新编译、重新运行才能看到效果。而热重载提供了这样一种特性&#xff0c;它允许你在项目正在运行时修改代码&#xff0c;并将代码更改立即应用于正在运行的应用程序上。热重载的目的是尽可能节省编辑之间的应用重启次数…

加速你的Hibernate引擎(上)

为什么80%的码农都做不了架构师&#xff1f;>>> 1.引言 Hibernate是最流行的对象关系映射&#xff08;ORM&#xff09;引擎之一&#xff0c;它提供了数据持久化和查询服务。 在你的项目中引入Hibernate并让它跑起来是很容易的。但是&#xff0c;要让它跑得好却是需…

WSUS服务器的建立以及客户端发布

http://yuelei.blog.51cto.com/202879/81676转载于:https://blog.51cto.com/439810/909642

Spring MVC 中 HandlerInterceptorAdapter过滤器的使用

一般情况下&#xff0c;对来自浏览器的请求的拦截&#xff0c;是利用Filter实现的&#xff0c;这种方式可以实现Bean预处理、后处理。 Spring MVC的拦截器不仅可实现Filter的所有功能&#xff0c;还可以更精确的控制拦截精度。 Spring为我们提供了org.springframework.web.s…

7部必看的纪录片,每一部都堪称经典,让人叹为观止!

全世界只有3.14 % 的人关注了爆炸吧知识纪录片的一大重要意义&#xff0c;就在于它能将我们的视野和脚步&#xff0c;引向我们无法企及的地方和领域&#xff0c;又能让那些我们曾经到过的地方、经历过的人事&#xff0c;变得更有深意。今天&#xff0c;就给大家分享7部顶级纪录…

通过SQL Server操作MySQL的步骤和方法

在多种数据库环境下&#xff0c;经常会遇见在不同数据库之间转换数据和互相进行操作的情况。以下简要介绍下用SQL Server操作MySQL的步骤和方法。 1 操作前的准备 1.1 安装MySQL驱动 想要在SQL Server中操作MySQL&#xff0c;首先要在SQL Server所在的服务器上安装MySQL的驱动。…

ubuntu 新增mysql用户_Ubuntu中给mysql添加新用户并分配权限

一.Ubuntu下启动mysql方法&#xff1a;/etc/init.d/sudo mysqld二.用户添加bingt;mysql -u rootmysqlgt; grant 权限1,权限2,...权限n on一.Ubuntu下启动mysql方法&#xff1a;/etc/init.d/sudo mysqld二.用户添加bin>mysql -u rootmysql> grant 权限1,权限2,...权限n on…

ABP Framework 5.0 RC.1 新特性和变更说明

.Net 6.0 发布之后&#xff0c;ABP Framework 也在第一时间进行了升级&#xff0c;并在一个多星期后&#xff08;2021-11-16&#xff09;发布了 5.0 RC.1 &#xff0c;新功能和重要变更基本已经确定。5.0版本新特性5.0版本新特性列表&#xff1a;•静态 C# 和 JavaScript 客户端…

技术成长的困扰

学习知识的来源都是微信公众号、微博、博客&#xff0c;太碎片化&#xff0c;造成的结果是没有自己的知识体系&#xff0c;不能从整个知识结构层面去看待问题。转载于:https://www.cnblogs.com/samniu/p/5147191.html

mysql分页原理和sqlserver里面序列的用法

mysql使用经验 1.比如分页 select * from table limit 6 和select * from table limit 0,6 等价select * from table limit 5,10&#xff1b; 一般前面的5放的是 漂移 后面的10放的是 一页多少行 拿到数据库 table里面的数据是 第6条到15条 42.121.56.21sqlserver里面序列的用…

土木工程到底有多惨?哭了哭了......

1 那我要去女寝当宿管&#xff01;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 别说了快哭了▼3 今夜我们都是尾款人▼4 童叟无欺&#xff0c;与图片完全一致▼5 好样的&#xff0c;兄弟&#xff01;▼6 土木工程有多惨&#xff1f;&#xff08;素材来源网络…

基于ip tunnel连接不同三个不同网络的×××

就以公司的环境来测试&#xff1a; 局域网网段为192.168.0.0/24 公司linux网关服务址器地绑定两块网卡 内网为192.168.0.3&#xff08;作为局域网的网关&#xff09;公网IP地址为&#xff1a;111.111.111.111 机房内网网段为10.10.0.0/16 一台服务器绑有两块网卡 内网地址…

Easy UI中dategrid的getSelections方法只能获取一个id的解决办法

解决方案&#xff1a;检查idField属性值是否与json数据中的id相同(区分大小写) 实在不行就 去掉 idField属性 也可以解决问题 具体效果请看图&#xff1a; 转载于:https://www.cnblogs.com/hanfeng1949/archive/2013/05/28/3104288.html

java合并list_怎么把两个list合并

第二步骤&#xff1a;list添加set集合1、在实际使用中addAll方法也可以将set集合中的内容添加到list中2、实际代码如下所示&#xff1a;import java.util.ArrayList;import java.util.HashSet;import java.util.List;import java.util.Set;public class ListTest {public stati…

mybatis和hibernate的对比总结

mybatis和hibernate 第一步&#xff0c; 首先让我们对mybatis和hibernate对比了解下 1、 Hibernate &#xff1a;Hibernate 是当前非常流行的ORM框架&#xff0c;对数据库结构提供了较为完整的封装&#xff0c;都是为了简化Dao层的操作。Mybatis&#xff1a;Mybatis同…

.NET 6新特性试用 | Controller支持IAsyncDisposable

前言在.NET中&#xff0c;拥有非托管资源的类通常会实现IDisposable接口&#xff0c;以提供一种同步释放非托管资源的机制。但是&#xff0c;在某些情况下&#xff0c;需要提供一种异步机制来释放非托管资源&#xff0c;这时候可以实现IAsyncDisposable接口。在实现此接口后&am…

ngnix之rewrite

2019独角兽企业重金招聘Python工程师标准>>> REWITE重写[rootlocalhost nginx]# cd conf[rootlocalhost conf]# lsfastcgi.conf koi-win scgi_paramsfastcgi.conf.default mime.types scgi_params.defaultfastcgi_params …

【转】服务器维护工程师悲惨的一个星期

2012.5.11 17点40分,接到**科技部的电话,告诉我IBMX346的服务器同时坏了2块SCSI146G硬盘,现在系统进不去了.问我周六周日能否去修复,和我的领导沟通后明确要周一才能拿到配件. 周一11点才拿到2块SCSI 146G硬盘,匆忙赶去该行,还好不要数据恢复,估计不是很重要的业务,吃完中饭之后…

清华博士生放弃科研,跑去当中学教师,值得吗?

全世界只有3.14 % 的人关注了爆炸吧知识本文来源&#xff1a;科学网博客 作者&#xff1a;程代展原清华大学程代展教授数年前发表博文《昨夜无眠&#xff0c;为了一个学生》&#xff0c;叙述一个亲传徒弟转行的事情。该文曾激起对科研有兴趣的网友的关注&#xff0c;也引发了人…