springboot shiro权限管理【二】:测试框架搭建之shiro权限认证

第一节简单说明了一下shiro是干什么的,今天上班闲的蛋疼,又不能让自己闲着

先补充个shiro权限认证的一个流程说明:

1. 获取当前的 Subject. 调用 SecurityUtils.getSubject();
2. 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated() ;
3. 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象;
1). 创建一个表单页面;
2). 把请求提交到 SpringMVC 的 Handler;
3). 获取用户名和密码;
4. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法; 
5. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro;
1). 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类;
2). 实现 doGetAuthenticationInfo(AuthenticationToken) 方法; 

6. 由 shiro 通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对;

7. 授权需要继承 AuthorizingRealm 类, 并实现其 doGetAuthorizationInfo 方法;

8. AuthorizingRealm 类继承自 AuthenticatingRealm, 但没有实现 AuthenticatingRealm 中的 

doGetAuthenticationInfo, 所以认证和授权只需要继承 AuthorizingRealm 就可以了. 同时实现他的两个抽象方法;

9. 使用 MD5 盐值加密: 
1). 在 doGetAuthenticationInfo 方法返回值创建 SimpleAuthenticationInfo 对象的时候, 需要使用
SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 构造器;
2). 使用 ByteSource.Util.bytes() 来计算盐值;
3). 盐值需要唯一: 一般使用随机字符串或username(必须唯一);
4). 使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 来计算盐值加密后的密码的值;
10. 把一个字符串加密为 MD5方式:
      替换当前 Realm 的 credentialsMatcher 属性. 直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可;

使用以下框架:

springboot (2.0.2RELEASE)

mybatis (1.3.2)

druid (1.1.2)

mysql (没看,无所谓了)

搭建一个基础框架,用来测试shiro(1.2.0);备注:前后端分离的

首先看目录结构


小二,上代码:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xiaol</groupId>
    <artifactId>shiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>shiro</name>
    <description>shiro project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.0.4</version>
        </dependency>
        <!-- shiro start -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!-- shiro end -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.22</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

启动类:ShiroApplication.java

package com.xiaol.shiro;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement //如果mybatis中service实现类中加入事务注解,需要此处添加该注解
@MapperScan("com.xiaol.shiro.mapper")  //扫描的是mapper.xml中namespace指向值的包位置
public class ShiroApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShiroApplication.class, args);
    }
}

本来打算配置文件用zookeeper的, 然而并没有用,哈哈,application.properties

# 主数据源,默认的
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456

# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.filters=stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
#Mybatis扫描的sqlxml文件位置和别名
mybatis.mapper-locations=classpath:mapper/*.xml  
mybatis.type-aliases-package=com.xiaol.shiro.entity  
#自定义springboot启动监听端口
server.port = 8083

接下来就是StartupRunner.java了(就打个日志用)

package com.xiaol.shiro.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

/**
 * 打印服务启动完成日志
 */
@Component
public class StartupRunner implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(StartupRunner.class.getName());
    @Value("${server.port}")
    private int port;
   @Override
   public void run(String... arg0) throws Exception {
       logger.info("服务启动完成! 服务端口:{}",port);
   }
}

那些什么controller,service,impl,entity,mapper,mapper.xml文件内容就不粘贴了哈,很简单了,想要的私信

接下来就是shiro的相关配置了

shiroConfig.java

package com.xiaol.shiro.config.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author 梁荣兵
 * @version 1.0
 * @Description:Shiro 配置  Apache Shiro 核心通过 Filter 来实现,类似SpringMvc 通过DispachServlet
   既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
 */
