Springmvc集成Shiro实现权限管理

Shiro是一个安全框架,他可以集成其他开发开发框架 如:Springmvc,实现用户身份认证、权限管理等等功能,shiro详细的介绍也就不讲了,这里给出一些关键的知识点吧:

知识点:

shiro中默认的过滤器

过滤器名称 过滤器类 描述
anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名过滤器
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter 如果继续操作,需要做对应的表单验证否则不能通过
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 基本http验证过滤,如果不通过,跳转屋登录页面
logout org.apache.shiro.web.filter.authc.LogoutFilter 登录退出过滤器
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter 没有session创建过滤器
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 权限过滤器
port org.apache.shiro.web.filter.authz.PortFilter 端口过滤器,可以设置是否是指定端口如果不是跳转到登录页面
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter http方法过滤器,可以指定如post不能进行访问等
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 角色过滤器,判断当前用户是否指定角色
ssl org.apache.shiro.web.filter.authz.SslFilter 请求需要通过ssl,如果不是跳转回登录页
user org.apache.shiro.web.filter.authc.UserFilter 如果访问一个已知用户,比如记住我功能,走这个过滤器

<!-- 访问 /member/queryMyUserInfo路径需要进行身份认证,并且只有当用户的角色为"member"且权限为"member:delete"时才可以访问该路径-->

/member/queryMyUserInfo=authc,roles[member],perms[member:delete]

Shiro相应的jsp标签:

jsp页面中导入:<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

  • guest标签  验证当前用户是否为“访客”,即未认证(包含未记住)的用户
  • user标签  认证通过或已记住的用户
  • authenticated标签  已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。
  • notAuthenticated标签  未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。
  • principal 标签  输出当前用户信息,通常为登录帐号信息
  • hasRole标签 验证当前用户是否属于该角色
  • lacksRole标签  与hasRole标签逻辑相反,当用户不属于该角色时验证通过
  • hasAnyRole标签  验证当前用户是否属于以下任意一个角色。
  • hasPermission标签  验证当前用户是否拥有制定权限
  • lacksPermission标签 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过

Ehcache缓存:

<!--

  • name:Cache的唯一标识
  • maxElementsInMemory:内存中最大缓存对象数
  • maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大
  • eternal:Element是否永久有效,一但设置了,timeout将不起作用
  • overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中
  • timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大
  • timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大
  • diskPersistent:是否缓存虚拟机重启期数据
  • diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
  • diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
  • memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)

-->

springmvc集成shiro项目的配置:

工程结构:

导入jar包:

1.web.xml配置:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>SecurityShiro</display-name>
  <!-- 执行顺序ServletContext,context-param , listener ,filter ,servlet -->
    <!-- 指定Spring的配置文件 -->
    <!-- 否则Spring会默认从WEB-INF下寻找配置文件,contextConfigLocation属性是Spring内部固定的 -->
    <!-- 通过ContextLoaderListener的父类ContextLoader的第120行发现CONFIG_LOCATION_PARAM固定为contextConfigLocation -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext-shiro.xml</param-value>
    </context-param>  

    <!-- 防止发生java.beans.Introspector内存泄露,应将它配置在ContextLoaderListener的前面 -->
    <!-- 详细描述见http://blog.csdn.net/jadyer/article/details/11991457 -->
    <listener>
        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
    </listener>  

    <!-- 实例化Spring容器 -->
    <!-- 应用启动时,该监听器被执行,它会读取Spring相关配置文件,其默认会到WEB-INF中查找applicationContext.xml -->
    <!-- http://starscream.iteye.com/blog/1107036 -->
    <!-- http://www.davenkin.me/post/2012-10-18/40039948363 -->
    <!-- WebApplicationContextUtils.getWebApplicationContext() -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>  

    <!-- 解决乱码问题 -->
    <!-- forceEncoding默认为false,此时效果可大致理解为request.setCharacterEncoding("UTF-8") -->
    <!-- forceEncoding=true后,可大致理解为request.setCharacterEncoding("UTF-8")和response.setCharacterEncoding("UTF-8") -->
    <filter>
        <filter-name>SpringEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>SpringEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>  

    <!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 -->
    <!-- 这里filter-name必须对应applicationContext.xml中定义的<bean id="shiroFilter"/> -->
    <!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过Shiro的过滤 -->
    <!-- 通常会将此filter-mapping放置到最前面(即其他filter-mapping前面),以保证它是过滤器链中第一个起作用的 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>  

    <!-- SpringMVC核心分发器 -->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

