第一节简单说明了一下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表,后面搞到授权的时候会加上其他表
搞到这里已经是可以实现用户认证登录了