SpringMVC+Mybatis+Mysql+Shiro

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代码


//验证登录的Controller
package 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");
    }

}

//自定义的Realm
package 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;

@Repository
public interface  UserDao {

    public User login(User user);

    public List getRole(User user);

    public List getPermissions(String role);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值