2.springmvc.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
 http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

<!-- 注解驱动 -->
<mvc:annotation-driven/>
<!-- 自动扫描包 -->
<context:component-scan base-package="com" />
<!-- 静态文件防止过滤 -->
<!-- 	<mvc:resources location="res" mapping="/res/**"/> -->
 	<mvc:default-servlet-handler/>  

<!-- 默认访问跳转到登录页面(即定义无需Controller的url<->view直接映射) -->
<mvc:view-controller path="/" view-name="forward:/login.jsp"/>

<!-- mvc返回页面的配置 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 模板路径为WEB-INF/pages/ -->
<property name="prefix">
<value>/</value>
</property>
<!-- 视图模板后缀为.JSP -->
<property name="suffix">
<value>.jsp</value>
</property>
</bean>

</beans>

3.applicationContext-shiro.xml
配置(shiro配置)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd">

<!-- 自定义的Realm -->
<bean id="myRealm" class="com.authc.shiro.MyRealm"></bean>

<!-- 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"></property>
</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"></property>
<!-- 登录时的链接 -->
<property name="loginUrl" value="/member/login"></property>

<!-- 登录成功后跳转到的链接 -->
<!-- <property name="successUrl" value="/member/main"></property> -->

 <!-- 用户访问未对其授权的资源时,所显示的连接 -->
<property name="unauthorizedUrl" value="/member/login"></property>
<!-- /error.jsp -->
  <!-- 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>
/member/login=anon
/member/getVerifyCodeImage=anon
/member/tologin=anon
<!-- 访问 /member/queryMyUserInfo路径需要进行身份认证,并且只有当用户的角色为"member"且权限为"member:delete"时才可以访问该路径
 /member/queryMyUserInfo=authc,roles[member],perms[member:delete]
-->
/member/queryMyUserInfo=authc
/admin/**=authc,perms[admin:manage]
</value>

</property>
</bean>

<!-- 保证实现了shiro内部Lifecycle函数的bean的执行  (生命周期)-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
</beans>

4. MyRealm.java

package com.authc.shiro;

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.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.AuthorizationException;
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;
import com.authc.entity.Permission;
import com.authc.entity.Role;
import com.authc.entity.User;
import com.authc.service.PermissionService;
import com.authc.service.RoleService;
import com.authc.service.UserService;
import com.authc.service.Impl.UserServiceImpl;

public class MyRealm extends AuthorizingRealm {

 private UserService userService =new UserServiceImpl();

 /* (non-Javadoc)
  * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
  * 为当前登录的Subject授予角色和权限
     * @see 经测试:本例中该方法的调用时机为需授权资源被访问时
     * @see 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
  */
 @Override
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

  //获取当前登录的用户名
  String username =(String) super.getAvailablePrincipal(principals);
  //根据用户名从数据库中获取用户信息
  User user =userService.queryUserByUserName(username);

  List<String> roleList = new ArrayList<String>();
  List<String> permList = new ArrayList<String>();
  System.out.println("对当前用户:["+username+"]进行授权!");
  if(null!=user)
  {
<!--因为我这里的的role和Permission 只是简单的实现,每个用户只有一个角色以及对于一个权限,所以不需要去遍历,实际工作中需要把用户的多个角色和多个权限都添加到 List<String> roleList和List<String> permList中 -->

   if(user.getRole()!=null && user.getRole().getRoleName()!=null)
   {
    roleList.add(user.getRole().getRoleName());

    if(user.getRole().getPermission()!=null && user.getRole().getPermission().getPermName()!=null)
    {
     permList.add(user.getRole().getPermission().getPermName());

     SimpleAuthorizationInfo info =new SimpleAuthorizationInfo();
     info.addRoles(roleList);
     info.addStringPermissions(permList);

     return info;
    }
   }

  }else
  {

   throw new AuthorizationException();
  }

  //若该方法什么都不做直接返回null的话,就会导致任何用户访问/admin/listUser.jsp时都会自动跳转到unauthorizedUrl指定的地址  

  return null;
 }

 /* (non-Javadoc)
  * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
  * 验证当前登录的Subject
     * @see 经测试:本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()时
  */
 @Override
 protected AuthenticationInfo doGetAuthenticationInfo(
   AuthenticationToken authToken) throws AuthenticationException {

  UsernamePasswordToken token =(UsernamePasswordToken) authToken;

  System.out.println("验证当前Subject时获取到token为" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));  

  User user =userService.queryUserByUserName(token.getUsername());

  if(user!=null)
  {
   AuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),getName());