@Configuration
public class ShiroConfig {
        
     
    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。 
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在 
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager 
     * 
        Filter Chain定义说明 
       1、一个URL可以配置多个Filter,使用逗号分隔 
       2、当设置多个过滤器时,全部验证通过,才视为通过 
       3、部分过滤器可指定参数,如perms,roles
     * 
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){

       ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();

       Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();//获取filters
       filters.put("authc", new CustomFormAuthenticationFilter());//将自定义 的FormAuthenticationFilter注入shiroFilter中,用于解决前后端分离返回json使用
        // 必须设置 SecurityManager   
       shiroFilterFactoryBean.setSecurityManager(securityManager);  
       //拦截器.
       Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();  
        
       //注意过滤器配置顺序 不能颠倒  
       //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl  
       filterChainDefinitionMap.put("/logout", "logout");  
       filterChainDefinitionMap.put("/user/login", "anon");
       filterChainDefinitionMap.put("/**", "authc");
       //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据  
       shiroFilterFactoryBean.setLoginUrl("/unauth");  
        
       shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);  
       return shiroFilterFactoryBean;  
    }

    @Bean(name = "shiroRealm")
    public ShiroRealm shiroRealm(HashedCredentialsMatcher matcher){
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(matcher);
        return shiroRealm;
    }

    /**
     * shiro核心控制器
     * @param matcher
     * @return
     */
    @Bean
    public SecurityManager securityManager(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm(matcher));
        return securityManager;
    }

    /**
     * 密码匹配凭证管理器
     *
     * @return
     */
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 采用MD5方式加密
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        // 设置加密次数
        hashedCredentialsMatcher.setHashIterations(1024);
        return hashedCredentialsMatcher;
    }
}  

shiroRealm.java

package com.xiaol.shiro.config.shiro;


import com.xiaol.shiro.service.UserService;
import com.xiaol.shiro.service.impl.UserServiceImpl;
import com.xiaol.shiro.utils.SpringContextUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;


/**
 * @author 梁荣兵
 * @version 1.0
 * @Description:
 * @date 2018年05月08日
 */
public class ShiroRealm extends AuthorizingRealm {

   /*
    * 登录信息和用户验证信息认证(non-Javadoc)
    * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
    */
   @Override
   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
       //获取service实例
       UserService userServiceImpl = (UserService) SpringContextUtils.getBean(UserService.class);
      UsernamePasswordToken userToken = (UsernamePasswordToken)token;
      String username = userToken.getUsername();              //得到用户名

       //从数据库中查询用户名密码是否正确
      Map user = userServiceImpl.findUserByUnameAndPwd(username);
      if (user != null) { //说明登录成功
         //第三个参数:盐值(这个盐是 username)
         ByteSource solt = ByteSource.Util.bytes(username);
         return new SimpleAuthenticationInfo(username, user.get("password"),solt, getName());
      }else {
         throw new UnknownAccountException("账号不存在");
      }
      //注:shiro内部会进行密码的比对 通过UsernamePasswordToken和SimpleAuthenticationInfo两个类中存储的用户名和密码进行对比
   }
   
   /*
    * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用,负责在应用程序中决定用户的访问控制的方法(non-Javadoc)
    * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
    */
   @Override
   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
      System.out.println("暂时还没有加上授权,一步一步来,后面会加上");
      return null;
   }

   public static void main(String[] args) {
      String hashAlgorithmName = "MD5";
      String credentials = "123456";
      String username = "xiaol";
      int hashIterations = 1024;
      Object obj = new SimpleHash(hashAlgorithmName, credentials, username, hashIterations);
      System.out.println(obj);
   }
}

CustomFormAuthenticationFilter.java

package com.xiaol.shiro.config.shiro;

import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 梁荣兵
 * @version 1.0
 * @Description: shiro拦截器(用于处理用户未登录或者登陆过期返回json提示信息,shiro默认会重定向到页面,会有问题,这里重写了shiro的拦截器)
 * @date 2018年05月08日
 */
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter{
    @Override  
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (this.isLoginRequest(request, response)) {  
            if (this.isLoginSubmission(request, response)) {  
                return this.executeLogin(request, response);  
            } else {  
                return true;  
            }  
        } else {  
            response.setContentType("application/json");  
            response.setCharacterEncoding("UTF-8");  
            PrintWriter out = null;
          try{
             //定义未登录返回的json信息
             Map<String,Object> map =new HashMap<>();
             map.put("msg", "用户未登录");
             map.put("code", 400);
             out = response.getWriter();
             out.write(JSONObject.toJSONString(map));
             out.flush();
          }catch(IOException e){
             e.printStackTrace();
          }
            return false;  
        }  
    }  
}
 
 

数据库的话表现在就建了一张,sys_user表,后面搞到授权的时候会加上其他表

搞到这里已经是可以实现用户认证登录了

猜你喜欢

转载自blog.csdn.net/kaola_l/article/details/80618808