- pom 添加shiro包
- 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>
- 配置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>
- web里引入shiro 也可以在application 里 import shiro.xml
<!-- 引入shiro -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application-shiro.xml</param-value>
</context-param>
- 编写自定义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);
}
}
}
- 编写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 + "/";
}
}
}
- 其他文件配置
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>
编写页面
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"/>
<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 登录