//将当前用户设置到Session中去以便获取当前用户信息
   this.setSession("currentUser", user);

   return info;
  }

  return null;
 }

 /**
     * 将一些数据放到ShiroSession中,以便于其它地方使用
     * @see 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到
     */
 private void setSession(Object key,Object value)
 {
  Subject currSubject =SecurityUtils.getSubject();
  if(currSubject!=null)
  {
   Session session = currSubject.getSession();
   System.out.println("Session默认超时时间为[" + session.getTimeout() + "]毫秒");  

   if(session!=null)
   {
    session.setAttribute(key, value);
   }
  }

 }
}

User.java

package com.authc.entity;

/**
 * @author lyx
 *
 * 2015-8-26上午8:42:15
 *
 *com.authc.entity.User
 *
 */
public class User {

 private String username;
 private String password;
 private Role role;
 public String getUsername() {
  return username;
 }
 public String getPassword() {
  return password;
 }
 public Role getRole() {
  return role;
 }
 public void setUsername(String username) {
  this.username = username;
 }
 public void setPassword(String password) {
  this.password = password;
 }
 public void setRole(Role role) {
  this.role = role;
 }
 public User(String username, String password, Role role) {
  super();
  this.username = username;
  this.password = password;
  this.role = role;
 }
 public User() {
  super();
 }

}

Role.java

package com.authc.entity;

/**
 * @author lyx
 *
 * 2015-8-26上午8:43:15
 *
 *com.authc.entity.Role
 *
 */
public class Role {

 private String roleName;
 private Permission permission;
 public String getRoleName() {
  return roleName;
 }
 public Permission getPermission() {
  return permission;
 }
 public void setRoleName(String roleName) {
  this.roleName = roleName;
 }
 public void setPermission(Permission permission) {
  this.permission = permission;
 }
 public Role() {
  super();
 }

}

Permission.java

package com.authc.entity;

/**
 * @author lyx
 *
 * 2015-8-26上午8:44:26
 *
 *com.authc.entity.Permission
 *
 */
public class Permission {

 private int permId;
 private String permName;

 public int getPermId() {
  return permId;
 }
 public String getPermName() {
  return permName;
 }
 public void setPermId(int permId) {
  this.permId = permId;
 }
 public void setPermName(String permName) {
  this.permName = permName;
 }

 public Permission(int permId, String permName) {
  super();
  this.permId = permId;
  this.permName = permName;
 }
 public Permission() {
  super();
 }
}

UserService.java

package com.authc.service;

import java.util.List;

import com.authc.entity.Role;
import com.authc.entity.User;

/**
 * @author lyx
 *
 * 2015-8-26上午9:17:25
 *
 *com.authc.service.Impl.UserService
 *
 */
public interface UserService {

 public List<User> queryAllUserInfo();

 public User queryUserByUserName(String username);

 public Role queryUserRoleByUsername(String username);
}

5.UserServiceImpl.java

package com.authc.service.Impl;

import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.authc.entity.Permission;
import com.authc.entity.Role;
import com.authc.entity.User;
import com.authc.service.UserService;

public class UserServiceImpl implements UserService {

 @Override
 public List<User> queryAllUserInfo() {

//没有连接数据库,直接在这里添加数据
  Permission perm =new Permission(1,"member:add");
  Role role =new Role();
  role.setRoleName("member");
  role.setPermission(perm);
  User user=new User();
  user.setUsername("liuyuxin");
  user.setPassword("111111");
  user.setRole(role);

  Permission perm1 =new Permission(2,"member:delete");
  Role role1 =new Role();
  role1.setRoleName("member");
  role1.setPermission(perm1);
  User user1=new User();
  user1.setUsername("liudong");
  user1.setPassword("111111");
  user1.setRole(role1);

  Permission perm2 =new Permission(3,"admin:manage");
  Role role2 =new Role();
  role2.setRoleName("admin");
  role2.setPermission(perm2);
  User user2=new User();
  user2.setUsername("liuhuan");
  user2.setPassword("111111");
  user2.setRole(role2);

  List<User> userList = new ArrayList<User>();
  userList.add(user);
  userList.add(user1);
  userList.add(user2);

  return userList;
 }

 @Override
 public User queryUserByUserName(String username) {

  List<User> userList =queryAllUserInfo();

  for (User user : userList) {

   if(user.getUsername().equals(username))
   {
    return user;
   }
  }

  return null;
 }

