SpringMVC+Mybatis+Mysql+Shiro

来源:互联网 发布:windows chakan elf 编辑:程序博客网 时间:2024/06/05 03:44

shiro权限框架

  1. shiro主要的组件:

    • SecurityManager:典型的Facade模式(该模式为子系统的各个类或方法提供一个简明一致的界面,隐藏子系统的复杂性,使子系统更加容易使用。)是Authenticator + Authorizer + SesssionFactory + CacheManager
    • Authenticator:登录认证,进行用户名和密码的匹配。
    • Authorizer:授权,即权限验证,验证某个已经通过认证(登录过的人)是否拥有某个权限。验证某个用户是否有相应的角色(管理员,普通人员等,这是粗粒度的验证),还可验证某个用户对某个资源是否具有权限(比如对删除按钮执行权的权限)。
    • SessionManager:会话管理,用户登录后就是一次会话,相当于JavaWeb中的Session。但是此处的Session既可在JavaSE下使用也可用在JavaWeb中。
    • Cryptography:密码加密。
    • Caching:缓存,用户登录后,其拥有的角色或权限不必每次去查,这样可以提高效率。
    • Concurrency:shiro支持多线程应用的并发执行,即如在一个线程中开启另一个线程,能把权限自动传播过去。
    • Remember Me:记住无,这是个非常常见的功能,即一次登录后,下次再来的话就不用登录了。


shiro安全模型(subject–>SecurityManage–>Realm)

  1. shiro安全模型

    • Subject:安全领域术语,除了代表人还可以是其他任何和当前应用交互的东西。手机号,邮箱登录等都可看作是subject。所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager,SecurityManager才是真正的执行者。
    • SecurityManager:shiro的核心,它负责和其他组件交互。
    • Realm:Shiro从Realm获取安全数据(如用户、角色、权限),SecurityManager要验证用户身份,那么他需要从Realm获取相应的用户进行比较以确定用户身份是否合法;从Realm中进行和数据库交互,得到相应的数据。


shiro身份认证流程

  1. 认证流程

    • 1. 首先调用Subject.login(Token)进行登录,其会自动委托给SecurityManage,调用之前必须通过SecurityUtils.setSecurityManager()设置;
    • 2. SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行什么验证;
    • 3. Authenticatoe才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;
    • 4. Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
    • 5. Authenticator会把相应的token传入Relam,从Realm获取什么验证信息,如果没有返回/抛出异常表示什么验证失败了,此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

代码实现登录认证和权限认证

  1. 数据表设计:用户表,角色表,权限表,用户角色表,角色权限表
