shiro整合spring(demo)

  1. pom 添加shiro包
  2. web.xml添加
<!-- 配置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>      
<!-- 设置Session超时时间为45分钟 -->     
<session-config>         
  <session-timeout>45</session-timeout>     
</session-config>
  1. 配置application-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ShiroDbRealm.java -->
    <bean id="myRealm" class="com.andy.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>
  1. web里引入shiro 也可以在application 里 import shiro.xml
   <!-- 引入shiro -->
     <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-shiro.xml</param-value>
    </context-param>
  1. 编写自定义realm
package com.andy.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验证用户登录的类
 * 这里定义了两个用户:jadyer(拥有admin角色和admin:manage权限)、xuanyu(无任何角色和权限)
 * Created by 玄玉<https://jadyer.github.io/> on 2013/09/29 15:15.
 */
public class MyRealm extends AuthorizingRealm {
    /**
     * 为当前登录的Subject授予角色和权限
     * -----------------------------------------------------------------------------------------------
     * 经测试:本例中该方法的调用时机为需授权资源被访问时
     * 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
     * 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache
     * 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache
     * -----------------------------------------------------------------------------------------------
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
        //获取当前登录的用户名
        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.isNotBlank(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;
        }
        if(null!=currentUsername && "xuanyu".equals(currentUsername)){
            System.out.println("当前用户[xuanyu]无授权(不需要为其赋予角色和权限)");
            return simpleAuthorInfo;
        }
        //若该方法什么都不做直接返回null的话
        //就会导致任何用户访问/admin/listUser.jsp时都会自动跳转到unauthorizedUrl指定的地址
        //详见applicationContext.xml中的<bean id="shiroFilter">的配置
        return null;
    }
 
    /**
     * 验证当前登录的Subject
     * 经测试:本例中该方法的调用时机为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.print("验证当前Subject时获取到token:");
        System.out.println(ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
        //User user = userService.getByUsername(token.getUsername());
        //if(null != user){
        //    String username = user.getUsername();
        //    String password = user.getPassword();
        //    String nickname = user.getNickname();
        //    AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(username, password, nickname);
        //    this.setSession("currentUser", user);
        //    return authcInfo;
        //}else{
        //    return null;
        //}
        //此处无需比对,比对的逻辑Shiro会做,我们只需返回一个和令牌相关的正确的验证信息
        //说白了就是第一个参数填登录用户名,第二个参数填合法的登录密码(可以是从数据库中取到的,本例中为了演示就硬编码了)
        //这样一来,在随后的登录页面上就只有这里指定的用户和密码才能通过验证
        if("jadyer".equals(token.getUsername())){
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("jadyer", "jadyer", this.getName());
            this.setAuthenticationSession("jadyer");
            return authcInfo;
        }
        if("xuanyu".equals(token.getUsername())){
            AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("xuanyu", "xuanyu", this.getName());
            this.setAuthenticationSession("xuanyu");
            return authcInfo;
        }
        //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
        return null;
    }
 
    /**
     * 将一些数据放到ShiroSession中,以便于其它地方使用
     * 比如Controller里面,使用时直接用HttpSession.getAttribute(key)就可以取到
     */
    private void setAuthenticationSession(Object value){
        Subject currentUser = SecurityUtils.getSubject();
        if(null != currentUser){
            Session session = currentUser.getSession();
            System.out.println("当前Session超时时间为[" + session.getTimeout() + "]毫秒");
            session.setTimeout(1000 * 60 * 60 * 2);
            System.out.println("修改Session超时时间为[" + session.getTimeout() + "]毫秒");
            session.setAttribute("currentUser", value);
        }
    }
}

  1. 编写controller
package com.andy.controller;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
/**
 * SpringMVC-3.2.4整合Shiro-1.2.2
 * Created by O
 */
@Controller
@RequestMapping("mydemo")
public class UserController {
    @RequestMapping("/logout")
    public String logout(HttpSession session){
        String currentUser = (String)session.getAttribute("currentUser");
        System.out.println("用户[" + currentUser + "]准备登出");
        SecurityUtils.getSubject().logout();
        System.out.println("用户[" + currentUser + "]已登出");
        return InternalResourceViewResolver.REDIRECT_URL_PREFIX + "/";
    }
 