 @Override
 public Role queryUserRoleByUsername(String username) {

 List<User> userList =queryAllUserInfo();
 Role role =new Role();

  for (User user : userList) {

   if(user.getUsername().equals(username))
   {
    role=user.getRole();

    return role;
   }
  }

  return null;
 }

}

6.UserController.java

package com.authc.controller;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
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.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import com.authc.entity.User;
import com.authc.service.UserService;
import com.authc.utils.SpringUtil;
import com.authc.utils.VerifyCodeUtil;
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.Producer;

/**
 * @author lyx
 *
 * 2015-8-26上午11:56:03
 *
 *com.authc.controller.UserController
 *
 */
@Controller
@RequestMapping("/member")
public class UserController {

 //登录
 @RequestMapping("/login")
 public String login()
 {
  return "/login";
 }

 /**
  * VerifyCodeUtil.java 类实现验证码
  */

 /**
     * 获取验证码图片和文本(验证码文本会保存在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_ALL_MIXED, 4, null);
        //将验证码放到HttpSession里面
        request.getSession().setAttribute("verifyCode", verifyCode);
        System.out.println("本次生成的验证码为[" + verifyCode + "],已存放到HttpSession中");
        //设置输出的内容的类型为JPEG图像
        response.setContentType("image/jpeg");
        BufferedImage bufferedImage = VerifyCodeUtil.generateImageCode(verifyCode, 90, 25, 4, true, Color.WHITE, Color.BLACK, null);
        //写给浏览器
        ImageIO.write(bufferedImage, "JPEG", response.getOutputStream());
    }  

 /**
  * @param request
  * @return
  * 登录验证
  */
 @RequestMapping("/toLogin")
 public String toLogin(HttpServletRequest request)
 {
  String username = request.getParameter("username");
  String password = request.getParameter("password");

  //返回地址
  String returnUrl="/login";

  //获取HttpSession验证码
  String verifyCode =(String) request.getSession().getAttribute("verifyCode");
  //获取用户输入的验证码
  String submitCode = WebUtils.getCleanParam(request,"verifyCode");

  System.out.println("用户输入的验证码是:"+submitCode+";系统生成的验证码是:"+verifyCode);

  if(StringUtils.isEmpty(submitCode)|| !StringUtils.equalsIgnoreCase(verifyCode, submitCode))
  {
   request.setAttribute("login_msg","验证码错误" );
   return returnUrl;
  }

  //根据获取的用户名和密码封装成Token
  UsernamePasswordToken token =new UsernamePasswordToken(username,password);
  //是否记住用户
  token.setRememberMe(true);
  //获取当前的subject
  Subject subject =SecurityUtils.getSubject();

  try {
   System.out.println("对用户:["+username+"]进行登录验证,验证开始...");
   //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
   //每个Realm都能在必要时对提交的AuthenticationTokens作出反应
   //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
   subject.login(token);
   System.out.println("对用户:["+username+"]进行登录验证,验证通过!");
   returnUrl="/main";

  }catch (UnknownAccountException e) {

   System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:未知账号");
   request.setAttribute("login_msg","未知账号");

  }catch (IncorrectCredentialsException e) {
   System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:密码错误");
   request.setAttribute("login_msg", "密码错误");

  }catch (LockedAccountException e) {
   System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:账号被锁定");
   request.setAttribute("login_msg", "账号被锁定");

  }catch (ExcessiveAttemptsException e) {
   System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:错误次数过多");
   request.setAttribute("login_msg", "密码或用户名输入错误次数过多");

  }catch (AuthenticationException e) {
   System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:用户名或密码不正确");
   request.setAttribute("login_msg", "身份认证失败,用户名或密码不正确");
  }

  //验证是否登录成功
  if(subject.isAuthenticated())
  {
   System.out.println("用户:["+username+"]进行登录验证通过");
  }else
  {
   token.clear();
  }
  return returnUrl;
 }

 /**
  * @param request
  * @return
  * 注销登录
  */

 @RequestMapping("/logout")
 public String logout(HttpServletRequest request)
 {
  SecurityUtils.getSubject().logout();
  return "/login";
 }

 @RequestMapping("/queryMyUserInfo")
 public String queryUserInfo(HttpServletRequest request)
 {
  //从Session中取得当前用户信息
  User currentUser = (User) request.getSession().getAttribute("currentUser");

  request.setAttribute("user", currentUser);

  return "/myinfo";
 }
}