--用户表 DROP TABLE IF EXISTS `a_user`;CREATE TABLE `a_user` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `username` varchar(20) CHARACTER SET utf8 NOT NULL,  `password` varchar(32) CHARACTER SET utf8 NOT NULL,  `age` int(2) DEFAULT NULL,  `gender` int(2) DEFAULT NULL,  `mail` varchar(30) CHARACTER SET utf8 DEFAULT NULL,  `tel` varchar(11) CHARACTER SET utf8 DEFAULT NULL,  `image` varchar(50) CHARACTER SET utf8 DEFAULT NULL,  `limits` int(2) DEFAULT NULL,  PRIMARY KEY (`id`),  UNIQUE KEY `username` (`username`)) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=latin1;--角色表DROP TABLE IF EXISTS `role`;CREATE TABLE `role` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(60) NOT NULL,  `type` varchar(20) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;--权限表DROP TABLE IF EXISTS `permission`;CREATE TABLE `permission` (  `id` int(11) NOT NULL,  `url` varchar(200) NOT NULL,  `name` varchar(200) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;--用户角色表DROP TABLE IF EXISTS `user_role`;CREATE TABLE `user_role` (  `uid` varchar(11) NOT NULL,  `rid` int(11) NOT NULL,  PRIMARY KEY (`uid`,`rid`),  KEY `kd_roleId` (`rid`),  CONSTRAINT `fk_userId` FOREIGN KEY (`uid`) REFERENCES `a_user` (`username`) ON DELETE CASCADE,  CONSTRAINT `kd_roleId` FOREIGN KEY (`rid`) REFERENCES `role` (`id`) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8;--角色权限表DROP TABLE IF EXISTS `role_permission`;CREATE TABLE `role_permission` (  `rid` int(11) NOT NULL,  `pid` int(11) NOT NULL,  PRIMARY KEY (`rid`,`pid`),  KEY `fk_permisssionId` (`pid`),  CONSTRAINT `fk_permisssionId` FOREIGN KEY (`pid`) REFERENCES `permission` (`id`) ON DELETE CASCADE,  CONSTRAINT `fk_roleId` FOREIGN KEY (`rid`) REFERENCES `role` (`id`) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2. 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">  <context-param>    <param-name>contextConfigLocation</param-name>    <param-value>classpath:applicationContext.xml</param-value>  </context-param>  <!-- 配置log4j -->   <context-param>    <param-name>log4jConfigLocation</param-name>    <param-value>classpath:log4j.properties</param-value>  </context-param>  <!-- 60秒检测日志配置 文件变化 -->  <context-param>    <param-name>log4jRefreshInterval</param-name>    <param-value>60000</param-value>  </context-param>    <!--配置shiroFilter,让其过滤所有的url请求。-->     <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>    <listener>        <description>spring监听器</description>        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    </listener>    <!-- 防止spring内存溢出监听器 -->    <listener>        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>    </listener>    <listener>        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>    </listener>    <!-- SpringMVC核心分发器 -->      <servlet>          <servlet-name>SpringMVC</servlet-name>          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>          <init-param>              <param-name>contextConfigLocation</param-name>              <param-value>classpath:applicationContext.xml</param-value>        </init-param>      </servlet>      <servlet-mapping>          <servlet-name>SpringMVC</servlet-name>          <url-pattern>/</url-pattern>      </servlet-mapping></web-app>

3. 配置applicationContext.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:mvc="http://www.springframework.org/schema/mvc"    xmlns:context="http://www.springframework.org/schema/context"    xsi:schemaLocation="http://www.springframework.org/schema/beans                          http://www.springframework.org/schema/beans/spring-beans-3.2.xsd                          http://www.springframework.org/schema/mvc                          http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd                          http://www.springframework.org/schema/context                          http://www.springframework.org/schema/context/spring-context-3.2.xsd">    <!-- springMvc的配置 -->    <mvc:annotation-driven></mvc:annotation-driven>    <!-- 开启注解配置Controller -->    <context:component-scan base-package="com.itdage.*"></context:component-scan>    <!-- 配置viewResolver -->    <bean id="viewResolver"        class="org.springframework.web.servlet.view.InternalResourceViewResolver">        <property name="prefix" value="/"></property>        <property name="suffix" value=".jsp"></property>    </bean>    <!-- Mybatis的配置 -->    <import resource="classpath:mybatisConfig.xml"/>    <!-- shiro的配置 -->    <import resource="classpath:shiroConfig.xml"/></beans>

4. 配置shiroConfig.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:mvc="http://www.springframework.org/schema/mvc"    xmlns:context="http://www.springframework.org/schema/context"    xsi:schemaLocation="http://www.springframework.org/schema/beans                          http://www.springframework.org/schema/beans/spring-beans-3.2.xsd                          http://www.springframework.org/schema/mvc                          http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd                          http://www.springframework.org/schema/context                          http://www.springframework.org/schema/context/spring-context-3.2.xsd">    <!--shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session-->    <!--这里主要是设置自定义的单Realm应用,若有多个Realm可使用realms属性来替代 -->    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">        <property name="realm" ref="myRealm" />        <!-- 使用配置的缓存管理器 -->        <property name="cacheManager" ref="cacheManager"></property>        <!-- 会话管理 -->        <property name="sessionManager" ref="sessionManager" />    </bean>    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">            <!-- session的失效时长,单位毫秒 -->            <property name="globalSessionTimeout" value="600000"/>            <!-- 删除失效的session -->            <property name="deleteInvalidSessions" value="true"/>    </bean>    <!--自己定义的Realm -->    <bean id="myRealm" class="com.itdage.realms.ShiroDbRealm">        <!-- <property name="credentialsMatcher">            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">                <property name="hashAlgorithmName" value="MD5"></property>            </bean>         </property>-->    </bean>    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />    <!--开启shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描Shiro注解的类 -->    <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>    <!-- 启用shiro授权注解拦截方式 -->    <!--Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->    <!--Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持    这里id的名字必须和web.xml中配置的一样,否则启动报错 -->    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">        <!-- 装配securityManager,这个属性是必须的 -->        <property name="securityManager" ref="securityManager" />        <!-- 配置登录页,非必须属性,默认会自动寻找Web工程根目录下的/login.jsp页面 -->        <property name="loginUrl" value="/login.jsp" />        <!-- 配置登录成功后的页面,可以不写,登录成功后在Controller中已经指定了 -->        <property name="successUrl" value="/list.jsp" />        <property name="unauthorizedUrl" value="/unauthorized.jsp" />        <!--下面value值得第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 -->        <!--anon:可以被匿名访问 -->        <!--authc:必须认证(登录)后才能访问 -->        <!--格式:url=拦截器[参数],拦截器[参数]            url模式使用Ant风格模式:Ant路径通配符支持?、*、**通配符不包括分隔符"/"            -?:匹配一个字符: 如/admin?将匹配/admin1,但不匹配/admin或/admin/            -*:匹配0或多个字符串,如/admin*将匹配/admin、/admin123但不匹配/admin/123            -**:匹配路径中的0或多个路径,如/admin/**将匹配/admin/a或/admin/a/b         -->        <!--URL权限采取第一次匹配优先的方式,即从头开始使用第一个匹配url模式的拦截器链        如: /bb/** = filter1                 /bb/aa = filter2                 /** = filter3                 如果请求的url是/bb/aa,因为按照声明顺序进行匹配,那么将使用filter1进行拦截。         -->        <property name="filterChainDefinitions">            <value>                <!-- 静态资源允许访问 -->                <!-- 登录页允许访问 -->                /login.jsp = anon                /test/login = anon                /logout = logout                <!-- 其他资源都需要认证 -->                /** = authc            </value>        </property>    </bean></beans>


5. Java代码

//验证登录的Controllerpackage com.itdage.controller;import java.io.IOException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.authz.annotation.RequiresPermissions;import org.apache.shiro.subject.Subject;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import com.itdage.entity.User;import com.itdage.serviceImpl.UserServiceImpl;@Controller@RequestMapping("/test")public class ShiroLogin {    /**     * 验证登录     * @param user     * @param request     * @return     */    @RequestMapping(value = "/login", method = RequestMethod.POST)    public String login(User user, HttpServletRequest request){        if(isRelogin(user)){            //已经登录过了,跳转到fail.jsp页面            return "fail";        }        HttpSession session = request.getSession();        session.setAttribute("successMsg", "登录成功!");        return shiroLogin(user);    }    /**     * 判断用户是否已经登录     * @param user     * @return     */    public boolean isRelogin(User user){        Subject subject = SecurityUtils.getSubject();        if(subject.isAuthenticated()){            return true; //参数未改变,无需重新登录,默认为已登录成功        }        return false;    }    /**     * 利用shiro进行登录     * @param user     * @return     */    public String shiroLogin(User user){        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword().toCharArray(), null);        token.setRememberMe(true);        try{            SecurityUtils.getSubject().login(token);        }catch(UnknownAccountException e){            System.out.println(e.getMessage());            return "用户不存在或密码不正确1";        }catch(IncorrectCredentialsException e){            System.out.println(e.getMessage());            return "用户名或密码不正确2";        }catch(AuthenticationException e){            System.out.println(e.getMessage());            return e.getMessage();        }        return "redirect:/list.jsp";    }    @RequestMapping("/logout")    public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException{        Subject subject = SecurityUtils.getSubject();        if(subject != null){            try{                subject.logout();                System.out.println("用户退出");            }catch(Exception e){                System.out.println("退出异常");            }        }        response.sendRedirect("/shiro-web3/login.jsp");    }}
//自定义的Realmpackage com.itdage.realms;import java.util.List;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.springframework.beans.factory.annotation.Autowired;import com.itdage.entity.Permission;import com.itdage.entity.Role;import com.itdage.entity.User;import com.itdage.serviceImpl.UserServiceImpl;public class ShiroDbRealm extends AuthorizingRealm{    @Autowired    private UserServiceImpl userService;    /*     * 授权     * 授权查询回调函数,进行权限鉴定但缓存中无用户的授权信息时回调,负责在应用程序中决定用户的访问控制的方法。     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(            PrincipalCollection principals) {        String username = (String) principals.getPrimaryPrincipal();        User user = new User();        user.setUsername(username);        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();        List roleList = userService.getRole(user);        if(roleList != null){            for (Role role : roleList) {                List permissions = userService.getPermissions(role.getName());                for (Permission permission2 : permissions) {                    authorizationInfo.addStringPermission(permission2.getUrl());                }                authorizationInfo.addRole(role.getName());            }            return authorizationInfo;        }        return null;    }    /*      * 验证登录     * 认证回调函数,登录信息和用户验证信息验证     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(            AuthenticationToken token) throws AuthenticationException {            User userLogin = tokenToUser((UsernamePasswordToken)token);            User user = userService.login(userLogin);            if(user == null){                return null;//异常处理,找不到数据            }            return new SimpleAuthenticationInfo(userLogin.getUsername(), userLogin.getPassword(), getName());    }    private User tokenToUser(UsernamePasswordToken token) {        User user = new User();        user.setUsername(token.getUsername());        user.setPassword(String.valueOf(token.getPassword()));        return user;    }}
//DAO层package com.itdage.dao;import java.util.List;import org.springframework.stereotype.Repository;import com.itdage.entity.Permission;import com.itdage.entity.Role;import com.itdage.entity.User;@Repositorypublic interface  UserDao {    public User login(User user);    public List getRole(User user);    public List getPermissions(String role);}
原创粉丝点击