    @RequestMapping(value="/login", method=RequestMethod.POST)
    public String login(String username, String password, HttpServletRequest request){
        System.out.println("-------------------------------------------------------");
        String rand = (String)request.getSession().getAttribute("rand");
        String captcha = WebUtils.getCleanParam(request, "captcha");
        System.out.println("用户["+username+"]登录时输入的验证码为["+captcha+"],HttpSession中的验证码为["+rand+"]");
        if(!StringUtils.equals(rand, captcha)){
            request.setAttribute("message_login", "验证码不正确");
            return InternalResourceViewResolver.FORWARD_URL_PREFIX + "/";
        }
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        token.setRememberMe(true);
        System.out.print("为验证登录用户而封装的Token:");
        System.out.println(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 + "]进行登录验证...验证通过");
        }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 + "]登录认证通过(这里可进行一些认证通过后的系统参数初始化操作)");
            return "main";
        }else{
            token.clear();
            return InternalResourceViewResolver.FORWARD_URL_PREFIX + "/";
        }
    }
}

  1. 其他文件配置
    spring-mvc.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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
 
    <mvc:view-controller path="/" view-name="forward:/login.jsp"/>
    <mvc:view-controller path="/tomain" view-name="forward:/main.jsp"/>
 
    <!-- 自动扫描该包,使SpringMVC认为包下用了@controller注解的类是控制器 -->
    <context:component-scan base-package="com.andy.controller" />
    <!-- 扩充了注解驱动,可以将请求参数绑定到控制器参数 -->
    <mvc:annotation-driven/>
 
    <!-- 定义跳转的文件的前后缀 ,视图模式配置-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 这里的配置我的理解是自动给后面action的方法return的字符串加上前缀和后缀,变成一个 可用的url地址 -->
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp" />
    </bean>
 
</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
    <display-name>Archetype Created Web Application</display-name>
 
    <!-- shiro配置文件 -->
      <context-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>,classpath:application-shiro.xml</param-value>
     </context-param>
    <!-- 编码过滤器 -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <async-supported>true</async-supported>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
 
    <!-- Spring监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- 防止Spring内存溢出监听器 -->
    <!-- <listener>
        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
    </listener>  -->
 
    <!-- Spring MVC servlet -->
    <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:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <!-- 此处可以可以配置成*.do,对应struts的后缀习惯 -->
        <url-pattern>/</url-pattern>
    </servlet-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>
    <welcome-file-list>
        <welcome-file>/index.jsp</welcome-file>
    </welcome-file-list>
 
</web-app>

编写页面


image.png

login.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
 
<div style="color:red; font-size:22px;">${message_login}</div>
 
<form action="${pageContext.request.contextPath}/mydemo/login" method="POST">
    姓名:<input type="text" name="username"/><br/>
    密码:<input type="text" name="password"/><br/>
    验证:<input type="text" name="captcha"/>&nbsp;
         <img style="cursor:pointer;" src="/captcha.jsp" onClick="this.src='/captcha.jsp?time'+Math.random();"/><br/>
         <input type="submit"/>
</form>
 

main.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
当前登录的用户为:${currentUser}
<br>
<br>
<a href="${pageContext.request.contextPath}/user/info-anon.jsp" target="_blank">匿名用户可访问的页面</a>
<br>
<br>
<a href="${pageContext.request.contextPath}/user/info.jsp" target="_blank">普通用户可访问的页面</a>
<br>
<br>
<a href="${pageContext.request.contextPath}/admin/list.jsp" target="_blank">管理员可访问的页面</a>
<br>
<br>
<a href="${pageContext.request.contextPath}/mydemo/logout" target="_blank">Logout</a>

captcha.jsp

<%@ page contentType="image/jpeg; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.awt.Color"%>
<%@ page import="java.util.Random"%>
<%@ page import="java.awt.image.BufferedImage"%>
<%@ page import="java.awt.Graphics"%>
<%@ page import="java.awt.Font"%>
<%@ page import="javax.imageio.ImageIO"%>
 
<%--
这是一个用于生成随机验证码图片的JSP文件
这里contentType="image/jpeg"用来告诉容器:该JSP文件的输出格式为图片格式
登录网站时,通常要求输入随机生成的验证码,这是为了防止有些软件会自动生成破解密码
这些验证码一般都是通过图片显示出来的,并且图片上有很多不规则的线条或者图案来干扰,使得软件不容易识别图案上的验证码
--%>
 
<%!
/**
 * 定义验证码类型(1--纯数字,2--纯汉字)
 * 这里也支持数字和英文字母组合,但考虑到不好辨认,故注释了这部分代码,详见第69行
 */
int captchaType = 1;
 
/**
 * 生成给定范围内的随机颜色
 */
Color getRandColor(Random random, int fc, int bc){
   if(fc>255) fc = 255;
   if(bc>255) bc = 255;
   int r = fc + random.nextInt(bc-fc);
   int g = fc + random.nextInt(bc-fc);
   int b = fc + random.nextInt(bc-fc);
   return new Color(r, g, b);
}
%>
 
<%
//设置页面不缓存
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
 
//创建随机类实例
Random random = new Random();
//定义图片尺寸
int width=60*this.captchaType, height=(this.captchaType==1)?20:30;
//创建内存图像
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//获取图形上下文
Graphics g = image.getGraphics();
//设定背景色
g.setColor(this.getRandColor(random, 200, 250));
//设定图形的矩形坐标及尺寸
g.fillRect(0, 0, width, height);
 