7.
AdminController.java

package com.authc.controller;

import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.authc.entity.User;
import com.authc.service.PermissionService;
import com.authc.service.RoleService;
import com.authc.service.UserService;
import com.authc.service.Impl.UserServiceImpl;

/**
 * @author lyx
 *
 * 2015-8-26上午11:56:45
 *
 *com.authc.controller.AdminController
 *
 */

@Controller
@RequestMapping("/admin")
public class AdminController {

 private UserService userService =new UserServiceImpl();

 @RequestMapping("/queryAllUserInfo")
 public String queryAllUserInfo(HttpServletRequest request)
 {
  List<User> userList = userService.queryAllUserInfo();

  request.setAttribute("userList", userList);

  return "/admin";
 }

}

8. SpringUtil.java

package com.authc.utils;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author lyx
 *
 * 2015-8-18下午3:53:19
 *
 *com.utils.SpringUtil
 *	TODO
 */
public class SpringUtil {

 private static ApplicationContext ctx =new ClassPathXmlApplicationContext("springmvc.xml");

 public static Object getBean(String beanId)
 {
  return ctx.getBean(beanId);
 }
}

VerifyCodeUtil.java
验证码

package com.authc.utils;

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;
}
}

9.login.jsp登录页面

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@page isELIgnored="false"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
request.setAttribute("home", path);
%>
 <!DOCTYPE HTML>
<html lang="en-US">
<!-- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html> -->
<script type="text/javascript" src="<%=request.getContextPath()%>/res/login/prefixfree.min.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/res/js/jquery-1.11.3.min.js"></script>

<head>
<meta charset="UTF-8">
<title>用户登录</title>
<link rel="stylesheet" href="<%=request.getContextPath()%>/res/login/login.css" type="text/css"></link>

<script type="text/javascript">
var home ="${home}";
var msg ="${login_msg }";

$(function(){  //生成验证码
    $('#verifyCodeImage').click(function () {
    $(this).hide().attr('src', '<%=path%>/member/getVerifyCodeImage?' + Math.floor(Math.random()*100) ).fadeIn(); });
});   

window.onbeforeunload = function(){
    //关闭窗口时自动退出
    if(event.clientX>360&&event.clientY<0||event.altKey){
        alert(parent.document.location);
    }
};  

function changeCode() {  //刷新
    $('#verifyCodeImage').hide().attr('src', '<%=path%>/member/getVerifyCodeImage?' + Math.floor(Math.random()*100) ).fadeIn();
    event.cancelBubble=true;
} 

if(msg!="")
{
alert(msg);
}
</script>

</head>
<body>

   <div class="content">
           <form action="<%=request.getContextPath()%>/member/toLogin" method="post" class="login-form">
               <div class="username">
                   <input type="text" name="username" placeholder="[email protected]" autocomplete="on" />
                     <div id="loginMsg"></div>
                    <span class="user-icon icon">u</span>
               </div>
               <div class="password">
                   <input type="password" name="password" placeholder="*******" />

                   <span class="password-icon icon">p</span>
               </div>

              <div class="code-div">
                <input type="text" name="verifyCode" placeholder="请输入验证码" />
        <img id="verifyCodeImage"  src="<%=request.getContextPath()%>/member/getVerifyCodeImage"/>
      <!--   <a href="javascript:void(0)" onclick="changeCode()">看不清?换一张</a>  -->
              </div> 

               <div class="account-control">
                   <input type="checkbox" name="rememberMe" id="Remember me" value="Remember me" checked="checked" />
                   <label for="Remember me" data-on="c" class="check"></label>
                   <label for="Remember me" class="info">Remember me</label>
                  <!--  <input type="hidden" name="rememberMe" value="true"> -->

                   <button type="submit">Login</button>
               </div>

            <p class="not-registered">Not a registered user yet?<a>Sign up now!</a></p>

           </form>

   </div>
</body>
</html>

10.main.jsp主界面

<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="utf-8"%>
 <%@page isELIgnored="false"%>
<!DOCTYPE html>
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title> 功能界面</title>
 </head>
 <body>
 <ul>
  <li>
  普通用户可访问<a href="<%=request.getContextPath()%>/member/queryMyUserInfo" target="_blank">用户信息页面</a>
  </li>
  <li>
  管理员可访问<a href="<%=request.getContextPath()%>/admin/queryAllUserInfo" target="_blank">用户列表页面</a>
   </li>
   <li>
  <a href="<%=request.getContextPath()%>/member/logout" target="_blank">Logout</a>
  </li>
 </ul>
 </body>

