SpringMVC整合Shiro

10 篇文章 0 订阅
9 篇文章 0 订阅

原帖地址:

http://my.oschina.net/u/2334022/blog/409004


首先是web.xml

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
  <? 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 >  
             <!-- 该值缺省为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 >  
         < 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.jsp</ location >  
     </ error-page >  
     < error-page >  
         < error-code >500</ error-code >  
         < location >/WEB-INF/500.jsp</ location >  
     </ error-page >  
     < error-page >  
         < error-code >java.lang.Throwable</ error-code >  
         < location >/WEB-INF/500.jsp</ location >  
     </ error-page >  
</ web-app >

下面是用于显示Request method 'GET' not supported的//WebRoot//WEB-INF//405.html

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  <! 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

  • ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <%@ 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 

?
1
2
3
4
5
6
7
8
  <%@ 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     

?
1
2
3
4
5
<%@ 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

?
1
2
3
4
5
<%@ page language="java" pageEncoding="UTF-8"%>  
当前登录的用户为${currUser}  
< br />  
< br />  
< a  href="<%=request.getContextPath()%>/mydemo/logout" target="_blank">Logout</ a >
  • 下面是//src//applicationContext.xml


?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<? 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 >  
   
     <!-- 默认访问跳转到登录页面(即定义无需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
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
   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 
      */  
     @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();  
             System.out.println( "Session默认超时时间为["  + session.getTimeout() +  "]毫秒" );  
             if ( null  != session){  
                 session.setAttribute(key, value);  
             }  
         }  
     }  
}
  • 下面是处理用户登录的LoginController.java
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
  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;  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值