String sRand = "";
if(this.captchaType == 1){
   //图片背景随机产生50条干扰线作为噪点
   g.setColor(this.getRandColor(random, 160, 200));
   g.setFont(new Font("Times New Roman", Font.PLAIN, 18));
   for(int i=0; i<50; i++){
      int x11 = random.nextInt(width);
      int y11 = random.nextInt(height);
      int x22 = random.nextInt(width);
      int y22 = random.nextInt(height);
      g.drawLine(x11, y11, x11+x22, y11+y22);
   }
   //取随机产生的4个数字作为验证码
   //String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
   //String str = "abcdefghkmnpqrstwxyABCDEFGHJKLMNPRSTWXYZ123456789";
   for(int i=0; i<4; i++){
      //String rand = String.valueOf(str.charAt(random.nextInt(62)));
      //String rand = String.valueOf(str.charAt(random.nextInt(49)));
      String rand = String.valueOf(random.nextInt(10));
      sRand += rand;
      g.setColor(this.getRandColor(random, 10, 150));
      //将此数字画到图片上
      g.drawString(rand, 13*i+6, 16);
   }
}else{
   //设定备选汉字
   String base = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740" +
             "\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b" +
             "\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1" +
             "\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001" +
             "\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1" +
             "\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1" +
             "\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0" +
             "\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee" +
             "\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168" +
             "\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d" +
             "\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c" +
             "\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0" +
             "\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f" +
             "\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf" +
             "\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597" +
             "\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3" +
             "\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be" +
             "\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173" +
             "\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757" +
             "\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752" +
             "\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6" +
             "\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531" +
             "\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5" +
             "\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4" +
             "\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c" +
             "\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a" +
             "\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834" +
             "\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce" +
             "\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559" +
             "\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645" +
             "\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165" +
             "\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c" +
             "\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6";
   //图片背景增加噪点
   g.setColor(this.getRandColor(random, 160, 200));
   g.setFont(new Font("Times New Roman", Font.PLAIN, 14));
   for(int i=0; i<6; i++){
      g.drawString("*********************************************", 0, 5*(i+2));
   }
   //设定验证码汉字的备选字体{"宋体", "新宋体", "黑体", "楷体", "隶书"}
   String[] fontTypes = {"\u5b8b\u4f53", "\u65b0\u5b8b\u4f53", "\u9ed1\u4f53", "\u6977\u4f53", "\u96b6\u4e66"};
   //取随机产生的4个汉字作为验证码
   for(int i=0; i<4; i++){
      int start = random.nextInt(base.length());
      String rand = base.substring(start, start+1);
      sRand += rand;
      g.setColor(this.getRandColor(random, 10, 150));
      g.setFont(new Font(fontTypes[random.nextInt(fontTypes.length)], Font.BOLD, 18+random.nextInt(4)));
      //将此汉字画到图片上
      g.drawString(rand, 24*i+10+random.nextInt(8), 24);
   }
}
 
//将验证码存入SESSION
session.setAttribute("rand", sRand);
 
//图像生效
g.dispose();
//输出图像到页面
ImageIO.write(image, "PNG", response.getOutputStream());
 
//若无下面两行代码,则每次请求生成验证码图片时
//尽管不会影响到图片的生成以及验证码的校验,但控制台都会滚动下面的异常
//java.lang.IllegalStateException: getOutputStream() has already been called for this response
out.clear();
out = pageContext.pushBody();
%>

info.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
当前登录的用户为:${currentUser}
<br>
<br>
这是允许普通用户查看的页面

info-anon.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
这是允许匿名用户查看的页面

list.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
当前登录的用户为:${currentUser}
<br>
<br>
这是允许管理员查看的页面

先用 xuanyu 登录,后用 jadyer 登录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • 网络结构模式 两层结构 客户端(UI)--- 服务器(数据库) 三层结构 展现层或web server;UI,显...
    b099e4f1c471阅读 102评论 0 0
  • 1、使用移动组件库mint-ui Mint UI是基于Vue.js的移动组件库,有很多可以使用,参考这里 使用时首...
    veb001阅读 346评论 0 0
  • 1、iptables基础知识 Netfilter组件防火墙功能是集成在内核中的,通过以下命令可以看出防火墙功能是以...
    阿丧小威阅读 403评论 0 0
  • COMPAS要求 依赖库 为了充分利用COMPAS,我们建议安装科学的Python发行版,如Anaconda或EP...
    nbit阅读 354评论 0 1
  • 1.露频,你真是一个心思细腻做事条理又利索的人。你看你三下五除二就把事儿办好了 2.亲爱的小伙伴们,你们太有创意啦...
    肖馨肖馨阅读 241评论 0 1