├── src ├── main │ ├── webapp │ │ ├── user │ │ │ ├── info-anon.jsp │ │ │ └── info.jsp │ │ ├── admin │ │ │ └── list.jsp │ │ ├── main.jsp │ │ ├── login.jsp │ │ ├── WEB-INF │ │ │ └── web.xml │ │ └── captcha.jsp │ ├── resources │ │ └── applicationContext.xml │ └── java │ │ └── com │ │ └── jadyer │ │ └── demo │ │ ├── controller │ │ └── UserController.java │ │ └── realm │ │ └── MyRealm.java └── test │ └── java │ └── com │ └── jadyer │ └── demo │ └── test │ └── JettyBootStrap.java ├── .gitignore ├── README.md ├── pom.xml └── LICENSE /src/main/webapp/user/info-anon.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" pageEncoding="UTF-8"%> 2 | 这是允许匿名用户查看的页面 -------------------------------------------------------------------------------- /src/main/webapp/admin/list.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" pageEncoding="UTF-8"%> 2 | 当前登录的用户为:${currentUser} 3 |
4 |
5 | 这是允许管理员查看的页面 -------------------------------------------------------------------------------- /src/main/webapp/user/info.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" pageEncoding="UTF-8"%> 2 | 当前登录的用户为:${currentUser} 3 |
4 |
5 | 这是允许普通用户查看的页面 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gitignore 2 | *.class 3 | *.jar 4 | *.war 5 | *.ear 6 | *.iml 7 | .idea 8 | .myeclipse 9 | .mymetadata 10 | .project 11 | .settings 12 | .classpath 13 | target/ 14 | bin/ 15 | META-INF/ 16 | logs 17 | LOG_PATH_IS_UNDEFINED -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # demo-springmvc-shiro [![License](https://img.shields.io/hexpm/l/plug.svg)](https://github.com/v5java/demo-springmvc-shiro/blob/master/LICENSE) 2 | 3 | SpringMVC整合Shiro的示例代码 4 | 5 | 博文内容见:[https://jadyer.github.io/2013/09/30/springmvc-shiro/](https://jadyer.github.io/2013/09/30/springmvc-shiro/) -------------------------------------------------------------------------------- /src/main/webapp/main.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" pageEncoding="UTF-8"%> 2 | 当前登录的用户为:${currentUser} 3 |
4 |
5 | 匿名用户可访问的页面 6 |
7 |
8 | 普通用户可访问的页面 9 |
10 |
11 | 管理员可访问的页面 12 |
13 |
14 | Logout -------------------------------------------------------------------------------- /src/main/webapp/login.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" pageEncoding="UTF-8"%> 2 | 3 |
${message_login}
4 | 5 |
6 | 姓名:
7 | 密码:
8 | 验证:  9 |
10 | 11 |
-------------------------------------------------------------------------------- /src/test/java/com/jadyer/demo/test/JettyBootStrap.java: -------------------------------------------------------------------------------- 1 | package com.jadyer.demo.test; 2 | 3 | import org.eclipse.jetty.server.Connector; 4 | import org.eclipse.jetty.server.Server; 5 | import org.eclipse.jetty.server.nio.SelectChannelConnector; 6 | import org.eclipse.jetty.webapp.WebAppContext; 7 | 8 | /** 9 | * 完整版见https://github.com/jadyer/JadyerEngine/blob/master/JadyerEngine-web/src/test/java/com/jadyer/engine/JettyBootStrap.java 10 | */ 11 | public class JettyBootStrap { 12 | private static int port = 80; 13 | private static String contextPath = "/"; 14 | 15 | public static void main(String[] args) { 16 | long beginTime = System.currentTimeMillis(); 17 | Server server = createServer(); 18 | try { 19 | server.start(); 20 | System.err.println(); 21 | System.out.println("*****************************************************************"); 22 | System.err.print("[INFO] Server running in " + (System.currentTimeMillis() - beginTime) + "ms "); 23 | System.err.println("at http://127.0.0.1" + (80==port?"":":"+port) + contextPath); 24 | System.out.println("*****************************************************************"); 25 | } catch (Exception e) { 26 | System.err.println("Jetty启动失败,堆栈轨迹如下"); 27 | e.printStackTrace(); 28 | System.exit(-1); 29 | } 30 | } 31 | 32 | @SuppressWarnings("ConstantConditions") 33 | private static Server createServer(){ 34 | Server server = new Server(); 35 | server.setStopAtShutdown(true); 36 | SelectChannelConnector connector = new SelectChannelConnector(); 37 | connector.setPort(port); 38 | connector.setReuseAddress(false); 39 | server.setConnectors(new Connector[]{connector}); 40 | String webAppPath = JettyBootStrap.class.getClassLoader().getResource(".").getFile(); 41 | webAppPath = webAppPath.substring(0, webAppPath.indexOf("target")) + "src/main/webapp"; 42 | WebAppContext context = new WebAppContext(webAppPath, contextPath); 43 | server.setHandler(context); 44 | return server; 45 | } 46 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.jadyer.demo 6 | demo-springmvc-shiro 7 | 1.1 8 | war 9 | 10 | 11 | UTF-8 12 | 13 | 14 | 15 | 16 | org.eclipse.jetty 17 | jetty-server 18 | 8.1.17.v20150415 19 | provided 20 | 21 | 22 | org.eclipse.jetty 23 | jetty-webapp 24 | 8.1.17.v20150415 25 | provided 26 | 27 | 28 | org.eclipse.jetty 29 | jetty-jsp 30 | 8.1.17.v20150415 31 | provided 32 | 33 | 34 | org.springframework 35 | spring-webmvc 36 | 3.2.6.RELEASE 37 | 38 | 39 | org.apache.commons 40 | commons-lang3 41 | 3.4 42 | 43 | 44 | org.apache.shiro 45 | shiro-spring 46 | 1.2.2 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-compiler-plugin 55 | 3.5.1 56 | 57 | 1.7 58 | 1.7 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | contextConfigLocation 9 | classpath:applicationContext.xml 10 | 11 | 12 | 13 | org.springframework.web.context.ContextLoaderListener 14 | 15 | 16 | 17 | SpringMVC 18 | org.springframework.web.servlet.DispatcherServlet 19 | 20 | contextConfigLocation 21 | classpath:applicationContext.xml 22 | 23 | 24 | 25 | SpringMVC 26 | / 27 | 28 | 29 | 35 | 36 | shiroFilter 37 | org.springframework.web.filter.DelegatingFilterProxy 38 | 39 | 40 | targetFilterLifecycle 41 | true 42 | 43 | 44 | 45 | shiroFilter 46 | /* 47 | 48 | 49 | 50 | 51 | 45 52 | 53 | -------------------------------------------------------------------------------- /src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 47 | 48 | 56 | 57 | 58 | /main** = authc 59 | /admin/list** = authc,perms[admin:manage] 60 | /user/info-anon** = anon 61 | /user/info** = authc 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 76 | 82 | -------------------------------------------------------------------------------- /src/main/java/com/jadyer/demo/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.jadyer.demo.controller; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 5 | import org.apache.commons.lang3.builder.ToStringStyle; 6 | import org.apache.shiro.SecurityUtils; 7 | import org.apache.shiro.authc.AuthenticationException; 8 | import org.apache.shiro.authc.ExcessiveAttemptsException; 9 | import org.apache.shiro.authc.IncorrectCredentialsException; 10 | import org.apache.shiro.authc.LockedAccountException; 11 | import org.apache.shiro.authc.UnknownAccountException; 12 | import org.apache.shiro.authc.UsernamePasswordToken; 13 | import org.apache.shiro.subject.Subject; 14 | import org.apache.shiro.web.util.WebUtils; 15 | import org.springframework.stereotype.Controller; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | import org.springframework.web.servlet.view.InternalResourceViewResolver; 19 | 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpSession; 22 | 23 | /** 24 | * SpringMVC-3.2.4整合Shiro-1.2.2 25 | * Created by 玄玉 on 2013/09/30 23:37. 26 | */ 27 | @Controller 28 | @RequestMapping("mydemo") 29 | public class UserController { 30 | @RequestMapping("/logout") 31 | public String logout(HttpSession session){ 32 | String currentUser = (String)session.getAttribute("currentUser"); 33 | System.out.println("用户[" + currentUser + "]准备登出"); 34 | SecurityUtils.getSubject().logout(); 35 | System.out.println("用户[" + currentUser + "]已登出"); 36 | return InternalResourceViewResolver.REDIRECT_URL_PREFIX + "/"; 37 | } 38 | 39 | @RequestMapping(value="/login", method=RequestMethod.POST) 40 | public String login(String username, String password, HttpServletRequest request){ 41 | System.out.println("-------------------------------------------------------"); 42 | String rand = (String)request.getSession().getAttribute("rand"); 43 | String captcha = WebUtils.getCleanParam(request, "captcha"); 44 | System.out.println("用户["+username+"]登录时输入的验证码为["+captcha+"],HttpSession中的验证码为["+rand+"]"); 45 | if(!StringUtils.equals(rand, captcha)){ 46 | request.setAttribute("message_login", "验证码不正确"); 47 | return InternalResourceViewResolver.FORWARD_URL_PREFIX + "/"; 48 | } 49 | UsernamePasswordToken token = new UsernamePasswordToken(username, password); 50 | token.setRememberMe(true); 51 | System.out.print("为验证登录用户而封装的Token:"); 52 | System.out.println(ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE)); 53 | //获取当前的Subject 54 | Subject currentUser = SecurityUtils.getSubject(); 55 | try { 56 | //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查 57 | //每个Realm都能在必要时对提交的AuthenticationTokens作出反应 58 | //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法 59 | System.out.println("对用户[" + username + "]进行登录验证...验证开始"); 60 | currentUser.login(token); 61 | System.out.println("对用户[" + username + "]进行登录验证...验证通过"); 62 | }catch(UnknownAccountException uae){ 63 | System.out.println("对用户[" + username + "]进行登录验证...验证未通过,未知账户"); 64 | request.setAttribute("message_login", "未知账户"); 65 | }catch(IncorrectCredentialsException ice){ 66 | System.out.println("对用户[" + username + "]进行登录验证...验证未通过,错误的凭证"); 67 | request.setAttribute("message_login", "密码不正确"); 68 | }catch(LockedAccountException lae){ 69 | System.out.println("对用户[" + username + "]进行登录验证...验证未通过,账户已锁定"); 70 | request.setAttribute("message_login", "账户已锁定"); 71 | }catch(ExcessiveAttemptsException eae){ 72 | System.out.println("对用户[" + username + "]进行登录验证...验证未通过,错误次数过多"); 73 | request.setAttribute("message_login", "用户名或密码错误次数过多"); 74 | }catch(AuthenticationException ae){ 75 | //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景 76 | System.out.println("对用户[" + username + "]进行登录验证...验证未通过,堆栈轨迹如下"); 77 | ae.printStackTrace(); 78 | request.setAttribute("message_login", "用户名或密码不正确"); 79 | } 80 | //验证是否登录成功 81 | if(currentUser.isAuthenticated()){ 82 | System.out.println("用户[" + username + "]登录认证通过(这里可进行一些认证通过后的系统参数初始化操作)"); 83 | return "main"; 84 | }else{ 85 | token.clear(); 86 | return InternalResourceViewResolver.FORWARD_URL_PREFIX + "/"; 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/main/java/com/jadyer/demo/realm/MyRealm.java: -------------------------------------------------------------------------------- 1 | package com.jadyer.demo.realm; 2 | 3 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 4 | import org.apache.commons.lang3.builder.ToStringStyle; 5 | import org.apache.shiro.SecurityUtils; 6 | import org.apache.shiro.authc.AuthenticationException; 7 | import org.apache.shiro.authc.AuthenticationInfo; 8 | import org.apache.shiro.authc.AuthenticationToken; 9 | import org.apache.shiro.authc.SimpleAuthenticationInfo; 10 | import org.apache.shiro.authc.UsernamePasswordToken; 11 | import org.apache.shiro.authz.AuthorizationInfo; 12 | import org.apache.shiro.authz.SimpleAuthorizationInfo; 13 | import org.apache.shiro.realm.AuthorizingRealm; 14 | import org.apache.shiro.session.Session; 15 | import org.apache.shiro.subject.PrincipalCollection; 16 | import org.apache.shiro.subject.Subject; 17 | 18 | /** 19 | * 自定义的指定Shiro验证用户登录的类 20 | * 这里定义了两个用户:jadyer(拥有admin角色和admin:manage权限)、xuanyu(无任何角色和权限) 21 | * Created by 玄玉 on 2013/09/29 15:15. 22 | */ 23 | public class MyRealm extends AuthorizingRealm { 24 | /** 25 | * 为当前登录的Subject授予角色和权限 26 | * ----------------------------------------------------------------------------------------------- 27 | * 经测试:本例中该方法的调用时机为需授权资源被访问时 28 | * 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache 29 | * 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache 30 | * 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache 31 | * ----------------------------------------------------------------------------------------------- 32 | */ 33 | @Override 34 | protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){ 35 | //获取当前登录的用户名 36 | String currentUsername = (String)super.getAvailablePrincipal(principals); 37 | ////从数据库中获取当前登录用户的详细信息 38 | //List roleList = new ArrayList(); 39 | //List permissionList = new ArrayList(); 40 | //User user = userService.getByUsername(currentUsername); 41 | //if(null != user){ 42 | // //实体类User中包含有用户角色的实体类信息 43 | // if(null!=user.getRoles() && user.getRoles().size()>0){ 44 | // //获取当前登录用户的角色 45 | // for(Role role : user.getRoles()){ 46 | // roleList.add(role.getName()); 47 | // //实体类Role中包含有角色权限的实体类信息 48 | // if(null!=role.getPermissions() && role.getPermissions().size()>0){ 49 | // //获取权限 50 | // for(Permission pmss : role.getPermissions()){ 51 | // if(StringUtils.isNotBlank(pmss.getPermission())){ 52 | // permissionList.add(pmss.getPermission()); 53 | // } 54 | // } 55 | // } 56 | // } 57 | // } 58 | //}else{ 59 | // throw new AuthorizationException(); 60 | //} 61 | ////为当前用户设置角色和权限 62 | //SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo(); 63 | //simpleAuthorInfo.addRoles(roleList); 64 | //simpleAuthorInfo.addStringPermissions(permissionList); 65 | //实际中可能会像上面注释的那样,从数据库或缓存中取得用户的角色和权限信息 66 | SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo(); 67 | if(null!=currentUsername && "jadyer".equals(currentUsername)){ 68 | //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色 69 | simpleAuthorInfo.addRole("admin"); 70 | //添加权限 71 | simpleAuthorInfo.addStringPermission("admin:manage"); 72 | System.out.println("已为用户[jadyer]赋予了[admin]角色和[admin:manage]权限"); 73 | return simpleAuthorInfo; 74 | } 75 | if(null!=currentUsername && "xuanyu".equals(currentUsername)){ 76 | System.out.println("当前用户[xuanyu]无授权(不需要为其赋予角色和权限)"); 77 | return simpleAuthorInfo; 78 | } 79 | //若该方法什么都不做直接返回null的话 80 | //就会导致任何用户访问/admin/listUser.jsp时都会自动跳转到unauthorizedUrl指定的地址 81 | //详见applicationContext.xml中的的配置 82 | return null; 83 | } 84 | 85 | /** 86 | * 验证当前登录的Subject 87 | * 经测试:本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()的时候 88 | */ 89 | @Override 90 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { 91 | //获取基于用户名和密码的令牌 92 | //实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的 93 | //两个token的引用都是一样的,本例中是:org.apache.shiro.authc.UsernamePasswordToken@33799a1e 94 | UsernamePasswordToken token = (UsernamePasswordToken)authcToken; 95 | System.out.print("验证当前Subject时获取到token:"); 96 | System.out.println(ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE)); 97 | //User user = userService.getByUsername(token.getUsername()); 98 | //if(null != user){ 99 | // String username = user.getUsername(); 100 | // String password = user.getPassword(); 101 | // String nickname = user.getNickname(); 102 | // AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(username, password, nickname); 103 | // this.setSession("currentUser", user); 104 | // return authcInfo; 105 | //}else{ 106 | // return null; 107 | //} 108 | //此处无需比对,比对的逻辑Shiro会做,我们只需返回一个和令牌相关的正确的验证信息 109 | //说白了就是第一个参数填登录用户名,第二个参数填合法的登录密码(可以是从数据库中取到的,本例中为了演示就硬编码了) 110 | //这样一来,在随后的登录页面上就只有这里指定的用户和密码才能通过验证 111 | if("jadyer".equals(token.getUsername())){ 112 | AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("jadyer", "jadyer", this.getName()); 113 | this.setAuthenticationSession("jadyer"); 114 | return authcInfo; 115 | } 116 | if("xuanyu".equals(token.getUsername())){ 117 | AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("xuanyu", "xuanyu", this.getName()); 118 | this.setAuthenticationSession("xuanyu"); 119 | return authcInfo; 120 | } 121 | //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常 122 | return null; 123 | } 124 | 125 | /** 126 | * 将一些数据放到ShiroSession中,以便于其它地方使用 127 | * 比如Controller里面,使用时直接用HttpSession.getAttribute(key)就可以取到 128 | */ 129 | private void setAuthenticationSession(Object value){ 130 | Subject currentUser = SecurityUtils.getSubject(); 131 | if(null != currentUser){ 132 | Session session = currentUser.getSession(); 133 | System.out.println("当前Session超时时间为[" + session.getTimeout() + "]毫秒"); 134 | session.setTimeout(1000 * 60 * 60 * 2); 135 | System.out.println("修改Session超时时间为[" + session.getTimeout() + "]毫秒"); 136 | session.setAttribute("currentUser", value); 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /src/main/webapp/captcha.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="image/jpeg; charset=UTF-8" pageEncoding="UTF-8"%> 2 | <%@ page import="java.awt.Color"%> 3 | <%@ page import="java.util.Random"%> 4 | <%@ page import="java.awt.image.BufferedImage"%> 5 | <%@ page import="java.awt.Graphics"%> 6 | <%@ page import="java.awt.Font"%> 7 | <%@ page import="javax.imageio.ImageIO"%> 8 | 9 | <%-- 10 | 这是一个用于生成随机验证码图片的JSP文件 11 | 这里contentType="image/jpeg"用来告诉容器:该JSP文件的输出格式为图片格式 12 | 登录网站时,通常要求输入随机生成的验证码,这是为了防止有些软件会自动生成破解密码 13 | 这些验证码一般都是通过图片显示出来的,并且图片上有很多不规则的线条或者图案来干扰,使得软件不容易识别图案上的验证码 14 | --%> 15 | 16 | <%! 17 | /** 18 | * 定义验证码类型(1--纯数字,2--纯汉字) 19 | * 这里也支持数字和英文字母组合,但考虑到不好辨认,故注释了这部分代码,详见第69行 20 | */ 21 | int captchaType = 1; 22 | 23 | /** 24 | * 生成给定范围内的随机颜色 25 | */ 26 | Color getRandColor(Random random, int fc, int bc){ 27 | if(fc>255) fc = 255; 28 | if(bc>255) bc = 255; 29 | int r = fc + random.nextInt(bc-fc); 30 | int g = fc + random.nextInt(bc-fc); 31 | int b = fc + random.nextInt(bc-fc); 32 | return new Color(r, g, b); 33 | } 34 | %> 35 | 36 | <% 37 | //设置页面不缓存 38 | response.setHeader("Pragma", "No-cache"); 39 | response.setHeader("Cache-Control", "no-cache"); 40 | response.setDateHeader("Expires", 0); 41 | 42 | //创建随机类实例 43 | Random random = new Random(); 44 | //定义图片尺寸 45 | int width=60*this.captchaType, height=(this.captchaType==1)?20:30; 46 | //创建内存图像 47 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 48 | //获取图形上下文 49 | Graphics g = image.getGraphics(); 50 | //设定背景色 51 | g.setColor(this.getRandColor(random, 200, 250)); 52 | //设定图形的矩形坐标及尺寸 53 | g.fillRect(0, 0, width, height); 54 | 55 | String sRand = ""; 56 | if(this.captchaType == 1){ 57 | //图片背景随机产生50条干扰线作为噪点 58 | g.setColor(this.getRandColor(random, 160, 200)); 59 | g.setFont(new Font("Times New Roman", Font.PLAIN, 18)); 60 | for(int i=0; i<50; i++){ 61 | int x11 = random.nextInt(width); 62 | int y11 = random.nextInt(height); 63 | int x22 = random.nextInt(width); 64 | int y22 = random.nextInt(height); 65 | g.drawLine(x11, y11, x11+x22, y11+y22); 66 | } 67 | //取随机产生的4个数字作为验证码 68 | //String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 69 | //String str = "abcdefghkmnpqrstwxyABCDEFGHJKLMNPRSTWXYZ123456789"; 70 | for(int i=0; i<4; i++){ 71 | //String rand = String.valueOf(str.charAt(random.nextInt(62))); 72 | //String rand = String.valueOf(str.charAt(random.nextInt(49))); 73 | String rand = String.valueOf(random.nextInt(10)); 74 | sRand += rand; 75 | g.setColor(this.getRandColor(random, 10, 150)); 76 | //将此数字画到图片上 77 | g.drawString(rand, 13*i+6, 16); 78 | } 79 | }else{ 80 | //设定备选汉字 81 | String base = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740" + 82 | "\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b" + 83 | "\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1" + 84 | "\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001" + 85 | "\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1" + 86 | "\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1" + 87 | "\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0" + 88 | "\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee" + 89 | "\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168" + 90 | "\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d" + 91 | "\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c" + 92 | "\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0" + 93 | "\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f" + 94 | "\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf" + 95 | "\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597" + 96 | "\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3" + 97 | "\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be" + 98 | "\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173" + 99 | "\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757" + 100 | "\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752" + 101 | "\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6" + 102 | "\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531" + 103 | "\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5" + 104 | "\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4" + 105 | "\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c" + 106 | "\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a" + 107 | "\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834" + 108 | "\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce" + 109 | "\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559" + 110 | "\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645" + 111 | "\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165" + 112 | "\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c" + 113 | "\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6"; 114 | //图片背景增加噪点 115 | g.setColor(this.getRandColor(random, 160, 200)); 116 | g.setFont(new Font("Times New Roman", Font.PLAIN, 14)); 117 | for(int i=0; i<6; i++){ 118 | g.drawString("*********************************************", 0, 5*(i+2)); 119 | } 120 | //设定验证码汉字的备选字体{"宋体", "新宋体", "黑体", "楷体", "隶书"} 121 | String[] fontTypes = {"\u5b8b\u4f53", "\u65b0\u5b8b\u4f53", "\u9ed1\u4f53", "\u6977\u4f53", "\u96b6\u4e66"}; 122 | //取随机产生的4个汉字作为验证码 123 | for(int i=0; i<4; i++){ 124 | int start = random.nextInt(base.length()); 125 | String rand = base.substring(start, start+1); 126 | sRand += rand; 127 | g.setColor(this.getRandColor(random, 10, 150)); 128 | g.setFont(new Font(fontTypes[random.nextInt(fontTypes.length)], Font.BOLD, 18+random.nextInt(4))); 129 | //将此汉字画到图片上 130 | g.drawString(rand, 24*i+10+random.nextInt(8), 24); 131 | } 132 | } 133 | 134 | //将验证码存入SESSION 135 | session.setAttribute("rand", sRand); 136 | 137 | //图像生效 138 | g.dispose(); 139 | //输出图像到页面 140 | ImageIO.write(image, "PNG", response.getOutputStream()); 141 | 142 | //若无下面两行代码,则每次请求生成验证码图片时 143 | //尽管不会影响到图片的生成以及验证码的校验,但控制台都会滚动下面的异常 144 | //java.lang.IllegalStateException: getOutputStream() has already been called for this response 145 | out.clear(); 146 | out = pageContext.pushBody(); 147 | %> -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------