</html>

login登陆页面:

验证码:http://blog.csdn.net/u013147600/article/details/48022671

参考网址:http://blog.csdn.net/jadyer/article/details/12257865

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-14 04:27:28

Springmvc集成Shiro实现权限管理的相关文章

springmvc集成shiro注解权限

源代码下载:http://download.csdn.net/detail/u013147600/9066923 java.lang.ClassNotFoundException: org.aspectj.lang.annotation.Around错误解决方法:http://blog.csdn.net/u013147600/article/details/48132947 配置aop错误: org.springframework.beans.factory.xml.XmlBeanDefinit

SpringBoot 集成 Shiro:使用Shiro的权限管理(六)

上一章使用了Shiro的角色管理,现在加入粒度更小的权限管理,即根据用户角色分配的权限来判断用户能否访问页面 准备实体类和修改数据源 @Getter @Setter public class Role implements Serializable { private String name; private Set<Permission> permissions; public Role(String name) { this.name = name; this.permissions =

springmvc集成shiro登录失败处理

一般的登录流程会有:用户名不存在,密码错误,验证码错误等.. 在集成shiro后,应用程序的外部访问权限以及访问控制交给了shiro来管理. shiro提供了两个主要功能:认证(Authentication)和授权(Authorization);认证的作用是证明自身可以访问,一般是用户名加密码,授权的作用是谁可以访问哪些资源,通过开发者自己的用户角色权限系统来控制. shiro的会话管理和缓存管理不在本文范围内. 下面通过登录失败的处理流程来介绍springmvc与shiro的集成. 项目依赖:

SpringMVC集成shiro和redis

记录用maven集成shiro和redis. 先是代码结构: 然后是web.xml 1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://xmlns.jcp.org/xml/ns/javaee" 4 xsi:schemaL

Shiro Review——权限管理基础知识

只要是有用户参与的系统一般都会有权限管理,权限管理实现对用户的访问控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源. 权限管理包括用户认证和授权两部分. 一,用户认证 用户去访问系统,系统要验证用户身份的合法性.比较常见的认证方法:1,用户名密码方式:2,指纹识别,比如我们上班打卡:3,基于证书方式: 当系统验证了用户身份的合法性,用户方可访问系统的资源. 1, 用户认证流程 权限管理是基于资源的,当我们去访问资源的时候,先判断这个资源是否允许匿名访问,比如我们访问一个

spring boot 2 + shiro 实现权限管理

Shiro是一个功能强大且易于使用的Java安全框架,主要功能有身份验证.授权.加密和会话管理.看了网上一些文章,下面2篇文章写得不错.Springboot2.0 集成shiro权限管理 Spring Boot:整合Shiro权限框架 自己动手敲了下代码,在第一篇文章上加入了第二篇文章的Swagger测试,另外自己加入lombok简化实体类代码,一些地方代码也稍微修改了下,过程中也碰到一些问题,最终代码成功运行. 开发版本:IntelliJ IDEA 2019.2.2jdk1.8Spring B

shiro框架--权限管理

一.maven坐标 1 <!-- 权限控制 框架 --> 2 <dependency> 3 <groupId>org.apache.shiro</groupId> 4 <artifactId>shiro-all</artifactId> 5 <version>${shiro.version}</version> 6 </dependency> 二.shiro框架的四大功能 1.认证 2.授权 3.加

springMVC集成shiro权限认证框架,登录之后退出登录出现登录不上的问题

有两种解决方式: 1.在web.xml文件配置一段欢迎页面: <welcome-file-list> <welcome-file>/index.do</welcome-file> </welcome-file-list> 2.在自定义表单过滤器MyFormAuthenticationFilter里,添加清除shiro 在sesion存储的上一次访问地址 shiroSavedReques 1 package cn.zj.logistic.shiro; 2 3

springboot集成shiro实现权限缓存和记住我

到这节为止,我们已经实现了身份验证和权限验证.但是,如果我们登录之后多次访问http://localhost:8080/userInfo/userDel的话,会发现权限验证会每次都执行一次.这是有问题的,因为像用户的权限这些我们提供给shiro一次就够了. 下面,我们开始给shiro添加缓存支持: 1.添加依赖 <!-- shiro ehcache --> <dependency> <groupId>org.apache.shiro</groupId> &l